『Kotlin 將會取代 Java』
這是無庸置疑的,就好比『Swift 將會取代 Objective-C』,既然身為 Android 開發者的我們終究要面對,不如趁早來使用它!在使用 Kotlin 之前,我的 model 一直都是用 auto-value 來實作,原本我已經認為 auto-value 是極致的簡潔了(之前還特地寫了
一系列的文章介紹),但兩者相較之下 Kotlin 撰寫出來的程式碼又更勝一籌
●基本介紹
在 Kotlin 裡面,只要在 class 的前面加上 data 這個關鍵字,你的class就會自動升級為
data class(這不是
廢話嗎!),在變成所謂的 data class之後,Kotlin 的 compiler 便會根據你的 properties 自動幫你 override 掉原先的equals()/hashCode()/toString()這三個 method(當然如果你有自己的規則也是可以自己實作),並且幫你生成相對應的 getter and setter 以及 copy()
Before:
public class User {
private final String name;
private int age;
public User(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
User user = (User) o;
if (age != user.age) return false;
return name != null ? name.equals(user.name) : user.name == null;
}
@Override
public int hashCode() {
int result = name != null ? name.hashCode() : 0;
result = 31 * result + age;
return result;
}
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
After:
data class User(val name: String, var age: Int)
上面的 User 有兩個 property,其中 name 為常數(不可變),在Java裡面我們將它定義為 final,而且只提供 getter 沒有 setter;
在 Kotlin 中沒有 final 這個關鍵字,必須使用 val / var 來區分常數 / 變數
val = 常數 = 僅提供 getter
var = 變數 = 提供 getter and setter
data class 也很貼心的幫我們生成了一個名為 cope() 的 method,具體用法如下:
val anson = User(name = "Anson", age = 18)
val bnson = anson.copy(name = "Bnson")
val olderBnson = bnson.copy(age= 19)
●實作 Parcelable
如果我們要在 Kotlin 中去實作 Parcelable 介面,除了硬幹之外,其實也是有不錯的 3rd party library :
● Parceler
● PaperParcel
● Smuggler
這邊我使用的是
Smuggler 這套,用起來最為簡單方便
Before:
data class User(val name: String, val age: Int) : Parcelable {
constructor(parcel: Parcel) : this(
parcel.readString(),
parcel.readInt())
override fun writeToParcel(parcel: Parcel, flags: Int) {
parcel.writeString(name)
parcel.writeInt(age)
}
override fun describeContents(): Int {
return 0
}
companion object CREATOR : Parcelable.Creator<user> {
override fun createFromParcel(parcel: Parcel): User {
return User(parcel)
}
override fun newArray(size: Int): Array<user> {
return arrayOfNulls(size)
}
}
}
After:
data class User (val name: String, val age: Int) : AutoParcelable
● Extension data class?
試想一下,如果我們的 model 有繼承關係在呢?
如果有一些
property是所有 model 都會去用到的,那麼已往在 Java 時我們常常會將設計成一個Base類,然後其他 model 再去繼承他,大概會變成這樣(以下省略 methods 實作):
public abstract class Base {
public String token;
public Base(String token) {
this.token = token;
}
}
public class User extends Base implements Parcelable{
public String name;
public int age;
public User(String token, String name, int age) {
super(token);
this.name = name;
this.age = age;
}
protected User(Parcel in) {
super(in.readString());
name = in.readString();
age = in.readInt();
}
public static final Creator<user> CREATOR = new Creator<user>() {
@Override
public User createFromParcel(Parcel in) {
return new User(in);
}
@Override
public User[] newArray(int size) {
return new User[size];
}
};
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(token);
dest.writeString(name);
dest.writeInt(age);
}
}
但如果換到了 Kotlin,我們宣告成 data class 時便會造成一個奇怪的現象
究竟是 Base 要去實作 equals,toString...還是 User 呢 ?
其實 Kotlin 的官方部落格有探討過這個問題了
(原文)
那如果我們依舊要保留一樣的設計,去確保程式的嚴謹性時該怎麼辦呢?
這時只需要將原本的抽象類改成介面即可😃
interface Base {
val token: String
}
data class User(override val token: String, val name: String, val age: Int) : Base, AutoParcelable