超乾貨詳解:kotlin(4) java轉kotlin潛規則

前言

以一個java老鳥的角度,如何去看 kotlin。 Java源代碼應該如何用Kotlin重構。 如何正確學習kotlin並且應用到實際開發中。本文將會探究。

本文分兩大塊,重難點和潛規則。

重難點:Kotlin中可以獨立出來講解的大塊知識點。提供單獨Demo。這部分大多數是Kotlin開創的新概念(相比於Java)。

潛規則:Kotlin是谷歌用來替換Java的,它和java百分百完全兼容,但是實際上java轉成kotlin之後,需要我們手動修改很多東西,甚至某些部分必須打散重構來達到最優編碼。其中,kotlin的某些特性和java不同,甚至完全反轉。這部分知識點比較零碎,單獨Demo不方便提供,就以小例子的形式來寫。

  • 重難點lambda以及操作符高階函數以及操作符Kotlin泛型集合操作協程操作符重載
  • 潛規則Kotlin文件和類不存在一對一關係共生體繼承修飾符空指針問題

潛規則

從Java轉到kotlin,基本上都會存在java代碼與kotlin共存的問題。而且為了快速轉型,可能會直接把java類轉成kotlin類,而這個過程中,涉及到java和kotlin的交互,往往會磕磕碰碰,以下總結了一部分 java kotlin交互方面的問題.

Kotlin文件和類不存在一對一關係

kotlin的文件,可以和類名一致,也可以不一致。這種特性,和c++有點像,畢竟c++的.h 和 .cpp文件是分開的。只要最終編譯的時候對的上,文件名其實無所謂的。Java中,一個類文件的類名和文件名不一致,如果是public類,就會報異常。

在kotlin中,可以寫成一致,如:


超乾貨詳解:kotlin(4) java轉kotlin潛規則

不一致:


超乾貨詳解:kotlin(4) java轉kotlin潛規則

這樣做的意義在於:

如果有很多個行數很短的類:在java中可能要佔用大量的文件個數(Java中可以用內部類的形式解決),kotlin中則可以把這些類都放到同一個kt文件中,不用內部類也能解決。

共生體

Java中的靜態 static關鍵字,在kotlin中不復存在,作為替換,Kotlin提出了共生體的概念。如果是kt文件去調用kt類的“靜態”方法(不依賴對象),則要求後者的類結構中增加一個 companion object 成員變量。並且可以在 成員中寫上 你想要定義的"靜態"成員變量和成員方法

<code>class Test001(_name: String) : Person(_name) {
companion object {
const val s: String = ""
const val s2: String = ""

fun t1(){

}
}
}

fun main(){
Test001.s
Test001.t1()
}
/<code>

注:每一個kotlin類中,只能有一個共生體對象.

但是在java調用kt的"靜態"成員方法時,必須帶上共生體,但是,訪問"靜態"成員變量,則不能帶:

<code>public static void main(String[] args) {
Test001.Companion.t1();//Java訪問kt的t1()共生體方法,必須帶上Companion
String s2 = Test001.s;// 而訪問共生體成員變量,不能帶Companion
}
/<code>

好糾結。為什麼要這麼設計。算了。查了一下kt反編譯之後的Java源碼:

超乾貨詳解:kotlin(4) java轉kotlin潛規則

共生體變成了Java類中的靜態內部類,包含t1()方法。而s,s2 則是普通的靜態變量。

修飾符

修飾符指的是 類 和 成員變量,成員方法 前面的 權限訪問關鍵字。原 Java擁有 private ,protected,default ,public ,訪問權限分別為: 本類內部,同包名或子類,同包名,全局。

然而,kotlin新增了一個概念,internal ,表示,相同Module內可訪問,跨Module則不行。

並且,java和kotlin的 private ,protected,default ,public 的訪問權限還有區別,但是我這裡就不詳述了,因為我覺得意義不大。能不能訪問,寫代碼的時候編譯器會告訴你,當場警告你,你就會修改代碼。如果有問題。可以把kotlin Decompile成Java代碼自己去對比試試。如有需要,後期再說吧。

空指針問題

通常要快速的將 舊java代碼轉化成kotlin代碼,是拷貝java代碼粘貼到kotlin文件內,讓as自動轉化,但是這種方式,容易造成很多空指針問題,有一些是很直白的報了編譯錯誤,而有一些則是隱藏起來,等到程序運行時才會報錯。直接報錯的就不提了,下面演示隱藏的空指針問題:

Kotlin類:

<code>class Student(name:String) {
var name: String = name

fun showName(tag: String) {
println("$tag : $name")

}
}
/<code>

Java調用kt:

<code>public class Main {
public static void main(String[] args) {
Student s = new Student("zhou");
s.showName(null);
}
}
/<code>

此時,如果運行main函數,就會報出:

超乾貨詳解:kotlin(4) java轉kotlin潛規則

告訴我們參數tag不可為null。但是奇怪的是,在java代碼中,居然不會報編譯錯誤。賊特麼詭異。

解決方案:

在方法參數後面加上問號,變成這樣:

超乾貨詳解:kotlin(4) java轉kotlin潛規則

沒有基本數據類型

Kotlin之中沒有基本數據類型,它只有:Int,Short,Long,Float,Double,Byte ,Char,Boolean 這樣的包裝類型。為什麼沒有?沒有必要去糾結,但是隻提供包裝類型有一個好處,那就是 方便擴展函數的定義。我們可以很輕鬆地對 Int,類型去擴展函數。比如: Kotlin自帶了很多擴展函數:

超乾貨詳解:kotlin(4) java轉kotlin潛規則

這是系統定的,我們也可以自己來定義:

