"Effective Java"怎样影响Kotlin的设计(1)

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)

为了获得更好的阅读性,你也可以为必须的两个参数 servingSizeservings 也指定名称:

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

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

推荐阅读更多精彩内容