Java是一门伟大的编程语言,但是它有一些瑕疵,普通的错误和从早期版本(1995年发布的1.0版本)继承下来的不那么伟大的要素。有一本广受尊敬的书专门讲解怎么写好Java代码,如何避免常见的编程错误以及如何处理Java的这些弱点,那就是Joshua Bloch的大名鼎鼎的"Effective Java"。这本书包括78个小节,我们将每一个小节称为一个“项目”,每一个项目都会在Java语言的不同方面给出非常有价值的建议。
现代编程语言的创建者有一个很大的优势,因为他们可以分析已有语言的弱点然后把事情做得更好。已经开发了数个流行IDE的 Jetbrains 公司于2010年决定为他们自己的开发创建一门新的编程语言 Kotlin。该语言的目标就是在消除Java语言的一些弱点的同时更简洁和更具表达性。他们已有的IDE代码都是用 Java 开发的,所以他们需要一种和 Java 高度互操作并且可以编译成 Java 字节码的语言。他们同时希望 Java 开发者能够很容易的转移到 Kotlin 语言。Jetbrain 希望使用 Kotlin 创建一个更好的 Java。
当再次阅读 "Effective Java" 时,我发现其中的很多“项目”对 Kotlin 已经不适用,所以本文就是探讨一下 “Effective Java”这本书的内容是怎样影响到 Kotlin 的设计的。
1.有了Kotlin的默认值,不再需要builders
在Java中如果一个构造函数有很多可选参数时,代码将变得冗长,不易阅读并且很容易发生错误。为了解决这个问题,“Effective Java”的项目2描述了怎样有效的使用 Builder Pattern。创建这样一个对象需要很多的代码,就像下面的 nutrition facts 对象。它有两个必须的参数(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中不再需要使用 builder pattern,因为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)
2.更容易实现单例
"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 declarations的概念,它给我们提供了单例行为:
object KotlinElvis {
fun leaveTheBuilding() {}
}
不再需要手动构建单例。
3.自带equals() 和 hashCode()
源于函数式编程和简化代码的一个编程最佳实践就是尽量使用不可修改的对象。项目15包含这样的建议”所有的类都应该是不可修改的,除非你有非常好的理由使他们成为可修改的“。在Java中创建不可修改的类是非常单调乏味的,因为你必须覆盖每一个类的equals()
和hashCode()
方法。Joshua Bloch 用了18页在项目18和项目9中描述了如何遵守这两个方法的规则。例如,如果你覆盖了 equals()
, 你必须遵守这些规则:自反性,对称性,传递性和非空性。听起来像是数学研究而不是编程。
在Kotlin中,你只要简单的使用 data calss 即可,编译器会自动获得像 equals()
, hashCode()
等等其他更多的方法。这是可能的因为标准的功能可以机械得从对象的属性中获取。这一切只需要简单的在类名称前键入一个关键字 data
。这里不再需要18页的描述。
4.属性(Properties)取代字段(fields)
public class JavaPerson {
// don't use public fields in public classes!
public String name;
public Integer age;
}
项目14建议在公共类中使用访问器方法而不是直接使用公共字段。如果你不遵循这个建议你会陷入麻烦,因为字段可以直接访问导致你失去封装和灵活性所带来的好处。这意味着以后你如果不修改其公共接口就无法改变其内部表示。例如,以后你不能限制某一个字段的值如年龄。这就是为什么我们总是在 Java 中创建冗长的 getters 和 setters。
在Kotlin中这一最佳实践已被强制执行,因为它使用自带默认setter 和getter 的properties来取代 fields。
class KotlinPerson {
var name: String? = null
var age: Int? = null
}
从字面上你可以像Java中的field一样访问这些属性 person.name
或者 person.age
。你可以添加自定义的 getters 和setters 而不需修改这个类的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版本中引入了注解。其中最重要的一个就是 Override
, 这个注解用于标注一个方法是覆盖了其父类的一个方法。根据项目36,Override
应该被使用以避免一些恶毒的bug。当你以为你在覆盖一个父类的方法,但是实际上并没有这么做的时候编译器会抛出一个异常。只要你别忘记 Override
注解这就会起效。
在Kotlin中,override
不再是一个可选的注解,而是一个必须的关键字。所以这些严重的bug发生的机会不大。这些事情编译器会提前警告你。Kotlin坚持将这些事情变得显而易见
本文译自How “Effective Java” may have influenced the design of Kotlin — Part 1