本期做者:java
視頻:扔物線(朱凱)android
文章:hamber(羅瓊)面試
你們好,我是扔物線,我嘮叨兩句就滾。編程
歡迎你們來到碼上開學 Kotlin 系列上手教程。你們久等了,其實我也早就被大家催得不想活了,奈何我事情太多啊。好比我要旅遊吧?我要陪老婆吧?我要陪孩子吧?我要打孩子吧?我要打老婆吧?並且你們知道,我如今開了在線的 Android 進階課程,我得花大量時間精力在課程上面吧?否則客戶爸爸一個差評那我就要被團隊砍死啊。數組
不過無論怎麼樣,碼上開學終於開始生產了,並且咱們是備了一些存貨的喲。廢話很少說,視頻伺候!安全
若是你看不到上面的嗶哩嗶哩視頻,能夠點擊 這裏 去嗶哩嗶哩或者 這裏 去 YouTube 看。服務器
如下內容來自文章做者 hamber。多線程
在 Google I/O 2019 上,Google 宣佈 Kotlin 成爲 Android 的第一開發語言。這對於開發者來說意味着,未來全部的官方示例會首選 Kotlin,而且 Google 對 Kotlin 在開發、構建等各個方面的支持也會更優先。app
在這個大環境下,Kotlin 已經做爲不少公司的移動開發崗面試的考察點之一,甚至做爲 HR 簡歷篩選的必要條件。所以,學會並掌握 Kotlin 成了 Android 開發者的當務之急。編程語言
「Kotlin 真的有那麼好嗎」「到底要不要學 Kotlin」這樣的問題很快就要過期了,碼上開學這個項目的目的並不在於向各位安利 Kotlin,而在於怎樣讓但願學習 Kotlin 的人最快速地上手。
咱們的目的很是明確:這是一份給 Android 工程師的 Kotlin 上手指南。其中:
學習 Kotlin 的第一步就是要爲項目添加 Kotlin 語言的支持,這很是簡單。
若是你要新建一個支持 Kotlin 的 Android 項目,只須要以下操做:
別的都和建立一個普通的 Android 項目同樣,建立出的項目就會是基於 Kotlin 的了。
所謂「基於 Kotlin」,意思有兩點:
IDE 幫你自動建立出的 MainActivity
是用 Kotlin 寫的:
package org.kotlinmaster
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
class MainActivity : AppCompatActivity() {
...
}
複製代碼
掃一眼就好,不用讀代碼,咱們後面都會講。
項目中的 2 個 bulid.gradle
文件比 Java 的 Android 項目多了幾行代碼(以「👇」標註),它們的做用是添加 Kotlin 的依賴:
項目根目錄下的 build.gradle
:
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
:
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,只須要像上面這樣操做,把兩個 build.gradle
中標註的代碼對應貼到你的項目裏就能夠了。
筆者建議剛開始學習的時候仍是新建一個基於 Kotlin 的項目,按照上面的步驟練習一下。
前面咱們提到,若是新建的項目是基於 Kotlin 的,IDE 會幫咱們建立好 MainActivity
,它實際上是有一個 .kt
的文件後綴名(打開的時候能夠看到)。
Kotlin 文件都是以
.kt
結尾的,就像 Java 文件是以.java
結尾。
咱們看看這個 MainActivity.kt
裏到底有些什麼:
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 裏是沒見過的。
爲了暫時避開這些干擾,咱們本身新建一個文件。
建立完成後的 Sample.kt
:
package org.kotlinmaster
class Sample {}
複製代碼
這個類僅包含 package
和 class
兩個關鍵字,咱們暫時先當作和 Java 差很少(其實真的就是差很少)的概念,這樣就都是咱們熟悉的東西了。
接下來,讓咱們開始學習基礎語法吧。
這裏講一個 Java 和 Kotlin 命名由來的小插曲。
咱們知道 Java 就是著名的爪哇島,爪哇島盛產咖啡,聽說就是一羣研究出 Java 語言的牛人們在爲它命名時因爲聞到香濃的咖啡味,遂決定採用此名稱。
Kotlin 來源於芬蘭灣中的 Kotlin 島。
所以,咱們在代碼段的開頭以「☕️」來表示 Java 代碼段,「🏝️」來表示 Kotlin 代碼段。
咱們回憶下 Java 裏聲明一個 View 類型的變量的寫法:
☕️
View v;
複製代碼
Kotlin 裏聲明一個變量的格式是這樣的:
🏝️
var v: View
複製代碼
這裏有幾處不一樣:
var
關鍵字看上去只是語法格式有些不一樣,但若是真這麼寫,IDE 會報錯:
🏝️
class Sample {
var v: View
// 👆這樣寫 IDE 會報以下錯誤
// Property must be initialized or be abstract
}
複製代碼
這個提示是在說,屬性須要在聲明的同時初始化,除非你把它聲明成抽象的。
那什麼是屬性呢?這裏咱們能夠簡單類比 Java 的 field 來理解 Kotlin 的 Property,雖然它們其實有些不同,Kotlin 的 Property 功能會多些。
變量竟然還能聲明成抽象的?嗯,這是 Kotlin 的功能,不過這裏先不理它,後面會講到。
屬性爲何要求初始化呢?由於 Kotlin 的變量是沒有默認值的,這點不像 Java,Java 的 field 有默認值:
☕️
String name; // 👈默認值是 null
int count; // 👈默認值是 0
複製代碼
但這些 Kotlin 是沒有的。不過其實,Java 也只是 field 有默認值,局部變量也是沒有默認值的,若是不給它初始值也會報錯:
☕️
void run() {
int count;
count++;
// 👆IDE 報錯,Variable 'count' might not have been initialized
}
複製代碼
既然這樣,那咱們就給它一個默認值 null 吧,遺憾的是你會發現仍然報錯。
🏝️
class Sample {
var v: View = null
// 👆這樣寫 IDE 仍然會報錯,Null can not be a value of a non-null type View
}
複製代碼
又不行,IDE 告訴我須要賦一個非空的值給它才行,怎麼辦?Java 的那套無論用了。
其實這都是 Kotlin 的空安全設計相關的內容。不少人嘗試上手 Kotlin 以後快速放棄,就是由於搞不明白它的空安全設計,致使代碼各類拒絕編譯,最終只能放棄。因此咱先別急,我先來給你講一下 Kotlin 的空安全設計。
簡單來講就是經過 IDE 的提示來避免調用 null 對象,從而避免 NullPointerException。其實在 androidx 裏就有支持的,用一個註解就能夠標記變量是否可能爲空,而後 IDE 會幫助檢測和提示,咱們來看下面這段 Java 代碼:
☕️
@NonNull
View view = null;
// 👆IDE 會提示警告,'null' is assigned to a variable that is annotated with @NotNull
複製代碼
而到了 Kotlin 這裏,就有了語言級別的默認支持,並且提示的級別從 warning 變成了 error(拒絕編譯):
🏝️
var view: View = null
// 👆IDE 會提示錯誤,Null can not be a value of a non-null type View
複製代碼
在 Kotlin 裏面,全部的變量默認都是不容許爲空的,若是你給它賦值 null,就會報錯,像上面那樣。
這種有點強硬的要求,實際上是很合理的:既然你聲明瞭一個變量,就是要使用它對吧?那你把它賦值爲 null 幹嗎?要儘可能讓它有可用的值啊。Java 在這方面很寬鬆,咱們成了習慣,但 Kotlin 更強的限制其實在你熟悉了以後,是會減小不少運行時的問題的。
不過,仍是有些場景,變量的值真的沒法保證空與否,好比你要從服務器取一個 JSON 數據,並把它解析成一個 User 對象:
🏝️
class User {
var name: String = null // 👈這樣寫會報錯,但該變量沒法保證空與否
}
複製代碼
這個時候,空值就是有意義的。對於這些能夠爲空值的變量,你能夠在類型右邊加一個 ?
號,解除它的非空限制:
🏝️
class User {
var name: String? = null
}
複製代碼
加了問號以後,一個 Kotlin 變量就像 Java 變量同樣沒有非空的限制,自由自在了。
你除了在初始化的時候能夠給它賦值爲空值,在代碼裏的任何地方也均可以:
🏝️
var name: String? = "Mike"
...
name = null // 👈原來不是空值,賦值爲空值
複製代碼
這種類型以後加 ?
的寫法,在 Kotlin 裏叫可空類型。
不過,當咱們使用了可空類型的變量後,會有新的問題:
因爲對空引用的調用會致使空指針異常,因此 Kotlin 在可空變量直接調用的時候 IDE 會報錯:
🏝️
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 不接受這種作法:
🏝️
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 裏是這麼解決這個問題的呢?它用的不是 .
而是 ?.
:
🏝️
view?.setBackgroundColor(Color.RED)
複製代碼
這個寫法一樣會對變量作一次非空確認以後再調用方法,這是 Kotlin 的寫法,而且它能夠作到線程安全,所以這種寫法叫作「safe call」。
另外還有一種雙感嘆號的用法:
🏝️
view!!.setBackgroundColor(Color.RED)
複製代碼
意思是告訴編譯器,我保證這裏的 view 必定是非空的,編譯器你不要幫我作檢查了,有什麼後果我本身承擔。這種「確定不會爲空」的斷言式的調用叫作 「non-null asserted call」。一旦用了非空斷言,實際上和 Java 就沒什麼兩樣了,但也就享受不到 Kotlin 的空安全設計帶來的好處(在編譯時作檢查,而不是運行時拋異常)了。
以上就是 Kotlin 的空安全設計。
理解了它以後再來看變量聲明,跟 Java 雖然徹底不同,只是寫法上不一樣而已。
不少人在上手的時候都被變量聲明搞懵,緣由就是 Kotlin 的空安全設計所致使的這些報錯:
?
設置爲可空的時候,使用的時候由於「可能爲空」又報錯。明白了空安全設計的原理後,就很容易可以解決上面的問題了。
關於空安全,最重要的是記住一點:所謂「可空不可空」,關注的全都是使用的時候,即「這個變量在使用時是否可能爲空」。
另外,Kotlin 的這種空安全設計在與 Java 的互相調用上是徹底兼容的,這裏的兼容指:
Java 裏面的 @Nullable 註解,在 Kotlin 裏調用時一樣須要使用 ?.
。
☕️
@Nullable
String name;
複製代碼
🏝️
name?.length
複製代碼
Java 裏面的 @Nullable 和 @NonNull 註解,在轉換成 Kotlin 後對應的就是可空變量和不可空變量,至於怎麼將 Java 代碼轉換爲 Kotlin,Android Studio 給咱們提供了很方便的工具(但並不完美),後面會講。
☕️
@Nullable
String name;
@NonNull
String value = "hello";
複製代碼
🏝️
var name: String? = null
var value: String = "hello"
複製代碼
空安全咱們講了這麼多,可是有些時候咱們聲明一個變量是不會讓它爲空的,好比 view,其實在實際場景中咱們但願它一直是非空的,可空並無業務上的實際意義,使用 ?.
影響代碼可讀性。
但若是你在 MainActivity
裏這麼寫:
🏝️
class MainActivity : AppCompatActivity() {
👇
var view: View = findViewById(R.id.tvContent)
}
複製代碼
雖然編譯器不會報錯,但程序一旦運行起來就 crash 了,緣由是 findViewById() 是在 onCreate 以後才能調用。
那怎麼辦呢?其實咱們很想告訴編譯器「我很肯定我用的時候絕對不爲空,但第一時間我無法給它賦值」。
Kotlin 給咱們提供了一個選項:延遲初始化。
具體是這麼寫的:
🏝️
lateinit var view: View
複製代碼
這個 lateinit
的意思是:告訴編譯器我無法第一時間就初始化,但我確定會在使用它以前完成初始化的。
它的做用就是讓 IDE 不要對這個變量檢查初始化和報錯。換句話說,加了這個 lateinit
關鍵字,這個變量的初始化就全靠你本身了,編譯器不幫你檢查了。
而後咱們就能夠在 onCreate 中進行初始化了:
🏝️
👇
lateinit var view: View
override fun onCreate(...) {
...
👇
view = findViewById(R.id.tvContent)
}
複製代碼
哦對了,延遲初始化對變量的賦值次數沒有限制,你仍然能夠在初始化以後再賦其餘的值給 view
。
Kotlin 有個很方便的地方是,若是你在聲明的時候就賦值,那不寫變量類型也行:
🏝️
var name: String = "Mike"
👇
var name = "Mike"
複製代碼
這個特性叫作「類型推斷」,它跟動態類型是不同的,咱們不能像使用 Groovy 或者 JavaScript 那樣使用在 Kotlin 裏這麼寫:
🏝️
var name = "Mike"
name = 1
// 👆會報錯,The integer literal does not conform to the expected type String
複製代碼
// Groovy
def a = "haha"
a = 1
// 👆這種先賦值字符串再賦值數字的方式在 Groovy 裏是能夠的
複製代碼
「動態類型」是指變量的類型在運行時能夠改變;而「類型推斷」是你在代碼裏不用寫變量類型,編譯器在編譯的時候會幫你補上。所以,Kotlin 是一門靜態語言。
除了變量賦值這個場景,類型推斷的其餘場景咱們以後也會遇到。
聲明變量的方式也不止 var 一種,咱們還可使用 val:
🏝️
val size = 18
複製代碼
val 是 Kotlin 在 Java 的「變量」類型以外,又增長的一種變量類型:只讀變量。它只能賦值一次,不能修改。而 var 是一種可讀可寫變量。
var 是 variable 的縮寫,val 是 value 的縮寫。
val 和 Java 中的 final 相似:
☕️
final int size = 18;
複製代碼
不過其實它們仍是有些不同的,這個咱們以後再講。總之直接進行從新賦值是不行的。
看到這裏,咱們彷佛都沒有在 Kotlin 裏看到相似 Java 裏的 public、protected、private 這些表示變量可見性的修飾符,由於在 Kotlin 裏變量默認就是 public 的,而對於其餘可見性修飾符,咱們以後會講,這裏先不用關心。
至此,我相信你對變量這部分已經瞭解得差很少了,能夠根據前面的例子動手嘗試嘗試。
Kotlin 除了變量聲明外,函數的聲明方式也和 Java 的方法不同。Java 的方法(method)在 Kotlin 裏叫函數(function),其實沒啥區別,或者說其中的區別咱們能夠忽略掉。對任何編程語言來說,變量就是用來存儲數據,而函數就是用來處理數據。
咱們先來看看 Java 裏的方法是怎麼寫的:
☕️
Food cook(String name) {
...
}
複製代碼
而到了 Kotlin,函數的聲明是這樣:
🏝️
👇 👇
fun cook(name: String): Food {
...
}
複製代碼
那若是沒有返回值該怎麼辦?Java 裏是返回 void:
☕️
void main() {
...
}
複製代碼
Kotlin 裏是返回 Unit,而且能夠省略:
🏝️
👇
fun main(): Unit {}
// Unit 返回類型能夠省略
fun main() {}
複製代碼
函數參數也能夠有可空的控制,根據前面說的空安全設計,在傳遞時須要注意:
🏝️
// 👇可空變量傳給不可空參數,報錯
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
關鍵字的時候,下面會講到。
咱們知道,在 Java 裏面的 field 常常會帶有 getter/setter 函數:
☕️
public class User {
String name;
public String getName() {
return this.name;
}
public void setName(String name) {
this.name = name;
}
}
複製代碼
它們的做用就是能夠自定義函數內部實現來達到「鉤子」的效果,好比下面這種:
☕️
public class User {
String name;
public String getName() {
return this.name + " nb";
}
public void setName(String name) {
this.name = "Cute " + name;
}
}
複製代碼
在 Kotlin 裏,這種 getter / setter 是怎麼運做的呢?
🏝️
class User {
var name = "Mike"
fun run() {
name = "Mary"
// 👆的寫法其實是👇這麼調用的
// setName("Mary")
// 建議本身試試,IDE 的代碼補全功能會在你打出 setn 的時候直接提示 name 而不是 setName
println(name)
// 👆的寫法其實是👇這麼調用的
// print(getName())
// IDE 的代碼補全功能會在你打出 getn 的時候直接提示 name 而不是 getName
}
}
複製代碼
那麼咱們如何來操做前面提到的「鉤子」呢?看下面這段代碼:
🏝️
class User {
var name = "Mike"
👇
get() {
return field + " nb"
}
👇 👇
set(value) {
field = "Cute " + value
}
}
複製代碼
格式上和 Java 有一些區別:
除此以外還多了一個叫 field 的東西。這個東西叫作「Backing Field」,中文翻譯是幕後字段或後備字段(馬雲背後的女人😝)。具體來講,你的這個代碼:
🏝️
class Kotlin {
var name = "kaixue.io"
}
複製代碼
在編譯後的字節碼大體等價於這樣的 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 函數:
🏝️
val name = "Mike"
get() {
return field + " nb"
}
複製代碼
val 所聲明的只讀變量,在取值的時候仍然可能被修改,這也是和 Java 裏的 final 的不一樣之處。
關於「鉤子」的做用,除了修改取值和賦值,也能夠加一些本身的邏輯,就像咱們在 Activity 的生命週期函數裏作的事情同樣。
講完了變量和函數,接下來咱們能夠系統性地學習下 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 的:
☕️
int a = 1;
Integer b = 2; // 👈會被自動裝箱 autoboxing
複製代碼
Kotlin 裏,Int 是否裝箱根據場合來定:
🏝️
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 中的數組的寫法也有區別:
☕️
int[] array = new int[] {1, 2};
複製代碼
而在 Kotlin 裏,上面的寫法是這樣的:
🏝️
var array: IntArray = intArrayOf(1, 2)
// 👆這種也是 unbox 的
複製代碼
簡單來講,原先在 Java 裏的基本類型,類比到 Kotlin 裏面,條件知足以下之一就不裝箱:
不可空類型。
使用 IntArray、FloatArray 等。
如今能夠來看看咱們的老朋友 MainActivity
了,從新認識下它:
🏝️
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
...
}
}
複製代碼
咱們能夠對比 Java 的代碼來看有哪些不一樣:
☕️
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
...
}
}
複製代碼
首先是類的可見性,Java 中的 public 在 Kotlin 中能夠省略,Kotlin 的類默認是 public 的。
類的繼承的寫法,Java 裏用的是 extends
,而在 Kotlin 裏使用 :
,但其實 :
不只能夠表示繼承,還能夠表示 Java 中的 implement
。
舉個例子,假設咱們有一個 interface 叫 Imple:
🏝️
interface Impl {}
複製代碼
Kotlin 裏定義一個 interface 和 Java 沒什麼區別。
☕️
public class Main2Activity extends AppCompatActivity implements Impl { }
複製代碼
🏝️
class MainActivity : AppCompatActivity(), Impl {}
複製代碼
構造方法的寫法不一樣。
Java 裏省略了默認的構造函數:
☕️
public class MainActivity extends AppCompatActivity {
// 👇默認構造函數
public MainActivity() {
}
}
複製代碼
Kotlin 裏咱們注意到 AppCompatActivity 後面的 ()
,這其實也是一種省略的寫法,等價於:
🏝️
class MainActivity constructor() : AppCompatActivity() {
👆
}
複製代碼
不過其實更像 Java 的寫法是這樣的:
🏝️
// 👇注意這裏 AppCompatActivity 後面沒有 '()'
class MainActivity : AppCompatActivity {
constructor() {
}
}
複製代碼
Kotlin 把構造函數單獨用了一個 constructor
關鍵字來和其餘的 fun
作區分。
override 的不一樣
@Override
是註解的形式。override
變成了關鍵字。protected
關鍵字,也就是說,Kotlin 裏的 override
函數的可見性是繼承自父類的。除了以上這些明顯的不一樣以外,還有一些不一樣點從上面的代碼裏看不出來,但當你寫一個類去繼承 MainActivity
時就會發現:
Kotlin 裏的 MainActivity 沒法繼承:
🏝️
// 👇寫法會報錯,This type is final, so it cannot be inherited from
class NewActivity: MainActivity() {
}
複製代碼
緣由是 Kotlin 裏的類默認是 final 的,而 Java 裏只有加了 final
關鍵字的類纔是 final 的。
那麼有什麼辦法解除 final 限制麼?咱們可使用 open
來作這件事:
🏝️
open class MainActivity : AppCompatActivity() {}
複製代碼
這樣一來,咱們就能夠繼承了。
🏝️
class NewActivity: MainActivity() {}
複製代碼
可是要注意,此時 NewActivity 仍然是 final 的,也就是說,open
沒有父類到子類的遺傳性。
而剛纔說到的 override
是有遺傳性的:
🏝️
class NewActivity : MainActivity() {
// 👇onCreate 仍然是 override 的
override fun onCreate(savedInstanceState: Bundle?) {
...
}
}
複製代碼
若是要關閉 override
的遺傳性,只須要這樣便可:
🏝️
open class MainActivity : AppCompatActivity() {
// 👇加了 final 關鍵字,做用和 Java 裏面同樣,關閉了 override 的遺傳性
final override fun onCreate(savedInstanceState: Bundle?) {
...
}
}
複製代碼
Kotlin 裏除了新增了 open
關鍵字以外,也有和 Java 同樣的 abstract
關鍵字,這倆關鍵字的區別就是 abstract
關鍵字修飾的類沒法直接實例化,而且一般來講會和 abstract
修飾的函數一塊兒出現,固然,也能夠沒有這個 abstract
函數。
🏝️
abstract class MainActivity : AppCompatActivity() {
abstract fun test()
}
複製代碼
可是子類若是要實例化,仍是須要實現這個 abstract 函數的:
🏝️
class NewActivity : MainActivity() {
override fun test() {}
}
複製代碼
當咱們聲明好一個類以後,咱們就能夠實例化它了,實例化在 Java 中使用 new
關鍵字:
☕️
void main() {
Activity activity = new NewActivity();
}
複製代碼
而在 Kotlin 中,實例化一個對象更加簡單,沒有 new
關鍵字:
🏝️
fun main() {
var activity: Activity = NewActivity()
}
複製代碼
經過 MainActivity
的學習,咱們知道了 Java 和 Kotlin 中關於類的聲明主要關注如下幾個方面:
剛纔講的實例化的例子中,咱們其實是把子類對象賦值給父類的變量,這個概念在 Java 裏叫多態,Kotlin 也有這個特性,但在實際工做中咱們極可能會遇到須要使用子類纔有的函數。
好比咱們先在子類中定義一個函數:
🏝️
class NewActivity : MainActivity() {
fun action() {}
}
複製代碼
那麼接下來這麼寫是沒法調用該函數的:
🏝️
fun main() {
var activity: Activity = NewActivity()
// 👆activity 是沒法調用 NewActivity 的 action 方法的
}
複製代碼
在 Java 裏,須要先使用 instanceof
關鍵字判斷類型,再經過強轉來調用:
☕️
void main() {
Activity activity = new NewActivity();
if (activity instanceof NewActivity) {
((NewActivity) activity).action();
}
}
複製代碼
Kotlin 裏一樣有相似解決方案,使用 is
關鍵字進行「類型判斷」,而且由於編譯器可以進行類型推斷,能夠幫助咱們省略強轉的寫法:
🏝️
fun main() {
var activity: Activity = NewActivity()
if (activity is NewActivity) {
// 👇的強轉因爲類型推斷被省略了
activity.action()
}
}
複製代碼
那麼能不能不進行類型判斷,直接進行強轉調用呢?可使用 as
關鍵字:
🏝️
fun main() {
var activity: Activity = NewActivity()
(activity as NewActivity).action()
}
複製代碼
這種寫法若是強轉類型操做是正確的固然沒問題,但若是強轉成一個錯誤的類型,程序就會拋出一個異常。
咱們更但願能進行安全的強轉,能夠更優雅地處理強轉出錯的狀況。
這一點,Kotlin 在設計上天然也考慮到了,咱們可使用 as?
來解決:
🏝️
fun main() {
var activity: Activity = NewActivity()
// 👇'(activity as? NewActivity)' 以後是一個可空類型的對象,因此,須要使用 '?.' 來調用
(activity as? NewActivity)?.action()
}
複製代碼
它的意思就是說若是強轉成功就執行以後的調用,若是強轉不成功就不執行。
好了,關於 Kotlin 的變量、函數和類型的內容就講到這裏,給你留 2 道思考題吧:
子類重寫父類的 override
函數,可否修改它的可見性?
如下的寫法有什麼區別?
🏝️
activity as? NewActivity
activity as NewActivity?
activity as? NewActivity?
複製代碼
View
),在 onCreate 函數中初始化它。View?
類型的方法,傳入剛纔的 View
類型屬性,並在該方法中打印出該 View?
的 id。