优秀代码的必知必会(一)?
1.使用静态工厂方法替代构造方法
静态工厂方法的优点:
不像构造方法,它是有名字的。
它不需要每次调用时都创建一个新对象。
它可以返回 其返回类型的任何子类型的对象。
返回对象的类可以根据输入参数的不同而不同。
在编写包含该方法的类时,返回的对象的类不需要存在。
2.当构造方法参数过多时请使用 builder 模式
当一个对象中,属性过多时,使用构造方法时,参数过多导致难以维护,并且很难读懂每个参数的含义是什么。
使用 构造方法 来构建必传参数,而用 setter 的方式来传可选参数呢?这看似参数少,创建实例也很容易,并且所生成的代码也易于阅读。但由于 JavaBeans 模式本身有严重的缺陷,由于构造方法在多次调用中被分割,导致 JavaBeans 可能处于不一致的状态,并且还需要额外增加工作以确保线程安全。
最好处理参数过多的方法是使用 builder 模式,它结合了可伸缩构造方法模式的安全性和 JavaBeans 模式的可读性,然后在调用 builder 对象的 setter 相似方法来设置每个可选参数。
3.使用依赖注入的方式注入资源
依赖注入的实践将极大地增强类的灵活性、可重用性和可测试性。
4.不要创建不必要的对象
如果对象是不可变的,它总是可以被重用的,比如 String 类是 final 的,不可变的,最好不要用 new String("test"), 因为它每次都会创建一个新的对象。在性能有严格要求的情况下,优先使用基本类型而不是装箱的基本类型,也要注意无意识的自动装箱,因为它们会影响性能。
5.消除过期的对象引用
这个也就是常说的内存泄露。当一个类自己管理内存时,应该警惕内存泄漏问题。 每当一个元素被释放时,元素中包含的任何对象引用都应该被清除。
另一个常见的内存泄漏来源是缓存。一旦将对象引用放入缓存中,很容易忘记它的存在,可以用 WeakHashMap 来表示缓存。
6.避免使用 Finalizer 和 Cleaner 机制,请使用 try-with-resources 语句
用于 Finalizer 和 Cleaner 机制的不确定性,我们不知道它到底何时会执行。例如,依赖于 Finalizer 和 Cleaner 机制来关闭文件是严重的错误,因为打开的文件描述符是有限的资源。 如果由于系统迟迟没有运行 Finalizer 和 Cleaner 机制而导致许多文件被打开,程序可能会失败,因为它不能再打开文件了。可以用 try-with-resources 语句 或 try-finally 语句,当打开多个文件时,try-finally 语句的可读性就不如 try-with-resources 语句,因此推荐使用 try-with-resources 语句。
7.重写 equals 方法时同时也要重写 hashcode 方法
重写 equals 方法时,需要满足以下特性。
自反性:一个对象必须与自身相等。
对称性:任何非空引用 x 和 y,如果且仅当 y.equals(x) 返回 true 时 x.equals(y) 必须返回 true。
传递性:如果第一个对象等于第二个对象,第二个对象等于第三个对象,那么第一个对象必须等于第三个对象。
一致性:如果两个对象是相等的,除非一个(或两个)对象被修改了, 那么它们必须始终保持相等。
非空性:对于任何非空引用 x,x.equals(null) 必须返回 false。
每次重写 equals 方法时都必须重写 hashCode 方法,否则程序将无法正常运行。 hashCode 方法必须遵从 Object 类指定的常规约定,将不同的哈希码分配给不同的实例对象。
8.始终重写 toString 方法
Object 类提供了 toString 方法的实现是包名+类名+@+哈希码的无符号十六进制。toString 的通用约定要求,返回的字符串应该是“一个简洁但内容丰富的表示,对人们来说是很容易阅读的”。就需要 toString 方法,方便可读性。
9.考虑实现 Comparable 接口
在需要可比较的场景中,最好实现 Comparable 接口,以便在基于比较的集合中轻松对其实例进行排序,搜索和使用。 比较 compareTo 方法的实现中的字段值时,请避免使用"<"和">"运算符。 最好使用包装类中的静态 compare 方法或 Comparator 接口中的构建方法。
10.使类和成员的可访问性最小化
一个优秀设计的组件隐藏了它的所有实现细节,干净地将它的 API 与它的实现分离开来。然后,组件只通过它们的 API 进行通信,并且对彼此的内部工作一无所知。称为信息隐藏或封装,是软件设计的基本原则。
Java中的四种访问级别:
private——只能自己访问。
package-private——默认级别,同包中的任意类访问。
protected——子类和自己可以访问。
public —— 任何类都可访问。
11.在公共类中使用访问方法而不是直接访问属性
这种设计不是优秀的设计方式。如果属性直接暴露出去,如果需要修改返回值,那基本是不可能的,因为调用方散布在很多地方。如果是提供的访问方法,我们只需要改方法本身即可。
12.最小化可变性,能用不可变对象最好用不可变对象
不可变对象的优点:
不可变对象是线程安全的。
不可变对象可以被自由地共享。
不可变对象也可以共享内部属性。
不可变对象可以被重复使用。
不可变对象提供了免费的原子失败机制。
13.该选择接口还是抽象类
在 Java 中,通过 接口 和 抽象类 来允许多个实现。在 Java 8 中也引入了接口的默认方法实现,所以这两种机制都允许为某些实例方法提供实现。由于 Java 中只允许单一继承,常用的方式是接口定义了类型,可能提供了一些默认的方法,抽象类去实现多个接口并实现通用接口方法,具体的实现类去继承抽象类并实现抽象类中的抽象方法。
14.该选择组合还是继承
继承是实现代码重用的有效方式,但并不总是最好的工具。与方法调用不同,继承打破了封装,一个子类依赖于其父类的实现细节来保证其正确的功能。如果父类发生了变化,即使子类并没有做任何的改动也会导致子类被破坏。
不要继承一个现有的类,而应该给你的新类增加一个私有属性,该属性是现有类的实例引用,这种设计被称为组合。现在不依赖于现有类的实现细节,即使将新的方法添加到现有的类中,也不会对新类产生影响。
继承是强大的,也可以最大化代码重用,只有在子类和父类之间存在真正的子类型关系时才适用。 如果子类与父类不在同一个包中,并且父类不是为继承而设计的,那就可以使用组合来代替继承。
15.接口最好只用来定义类型
接口不包含任何方法,它只包含静态 final 属性,每个输出一个常量,这样的接口叫常量接口,这是对接口的糟糕使用方式。
如果常量跟类的关系密切,那么我们可以把常量定义在类中。假如是公共的常量,我们应该用枚举类型来定义它们。
接口只能用于定义类型,它们不应该仅用于定义常量。
16.优先考虑使用泛型
一个集合对象中数据类型应该是一致的,如果不规定数据的具体类型,在编译期是没有任何问题的。但到程序运行时,如果类型不一致,就与可能抛出 ClassCastException 异常。因此,我们需要使用泛型强制约束数据的类型,让问题暴露在编译期,而不是在运行期。
17.使用限定通配符来增加 API 的灵活性
我们用泛型强制规定了参数的类型,因此它非常的不灵活,使用场景非常少。相对于提供的不可变的类型,有时需要比此更多的灵活性我们就需要使用通配符类型。
所以将类型从 List<T> 更改为 List<? extends T>。在 API 中使用通配符类型,虽然棘手,但使得 API 更加灵活。 如果编写一个将被广泛使用的类库,请使用通配符类型来最大化该 API 的灵活性。
18.小心泛型和可变参数的结合使用
因为可变参数和泛型不能很友好的限制,泛型可变参数不是类型安全的,但它们是合法的。如果选择使用泛型可变参数编写方法,请首先确保该方法类型一致,然后使用 @SafeVarargs 注解对其进行标注,以免造成使用不愉快。
19.不要为类打标签,而应使用类层次结构
一个类用 枚举声明,标签属性和 switch 语句来判断类型,会使代码可读性更差,容易出错的,而且效率低下。因此,带标签的类很应少最好别用。当遇到一个带有标签属性的现有类时,可以考虑将其重构为一个类层次中。
20.注意消除非检查警告
未经检查的警告是重要的,不要忽视他们。因为每个未经检查的警告代表在运行时出现 ClassCastException 异常的可能性,尽量消除这些警告。如果无法消除未经检查的警告,并且可以证明引发该警告的代码是安全类型的,则可以在尽可能小的范围内使用 @SuppressWarnings(“unchecked”) 注解来禁止警告。 并注释为什么是安全的。
21.该选择列表还是数组
数组和泛型具有非常不同的类型规则。 数组是协变和具体化的, 泛型是不变和类型擦除的。
因此,数组能保证运行时类型的安全性,但不保证编译时类型的安全性,反之亦然。
一般来说,数组和泛型不能很好地混合工作。如果你发现把它们混合在一起,得到编译时错误或者警告,首先应该使用列表来替换数组。
22.使用枚举类型替代整型常量
枚举的优点:
提供了编译时类型的安全性,可以使用 == 运算符来比较不同枚举类型的值。
具有相同名称常量的枚举类型可以和平共存,因为每种类型都有其自己的名称空间。
还允许添加任意方法和属性并实现任意接口,功能更强大。
枚举更具可读性,更安全,更强大。
23.始终使用 Override 注解
此注解只能在方法声明上使用,它表明带此注解的方法声明重写了父类的声明。如果始终使用这个注解,它将避免产生大量的恶意 bug。
如果在每个方法声明中使用 Override 注解,并且认为要重写父类声明,那么编译器可以保护免受很多错误的影响。
24.优先使用标准的函数式接口
java.util.function 包提供了大量标准函数式接口供你使用。 如果其中一个标准函数式接口完成这项工作,则通常应该优先使用它,而不是专门构建的函数式接口。 这将使你的 API 更容易学习,通过减少其不必要概念,并将提供重要的互操作性好处,因为许多标准函数式接口提供了有用的默认方法。
不要提供具有多个重载的方法,这些重载在相同的参数位置上使用不同的函数式接口,如果这样做可能会在调用方中产生歧义。
25.Stream
在 Java 8 中添加了 Stream API,以简化顺序或并行执行批量操作的任务。
流 (Stream),表示有限或无限的数据元素序列。
流管道 (stream pipeline),表示对这些元素的多级计算。
Stream 中的元素可以来自任何地方。常见的源包括集合,数组,文件,正则表达式模式匹配器,伪随机数生成器和其他流。
流可以很容易地做一些事情:
统一转换元素序列。
过滤元素序列。
使用单个操作组合元素序列。
将元素序列累积到一个集合中,可能通过一些公共属性将它们分组。
在元素序列中搜索满足某些条件的元素。
遍历元素序列。
Stream 类提供了很多好用又简单的方法,可以多加使用。
PS:
清山绿水始于尘,博学多识贵于勤。
我有酒,你有故事吗?
微信公众号:「清尘闲聊」。
欢迎一起谈天说地,聊代码。