原文地址:[Kotlin 的變量、函數和類型](https://kaixue.io/kotlin-basic-1/)
[TOC]
## 為項目添加 Kotlin 語言的支持
學習 Kotlin 的第一步就是要為項目添加 Kotlin 語言的支持,這非常簡單。
### 新建支持 Kotlin 的 Android 項目
如果你要新建一個支持 Kotlin 的 Android 項目,只需要如下操作:
* File -> New -> New Project …
* Choose your project -> Phone and Tablet -> Empty Activity
* Configure your project -> Language 選擇 「Kotlin」
別的都和創建一個普通的 Android 項目一樣,創建出的項目就會是基于 Kotlin 的了。
所謂「基于 Kotlin」,意思有兩點:
1. IDE 幫你自動創建出的`MainActivity`是用 Kotlin 寫的:
~~~kotlin
package org.kotlinmaster
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
class MainActivity : AppCompatActivity() {
...
}
~~~
>[info]掃一眼就好,不用讀代碼,我們后面都會講。
2. 項目中的 2 個`bulid.gradle`文件比 Java 的 Android 項目多了幾行代碼(以「??」標注),它們的作用是添加 Kotlin 的依賴:
* 項目根目錄下的`build.gradle`:
~~~groovy
buildscript {
??
ext.kotlin_version = '1.3.41'
repositories {
...
}
dependencies {
classpath 'com.android.tools.build:gradle:3.5.0-beta05'
??
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
}
}
~~~
app 目錄下的`build.gradle`:
~~~groovy
apply plugin: 'com.android.application'
??
apply plugin: 'kotlin-android'
...
android {
...
}
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
??
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
...
}
~~~
也就是說,如果你是要創建一個新項目,記得把語言選擇為 Kotlin,項目創建完成后你就可以用 Kotlin 來寫它了。
### 給現有項目添加 Kotlin 支持
如果是現有的項目要支持 Kotlin,只需要像上面這樣操作,把兩個`build.gradle`中標注的代碼對應貼到你的項目里就可以了。
筆者建議剛開始學習的時候還是新建一個基于 Kotlin 的項目,按照上面的步驟練習一下。
### 初識 MainActivity.kt
前面我們提到,如果新建的項目是基于 Kotlin 的,IDE 會幫我們創建好`MainActivity`,它其實是有一個`.kt`的文件后綴名(打開的時候可以看到)。
>[info] Kotlin 文件都是以`.kt`結尾的,就像 Java 文件是以`.java`結尾。
我們看看這個`MainActivity.kt`里到底有些什么:
~~~kotlin
package org.kotlinmaster
??
import android.os.Bundle
??
import androidx.appcompat.app.AppCompatActivity
??
class MainActivity : AppCompatActivity() {
??
?? ?? ?? ??
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
}
}
~~~
乍一看,「??」標注的`package` `import` `class`這些 Java 里的東西,Kotlin 也有;但是也有一些以「??」標注的在 Java 里是沒見過的。
為了暫時避開這些干擾,我們自己新建一個文件。
* 在新建 Java Class 的入口下面可以看見一個叫 「Kotlin File/Class」 的選項,這就是我們新建 Kotlin 文件的入口
* New Kotlin File/Class
* Name: Sample
* Kind: Class
創建完成后的`Sample.kt`:
~~~kotlin
package org.kotlinmaster
class Sample {}
~~~
這個類僅包含`package`和`class`兩個關鍵字,我們暫時先看成和 Java 差不多(其實真的就是差不多)的概念,這樣就都是我們熟悉的東西了。
接下來,讓我們開始學習基礎語法吧。
* * *
## 變量
### 變量的聲明與賦值
>[info] 這里講一個 Java 和 Kotlin 命名由來的小插曲。
我們知道 Java 就是著名的爪哇島,爪哇島盛產咖啡,據說就是一群研究出 Java 語言的牛人們在為它命名時由于聞到香濃的咖啡味,遂決定采用此名稱。
Kotlin 來源于芬蘭灣中的 Kotlin 島。
因此,我們在代碼段的開頭以「??」來表示 Java 代碼段,「???」來表示 Kotlin 代碼段。
我們回憶下 Java 里聲明一個 View 類型的變量的寫法:
~~~java
??
View v;
~~~
Kotlin 里聲明一個變量的格式是這樣的:
~~~kotlin
???
var v: View
~~~
這里有幾處不同:
* 有一個`var`關鍵字
* 類型和變量名位置互換了
* 中間是用冒號分隔的
* 結尾沒有分號(對,Kotlin 里面不需要分號)
看上去只是語法格式有些不同,但如果真這么寫,IDE 會報錯:
~~~kotlin
???
class Sample {
var v: View
// ??這樣寫 IDE 會報如下錯誤
// Property must be initialized or be abstract
}
~~~
這個提示是在說,屬性需要在聲明的同時初始化,除非你把它聲明成抽象的。
* 那什么是屬性呢?這里我們可以簡單類比 Java 的 field 來理解 Kotlin 的 Property,雖然它們其實有些不一樣,Kotlin 的 Property 功能會多些。
* 變量居然還能聲明成抽象的?嗯,這是 Kotlin 的功能,不過這里先不理它,后面會講到。
>[success]注意:這里的示例中的屬性是類中的屬性,如果是函數中的屬性,沒有這種硬性要求,可以出現這種形式:`var a: Float`
屬性為什么要求初始化呢?因為 **Kotlin 的變量是沒有默認值的**,這點不像 Java,Java 的 field 有默認值:
~~~java
??
String name; // ??默認值是 null
int count; // ??默認值是 0
~~~
但這些 Kotlin 是沒有的。不過其實,Java 也只是 field 有默認值,局部變量也是沒有默認值的,如果不給它初始值也會報錯:
~~~java
??
void run() {
int count;
count++;
// ??IDE 報錯,Variable 'count' might not have been initialized
}
~~~
既然這樣,那我們就給它一個默認值 null 吧,遺憾的是你會發現仍然報錯。
~~~kotlin
???
class Sample {
var v: View = null
// ??這樣寫 IDE 仍然會報錯,Null can not be a value of a non-null type View
}
~~~
又不行,IDE 告訴我需要賦一個非空的值給它才行,怎么辦?Java 的那套不管用了。
其實這都是 Kotlin 的空安全設計相關的內容。很多人嘗試上手 Kotlin 之后快速放棄,就是因為搞不明白它的空安全設計,導致代碼各種拒絕編譯,最終只能放棄。所以咱先別急,我先來給你講一下 Kotlin 的空安全設計。
### Kotlin 的空安全設計
簡單來說就是通過 IDE 的提示來避免調用 null 對象,從而避免 NullPointerException。其實在 androidx 里就有支持的,用一個注解就可以標記變量是否可能為空,然后 IDE 會幫助檢測和提示,我們來看下面這段 Java 代碼:
~~~java
??
@NonNull
View view = null;
// ??IDE 會提示警告,'null' is assigned to a variable that is annotated with @NotNull
~~~
而到了 Kotlin 這里,就有了語言級別的默認支持,而且提示的級別從 warning 變成了 error(拒絕編譯):
~~~kotlin
???
var view: View = null
// ??IDE 會提示錯誤,Null can not be a value of a non-null type View
~~~
**在 Kotlin 里面,所有的變量默認都是不允許為空的,如果你給它賦值 null,就會報錯**,像上面那樣。
這種有點強硬的要求,其實是很合理的:既然你聲明了一個變量,就是要使用它對吧?那你把它賦值為 null 干嘛?要盡量讓它有可用的值啊。Java 在這方面很寬松,我們成了習慣,但 Kotlin 更強的限制其實在你熟悉了之后,是會減少很多運行時的問題的。
不過,**還是有些場景,變量的值真的無法保證空與否**,比如你要從服務器取一個 JSON 數據,并把它解析成一個 User 對象:
~~~kotlin
???
class User {
var name: String = null // ??這樣寫會報錯,但該變量無法保證空與否
}
~~~
這個時候,空值就是有意義的。對于這些可以為空值的變量,你可以在類型右邊加一個`?`號,解除它的非空限制:
~~~kotlin
???
class User {
var name: String? = null
}
~~~
**加了問號之后,一個 Kotlin 變量就像 Java 變量一樣沒有非空的限制,自由自在了**。
你除了在初始化的時候可以給它賦值為空值,在代碼里的任何地方也都可以:
~~~kotlin
???
var name: String? = "Mike"
...
name = null // ??原來不是空值,賦值為空值
~~~
這種類型之后加`?`的寫法,在 Kotlin 里叫**可空類型**。
不過,當我們使用了可空類型的變量后,會有新的問題:
由于對空引用的調用會導致空指針異常,所以 Kotlin 在可空變量直接調用的時候 IDE 會報錯:
~~~kotlin
???
var view: View? = null
view.setBackgroundColor(Color.RED)
// ??這樣寫會報錯,Only safe (?.) or non-null asserted (!!.) calls are allowed on a nullable receiver of type View?
~~~
「可能為空」的變量,Kotlin 不允許用。那怎么辦?我們嘗試用之前檢查一下,但似乎 IDE 不接受這種做法:
~~~kotlin
???
if (view != null) {
view.setBackgroundColor(Color.RED)
// ??這樣寫會報錯,Smart cast to 'View' is impossible, because 'view' is a mutable property that could have been changed by this time
}
~~~
這個報錯的意思是**即使你檢查了非空也不能保證下面調用的時候就是非空,因為在多線程情況下,其他線程可能把它再改成空的**。
那么 Kotlin 里是這么解決這個問題的呢?它用的不是`.`而是`?.`:
~~~kotlin
???
view?.setBackgroundColor(Color.RED)
~~~
**這個寫法同樣會對變量做一次非空確認之后再調用方法,這是 Kotlin 的寫法**,并且它可以**做到線程安全**,因此這種寫法叫做「**safe call**」。
另外還有一種雙感嘆號的用法:
~~~kotlin
???
view!!.setBackgroundColor(Color.RED)
~~~
意思是告訴編譯器,我保證這里的 view 一定是非空的,編譯器你不要幫我做檢查了,有什么后果我自己承擔。這種「肯定不會為空」的斷言式的調用叫做 「**non-null asserted call**」。**一旦用了非空斷言,實際上和 Java 就沒什么兩樣了,但也就享受不到 Kotlin 的空安全設計帶來的好處(在編譯時做檢查,而不是運行時拋異常)了**。
以上就是 Kotlin 的空安全設計。
理解了它之后再來看變量聲明,跟 Java 雖然完全不一樣,只是寫法上不同而已。
很多人在上手的時候都被變量聲明搞懵,原因就是 Kotlin 的空安全設計所導致的這些報錯:
* 變量需要手動初始化,如果不初始化的話會報錯;
* 變量默認非空,所以初始化賦值 null 的話報錯,之后再次賦值為 null 也會報錯;
* 變量用`?`設置為可空的時候,使用的時候因為「可能為空」又報錯。
明白了空安全設計的原理后,就很容易能夠解決上面的問題了。
關于空安全,最重要的是記住一點:**所謂「可空不可空」,關注的全都是使用的時候,即「這個變量在使用時是否可能為空」**。
另外,Kotlin 的這種空安全設計在與 Java 的互相調用上是完全兼容的,這里的兼容指:
* Java 里面的 @Nullable 注解,在 Kotlin 里調用時同樣需要使用`?.`。
~~~java
??
@Nullable
String name;
~~~
~~~kotlin
???
name?.length
~~~
* Java 里面的 @Nullable 和 @NonNull 注解,在轉換成 Kotlin 后對應的就是可空變量和不可空變量,至于怎么將 Java 代碼轉換為 Kotlin,Android Studio 給我們提供了很方便的工具(但并不完美),后面會講。
~~~java
??
@Nullable
String name;
@NonNull
String value = "hello";
~~~
~~~kotlin
???
var name: String? = null
var value: String = "hello"
~~~
空安全我們講了這么多,但是有些時候我們聲明一個變量是不會讓它為空的,比如 view,其實在實際場景中我們希望它一直是非空的,可空并沒有業務上的實際意義,使用`?.`影響代碼可讀性。
但如果你在`MainActivity`里這么寫:
~~~kotlin
???
class MainActivity : AppCompatActivity() {
??
var view: View = findViewById(R.id.tvContent)
}
~~~
雖然編譯器不會報錯,但程序一旦運行起來就 crash 了,原因是 findViewById() 是在 onCreate 之后才能調用。
那怎么辦呢?其實我們很想告訴編譯器「**我很確定我用的時候絕對不為空,但第一時間我沒法給它賦值**」。
Kotlin 給我們提供了一個選項:**延遲初始化**。
### 延遲初始化
具體是這么寫的:
~~~kotlin
???
lateinit var view: View
~~~
**這個`lateinit`的意思是:告訴編譯器我沒法第一時間就初始化,但我肯定會在使用它之前完成初始化的**。
它的作用就是讓 IDE 不要對這個變量檢查初始化和報錯。換句話說,**加了這個`lateinit`關鍵字,這個變量的初始化就全靠你自己了,編譯器不幫你檢查了**。
然后我們就可以在 onCreate 中進行初始化了:
~~~kotlin
???
??
lateinit var view: View
override fun onCreate(...) {
...
??
view = findViewById(R.id.tvContent)
}
~~~
哦對了,**延遲初始化對變量的賦值次數沒有限制,你仍然可以在初始化之后再賦其他的值給`view`**。
### 類型推斷
**Kotlin 有個很方便的地方是,如果你在聲明的時候就賦值,那不寫變量類型也行**:
~~~kotlin
???
var name: String = "Mike"
??
var name = "Mike"
~~~
這個特性叫做**類型推斷,它跟動態類型是不一樣的**,我們不能像使用 Groovy 或者 JavaScript 那樣使用在 Kotlin 里這么寫:
~~~kotlin
???
var name = "Mike"
name = 1
// ??會報錯,The integer literal does not conform to the expected type String
~~~
~~~groovy
// Groovy
def a = "haha"
a = 1
// ??這種先賦值字符串再賦值數字的方式在 Groovy 里是可以的
~~~
**「動態類型」是指變量的類型在運行時可以改變;而「類型推斷」是你在代碼里不用寫變量類型,編譯器在編譯的時候會幫你補上。因此,Kotlin 是一門靜態語言**。
除了變量賦值這個場景,類型推斷的其他場景我們之后也會遇到。
### val 和 var
聲明變量的方式也不止 var 一種,我們還可以使用 val:
~~~kotlin
???
val size = 18
~~~
val 是 Kotlin 在 Java 的「變量」類型之外,又增加的一種變量類型:**只讀變量。它只能賦值一次,不能修改**。而 **var 是一種可讀可寫變量**。
>[info] var 是 variable 的縮寫,val 是 value 的縮寫。
**val 和 Java 中的 final 類似**:
~~~java
??
final int size = 18;
~~~
不過**其實它們還是有些不一樣的,這個我們之后再講。總之直接進行重新賦值是不行的**。
### 可見性
看到這里,我們似乎都沒有在 Kotlin 里看到類似 Java 里的 public、protected、private 這些表示變量可見性的修飾符,**因為在 Kotlin 里變量默認就是public的**,而對于其他可見性修飾符,我們之后會講,這里先不用關心。
至此,我相信你對變量這部分已經了解得差不多了,可以根據前面的例子動手嘗試嘗試。
## 函數
Kotlin 除了變量聲明外,函數的聲明方式也和 Java 的方法不一樣。Java 的方法(method)在 Kotlin 里叫函數(function),其實沒啥區別,或者說其中的區別我們可以忽略掉。**對任何編程語言來講,變量就是用來存儲數據,而函數就是用來處理數據**。
### 函數的聲明
我們先來看看 Java 里的方法是怎么寫的:
~~~java
??
Food cook(String name) {
...
}
~~~
而到了 Kotlin,函數的聲明是這樣:
~~~kotlin
???
?? ??
fun cook(name: String): Food {
...
}
~~~
* **以 fun 關鍵字開頭**
* **返回值寫在了函數和參數后面**
那如果**沒有返回值**該怎么辦?Java 里是返回 void:
~~~java
??
void main() {
...
}
~~~
**Kotlin 里是返回 Unit,并且可以省略**:
~~~kotlin
???
??
fun main(): Unit {}
// Unit 返回類型可以省略
fun main() {}
~~~
**函數參數也可以有可空的控制**,根據前面說的空安全設計,在傳遞時需要注意:
~~~kotlin
???
// ??可空變量傳給不可空參數,報錯,即便它后面賦值了
var myName : String? = "rengwuxian"
fun cook(name: String) : Food {}
cook(myName)
// ??可空變量傳給可空參數,正常運行
var myName : String? = "rengwuxian"
fun cook(name: String?) : Food {}
cook(myName)
// ??不可空變量傳給不可空參數,正常運行
var myName : String = "rengwuxian"
fun cook(name: String) : Food {}
cook(myName)
~~~
### 可見性
**函數如果不加可見性修飾符的話,默認的可見范圍和變量一樣也是 public 的,但有一種情況例外,這里簡單提一下,就是遇到了`override`關鍵字的時候**,下面會講到。
### 屬性的 getter/setter 函數
我們知道,在 Java 里面的 field 經常會帶有 getter/setter 函數:
~~~java
??
public class User {
String name;
public String getName() {
return this.name;
}
public void setName(String name) {
this.name = name;
}
}
~~~
它們的作用就是可以自定義函數內部實現來達到「鉤子」的效果,比如下面這種:
~~~java
??
public class User {
String name;
public String getName() {
return this.name + " nb";
}
public void setName(String name) {
this.name = "Cute " + name;
}
}
~~~
在 Kotlin 里,這種 getter / setter 是怎么運作的呢?
~~~kotlin
???
class User {
var name = "Mike"
fun run() {
name = "Mary"
// ??的寫法實際上是??這么調用的
// setName("Mary")
// 建議自己試試,IDE 的代碼補全功能會在你打出 setn 的時候直接提示 name 而不是 setName
println(name)
// ??的寫法實際上是??這么調用的
// print(getName())
// IDE 的代碼補全功能會在你打出 getn 的時候直接提示 name 而不是 getName
}
}
~~~
那么我們如何來操作前面提到的「鉤子」呢?看下面這段代碼:
~~~kotlin
???
class User {
var name = "Mike"
??
get() {
return field + " nb"
}
?? ??
set(value) {
field = "Cute " + value
}
}
~~~
格式上和 Java 有一些區別:
* getter / setter 函數有了專門的關鍵字 get 和 set
* getter / setter 函數位于 var 所聲明的變量下面
* setter 函數參數是 value
除此之外還多了一個叫 field 的東西。這個東西叫做「**Backing Field**」,中文翻譯是**幕后字段**或**后備字段**(馬云背后的女人??)。具體來說,你的這個代碼:
~~~kotlin
???
class Kotlin {
var name = "kaixue.io"
}
~~~
在編譯后的字節碼大致等價于這樣的 Java 代碼:
~~~java
??
public final class Kotlin {
@NotNull
private String name = "kaixue.io";
@NotNull
public final String getName() {
return this.name;
}
public final void setName(@NotNull String name) {
this.name = name;
}
}
~~~
**上面的那個`String name`就是 Kotlin 幫我們自動創建的一個 Java field。這個 field 對編碼的人不可見,但會自動應用于 getter 和 setter,因此它被命名為Backing Field(backing 的意思是在背后進行支持,例如你闖了大禍,我動用能量來保住你的人頭,我就是在 back you)**。
所以,**雖然 Kotlin 的這個`field`本質上確實是一個 Java 中的 field,但對于 Kotlin 的語法來講,它和 Java 里面的 field 完全不是一個概念。在 Kotlin 里,它相當于每一個 var 內部的一個變量**。
我們前面講過 val 是只讀變量,只讀的意思就是說 val 聲明的變量不能進行重新賦值,也就是說不能調用 setter 函數,因此,**val 聲明的變量是不能重寫 setter 函數的,但它可以重寫 getter 函數**:
~~~kotlin
???
val name = "Mike"
get() {
return field + " nb"
}
~~~
**val 所聲明的只讀變量,在取值的時候仍然可能被修改,這也是和 Java 里的 final 的不同之處**。
關于「鉤子」的作用,除了修改取值和賦值,也可以加一些自己的邏輯,就像我們在 Activity 的生命周期函數里做的事情一樣。
## 類型
講完了變量和函數,接下來我們可以系統性地學習下 Kotlin 里的類型。
### 基本類型
**在 Kotlin 中,所有東西都是對象,Kotlin 中使用的基本類型有:數字、字符、布爾值、數組與字符串**。
~~~kotlin
???
var number: Int = 1 // ??還有 Double Float Long Short Byte 都類似
var c: Char = 'c'
var b: Boolean = true
var array: IntArray = intArrayOf(1, 2) // ??類似的還有 FloatArray DoubleArray CharArray 等,intArrayOf 是 Kotlin 的 built-in 函數
var str: String = "string"
~~~
這里有兩個地方和 Java 不太一樣:
* **Kotlin 里的 Int 和 Java 里的 int 以及 Integer 不同,主要是在裝箱方面不同**。
Java 里的 int 是 unbox 的,而 Integer 是 box 的:
~~~java
??
int a = 1;
Integer b = 2; // ??會被自動裝箱 autoboxing
~~~
**Kotlin 里,Int 是否裝箱根據場合來定**:
~~~kotlin
???
var a: Int = 1 // unbox
var b: Int? = 2 // box
var list: List<Int> = listOf(1, 2) // box
~~~
Kotlin 在語言層面簡化了 Java 中的 int 和 Integer,但是我們對是否裝箱的場景還是要有一個概念,因為這個**牽涉到程序運行時的性能開銷**。
**因此在日常的使用中,對于 Int 這樣的基本類型,盡量用不可空變量**。
* Java 中的數組和 Kotlin 中的數組的寫法也有區別:
~~~java
??
int[] array = new int[] {1, 2};
~~~
而在 Kotlin 里,上面的寫法是這樣的:
~~~kotlin
???
var array: IntArray = intArrayOf(1, 2)
// ??這種也是 unbox 的
~~~
簡單來說,原先在 Java 里的基本類型,類比到 Kotlin 里面,條件滿足如下之一就**不裝箱**:
* **不可空類型**。
* **使用 IntArray、FloatArray 等**。
### 類和對象
現在可以來看看我們的老朋友`MainActivity`了,重新認識下它:
~~~kotlin
???
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
...
}
}
~~~
我們可以對比 Java 的代碼來看有哪些不同:
~~~java
??
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
...
}
}
~~~
* 首先是類的可見性,Java 中的 public 在 Kotlin 中可以省略,**Kotlin 的類默認是 public 的**。
* 類的繼承的寫法,Java 里用的是`extends`,而在 Kotlin 里使用`:`,但其實`:`不僅可以表示繼承,還可以表示 Java 中的`implement`。
舉個例子,假設我們有一個 interface 叫 Impl:
~~~kotlin
???
interface Impl {}
~~~
>[info] Kotlin 里定義一個 interface 和 Java 沒什么區別。
~~~java
??
public class Main2Activity extends AppCompatActivity implements Impl { }
~~~
~~~kotlin
???
class MainActivity : AppCompatActivity(), Impl {}
~~~
* 構造方法的寫法不同。
* Java 里省略了默認的構造函數:
* ~~~java
??
public class MainActivity extends AppCompatActivity {
// ??默認構造函數
public MainActivity() {
}
}
~~~
* Kotlin 里我們注意到 AppCompatActivity 后面的`()`,這其實也是一種省略的寫法,等價于:
* ~~~kotlin
???
class MainActivity constructor() : AppCompatActivity() {
??
}
~~~
不過其實更像 Java 的寫法是這樣的:
~~~kotlin
???
// ??注意這里 AppCompatActivity 后面沒有 '()'
class MainActivity : AppCompatActivity {
constructor() {
}
}
~~~
**Kotlin 把構造函數單獨用了一個`constructor`關鍵字來和其他的普通函數`fun`做區分**。
* override 的不同
* Java 里面`@Override`是注解的形式。
* Kotlin 里的`override`變成了關鍵字。
* Kotlin 省略了`protected`關鍵字,也就是說,**Kotlin 里的`override`函數的可見性是繼承自父類的**。
除了以上這些明顯的不同之外,還有一些不同點從上面的代碼里看不出來,但當你寫一個類去繼承`MainActivity`時就會發現:
* **Kotlin 里的 MainActivity 無法繼承**:
~~~kotlin
???
// ??寫法會報錯,This type is final, so it cannot be inherited from
class NewActivity: MainActivity() {
}
~~~
原因是 **Kotlin 里的類默認是 final 的**,而 Java 里只有加了`final` 關鍵字的類才是 final 的。
那么有什么辦法解除 final 限制么?我們**可以使用`open`來做這件事**:
~~~kotlin
???
open class MainActivity : AppCompatActivity() {}
~~~
這樣一來,我們就可以繼承了。
~~~kotlin
???
class NewActivity: MainActivity() {}
~~~
但是要注意,**此時 NewActivity 仍然是 final 的**,也就是說,**`open`沒有父類到子類的遺傳性**。
而**剛才說到的`override`是有遺傳性的**:
~~~kotlin
???
class NewActivity : MainActivity() {
// ??onCreate 仍然是 override 的
override fun onCreate(savedInstanceState: Bundle?) {
...
}
}
~~~
**如果要關閉`override`的遺傳性,只需要這樣即可**:
~~~kotlin
???
open class MainActivity : AppCompatActivity() {
// ??加了 final 關鍵字,作用和 Java 里面一樣,關閉了 override 的遺傳性
final override fun onCreate(savedInstanceState: Bundle?) {
...
}
}
~~~
* Kotlin 里除了新增了`open`關鍵字之外,也有和 Java 一樣的`abstract`關鍵字,這倆關鍵字的區別就是`abstract`關鍵字修飾的類無法直接實例化,并且通常來說會和`abstract`修飾的函數一起出現,當然,也可以沒有這個`abstract`函數。
~~~kotlin
???
abstract class MainActivity : AppCompatActivity() {
abstract fun test()
}
~~~
**但是子類如果要實例化,還是需要實現這個 abstract 函數的**:
~~~kotlin
???
class NewActivity : MainActivity() {
override fun test() {}
}
~~~
當我們聲明好一個類之后,我們就可以實例化它了,實例化在 Java 中使用`new`關鍵字:
~~~java
??
void main() {
Activity activity = new NewActivity();
}
~~~
而**在 Kotlin 中,實例化一個對象更加簡單,沒有`new`關鍵字**:
~~~kotlin
???
fun main() {
var activity: Activity = NewActivity()
}
~~~
通過`MainActivity`的學習,我們知道了 Java 和 Kotlin 中關于類的聲明主要關注以下幾個方面:
* 類的可見性和開放性
* 構造方法
* 繼承
* override 函數
### 類型的判斷和強轉
剛才講的實例化的例子中,我們實際上是把子類對象賦值給父類的變量,這個概念在 Java 里叫多態,**Kotlin 也有這個特性,但在實際工作中我們很可能會遇到需要使用子類才有的函數**。
比如我們先在子類中定義一個函數:
~~~kotlin
???
class NewActivity : MainActivity() {
fun action() {}
}
~~~
那么接下來這么寫是無法調用該函數的:
~~~kotlin
???
fun main() {
var activity: Activity = NewActivity()
// ??activity 是無法調用 NewActivity 的 action 方法的
}
~~~
在 Java 里,需要先使用`instanceof`關鍵字判斷類型,再通過強轉來調用:
~~~java
??
void main() {
Activity activity = new NewActivity();
if (activity instanceof NewActivity) {
((NewActivity) activity).action();
}
}
~~~
Kotlin 里同樣有類似解決方案,**使用`is`關鍵字進行「類型判斷」,并且因為編譯器能夠進行類型推斷,可以幫助我們省略強轉的寫法**:
~~~kotlin
???
fun main() {
var activity: Activity = NewActivity()
if (activity is NewActivity) {
// ??的強轉由于類型推斷被省略了
activity.action()
}
}
~~~
那么能不能**不進行類型判斷,直接進行強轉調用呢?可以使用`as`關鍵字**:
~~~kotlin
???
fun main() {
var activity: Activity = NewActivity()
(activity as NewActivity).action()
}
~~~
這種寫法如果強轉類型操作是正確的當然沒問題,但**如果強轉成一個錯誤的類型,程序就會拋出一個異常**。
我們**更希望能進行安全的強轉,可以更優雅地處理強轉出錯的情況**。
這一點,**Kotlin 在設計上自然也考慮到了,我們可以使用`as?`來解決**:
~~~kotlin
???
fun main() {
var activity: Activity = NewActivity()
// ??'(activity as? NewActivity)' 之后是一個可空類型的對象,所以,需要使用 '?.' 來調用
(activity as? NewActivity)?.action()
}
~~~
它的**意思就是說如果強轉成功就執行之后的調用,如果強轉不成功就不執行**。
* * *
好了,關于 Kotlin 的變量、函數和類型的內容就講到這里,給你留 2 道思考題吧
1. 子類重寫父類的`override`函數,能否修改它的可見性?//不能
2. 以下的寫法有什么區別?
~~~kotlin
???
activity as? NewActivity
activity as NewActivity?
activity as? NewActivity?
~~~
- 前言
- Kotlin簡介
- IntelliJ IDEA技巧總結
- idea設置類注釋和方法注釋模板
- 像Android Studion一樣創建工程
- Gradle
- Gradle入門
- Gradle進階
- 使用Gradle創建一個Kotlin工程
- 環境搭建
- Androidstudio平臺搭建
- Eclipse的Kotlin環境配置
- 使用IntelliJ IDEA
- Kotlin學習路線
- Kotlin官方中文版文檔教程
- 概述
- kotlin用于服務器端開發
- kotlin用于Android開發
- kotlin用于JavaScript開發
- kotlin用于原生開發
- Kotlin 用于數據科學
- 協程
- 多平臺
- 新特性
- 1.1的新特性
- 1.2的新特性
- 1.3的新特性
- 開始
- 基本語法
- 習慣用法
- 編碼規范
- 基礎
- 基本類型
- 包與導入
- 控制流
- 返回與跳轉
- 類與對象
- 類與繼承
- 屬性與字段
- 接口
- 可見性修飾符
- 擴展
- 數據類
- 密封類
- 泛型
- 嵌套類
- 枚舉類
- 對象
- 類型別名
- 內嵌類
- 委托
- 委托屬性
- 函數與Lambda表達式
- 函數
- Lambda表達式
- 內聯函數
- 集合
- 集合概述
- 構造集合
- 迭代器
- 區間與數列
- 序列
- 操作概述
- 轉換
- 過濾
- 加減操作符
- 分組
- 取集合的一部分
- 取單個元素
- 排序
- 聚合操作
- 集合寫操作
- List相關操作
- Set相關操作
- Map相關操作
- 多平臺程序設計
- 平臺相關聲明
- 以Gradle創建
- 更多語言結構
- 解構聲明
- 類型檢測與轉換
- This表達式
- 相等性
- 操作符重載
- 空安全
- 異常
- 注解
- 反射
- 作用域函數
- 類型安全的構造器
- Opt-in Requirements
- 核心庫
- 標準庫
- kotlin.test
- 參考
- 關鍵字與操作符
- 語法
- 編碼風格約定
- Java互操作
- Kotlin中調用Java
- Java中調用Kotlin
- JavaScript
- 動態類型
- kotlin中調用JavaScript
- JavaScript中調用kotlin
- JavaScript模塊
- JavaScript反射
- JavaScript DCE
- 原生
- 并發
- 不可變性
- kotlin庫
- 平臺庫
- 與C語言互操作
- 與Object-C及Swift互操作
- CocoaPods集成
- Gradle插件
- 調試
- FAQ
- 協程
- 協程指南
- 基礎
- 取消與超時
- 組合掛起函數
- 協程上下文與調度器
- 異步流
- 通道
- 異常處理與監督
- 共享的可變狀態與并發
- Select表達式(實驗性)
- 工具
- 編寫kotlin代碼文檔
- 使用Kapt
- 使用Gradle
- 使用Maven
- 使用Ant
- Kotlin與OSGI
- 編譯器插件
- 編碼規范
- 演進
- kotlin語言演進
- 不同組件的穩定性
- kotlin1.3的兼容性指南
- 常見問題
- FAQ
- 與Java比較
- 與Scala比較(官方已刪除)
- Google開發者官網簡介
- Kotlin and Android
- Get Started with Kotlin on Android
- Kotlin on Android FAQ
- Android KTX
- Resources to Learn Kotlin
- Kotlin樣品
- Kotlin零基礎到進階
- 第一階段興趣入門
- kotlin簡介和學習方法
- 數據類型和類型系統
- 入門
- 分類
- val和var
- 二進制基礎
- 基礎
- 基本語法
- 包
- 示例
- 編碼規范
- 代碼注釋
- 異常
- 根類型“Any”
- Any? 可空類型
- 可空性的實現原理
- kotlin.Unit類型
- kotlin.Nothing類型
- 基本數據類型
- 數值類型
- 布爾類型
- 字符型
- 位運算符
- 變量和常量
- 語法和運算符
- 關鍵字
- 硬關鍵字
- 軟關鍵字
- 修飾符關鍵字
- 特殊標識符
- 操作符和特殊符號
- 算術運算符
- 賦值運算符
- 比較運算符
- 邏輯運算符
- this關鍵字
- super關鍵字
- 操作符重載
- 一元操作符
- 二元操作符
- 字符串
- 字符串介紹和屬性
- 字符串常見方法操作
- 字符串模板
- 數組
- 數組介紹創建及遍歷
- 數組常見方法和屬性
- 數組變化以及下標越界問題
- 原生數組類型
- 區間
- 正向區間
- 逆向區間
- 步長
- 類型檢測與類型轉換
- is、!is、as、as-運算符
- 空安全
- 可空類型變量
- 安全調用符
- 非空斷言
- Elvis操作符
- 可空性深入
- 可空性和Java
- 函數
- 函數式編程概述
- OOP和FOP
- 函數式編程基本特性
- 組合與范疇
- 在Kotlin中使用函數式編程
- 函數入門
- 函數作用域
- 函數加強
- 命名參數
- 默認參數
- 可變參數
- 表達式函數體
- 頂層、嵌套、中綴函數
- 尾遞歸函數優化
- 函數重載
- 控制流
- if表達式
- when表達式
- for循環
- while循環
- 循環中的 Break 與 continue
- return返回
- 標簽處返回
- 集合
- list集合
- list集合介紹和操作
- list常見方法和屬性
- list集合變化和下標越界
- set集合
- set集合介紹和常見操作
- set集合常見方法和屬性
- set集合變換和下標越界
- map集合
- map集合介紹和常見操作
- map集合常見方法和屬性
- map集合變換
- 集合的函數式API
- map函數
- filter函數
- “ all ”“ any ”“ count ”和“ find ”:對集合應用判斷式
- 別樣的求和方式:sumBy、sum、fold、reduce
- 根據人的性別進行分組:groupBy
- 扁平化——處理嵌套集合:flatMap、flatten
- 惰性集合操作:序列
- 區間、數組、集合之間轉換
- 面向對象
- 面向對象-封裝
- 類的創建及屬性方法訪問
- 類屬性和字段
- 構造器
- 嵌套類(內部類)
- 枚舉類
- 枚舉類遍歷&枚舉常量常用屬性
- 數據類
- 密封類
- 印章類(密封類)
- 面向對象-繼承
- 類的繼承
- 面向對象-多態
- 抽象類
- 接口
- 接口和抽象類的區別
- 面向對象-深入
- 擴展
- 擴展:為別的類添加方法、屬性
- Android中的擴展應用
- 優化Snackbar
- 用擴展函數封裝Utils
- 解決煩人的findViewById
- 擴展不是萬能的
- 調度方式對擴展函數的影響
- 被濫用的擴展函數
- 委托
- 委托類
- 委托屬性
- Kotlin5大內置委托
- Kotlin-Object關鍵字
- 單例模式
- 匿名類對象
- 伴生對象
- 作用域函數
- let函數
- run函數
- with函數
- apply函數
- also函數
- 標準庫函數
- takeIf 與 takeUnless
- 第二階段重點深入
- Lambda編程
- Lambda成員引用高階函數
- 高階函數
- 內聯函數
- 泛型
- 泛型的分類
- 泛型約束
- 子類和子類型
- 協變與逆變
- 泛型擦除與實化類型
- 泛型類型參數
- 泛型的背后:類型擦除
- Java為什么無法聲明一個泛型數組
- 向后兼容的罪
- 類型擦除的矛盾
- 使用內聯函數獲取泛型
- 打破泛型不變
- 一個支持協變的List
- 一個支持逆變的Comparator
- 協變和逆變
- 第三階段難點突破
- 注解和反射
- 聲明并應用注解
- DSL
- 協程
- 協程簡介
- 協程的基本操作
- 協程取消
- 管道
- 慕課霍丙乾協程筆記
- Kotlin與Java互操作
- 在Kotlin中調用Java
- 在Java中調用Kotlin
- Kotlin與Java中的操作對比
- 第四階段專題練習
- 朱凱Kotlin知識點總結
- Kotlin 基礎
- Kotlin 的變量、函數和類型
- Kotlin 里那些「不是那么寫的」
- Kotlin 里那些「更方便的」
- Kotlin 進階
- Kotlin 的泛型
- Kotlin 的高階函數、匿名函數和 Lambda 表達式
- Kotlin協程
- 初識
- 進階
- 深入
- Kotlin 擴展
- 會寫「18.dp」只是個入門——Kotlin 的擴展函數和擴展屬性(Extension Functions / Properties)
- Kotlin實戰-開發Android