“Effective Java” 可能对 Kotlin 的设计造成了怎样的影响——第一部分

简评:作者从《Effective Java》中选出了几条项目,举例分析 Java 和 Kotlin 写法不同之处,Kotlin 从中得到启发,写法更加简洁。

Java 是一种很好的语言但是有些众所周知的瑕疵,常见的陷阱以及早期(1.0 在 1995 年发布)继承过来的不是很好的元素。一本备受推崇的关于如何写好 Java 代码的书,避免常见的编码失误,处理它的弱点的书就是 Joshua Bloch 的《Effective Java》。它包含 78 个部分,为读者提供有关语言不同方面的宝贵意见。

现代编程语言的创造者有个很大的优势,因为它们可以分析已经创立的语言的弱点,从而改进它们。Jetbrains,创建了几款非常流行的 IDE 的公司,在 2010 年决定创建 Kotlin 编程语言作为自己的开发语言。它的目标是更简洁和更清晰的表达,同时消除Java的一些弱点。他们之前写 IDE 的代码都是用 Java 写的,所以他们需要一种与 Java 高度互操作的语言,并编译成Java 字节码。他们也想要 Java 开发者非常容易上手 Kotlin。使用 Kotlin,Jetbrains 想要构建更好的 Java。

1. 使用 Kotlin 的默认值,不再需要 builder 模式

当你在 Java 的构造器中有很多可选的参数时,代码就会变得冗长,难读而且容易出错。为了避免这种情况,《Effective Java》的第二条项目描述了如何高效使用 Builder 模式。构建这样的对象需要很多代码,比如下面的营养成分对象代码示例。它需要两个必需参数(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 模式,因为它有默认参数的功能,允许你定义每个可选构造器参数的默认值。

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》的第三条建议展示了将一个 Java 对象设计成单例,意味着只有一个实例能被实例化。下面的代码片段显示了 “ monoelvistic” 宇宙,只有一个 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() {}
}
view raw

再也不需要手动构建单例模式了!

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

一个良好的编程实践起源于函数编程,简化代码主要是使用不可变值对象。第 15 项就是“类应该是不可变的除非有一个非常好的理由让它们可变。”不可变值对象在 Java 中的创建非常乏味,因为每一个对象你都要重写 equals() 和 hashCode() 方法。这在第 8 和第 9 项中花费了 Joshua Bloch 18 页来描述如何遵循这一详尽的普遍的准则。例如,如果你重写 equals(),则必须确保灵活性,对称性,传递性,一致性和无效性的合同都得到满足。听起来更像数学而不是编程。

在 Kotlin 中,你只要使用 data 类即可。编译器自动导出像 equals() 和 hashCode() 等方法。这是可能的,因为标准函数可以从对象的属性机械地派生。只要在你的类前面输入关键字 data 即可。不需要 18 页的描述了。

提示:最近 AutoValue 在 Java 中变得很流行,这个库为 Java 1.6+ 生成不可变值类。

4. 属性替代字段

public class JavaPerson {

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

第 14 项建议在公共类中使用访问器方法而不是公共字段。如果你不遵循这个建议可能会有麻烦,因为如果字段可以直接访问那么就丧失了封装和灵活性带来的好处。这意味着将来你不更改其公共 API 将无法改变你的类的内部表示。例如,你无法限制字段的值,如某人的年龄等等。这是一个为什么我们总是创建冗长的 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 版本中增加了。最重要的一个就是 Override,标记着一个方法正在重写父类的方法。根据第 36 项,为了避免歹毒的 bug,可选的注解应该不断地被使用。当你认为你正在重写父类的方法而实际上没有时,编译器会抛出一个错误。只要当你没有忘记使用 Override 注解,那么就没有问题。

在 Kotlin 中,override 不再是可选的注解而是强制的关键字。所以像这类难受的 bug 不会再出现,编译器将会提前警告你。Kotlin 坚持把这些事弄清楚

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

延伸阅读:

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 196,264评论 5 462
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 82,549评论 2 373
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 143,389评论 0 325
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,616评论 1 267
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,461评论 5 358
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,351评论 1 273
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,776评论 3 387
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,414评论 0 255
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,722评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,760评论 2 314
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,537评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,381评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,787评论 3 300
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,030评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,304评论 1 252
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,734评论 2 342
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,943评论 2 336

推荐阅读更多精彩内容