Java是一門偉大的編程語言喷户,但是它有一些瑕疵,普通的錯誤和從早期版本(1995年發(fā)布的1.0版本)繼承下來的不那么偉大的要素庞钢。有一本廣受尊敬的書專門講解怎么寫好Java代碼拔恰,如何避免常見的編程錯誤以及如何處理Java的這些弱點,那就是Joshua Bloch的大名鼎鼎的"Effective Java"基括。這本書包括78個小節(jié)颜懊,我們將每一個小節(jié)稱為一個“項目”,每一個項目都會在Java語言的不同方面給出非常有價值的建議风皿。
現代編程語言的創(chuàng)建者有一個很大的優(yōu)勢河爹,因為他們可以分析已有語言的弱點然后把事情做得更好。已經開發(fā)了數個流行IDE的 Jetbrains 公司于2010年決定為他們自己的開發(fā)創(chuàng)建一門新的編程語言 Kotlin桐款。該語言的目標就是在消除Java語言的一些弱點的同時更簡潔和更具表達性咸这。他們已有的IDE代碼都是用 Java 開發(fā)的,所以他們需要一種和 Java 高度互操作并且可以編譯成 Java 字節(jié)碼的語言魔眨。他們同時希望 Java 開發(fā)者能夠很容易的轉移到 Kotlin 語言媳维。Jetbrain 希望使用 Kotlin 創(chuàng)建一個更好的 Java。
當再次閱讀 "Effective Java" 時冰沙,我發(fā)現其中的很多“項目”對 Kotlin 已經不適用侨艾,所以本文就是探討一下 “Effective Java”這本書的內容是怎樣影響到 Kotlin 的設計的。
1.有了Kotlin的默認值拓挥,不再需要builders
在Java中如果一個構造函數有很多可選參數時,代碼將變得冗長袋励,不易閱讀并且很容易發(fā)生錯誤侥啤。為了解決這個問題当叭,“Effective Java”的項目2描述了怎樣有效的使用 Builder Pattern。創(chuàng)建這樣一個對象需要很多的代碼盖灸,就像下面的 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代碼來創(chuàng)建一個對象時大致就是這樣:
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來創(chuàng)建一個對象大致就是這樣的:
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一樣,這里創(chuàng)建的這個對象也是不可修改的徙垫。
我們將Java中的47行代碼在Kotlin中縮減成了7行讥裤,這會大大的提高生產效率。
提示:如果你想在Java中創(chuàng)建 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 對象使它的行為符合一個單例,就是只能創(chuàng)建一個實例的對象笆焰。下面的代碼片段展示了一個“單例的”世界劫谅,這里只能有一個 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中創(chuàng)建不可修改的類是非常單調乏味的未檩,因為你必須覆蓋每一個類的equals()
和hashCode()
方法。Joshua Bloch 用了18頁在項目18和項目9中描述了如何遵守這兩個方法的規(guī)則粟焊。例如冤狡,如果你覆蓋了 equals()
, 你必須遵守這些規(guī)則:自反性,對稱性项棠,傳遞性和非空性悲雳。聽起來像是數學研究而不是編程。
在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 中創(chuàng)建冗長的 getters 和 setters西壮。
在Kotlin中這一最佳實踐已被強制執(zhí)行遗增,因為它使用自帶默認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發(fā)生的機會不大。這些事情編譯器會提前警告你鹦付。Kotlin堅持將這些事情變得顯而易見
本文譯自How “Effective Java” may have influenced the design of Kotlin?—?Part 1