“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

延伸閱讀:

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容