Kotlin與java的糾纏史

1004145468發表於2018-09-01

1. 背景

Jetbrains早在2010年推出Kotlin,在今年(2017)5月18,谷歌在今日舉行的I/O開發者大會上宣佈,將Kotlin語言作為安卓開發的一級程式語言,這個可愛的語言可於Java進行無縫混編。之前做過一段時間的C#和python開發,感覺三者之間總兩兩相似,不清楚是誰在模仿誰,誰在吃著誰的語法糖。

2. 差異與對比

2.1 構造器

java Kotlin
構造器 主構造器
- 次構造器
  • java版:
public class Model {

    public Model(){}

    public Model(String name){}
}
複製程式碼
  • Kotlin版:
    • 主構造器
class KtModel constructor(name: String)

ps:如果不私有化構造器或者註解構造器,constructor可以省略,即:

 -> class KtModel (name: String)
複製程式碼

由於Kotlin主構造器寫法的出現,可以簡化Java中JavaBean,關鍵字:data

data class KtModel(var id: Int, var name: String)
複製程式碼
  • Kotlin版
    • 次構造器
class Model{
    var mName:String? = null
    constructor(name:String){
        mName = name
    }
}

//由於Kotlin中有引數預設寫法,比如:
class Model{
    var mName:String? = null
    constructor(name:String,age:Int = 0){
        mName = name
    }
}

//在java中例項化這個Model還是需要:
Model model = new Model("Inke",20)

//為了讓java也享受到這個福利,Kotlin支援@JvmOverloads註解,為Java過載構造器
class Model{
    var mName:String? = null
    @JvmOverloads
    constructor(name:String,age:Int = 0){
        mName = name
    }
}

//此時:java呼叫
Model model = new Model("Inke",20);
Model model = new Model("Inke");
複製程式碼

2.2 自定義setter和getter

由data宣告的javabean預設實現了getter方法,如果變數為var型別也同時實現了getter方法,並且像equal() hashcode() toString()方法也自動複寫了。除此,也支援自定義setter和getter方法,寫法和c#的方式一樣。

class Student(var name: String, var grade: Int) {

    // Kotlin中的enum
    enum class LEVEL {GOOD, JUSTSOSO }
    //預設規則賦值
    var level: LEVEL = if (grade > 90) LEVEL.GOOD else LEVEL.JUSTSOSO
        set(value) {
            field = value
        }
        get() = field
}
ps: 通過field代表set get的屬性

複製程式碼

2.3 條件篩選

java中對某一欄位型別根據不同的值進行篩選,同使用Switch“語句”,如:

switch (itemViewType) {
            case ITEM_VIEWTYPE_LINKMSG:// 帶有link欄位的連結訊息
                return new LinkMsgViewHolder(inflater);
            case ITEM_VIEWTYPE_TEXTMSG_BYME:// 自己的文字訊息
                return new TextMsgByMeViewHolder(inflater);
            case ITEM_VIEWTYPE_TEXTMSG_BYOTHER:// 別人的文字訊息
                return new TextMsgByOtherViewHolder(inflater);
            case ITEM_VIEWTYPE_PIC_MSG_ME:// 自己的圖片訊息
                return new PicMsgByMeViewHolder(inflater);
            case ITEM_VIEWTYPE_PIC_MSG_OTHER:// 別人的圖片訊息
                return new PicMsgByOtherViewHolder(inflater);
            case ITEM_VIEWTYPE_AUDIO_MSG_ME:// 自己的語音訊息
            ...
            }
複製程式碼

 kotlin中對某一欄位型別篩選的關鍵字when,但它不屬於語句,它屬於表示式,真的是表示式哦。

fun getWeekInfo(day: Int): String {
    //因為是函式 所以可以直接使用在return後面
    return when (day) {
        1, 2, 3, 4, 5 -> "工作日"
        6, 7 -> "週末"
        else -> "unknow"
    }
}

在Kotlin中幾乎一切都是表示式,所以上面更有簡化版:

fun getWeekInfo(day: Int) = when (day) {
        1, 2, 3, 4, 5 -> "工作日"
        6, 7 -> "週末"
        else -> "unknow"  //與java的default不能,else必須寫,除非密封類(sealed class)可以不寫,後面會詳細講解。
}
複製程式碼

難道僅僅這些就夠了,錯,還有厲害的!前方高能。

fun getWeekInfo(student: Student) = when {
    student.name.equals("馬雲") -> "考什麼試!"
    student.grade >= 60 -> "Pass"
    student.grade < 60 -> "fail"
    student is Student -> "is 相當於 instanceof"
    else -> "unknow"
}

1. when可以不一定帶引數,就使用方法引數
2. when中的條件不需要同一種方式判斷,只需要返回boolean型別
複製程式碼

2.4 迴圈結構

語言 次數迭代 物件池迭代
java 支援 支援
kotlin 強支援 支援

java版本兩種迴圈方式的寫法

for (int i = 0; i < 10; i++) {
        //TODO
    }

for (String arg : args) {
        //TODO
    }
複製程式碼

kotlin版本,與python寫法相似

