其實在剛接觸 Kotlin 的時候,我心裏是拒絕的,因爲看習慣了 Java 的代碼,以致於看 Kotlin 代碼時有一股極大的彆扭感,讓我不想去學習(其實在我學 C 語言的時候也是這個感受)。可是沒辦法呀,誰叫這貨是 Android 官方開發語言呢,再加上如今的招聘要求多多少少也要會 Kotlin 了,因而只能硬着頭皮上咯。java
相比較 Java ,Kotlin 沒有了基本數據類型,能夠看做是 Java 的基本類型包裝類拿了過來部分改了名字,以下:程序員
Java | Kotlin |
---|---|
byte | Byte |
short | Short |
int | Int |
long | Long |
float | Float |
double | Double |
boolean | Boolean |
char | Char |
另外字符串是支持模板的,看下面:數組
fun main() {
val hello = "Hello"
println("$hello, World!")
println("length = ${hello.length}")
}
// 輸出:Hello, World!
// length = 5
複製代碼
Kotlin 中沒有了 [] 進行數組聲明或建立,8 種基本數據類型有本身的數組類型,以下:安全
Java | Kotlin |
---|---|
byte[] | ByteArray |
short[] | ShortArray |
int[] | IntArray |
long[] | LongArray |
float[] | FloatArray |
double[] | DoubleArray |
boolean[] | BooleanArray |
char[] | CharArray |
那引用類型呢?例如咱們要使用一個 String 數組,咱們須要用 Array<T>
這麼一個類,泛型表明數組所屬類型。同理,基本數據類型也能夠用 Array<T>
來聲明,如 Array<Int>
,Array<Boolean>
等等。bash
初始化既能夠用它們的構造函數,也能夠用 Kotlin 提供的全局函數,arrayOf()
,emptyArray()
,前者接收一個可變參數,後者則是一個空數組。閉包
public 、protected 、private 和 internal 做爲 4 大訪問權限,去除了 default 。ide
public 做爲默認訪問權限,所以不少時候咱們能夠不用寫上 public 。函數
protected 相較於 Java 不變。學習
private 除了能夠修飾類級別,其他不變。爲何能夠修飾類呢?這是由於 Kotlin 中一個文件能夠聲明 1 個以上的類,對這些類加上 private 則表示僅能夠在當前文件中使用。ui
internal 表示模塊級別的訪問權限,被 internal 標識則表示僅在當前模塊可見,這在模塊封裝上是頗有幫助的。
Kotlin 中,類、字段和函數默認是 final 的,抽象類和接口除外。所以若是咱們想要它們變得可繼承,須要在前面加上 open 關鍵字。
對於普通類,若是想要字段或者函數可繼承,僅在字段或函數上加 open 是不夠的,由於這時候類仍是不可繼承的,所以類也須要加上 open ,這時字段和函數才真正可繼承。
若是不想一個已 open 的字段或函數可繼承,能夠用 final 關鍵字進行修飾。
Kotlin 中將類型進行了後置,什麼意思呢?舉個栗子:
// Java 中咱們這樣聲明變量和函數
public final class Person {
public final String name;
public int age;
public final void talk(String str) {
int temp = 0
}
}
// Kotlin 中等價於
class Person {
val name: String? = null
var age: Int = 0
fun talk(str: String?): Unit {
var temp = 0
}
}
複製代碼
首先 Kotlin 中是能夠省略分號的,固然前提是你一個語句一行,若是一行有兩個語句的話就不能省略了,正常狀況下我們都是一行一語句的,接着來分析上面的代碼。
變量:
因爲類型後置,所以 Kotlin 使用關鍵字 val 和 var 來表示變量,val (value) 表示只讀變量,在第一次賦值後就不可修改了;var (variable) 就是咱們在 Java 中經常使用的變量了。
在 Kotlin 中,成員變量是沒有默認值的,須要咱們手動賦值。
另外,Kotlin 是支持類型推斷的,在聲明變量時,能夠不用寫類型,前提是有提供初始值。儘管如此,Kotlin 仍然是強類型語言,類型不匹配照樣會報錯,例如 var age = 0
,在這個聲明中,儘管咱們沒有顯式提供類型,但初始值代表了這個變量就是一個 Int 變量(整型默認爲 Int),所以後續咱們只能賦 Int 值。
函數:
因爲類型後置,所以 Kotlin 使用關鍵字 fun 來表示函數,而且參數列表也遵循類型後置規則,Kotlin 中函數的參數是能夠有默認值的,如:
fun setName(name: String = "aaronzzx"): Unit {
// ...
}
複製代碼
這個 : Unit 是啥?Unit 至關於 Java 的 void ,: Unit 表示返回 Unit 類型,至關於沒有返回值,固然更多時候咱們不須要寫上這個多餘的東西,上面的代碼等價於:
fun setName(name: String = "aaronzzx") {
// ...
}
複製代碼
函數也能夠進行類型推斷,這裏指返回值推斷,僅在簡寫函數時可用,舉個栗子:
fun getAge(): Int {
return age;
}
// 等價於
fun getAge() = age
// 當咱們的函數只有 1 個語句時,即便沒有返回值,也能夠簡寫。
// 前提這個語句不是賦值語句,否則就 "=" 衝突了
fun function() {
operate()
}
// 等價於
fun function() = operate()
// 擁有可變參數的函數
fun function(vararg strs: String) {
for (string in strs) {
println(string)
}
}
// 參數爲接口或抽象類
fun initView() {
HttpUtils.request(object: Callback {
override fun onSuccess() {
// ...
}
override fun onFailure() {
// ...
}
})
}
複製代碼
Ps:你可能有發現類型後面跟了個 ?
,這個表示此變量可被賦值 null ,若是不加問號是沒辦法賦值 null 的,這和 Kotlin 的「空安全」有關,後面會說。
Kotlin 的構造函數在第一次看到可能會優勢懵逼,先來看下與 Java 的對比:
// Java
public class Person {
private String name;
private int age;
public Person() {
}
public Person(String name) {
this.name = name;
}
public Person(String name, int age) {
this.name = name;
this.age = age;
}
}
// Kotlin
class Person(
private var name: String? = null,
private var age: Int = 0
)
// 等價於
class Person(
name: String? = null,
age: Int = 0
) {
private var name: String? = name
private var age: Int = age
init {
// 可選
}
}
// 等價於
class Person() {
private var name: String? = null
private var age: Int = 0
constructor(name: String?): this() {
this.name = name
}
constructor(name: String?, age: Int): this() {
this.name = name
this.age = age
}
}
複製代碼
在這裏能夠看到,Kotlin 具備碾壓性的優點,對於須要重載構造函數,Kotlin 最少可用 4 行代碼搞定,接下來分析。
首先 Kotlin 能夠省略類體,由於主構造函數被設定在了類名上,主函數能夠有參數,也能夠沒有。若是有參數,能夠在參數名前面加關鍵字 val 或 var ,表示將這個參數做爲成員變量,在關鍵字前面能夠用訪問權限或註解進行修飾。
init 表示主構造函數的函數體,若是沒有特殊用途能夠被省略。
constructor 表示次構造函數,在擁有主構造函數的狀況下須要顯示調用主構造函數。
在使用 Java 時,對於 NullPointerException 真的是迫不得已,一不當心就報空指針異常,而後程序就 Crash 了。所以咱們只能常常寫以下的判空代碼:
public String getMobile(Response response) {
String mobile = null;
if (response != null) {
Data data = response.getData();
if (data != null) {
mobile = data.getMobile();
}
}
return mobile;
}
複製代碼
或許是知道 Java 程序員處於水深火熱之中,Kotlin 添加了空安全這一語言特性,使用 ?
對可空變量進行標識,而沒被 ?
標識的變量呢?理所固然的是不會爲空了,因而咱們對於不爲空的變量就不用寫繁瑣的判空代碼了,由於它不會出現空指針異常了。
對於可爲空的變量,咱們仍是須要進行判空的,可是形式不與 Java 同樣,因而上面的代碼能夠這麼寫:
fun getMobile(response: Response?): String? {
return response?.data?.mobile
}
複製代碼
?.
表示若是當前對象不爲空就調用,與 Java 相比,簡潔了好多。
即 companion object ,這是個啥呢?看下面這段代碼,你應該就明白了。
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val url = intent.getStringExtra(EXTRA_URL)
// ...
}
companion object {
private const val EXTRA_URL = "EXTRA_URL"
fun start(context: Context, url: String) {
val intent = Intent(context, MainActivity::class.java)
intent.putExtra(EXTRA_URL, url)
context.startActivity(intent)
}
}
}
複製代碼
說白了,這東西就至關於 Java 的 static ,只是 Kotlin 中沒有 static 這個概念,因此若是咱們要聲明靜態變量或者靜態函數,就須要用到 companion object 。
另外上面的代碼,可能有些地方你看起來不太明白,這裏說一下。
繼承與實現,統統使用 :
操做符,不一樣的類或接口用 ,
隔開,而且須要顯式調用父類構造函數。
首先 Kotlin 中建立對象是省略 new 關鍵字的,直接用構造函數便可。
Java 中的 @Override
註解在 Kotlin 中變成了關鍵字 override ,重寫必不可少。
對於無參的 getter 函數,能夠以變量名的形式書寫,例如 getIntent()
就是 intent
。
const val 關鍵字用來聲明常量,僅在 object 、companion object 和 頂層變量 中可用。
object 匿名內部類和單例關鍵字,若是要建立一個單例類,只需這麼寫。
object Singleton {
fun exec() {
// ...
}
}
class Test {
fun function() {
// 儘管看起來有點像靜態調用,但這就是單例
Singleton.exec()
}
}
複製代碼
頂層變量,用代碼比較直觀,以下:
const val TAG = "當前文件"
class MainActivity : AppCompatActivity() {
// ...
}
複製代碼
頂層變量 屬於文件,不屬於類或接口等等。
在 Kotlin 中分支語句屬於表達式,所以咱們能夠這麼寫代碼:
fun function(type: Int) {
// if-else
var str = if (type == 1) {
"Hello, World!"
} else if (type == 2) {
"Goodbye!"
} else ""
// switch ,在 Kotlin 中關鍵字變爲 when
str = when (type) {
1 -> "Hello, World!"
2 -> "Goodbye!"
else -> ""
}
// 或者 when 不帶參數
str = when {
type == 1 -> "Hello, World!"
type == 2 -> "Goodbye!"
else -> ""
}
}
複製代碼
當分支語句做爲表達式時,除非你的條件已經包括全部狀況了,不然必定要有 else 。
while 和 do-while 相較於 Java 不變,for 有改動,舉個栗子:
fun function() {
// for (int i = 0; i < 10; i++)
// 打印的 i 值 是同樣的
for (i in 0..9) {
println(i)
}
for (i in 0 until 10) {
println(i)
}
// 下面這段將依次打印 10-0
for (i in 10 downTo 0) {
println (i)
}
// 還能夠控制步長
// 依次打印 0, 2, 4, 6, 8, 10
for (i in 0..10 step 2) {
println(i)
}
}
複製代碼
對於任何類型,咱們均可以擅自給他添加新的函數,而後就能夠用這個類對象調用咱們自定義的擴展函數了,直接看代碼:
fun String.suffix(suffix: String): String {
return this + suffix
}
fun function() {
val str = "Hello"
val newStr = str.suffix(", World!")
println(str)
println(newStr)
}
// 將依次輸出:Hello
// Hello, World!
複製代碼
高階函數是將函數用做參數或返回值的函數。
在這裏咱們會接觸到一個東西:閉包, 閉包就是可以讀取其餘函數內部變量的函數。
class Operator {
fun add(a: Int, b: Int) = a + b
fun minus(a: Int, b: Int) = a - b
fun change( a: Int, b: Int, closure: (Int, Int) -> Int
): String {
return "${closure(a, b)}"
}
}
fun main() {
val operator = Operator()
val add: (Int, Int) -> Int = operator::add
val str1 = operator.change(2, 2, add)
val str2 = operator.change(5, 2) { a: Int, b: Int ->
a - b
}
println("str1 = $str1")
println("str2 = $str2")
}
// 輸出:str1 = 4
// str2 = 3
複製代碼
從 main 函數中,能夠看到變量 add 擁有函數類型 (Int, Int) -> Int ,這意味着這個變量只能被函數賦值,對象名::add
表示拿到這個函數的函數類型。
接着看下面的 change 函數,這個大括號裏面的代碼其實就是閉包,雖然看起來好像是從新定義了一個函數,當一個函數的參數列表最後一個參數是一個閉包時,能夠單獨將閉包抽出來放在小括號後面,若是參數列表只有一個閉包,那麼小括號均可以省略。閉包中,->
左邊表示閉包的參數,類型能夠省略。
閉包其實看起來有點像回調,咱們先寫好代碼,而後在 change 函數中進行調用。
函數類型能夠沒有參數,可是小括號是不能省略的,包括返回類型也不可省略,例如一個沒有參數沒有返回值的函數類型,能夠這樣表示:() -> Unit
。
另外函數類型也是可空的,舉個栗子:
fun main() {
// 當加上?時,函數類型須要用括號括起來
val function: (() -> Unit)? = {
// ...
}
// 調用時須要用 invoke 進行調用
function?.invoke()
}
複製代碼