转载

“Effective Java” 对 Kotlin 设计的影响(1)

Java 是一门非常不错的编程语言,但也存在一些缺陷,部分缺陷从 1995 年的早期版本延续至今。在 Joshua Bloch 出版的 Effective Java 一书中,作者详细介绍了避免常见编码错误及处理的方式。它包含 78 项,从语言的不同方面给读者提供了宝贵的意见。

现代编程语言创造很占优势,因为创造者可以从已创建的语言中学习和借鉴,然后开发出更好的编程语言。Jetbrains,一家捷克的软件开发公司,已创建了多个知名的 IDE,并于 2010 年创建了编程语言 Kotlin。该语言消除了 Java 中存在的一些问题,更简洁也更易表达。因之前的 IDE 全部用 Java 编写,现在他们急需一种与 Java 高度互操作的语言,并编译为 Java 字节码。Jetbrains 希望 Java 开发者能快速适应 Kotlin,同时用 Kotlin 构建一个更好的 Java。

“Effective Java” 对 Kotlin 设计的影响(1)

重读 Effective Java 时,我发现许多建议对 Kotlin 来说都不那么必要了,所以, 在这篇文章中,我想总结性地介绍一下这本书是如何影响 Kotlin 的设计的。

1、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,从而有效提高了生产力。

“Effective Java” 对 Kotlin 设计的影响(1)

提示:如果要在 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 KotlinElvis {

    fun leaveTheBuilding() {}
}

完全不用手动构建!:)

3、equals() 和 hashCode() 开箱即用

函数式编程和简化代码的良好实践,主要是为了使用不可变值对象。第 15 项中给出了建议:“除非有可变的合适理由,否则类不可变。”Java 中的不可变值对象非常繁琐,因为对于每个对象,你都必须重写 equals() 和 hashCode() 函数。Joshua Bloch 花了 18 页来描述如何遵守第 8 项和第 9 项提到的约束。例如,如果你重写 equals(),你必须保证反身性,对称性,传递性,一致性和非零性的约束都得到满足。这听起来更像是数学而不是编程。

在 Kotlin 中,你可以简单地使用数据类,编译器会自动派生 equals() 和 hashCode() 等方法。这是可以实现的,因为标准功能可以机械地从对象属性导出,你只需在类前输入关键字 data 即可。

提示:最近,Java 的 AutoValue 开始流行起来,这个库为 Java 1.6+ 生成不可变的值类。

4、属性而非字段

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 的属性,我们可以得到更简洁的类,具有更大的灵活性。

5、重载必须关键字而不是可选注释

Java 1.5 中添加了注释,其中最重要的一个是 Override,它标志着一个方法重载了超类的一个方法。第 36 项介绍说,这个注释用以避免恶性 Bug。当你认为你在重写超类中的一个方法,但实际上并不是的时候,编译器将抛出一个错误。只要你别忘记写 Override 注解,它就能起作用。

在 Kotlin 中,override 不是可选注解,而是必须关键字,所以 Bug 出现机会不多。

(未完待续)

原文: How “Effective Java” may have influenced the design of Kotlin — Part 1

责任编辑:开源中国 —达尔文

原文  https://my.oschina.net/editorial-story/blog/827404
正文到此结束
Loading...