// 次數迭代
for(i in 1..10){  //[1,10]區間往上遍歷
        print(i)
    }

for(i in 10 downTo 1){ // [1,10]區間往下遍歷
        print(i)
    }
    
for(i in 1 until 10){ // [1,10)區間往上遍歷
        print(i)
    }


for(i in 1..10 step 2){ //設定步數往上走
        print(i)
    }
    
    
    
//物件池迭代
for (arg in args){
        print(arg)
    }
複製程式碼

2.5 引入靜態方法

引入靜態方法的目的:

java Kotlin
工具方法 類的擴充套件

引入方式:

  • java: import static com.meelive.ingkee.base.utils.guava.Preconditions.checkArgument;
  • Kotlin: import com.inke.utils.showToast

靜態方法的寫法

  • java:純工具類的寫法
  • kotlin:
fun Context.showToast(message: String) : Toast {
    var toast : Toast = Toast.makeText(this,message,Toast.LENGTH_SHORT)
    toast.setGravity(Gravity.CENTER,0,0)
    toast.show()
    return toast
}

作為Context類的擴充方法,在Activity,Service中,可以直接使用,物件導向的封裝性顯得更加的嚴實(個人看法)。
複製程式碼

2.6 型別轉換

java中對於型別的轉化,寫的特別想吐的語句如下:

Person p = new Person();
if(p instanceof Student){
    Student student = (Student) p;
    String id = student.uid;
}
複製程式碼

然後看看Kotlin的模仿版:

val p = Person()
if (p is Student) {
    val student = p as Student
    var uid = student.uid
}
複製程式碼

瞪大眼睛看看簡化版,找不同的時間到了!

val p = Person()
if (p is Student) {
    var uid = p.uid
}

is 進行判斷後,Person類例項就已經裝成Student類例項了,這就是Kotlin的智慧型別轉化。
複製程式碼

2.7 單例

  • java版單例(懶漢式和餓漢式),懶漢式如下:
public static RoomManager ins() {
    return SingletonHolder.INSTANCE;
}

private static class SingletonHolder {
    private static final RoomManager INSTANCE = new RoomManager();
}
複製程式碼
  • kotlin版
class AppManager private constructor(var info: String) {
    companion object {
        private var INSTANCE: AppManager? = null
        fun getInstance(info: String): AppManager {
            if (INSTANCE == null) {
                synchronized(AppManager::class.java) {
                    if (INSTANCE == null) {
                        INSTANCE = AppManager(info)
                    }
                }
            }
            return INSTANCE!!
        }
    }
    
    fun getPhoneInfo(arg: String): String? {
        return null
    }
}
複製程式碼

如果是無參單例,有更為簡單的寫法

/**
 * Created by YangLang on 2017/7/29.
 */
object AppManager {

    fun getPhoneInfo(arg: String): String? {
        return null
    }
}
複製程式碼

2.8 正規表示式

  • java的寫法
String info = "12,34,56.789";
String[] split = info.split("[,.]");
for (String s : split) {
    System.out.println(s);
}

java預設使用正則去切割
複製程式碼
  • Kotlin寫法
 var info = "12,34,56.789"
    for (s in info.split(",",".")) {
        println(s)
    }
    
kotlin 為了程式設計更加口頭化,生活化,預設不使用正則,上面寫法的理解是,把info用","或者"."進行分割。

//進一步說明
info.split(".")
1. 在java中輸出為空,因為.在正則規則中為統配符
2. 在kotlin中輸出為12,34,56和789,因為Kotlin預設不使用正則,所以.只是一個普通的字元。
複製程式碼

2.9 巢狀類和內部類

說明: 巢狀類不含有外部類的引用,內部類擁有外部類的引用,正因為這樣,也可以造成Activity沒有釋放,而記憶體洩漏問題。

  • java
public class Outter {

   //包含Outter類的引用,可以使用Outter的方法
    class Intter{}

    // 不包含Outter類引用,不能使用Outter的方法
    static class NotIntter{}
}
複製程式碼
  • Kotlin
class Outter {

    inner class Intter

    class NotIntter
}
複製程式碼

java預設為內部類,Kotlin預設為巢狀類,一定程度的避免造成記憶體洩漏無意識寫法。

2.10 密封類

密封類的概念和C#一樣,密封類可以被繼承,但本身不能被例項化。所有的子類必須在同一檔案中,相當於把密封類的子類都封裝在一塊,可以配合when使用。

sealed class BaseModel{}
class FirstModel:BaseModel(){}
class SecondModel:BaseModel(){}
複製程式碼
fun justDo(baseModel: BaseModel) = when(baseModel){
    is FirstModel -> ""
    is SecondModel -> ""
}

沒有使用else,但是必須要寫全baseModel的子類,如果編寫期間新建立一個類繼承BaseModel,此處when直接報錯,密封類的這種特性,保證編寫when表示式時,可以無一疏漏的考慮到每一種情況,約束程式碼的編寫。
複製程式碼

2.11 代理

代理方式\語言 java Kotlin
靜態代理
動態代理
屬性代理 ×
標準代理 ×

