Java 是一门非常不错的编程语言,但也存在一些缺陷,部分缺陷从 1995 年的早期版本延续至今。在 Joshua Bloch 出版的 Effective Java 一书中,作者详细介绍了避免常见编码错误及处理的方式。它包含 78 项,从语言的不同方面给读者提供了宝贵的意见。
现代编程语言创造很占优势,因为创造者可以从已创建的语言中学习和借鉴,然后开发出更好的编程语言。Jetbrains,一家捷克的软件开发公司,已创建了多个知名的 IDE,并于 2010 年创建了编程语言 Kotlin。该语言消除了 Java 中存在的一些问题,更简洁也更易表达。因之前的 IDE 全部用 Java 编写,现在他们急需一种与 Java 高度互操作的语言,并编译为 Java 字节码。Jetbrains 希望 Java 开发者能快速适应 Kotlin,同时用 Kotlin 构建一个更好的 Java。
重读 Effective Java 时,我发现许多建议对 Kotlin 来说都不那么必要了,所以, 在这篇文章中,我想总结性地介绍一下这本书是如何影响 Kotlin 的设计的。
当 Java 的 constructor 中存在多个可选参数时,代码会变得冗长,难读且容易出错。为解决这一问题,Effective Java 在第 2 项中介绍了如何有效的使用 构建器模式 。此类对象的构建需要用到许多代码,如下面代码示例中的 Nutrition 成分对象,它包含两个必须的参数(servingSize, servings)和四个可选参数(calories, fat, sodium, carbohydrates):
public class JavaNutritionFacts { private final int servingSize; private final int servings; private final int calories; private final int fat; private final int sodium; private final int carbohydrate; public static class Builder { // Required parameters private final int servingSize; private final int servings; // Optional parameters - initialized to default values private int calories = 0; private int fat = 0; private int carbohydrate = 0; private int sodium = 0; public Builder(int servingSize, int servings) { this.servingSize = servingSize; this.servings = servings; } public Builder calories(int val) { calories = val; return this; } public Builder fat(int val) { fat = val; return this; } public Builder carbohydrate(int val) { carbohydrate = val; return this; } public Builder sodium(int val) { sodium = val; return this; } public JavaNutritionFacts build() { return new JavaNutritionFacts(this); } } private JavaNutritionFacts(Builder builder) { servingSize = builder.servingSize; servings = builder.servings; calories = builder.calories; fat = builder.fat; sodium = builder.sodium; carbohydrate = builder.carbohydrate; } }
用 Java 实例化对象,如下所示:
final JavaNutritionFacts cocaCola = new JavaNutritionFacts.Builder(240,8) .calories(100) .sodium(35) .carbohydrate(27) .build();
使用 Kotlin,则不需要使用构建器模式,因为有默认参数的功能,你可以为每个可选构建函数参数定义默认值:
class KotlinNutritionFacts( private val servingSize: Int, private val servings: Int, private val calories: Int = 0, private val fat: Int = 0, private val sodium: Int = 0, private val carbohydrates: Int = 0)
在 Kotlin 中创建一个对象,如:
val cocaCola = KotlinNutritionFacts(240,8, calories = 100, sodium = 35, carbohydrates = 27)
为了实现更佳的可读性,你还可命名所需的参数 servingSize 和 servings:
val cocaCola = KotlinNutritionFacts( servingSize = 240, servings = 8, calories = 100, sodium = 35, carbohydrates = 27)
和 Java 一样,此处创建的对象不可变。
我们将代码行数从 Java 中的 47 减少到了 Kotlin 的 7,从而有效提高了生产力。
提示:如果要在 Java 中创建 KotlinNutrition 对象,我们可以这样做,但是必须为每个可选参数指定一个值。幸运的是,如果添加 JvmOverloads 注解,则会生成多个构造函数。 注意,如果要使用注解,则需要关键字 constructor:
class KotlinNutritionFacts @JvmOverloads constructor( private val servingSize: Int, private val servings: Int, private val calories: Int = 0, private val fat: Int = 0, private val sodium: Int = 0, private val carbohydrates: Int = 0)
Effective Java 的第 3 项介绍如何将 Java 对象设计为单例,它实际上是一个对象,其中只有一个示例可以实例化。下列示例进行了演示,其中只有一个 Elvis 存在:
public class JavaElvis { private static JavaElvis instance; private JavaElvis() {} public static JavaElvis getInstance() { if (instance == null) { instance = new JavaElvis(); } return instance; } public void leaveTheBuilding() { } }
Kotlin 有对象声明的概念,它给我们提供了一个单例的行为:
object KotlinElvis { fun leaveTheBuilding() {} }
完全不用手动构建!:)
函数式编程和简化代码的良好实践,主要是为了使用不可变值对象。第 15 项中给出了建议:“除非有可变的合适理由,否则类不可变。”Java 中的不可变值对象非常繁琐,因为对于每个对象,你都必须重写 equals() 和 hashCode() 函数。Joshua Bloch 花了 18 页来描述如何遵守第 8 项和第 9 项提到的约束。例如,如果你重写 equals(),你必须保证反身性,对称性,传递性,一致性和非零性的约束都得到满足。这听起来更像是数学而不是编程。
在 Kotlin 中,你可以简单地使用数据类,编译器会自动派生 equals() 和 hashCode() 等方法。这是可以实现的,因为标准功能可以机械地从对象属性导出,你只需在类前输入关键字 data 即可。
提示:最近,Java 的 AutoValue 开始流行起来,这个库为 Java 1.6+ 生成不可变的值类。
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.nameor 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 的属性,我们可以得到更简洁的类,具有更大的灵活性。
Java 1.5 中添加了注释,其中最重要的一个是 Override,它标志着一个方法重载了超类的一个方法。第 36 项介绍说,这个注释用以避免恶性 Bug。当你认为你在重写超类中的一个方法,但实际上并不是的时候,编译器将抛出一个错误。只要你别忘记写 Override 注解,它就能起作用。
在 Kotlin 中,override 不是可选注解,而是必须关键字,所以 Bug 出现机会不多。
(未完待续)
原文: How “Effective Java” may have influenced the design of Kotlin — Part 1
责任编辑:开源中国 —达尔文