优秀代码的必知必会(二)?

317 阅读7分钟

1.在公共类中使用访问方法而不是直接访问属性

这种设计不是优秀的设计方式。如果属性直接暴露出去,如果需要修改返回值,那基本是不可能的,因为调用方散布在很多地方。如果是提供的访问方法,我们只需要改方法本身即可。

2.最小化可变性,能用不可变对象最好用不可变对象

不可变对象的优点:
不可变对象是线程安全的。
不可变对象可以被自由地共享。
不可变对象也可以共享内部属性。
不可变对象可以被重复使用。
不可变对象提供了免费的原子失败机制。

3.该选择接口还是抽象类

在 Java 中,通过 接口 和 抽象类 来允许多个实现。在 Java 8 中也引入了接口的默认方法实现,所以这两种机制都允许为某些实例方法提供实现。由于 Java 中只允许单一继承,常用的方式是接口定义了类型,可能提供了一些默认的方法,抽象类去实现多个接口并实现通用接口方法,具体的实现类去继承抽象类并实现抽象类中的抽象方法。

4.该选择组合还是继承

继承是实现代码重用的有效方式,但并不总是最好的工具。与方法调用不同,继承打破了封装,一个子类依赖于其父类的实现细节来保证其正确的功能。如果父类发生了变化,即使子类并没有做任何的改动也会导致子类被破坏。

不要继承一个现有的类,而应该给你的新类增加一个私有属性,该属性是现有类的实例引用,这种设计被称为组合。现在不依赖于现有类的实现细节,即使将新的方法添加到现有的类中,也不会对新类产生影响。

继承是强大的,也可以最大化代码重用,只有在子类和父类之间存在真正的子类型关系时才适用。 如果子类与父类不在同一个包中,并且父类不是为继承而设计的,那就可以使用组合来代替继承。

5.接口最好只用来定义类型

接口不包含任何方法,它只包含静态 final 属性,每个输出一个常量,这样的接口叫常量接口,这是对接口的糟糕使用方式。

如果常量跟类的关系密切,那么我们可以把常量定义在类中。假如是公共的常量,我们应该用枚举类型来定义它们。

接口只能用于定义类型,它们不应该仅用于定义常量。

6.优先考虑使用泛型

一个集合对象中数据类型应该是一致的,如果不规定数据的具体类型,在编译期是没有任何问题的。但到程序运行时,如果类型不一致,就与可能抛出 ClassCastException 异常。因此,我们需要使用泛型强制约束数据的类型,让问题暴露在编译期,而不是在运行期。

7.使用限定通配符来增加 API 的灵活性

我们用泛型强制规定了参数的类型,因此它非常的不灵活,使用场景非常少。相对于提供的不可变的类型,有时需要比此更多的灵活性我们就需要使用通配符类型。

所以将类型从 List 更改为 List<? extends T>。在 API 中使用通配符类型,虽然棘手,但使得 API 更加灵活。 如果编写一个将被广泛使用的类库,请使用通配符类型来最大化该 API 的灵活性。

8.小心泛型和可变参数的结合使用

因为可变参数和泛型不能很友好的限制,泛型可变参数不是类型安全的,但它们是合法的。如果选择使用泛型可变参数编写方法,请首先确保该方法类型一致,然后使用 @SafeVarargs 注解对其进行标注,以免造成使用不愉快。

9.不要为类打标签,而应使用类层次结构

一个类用 枚举声明,标签属性和 switch 语句来判断类型,会使代码可读性更差,容易出错的,而且效率低下。因此,带标签的类很应少最好别用。当遇到一个带有标签属性的现有类时,可以考虑将其重构为一个类层次中。

10.注意消除非检查警告

未经检查的警告是重要的,不要忽视他们。因为每个未经检查的警告代表在运行时出现 ClassCastException 异常的可能性,尽量消除这些警告。如果无法消除未经检查的警告,并且可以证明引发该警告的代码是安全类型的,则可以在尽可能小的范围内使用 @SuppressWarnings(“unchecked”) 注解来禁止警告。 并注释为什么是安全的。

11.该选择列表还是数组

数组和泛型具有非常不同的类型规则。 数组是协变和具体化的, 泛型是不变和类型擦除的。

因此,数组能保证运行时类型的安全性,但不保证编译时类型的安全性,反之亦然。

一般来说,数组和泛型不能很好地混合工作。如果你发现把它们混合在一起,得到编译时错误或者警告,首先应该使用列表来替换数组。

12.使用枚举类型替代整型常量

枚举的优点:
提供了编译时类型的安全性,可以使用 == 运算符来比较不同枚举类型的值。
具有相同名称常量的枚举类型可以和平共存,因为每种类型都有其自己的名称空间。
还允许添加任意方法和属性并实现任意接口,功能更强大。

枚举更具可读性,更安全,更强大。

13.始终使用 Override 注解

此注解只能在方法声明上使用,它表明带此注解的方法声明重写了父类的声明。如果始终使用这个注解,它将避免产生大量的恶意 bug。

如果在每个方法声明中使用 Override 注解,并且认为要重写父类声明,那么编译器可以保护免受很多错误的影响。

14.优先使用标准的函数式接口

java.util.function 包提供了大量标准函数式接口供你使用。 如果其中一个标准函数式接口完成这项工作,则通常应该优先使用它,而不是专门构建的函数式接口。 这将使你的 API 更容易学习,通过减少其不必要概念,并将提供重要的互操作性好处,因为许多标准函数式接口提供了有用的默认方法。

不要提供具有多个重载的方法,这些重载在相同的参数位置上使用不同的函数式接口,如果这样做可能会在调用方中产生歧义。

15.Stream

在 Java 8 中添加了 Stream API,以简化顺序或并行执行批量操作的任务。

流 (Stream),表示有限或无限的数据元素序列。
流管道 (stream pipeline),表示对这些元素的多级计算。

Stream 中的元素可以来自任何地方。常见的源包括集合,数组,文件,正则表达式模式匹配器,伪随机数生成器和其他流。

流可以很容易地做一些事情:
统一转换元素序列。
过滤元素序列。
使用单个操作组合元素序列。
将元素序列累积到一个集合中,可能通过一些公共属性将它们分组。
在元素序列中搜索满足某些条件的元素。
遍历元素序列。

Stream 类提供了很多好用又简单的方法,可以多加使用。

PS:
清山绿水始于尘,博学多识贵于勤。
我有酒,你有故事吗?
微信公众号:「清尘闲聊」。
欢迎一起谈天说地,聊代码。