這裡不講java的設計模式,主要關注Kotlin的代理模式,從類代理、屬性代理、標準代理三個方面

  • 類代理

假如我們需要建立AnimatorListener的例項,Java中最常見的方式可能是匿名內部類,我們需要過載它的四個方法,雖然我們不都用,這樣程式碼就顯得多餘。Kotlin中就可以用該介面得一個實現類代為過載,並處理自己的TODO。

class MyAnimatorListener(var animatorListener: Animator.AnimatorListener):Animator.AnimatorListener by animatorListener{

    override fun onAnimationEnd(animation: Animator?) {
        //TODO 可以進行預處理
        animatorListener.onAnimationEnd(animation)
    }
}

介面Animator.AnimatorListener需要的方法由傳入引數animatorListener實現了,該MyAnimatorListener也通過關鍵字by,將需要實現的方法讓animatorListener代為實現,過載方法後可以做預處理操作。
複製程式碼
  • 屬性代理

當使用val修飾屬性時,表明屬性為不可修改,代理類繼承ReadOnlyProperty<被代理類,型別>類

當使用var修飾時,屬性可修改,代理類繼承ReadWriteProperty<Water,Boolean>

/**
 * Created by YangLang on 2017/7/29.
 */
fun main(args: Array<String>) {
    var p = Person("inke", 21)
    println("是否已經成人: ${p.isAdult}")
}

class Person(var name: String, var age: Int) {

    var isAdult: Boolean by isAdultProperty()
}

class isAdultProperty : ReadWriteProperty<Person, Boolean> {
    override fun setValue(thisRef: Person, property: KProperty<*>, value: Boolean) {
    }

    override fun getValue(thisRef: Person, property: KProperty<*>): Boolean {
        return thisRef.age > 18
    }
}
複製程式碼
  • 標準代理

Kotlin標準代理有lazy(),observable(),notNull().. 所有這樣Kotlin自建代理都在delegates類下,主要講lazy(懶載入) 在java中使用supplier和suppliers進行懶載入(延時求值),在kotlin中通過方法lazy直接延時求值,更快更強!!

class InfoConfig {
    val info: String by lazy {
        "Inke info"
    }
}

fun main(args: Array<String>) {
    print(InfoConfig().info)
}

第一次獲取info值時,會通過lazy函式傳入的函式引數進行求值,以後再獲取該值時,直接返回,所以懶載入可進行復雜計算。
複製程式碼

2.12 屬性賦初值問題

屬性不管申明為val還是var都必須賦初值,我們只能被迫寫成 var info:String? = null。但是實際情況下有些屬性不允許賦予無意義的初值如null,我總結有三種方式處理(可能不全面):

  • lateinit var info: String(只能用在var型別上)
  • class Student(var name: String, var age: Int){ val isAudit = age > 18} (可用在var或者val上,但只是在初始化時賦一次值~)
  • val info: String by lazy { "Inke info" } (只能用在val型別上)

2.13 lambda的概要(物件引用,陣列用法)

用lambda表示式建立函式“變形體”

 var add = { x: Int, y: Int -> x + y }
 print(add(1,2))
複製程式碼

lambda縮寫演史

// 1. 匿名內部類
rb_home.setOnClickListener(object:View.OnClickListener{
            override fun onClick(v: View?) {
            }
})

// 2. 如果內部類中只有一個方法,縮寫成lambda表示式
 rb_home.setOnClickListener({v -> Unit})
 
// 3. 如果函式引數最後一個引數為lambda表示式,可以提取放在函式引數括號外面
 rb_home.setOnClickListener(){v -> Unit}
 
 // 4. 如果函式引數括號為空,可以省略
 rb_home.setOnClickListener{v -> Unit}
 
 // 5. 如果lambda表示式形參咩有使用,可以省略
 rb_home.setOnClickListener{Unit} <==> rb_home.setOnClickListener{//TODO}

複製程式碼

可以根據以上的省略原則可以定義我們自己的的函式。如在java中做版本的判斷,我們會寫:

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
    webSettings.setAllowUniversalAccessFromFileURLs(true);
}

這樣寫起來不流程,而且不易複用
複製程式碼

Kotlin上面可以這樣定義函式

inline fun UP_VERSIONJELLY_BEAN(function: () -> Unit){
    if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEA){
        function()
    }
}

在使用時,可以毫不猶豫的寫成這樣:
UP_VERSIONJELLY_BEAN{
    //TODO
}

複製程式碼

2.14 反射中優質提升

public class Test<T> {

    public Test() {}
}

複製程式碼

以上的泛型寫法,在java中執行時由於型別擦除的原因,無法確定T的型別,很難獲取該物件的位元組碼物件,不過在早兩天看到Guava支援執行時獲取泛型型別,通過TypeToken工具類。但是在Kotlin可以輕鬆解決這樣問題,通過標註關鍵字reified。

inline fun <reified T: Activity> Activity.newIntent() {
    val intent = Intent(this, T::class.java)
    startActivity(intent)
}
複製程式碼

2.15 let apply run with的區別

傳送門地址

相關文章