[外文翻译]《Effective Java》在哪些方面影响了 Ko(3)

良好的编程实践起源于功能编程,简化代码主要靠使用不可变值对象。条目15建议“类应该是不可变的,除非有足够的理由将它们设为可变”。创建不可变的值对象在Java中非常繁琐,因为你必须为每个对象重写equals()和hashCode()。Joshua Bloch在条目8和9用了足足18页描述了关于这两种方法的准则。例如,如果你重写equals(),你必须保证自反性、对称性、传递性、一致性和无效性,听起来不像在编程而更像数学。

在Kotlin中,这种情况下你可以直接使用数据类,编译器会自动导出equals(),hashCode()等方法。这是因为标准方法可以从对象的属性中直接派生出来,只需在类前面输入关键字数据即可,完全不需要18页的描述。

提示:最近,Java的AutoValue很流行,该库可为Java 1.6+生成不可变值类。

4. 属性(properties)取代域(fields)

public class JavaPerson { // don't use public fields in public classes! public String name; public Integer age; }

条目14建议在公有类中使用访问器方法而不是公有字段。如果您不这么做的话可能会遇到麻烦,因为域可以直接访问,导致完全享受不到封装好处。这意味着日后你将无法在不改动其公共API的情况下更改该类的内部表达。比如,后面你就不能再去限制某个字段的值,例如人的年龄。这就是为什么我们总是在Java中创建这些冗长的默认getter和setter的原因之一。

而Kotlin直接用自动生成默认getter和setter的属性取代了字段/域。

class KotlinPerson { var name: String? = null var age: Int? = null }

从语法上来说,你可以使用person.name 或者 person.age访问Java中的公共字段等属性。之后也可以添加自定义的getter和setter而无需更改类的API:

class KotlinPerson { var name: String? = null var age: Int? = null set(value) { if (value in 0..120){ field = value } else{ throw IllegalArgumentException() } } }

长话短说:有了Kotlin的属性,我们的类将更简洁,同时还具有与生俱来的灵活性。

5. override成为强制关键字而不是可选注解

Java 1.5 中加入了注解(annotation),其中最重要的一个是重写(override),表示这个方法是对超类中该方法的重写。基于书中条目36,应该尽量使用这个可选注解以避免一些恶心的bug。比如当你以为你重写了超类的方法但其实并没有时,编译器会抛出一个错误。不过如果你记得加上了override注解的话就没事。

在Kotlin中,override不是可选的注解而是强制关键字。所以由此引发的bug就不会再有了,编译器会提前警告你。

6. 默认的 final 类

《Effective Java》在第17条说,要么为继承而设计,并提供文档说明,要么就禁止继承。在Java中,除非将类显式指定为final,否则每个类都可以被继承。如果你忘记把类指定为final,也没有好好为继承而设计,那么当客户创建子类并覆盖某些方法时,很可能功能会出问题。

在Kotlin,所有类默认都是final的。如果要允许继承,则必须明确使用关键字open,这与Java的final完全相反。这样可以避免创建并非有意设计继承的非final类。

Kotlin社区有人对这个 “默认的final” 设计很不满。Kotlin论坛对此进行了激烈的讨论。后来,在Kotlin 1.1 beta版中提供了一个编译器插件,可以让class默认是open.

7. 没有检查型异常(checked exceptions)

Java有一个广为人知的特性,即检查型异常,编译器会强制函数的调用者捕获(或重新抛出)异常,这个功能总是容易出问题。 《Effective Java》花了一整个章节来阐述如何正确的使用和处理检查型和非检查型(即运行时)异常。

[外文翻译]《Effective Java》在哪些方面影响了 Ko

如Kotlin的文档中所述,检查型异常的一个问题是你有时必须捕获永远不会发生的异常,这将导致空的catch块和冗余代码。而且开发人员经常在被迫处理异常感到麻烦而直接忽略它们,这也会导致空的catch块。第65项说“不要忽视异常”。

根据第59条,检查型异常往往是不必要的,而且这应该通过在调用对象之前检查对象的状态,或者通过判断返回值(比如null)来避免。

我还发现了检查型异常的其它问题:

throws子句把实现细节加入接口,这种做法不好;

版本化可能有问题。如果你修改了类的实现并向函数中添加了一个throws子句,那么API就发生了变化;

调用函数不应该规定调用者如何处理异常;

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/cc8c7fb36c2398f444afd229c7b5ef12.html