<code>fun Int.plus100(): Int {//自定義擴展
return this + 100
}
fun main() {
val a: Int = 20
println("${a.plus100()}")
}
/<code>

繼承

在用kt重構部分模塊的過程中,我發現頻繁出現下面的問題:

Kotlin基類:

<code>abstract class Person(name: String) {
var name: String? = name
}
/<code>

Java子類:

超乾貨詳解:kotlin(4) java轉kotlin潛規則

由於我是從基礎類開始重構,所以,在原先的Java代碼中頻繁出現了類似這種 訪問權限不足的問題。一個一個去改成setName函數,工作量巨大。後來找到一個辦法:

在kotin中加入 @JvmField 變成這樣:

<code>abstract class Person(name: String) {
@JvmField
var name: String? = name
}
/<code>

@JvmField可以讓 kotlin的成員屬性變成公有的,kt轉化成java時,會是如下這樣:

<code>public abstract class Person {
@JvmField
@Nullable
public String name;

public Person(@NotNull String name) {
Intrinsics.checkParameterIsNotNull(name, "name");
super();
this.name = name;
}
}
/<code>

兼容原先的Java代碼。不用大面積修改了。

默認支持可選命名參數

瞭解高級語言語法的同學肯定知道 可選命名參數可選位置參數,經測試: Kotlin的任何方法(包括構造方法和普通和方法),可以這麼寫:

<code>fun test001(s: String, s1: String) {
println("$s - $s1")
}

fun main() {
test001(s = "1111", s1 = "2222") //臥槽,Kotlin默認支持 可選命名參數

}
/<code>

這種特性可以很好的避免了Java中出現的一個方法包含N個參數 把人眼睛看花的情況:

<code>private void test(String s1, String s2, String s3, String s5, String s6, String s7, String s8, String s9, String s10, String s11, String s12) {
//...
}
/<code>

比如如上面所示,一個方法,有12個String參數,看多了會罵娘,誰特麼寫的。然而,用kotlin:

<code>fun test(s1: String, s2: String, s3: String, s4: String, s5: String, s6: String, s7: String, s8: String, s9: String, s10: String, s11: String, s12: String) {}
fun main() {
test(s1 = "",s2 = "",s3 = "",s4 = "",s5 = "",s6 = "",s7 = "",s8 = "",s9 = "",s10 = "",s11 = "",s12 = "")
}
/<code>

直覺上這種語法,融入了 建造者設計模式。讓同一個函數的多個參數不再混亂。當然如果你懷舊的話,你也可以用原始方法,12個string依次擺下去。反正我不會這麼做。

類,成員方法 默認封閉

和Java相反,kotlin給類,成員方法 都採用了默認封閉原則。具體體現為:類,默認不可繼承,成員方法默認不可重寫(繼承時)。如果要繼承類,或者重寫父類方法,必須在父類中手動加入 open 關鍵字,子類中重寫方法必須加上override關鍵字 :

kotlin父類:

<code>open class Student(name:String) {
var name: String = name

open fun showName(tag: String?) {
println("$tag : $name")
}
}
/<code>

kotlin子類:

<code>class StudentExt(name: String) : Student(name) {
override fun showName(tag: String?) {
super.showName(tag)
println("xxxxx")
}
}
/<code>

Kotlin中方法和函數的區別

函數,是c/c++的詞彙,而方法,則是Java裡面。現在kotlin中同時存在了方法和函數,那麼區別在哪裡?

通常我們人為:在Kotlin類內部,稱為成員方法。而在類外部定義的,則成為全局函數(這裡就不用去討論kotlin變成java之後長什麼樣)。

應用到具體場景,一句話解釋清楚:

A.kt 中有一個A類,它有a()成員方法。 同時我們可以在 B.kt中給A類擴展一個函數。創建一個A類對象之後,我們既可以調用a()成員方法,又可以調用它的擴展函數。

A.kt

<code>class A {
fun a() {}
}
/<code>

B.kt

<code>fun A.foo(){}// 擴展A的一個函數

fun main() {
val a = A()//創建對象
a.a() //調用成員方法
a.foo() //調用擴展函數
}
/<code>

結語

Java轉kotlin,給我的感覺就是:

  1. kotlin對於一個function內部的管理更加有條理,它引入了 scope 作用域的概念,利用基於lambda表達式的高階函數,把function內部的代碼塊管理起來,讓代碼可讀性更高
  2. kotlin的代碼行數會大大減少,因為kotlin設計了很多頂層函數,高階函數,使用大量的鏈式調用,把可以佔用行數的代碼都濃縮在一行。這樣做的結果是,一行代碼的信息量大大增加,對於新手是噩夢,但是對於kotlin熟手,會感覺很美妙。
  3. 關於協程,本文只做了最簡單的管中窺豹描述,未曾詳細說到的東西還有很多。但是可以肯定一點,協程的出現,顛覆了 android開發的異步編程思維,原本很多不敢想的,原本很多java實現起來要繞很多路的,在kotlin上都可以很優雅地實現。
超乾貨詳解:kotlin(4) java轉kotlin潛規則

Kotlin in Action 下載pdf書籍:(https://www.7down.com/soft/209822.html)

菜鳥教程: https://www.runoob.com/kotlin/kotlin-tutorial.htmlkotlin

中文站: https://www.kotlincn.net

最後,今天的分享到這裡就結束了,小編這裡也給大家整理了一份阿里的資源文檔,算是當做福利送給大家吧。

超乾貨詳解:kotlin(4) java轉kotlin潛規則

超乾貨詳解:kotlin(4) java轉kotlin潛規則

免費領取方式,本文轉發+關注私信資料 即可

這裡小on 祝大家學業昌隆哦!!!


分享到:


相關文章: