摘要:本章我們來(lái)學(xué)習(xí)一下的基本數(shù)據(jù)類(lèi)型與類(lèi)型系統(tǒng)。字符串就是一個(gè)抽象數(shù)據(jù)類(lèi)型。如果程序語(yǔ)言的語(yǔ)法中含有類(lèi)型標(biāo)記,就稱該語(yǔ)言是顯式類(lèi)型化的,否則就稱為隱式類(lèi)型化的。但是,可以把中對(duì)應(yīng)的這幾種基本數(shù)據(jù)類(lèi)型,理解為的基本類(lèi)型的裝箱類(lèi)。
第4章 基本數(shù)據(jù)類(lèi)型與類(lèi)型系統(tǒng) 《Kotlin極簡(jiǎn)教程》正式上架:
點(diǎn)擊這里 > 去京東商城購(gòu)買(mǎi)閱讀 點(diǎn)擊這里 > 去天貓商城購(gòu)買(mǎi)閱讀非常感謝您親愛(ài)的讀者,大家請(qǐng)多支持?。?!有任何問(wèn)題,歡迎隨時(shí)與我交流~
到目前為止,我們已經(jīng)了解了Kotlin的基本符號(hào)以及基礎(chǔ)語(yǔ)法。我們可以看出,使用Kotlin寫(xiě)的代碼更簡(jiǎn)潔、可讀性更好、更富有生產(chǎn)力。
本章我們來(lái)學(xué)習(xí)一下Kotlin的基本數(shù)據(jù)類(lèi)型與類(lèi)型系統(tǒng)。
道生一,一生二,二生三,三生萬(wàn)物 (老子《道德經(jīng)》第四十二章)
在計(jì)算機(jī)科學(xué)中,最早的類(lèi)型系統(tǒng)用來(lái)區(qū)別數(shù)字里面的整數(shù)和浮點(diǎn)數(shù)。
在20世紀(jì)五六十年代,這種分類(lèi)擴(kuò)展到了結(jié)構(gòu)化的數(shù)據(jù)和高階函數(shù)中。
70年代,引入了幾個(gè)更為豐富的概念,例如:參數(shù)化類(lèi)型,抽象數(shù)據(jù)類(lèi)型,模塊系統(tǒng),子類(lèi)型等等,類(lèi)型系統(tǒng)作為一個(gè)獨(dú)立的領(lǐng)域形成了。
在每一門(mén)編程語(yǔ)言中,都有一個(gè)特定的類(lèi)型系統(tǒng)(Type System)。類(lèi)型系統(tǒng)是一門(mén)編程語(yǔ)言最核心也是最基礎(chǔ)的部分。我們這里說(shuō)的類(lèi)型系統(tǒng),可以簡(jiǎn)單理解為以下兩個(gè)部分:
一組基本類(lèi)型構(gòu)成的PTS(Primary Type Set,基本類(lèi)型集合);
PTS上定義的一系列組合、運(yùn)算、轉(zhuǎn)換規(guī)則等。
這一簡(jiǎn)單優(yōu)雅而驚人的世界構(gòu)成觀,貫穿了人類(lèi)現(xiàn)實(shí)世界和計(jì)算機(jī)編程語(yǔ)言所定義的虛擬世界?;蛟S語(yǔ)言的設(shè)計(jì)者也沒(méi)有料想到,但是最終的結(jié)果確實(shí)是有限的設(shè)計(jì)導(dǎo)出了無(wú)限的可能性。
本章我們將學(xué)習(xí)Kotlin語(yǔ)言的基本類(lèi)型,以及簡(jiǎn)單介紹Kotlin的類(lèi)型系統(tǒng)。
4.1 什么是類(lèi)型?一切皆是映射
在計(jì)算機(jī)中,任何數(shù)值都是以一組比特(01)組成的,硬件無(wú)法區(qū)分內(nèi)存地址、腳本、字符、整數(shù)、以及浮點(diǎn)數(shù)。這個(gè)時(shí)候,我們使用類(lèi)型賦予一組比特以特定的意義。
類(lèi)型(Type),本質(zhì)上就是內(nèi)存中的數(shù)值或變量對(duì)象的邏輯映射。
《周易》有云:
易有太極,是生兩儀,兩儀生四象,四象生八卦。(《易傳·系辭上傳》) 。
這里所包含的思想,跟我們這里所說(shuō)的類(lèi)型系統(tǒng)的思想有著異曲同工之妙。
類(lèi)型系統(tǒng)用于定義如何將編程語(yǔ)言中的數(shù)值和表達(dá)式歸類(lèi)為許多不同的類(lèi)型,如何操作這些類(lèi)型,這些類(lèi)型如何互相作用等。
類(lèi)型系統(tǒng)在各種語(yǔ)言之間有非常大的不同,主要的差異存在于編譯時(shí)期的語(yǔ)法,以及運(yùn)行時(shí)期的操作實(shí)現(xiàn)方式。
類(lèi)型系統(tǒng)提供的主要功能有:
安全性
編譯器可以使用類(lèi)型來(lái)檢查無(wú)意義的,或者是可能無(wú)效的代碼。例如,在強(qiáng)類(lèi)型的語(yǔ)言中,如果沒(méi)有對(duì)字符串的+進(jìn)行重載,那么表達(dá)式
"Hello, World" + 3
就會(huì)被編譯器檢測(cè)出來(lái),因?yàn)椴荒軐?duì)字符串加上一個(gè)整數(shù)。強(qiáng)類(lèi)型提供更多的安全性。
但是,為了讓程序員可以寫(xiě)出極簡(jiǎn)的代碼,很多語(yǔ)言都提供了操作符重載的機(jī)制。比如說(shuō),在Scala中,上面的代碼是可以被正確執(zhí)行的(重載了+操作符)
scala> "Hello,World"+1 res15: String = Hello,World1 scala> 1+"Hello,World" res16: String = 1Hello,World
但是在Kotlin中, 由于Int類(lèi)型沒(méi)有對(duì)+實(shí)現(xiàn)重載,所以情況是這樣
>>> "Hello,World"+1 Hello,World1 >>> 1+"Hello,World" error: none of the following functions can be called with the arguments supplied: public final operator fun plus(other: Byte): Int defined in kotlin.Int public final operator fun plus(other: Double): Double defined in kotlin.Int public final operator fun plus(other: Float): Float defined in kotlin.Int public final operator fun plus(other: Int): Int defined in kotlin.Int public final operator fun plus(other: Long): Long defined in kotlin.Int public final operator fun plus(other: Short): Int defined in kotlin.Int 1+"Hello,World" ^
最優(yōu)化
靜態(tài)類(lèi)型檢查可提供有用的信息給編譯器。編譯器可以使用更有效率的機(jī)器指令,實(shí)現(xiàn)編譯器優(yōu)化。
可讀性
抽象化(或模塊化)
類(lèi)型本質(zhì)上是對(duì)較低層次的邏輯單元進(jìn)行高層次的邏輯抽象。這樣我們就可以直接使用類(lèi)型在較高層次的方式思考,而不是繁重的低層次實(shí)現(xiàn)。
例如,我們可以將字符串想成一個(gè)值,以此取代僅僅是字節(jié)的數(shù)組。字符串就是一個(gè)抽象數(shù)據(jù)類(lèi)型。
從01到類(lèi)型,從類(lèi)型到接口API,再到軟件服務(wù),都可以看做是廣義的“類(lèi)型”范疇。
程序中的變量在程序執(zhí)行期間,可能會(huì)有不同的取值范圍,我們可以把變量可取值的最大范圍稱為這個(gè)變量的類(lèi)型。例如,具有類(lèi)型Boolean的變量x,在程序執(zhí)行期間,只能取布爾值。指定變量類(lèi)型的程序設(shè)計(jì)語(yǔ)言,稱為類(lèi)型化的語(yǔ)言(typed language)。
如果一個(gè)語(yǔ)言,不限制變量的取值,稱為無(wú)類(lèi)型語(yǔ)言(untyped language),我們既可以說(shuō)它不具有類(lèi)型,也可以說(shuō)它具有一個(gè)通用類(lèi)型,這個(gè)類(lèi)型的取值范圍是程序中所有可能的值。
類(lèi)型系統(tǒng)是類(lèi)型化語(yǔ)言的一個(gè)組成部分,它用來(lái)計(jì)算和跟蹤程序中所有表達(dá)式的類(lèi)型,從而判斷某段程序是否表現(xiàn)良好(well behaved)。
如果程序語(yǔ)言的語(yǔ)法中含有類(lèi)型標(biāo)記,就稱該語(yǔ)言是顯式類(lèi)型化的(explicitly typed),否則就稱為隱式類(lèi)型化的(implicitly typed)。
像C、C++、Java等語(yǔ)言,都是顯式類(lèi)型化的。而像ML、Haskell、Groovy等可以省略類(lèi)型聲明,它們的類(lèi)型系統(tǒng)會(huì)自動(dòng)推斷出程序的類(lèi)型。
4.2 編譯時(shí)類(lèi)型與運(yùn)行時(shí)類(lèi)型4.2.1 弱類(lèi)型(Weakly checked language)與強(qiáng)類(lèi)型(Strongly checked language)Koltin是一門(mén)強(qiáng)類(lèi)型的、靜態(tài)類(lèi)型、支持隱式類(lèi)型的顯式類(lèi)型語(yǔ)言。
類(lèi)型系統(tǒng)最主要的作用是,通過(guò)檢查類(lèi)型的運(yùn)算和轉(zhuǎn)換過(guò)程,來(lái)減少類(lèi)型錯(cuò)誤的發(fā)生。如果一個(gè)語(yǔ)言的編譯器引入越多的類(lèi)型檢查的限制,就可以稱這個(gè)語(yǔ)言的類(lèi)型檢查越強(qiáng),反之越弱。根據(jù)類(lèi)型檢查的強(qiáng)弱,我們把編程語(yǔ)言分為
弱類(lèi)型語(yǔ)言
強(qiáng)類(lèi)型語(yǔ)言
弱類(lèi)型語(yǔ)言在運(yùn)行時(shí)會(huì)隱式做數(shù)據(jù)類(lèi)型轉(zhuǎn)換。
強(qiáng)類(lèi)型語(yǔ)言在運(yùn)行時(shí)會(huì)確保不會(huì)發(fā)生未經(jīng)明確轉(zhuǎn)換(顯式調(diào)用)的類(lèi)型轉(zhuǎn)換。
但是另一方面,強(qiáng)和弱只是相對(duì)的。
Kotlin是強(qiáng)類(lèi)型語(yǔ)言。
4.2.2 靜態(tài)類(lèi)型(Statically checked language)與動(dòng)態(tài)類(lèi)型(Dynamicallychecked language)
類(lèi)型檢查可發(fā)生在編譯時(shí)期(靜態(tài)檢查)或運(yùn)行時(shí)期(動(dòng)態(tài)檢查)。這樣我們將編程語(yǔ)言分為
靜態(tài)類(lèi)型語(yǔ)言
動(dòng)態(tài)類(lèi)型語(yǔ)言
靜態(tài)類(lèi)型檢查是基于編譯器來(lái)分析源碼本身來(lái)確保類(lèi)型安全。靜態(tài)類(lèi)型檢查能讓很多bug在編碼早期被捕捉到,并且它也能優(yōu)化運(yùn)行。因?yàn)槿绻幾g器在編譯時(shí)已經(jīng)證明程序是類(lèi)型安全的,就不用在運(yùn)行時(shí)進(jìn)行動(dòng)態(tài)的類(lèi)型檢查,編譯過(guò)后的代碼會(huì)更優(yōu)化,運(yùn)行更快。
動(dòng)態(tài)類(lèi)型語(yǔ)言是在運(yùn)行時(shí)期進(jìn)行類(lèi)型標(biāo)記的檢查,因?yàn)樽兞克s束的值,可經(jīng)由運(yùn)行路徑獲得不同的標(biāo)記。關(guān)于動(dòng)態(tài)類(lèi)型,有個(gè)很形象的說(shuō)法:
當(dāng)看到一只鳥(niǎo)走起來(lái)像鴨子、游泳起來(lái)像鴨子、叫起來(lái)也像鴨子,那么這只鳥(niǎo)就可以被稱為鴨子?!材匪埂せ萏乜颇贰とR利(James Whitcomb Riley,1849-1916)
Kotlin是靜態(tài)類(lèi)型語(yǔ)言。
4.2.3 顯式類(lèi)型(Explicitly typed language)與隱式類(lèi)型(Implicitly typed language)還有一種區(qū)分方法是,根據(jù)變量名是否需要顯式給出類(lèi)型的聲明,來(lái)將語(yǔ)言分為
顯式類(lèi)型語(yǔ)言
隱式類(lèi)型語(yǔ)言
前者需要在定義變量時(shí)顯式給出變量的類(lèi)型,而后者可以使用類(lèi)型推論來(lái)確定變量的類(lèi)型。
大多數(shù)靜態(tài)類(lèi)型語(yǔ)言,例如 Java、C/C++ 都是顯式類(lèi)型語(yǔ)言。但是有些則不是,如 Haskell、ML 等,它們可以基于變量的操作來(lái)推斷其類(lèi)型;
Scala 是靜態(tài)類(lèi)型語(yǔ)言,它使用類(lèi)型推斷功能來(lái)支持隱式類(lèi)型。
Kotlin 跟Scala類(lèi)似,它也使用類(lèi)型推斷支持隱式類(lèi)型。但是,在一些場(chǎng)景下也需要顯式聲明變量的類(lèi)型,所以我們可以說(shuō),同時(shí)也是顯式類(lèi)型。
4.3 根類(lèi)型AnyKotlin 中所有類(lèi)都有一個(gè)共同的超類(lèi) Any ,如果類(lèi)聲明時(shí)沒(méi)有指定超類(lèi),則默認(rèn)為 Any 。我們來(lái)看一段代碼:
>>> val any = Any() >>> any java.lang.Object@2e377400 >>> any::class class kotlin.Any >>> any::class.java class java.lang.Object
也就是說(shuō),Any在運(yùn)行時(shí),其類(lèi)型自動(dòng)映射成java.lang.Object。我們知道,在Java中Object類(lèi)是所有引用類(lèi)型的父類(lèi)。但是不包括基本類(lèi)型:byte int long等,基本類(lèi)型對(duì)應(yīng)的包裝類(lèi)是引用類(lèi)型,其父類(lèi)是Object。而在Kotlin中,直接統(tǒng)一——所有類(lèi)型都是引用類(lèi)型,統(tǒng)一繼承父類(lèi)Any。
Any是Java的等價(jià)Object類(lèi)。但是跟Java不同的是,Kotlin中語(yǔ)言內(nèi)部的類(lèi)型和用戶定義類(lèi)型之間,并沒(méi)有像Java那樣劃清界限。它們是同一類(lèi)型層次結(jié)構(gòu)的一部分。
Any 只有 equals() 、 hashCode() 和 toString() 三個(gè)方法。其源碼是
public open class Any { /** * Indicates whether some other object is "equal to" this one. Implementations must fulfil the following * requirements: * * * Reflexive: for any non-null reference value x, x.equals(x) should return true. * * Symmetric: for any non-null reference values x and y, x.equals(y) should return true if and only if y.equals(x) returns true. * * Transitive: for any non-null reference values x, y, and z, if x.equals(y) returns true and y.equals(z) returns true, then x.equals(z) should return true * * Consistent: for any non-null reference values x and y, multiple invocations of x.equals(y) consistently return true or consistently return false, provided no information used in equals comparisons on the objects is modified. * * Note that the `==` operator in Kotlin code is translated into a call to [equals] when objects on both sides of the * operator are not null. */ public open operator fun equals(other: Any?): Boolean /** * Returns a hash code value for the object. The general contract of hashCode is: * * * Whenever it is invoked on the same object more than once, the hashCode method must consistently return the same integer, provided no information used in equals comparisons on the object is modified. * * If two objects are equal according to the equals() method, then calling the hashCode method on each of the two objects must produce the same integer result. */ public open fun hashCode(): Int /** * Returns a string representation of the object. */ public open fun toString(): String }4.3.1 對(duì)象相等性
從Any的源碼注釋中,我們可以看到,判斷兩個(gè)對(duì)象是否相等,需要滿足以下條件:
自反性:對(duì)于任何非空引用值x,x.equals(x)應(yīng)返回true。
對(duì)稱性:對(duì)于任何非空引用值x和y,x.equals(y)應(yīng)返回true當(dāng)且僅當(dāng)y.equals(x)返回true。
傳遞性:對(duì)于任何非空引用值x,y,z,如果x.equals(y)返回true,y.equals(z)返回true,那么x.equals(z)應(yīng)返回true
一致性:對(duì)于任何非空引用值x和y,多次調(diào)用x.equals(y)始終返回true或者始終返回false。
另外,在Kotlin中,操作符==會(huì)被編譯器翻譯成調(diào)用equals() 函數(shù)。
4.4 基本類(lèi)型(Primitive Types)本節(jié)我們來(lái)探討學(xué)習(xí):Kotlin的基礎(chǔ)類(lèi)型:數(shù)字、字符、布爾和數(shù)組等。
我們知道Java的類(lèi)型分成兩種:一種是基本類(lèi)型,一種是引用類(lèi)型。它們的本質(zhì)區(qū)別是:
基本類(lèi)型是在堆棧處分配空間存“值”,而引用類(lèi)型是在堆里面分配空間存“值”。
Java的基本類(lèi)型有: byte、int、short、long、float、double、char、boolean,這些類(lèi)都有對(duì)應(yīng)的裝箱類(lèi)(引用類(lèi)型)。
另外,void也可以算是一種特殊的基本類(lèi)型,它也有一個(gè)裝箱類(lèi)Void(跟我們后文講到的Unit、Nothing相關(guān))。因?yàn)?,Void是不能new出來(lái)的,也就是不能在堆里面分配空間存對(duì)應(yīng)的值。所以,Void是一開(kāi)始在堆棧處分配好空間。所以,將Void歸成基本類(lèi)型。
在Kotlin中,一切皆是對(duì)象。所有類(lèi)型都是引用類(lèi)型。沒(méi)有類(lèi)似Java中的基本類(lèi)型。但是,可以把Kotlin中對(duì)應(yīng)的這幾種基本數(shù)據(jù)類(lèi)型,理解為Java的基本類(lèi)型的裝箱類(lèi)。
Integer.java
public final class Integer extends Number implements Comparable{ /** * A constant holding the minimum value an {@code int} can * have, -231. */ @Native public static final int MIN_VALUE = 0x80000000; /** * A constant holding the maximum value an {@code int} can * have, 231-1. */ @Native public static final int MAX_VALUE = 0x7fffffff; /** * The {@code Class} instance representing the primitive type * {@code int}. * * @since JDK1.1 */ @SuppressWarnings("unchecked") public static final Class TYPE = (Class ) Class.getPrimitiveClass("int"); ... }
Kotlin中的Int類(lèi)型:
public class Int private constructor() : Number(), Comparable{ companion object { /** * A constant holding the minimum value an instance of Int can have. */ public const val MIN_VALUE: Int = -2147483648 /** * A constant holding the maximum value an instance of Int can have. */ public const val MAX_VALUE: Int = 2147483647 } ... }
我們通過(guò)Java的Integer封裝類(lèi),跟Kotlin的Int類(lèi)的定義可以看出兩者的思想上的同源性。
Kotlin的基本類(lèi)型的類(lèi)圖結(jié)構(gòu)如下圖所示
4.4.1 數(shù)字(Number)類(lèi)型Kotlin 提供了如下的內(nèi)置類(lèi)型來(lái)表示數(shù)字(與 Java 很相近):
類(lèi)型 | 寬度(Bit) |
---|---|
Double | 64 |
Float | 32 |
Long | 64 |
Int | 32 |
Short | 16 |
Byte | 8 |
從上面的Kotlin的基本類(lèi)型的類(lèi)的結(jié)構(gòu)圖,我們可以看出這些內(nèi)置的數(shù)據(jù)類(lèi)型,都繼承了Number和 Comparable類(lèi)。例如,Byte類(lèi)型的聲明:
public class Byte private constructor() : Number(), Comparable{ ... }
Kotlin 的數(shù)字類(lèi)型跟 Java基本相同。有一點(diǎn)不同的是,Kotlin對(duì)于數(shù)字沒(méi)有隱式拓寬轉(zhuǎn)換(如 Java 中 int 可以隱式轉(zhuǎn)換為long)。
注意在 Kotlin 中字符Char不是數(shù)字。這些基本數(shù)據(jù)類(lèi)型,會(huì)在運(yùn)行時(shí)自動(dòng)優(yōu)化為Java的double、float、long、int、short、byte。
字面常量值(literal constant values)數(shù)值常量字面值有以下幾種:
十進(jìn)制: 123
Long 類(lèi)型用大寫(xiě) L 標(biāo)記: 123L
十六進(jìn)制: 0x0F
二進(jìn)制: 0b00001011
代碼示例:
>>> 123 123 >>> 123::class class kotlin.Int >>> 123::class.java int >>> 123L 123 >>> 123L::class class kotlin.Long >>> 123L::class.java long >>> val b:Byte=128 error: the integer literal does not conform to the expected type Byte val b:Byte=128 ^ >>> val b:Byte=127 >>> b::class class kotlin.Byte >>> b::class.java byte >>> 0x0f 15 >>> 0x0F 15 >>> 0b1000 8
同樣的,當(dāng)我們賦值超過(guò)變量的類(lèi)型的取值范圍時(shí),編譯器會(huì)直接拋錯(cuò)。
注意: 不支持八進(jìn)制
Kotlin 同樣支持浮點(diǎn)數(shù)的常規(guī)表示方法:
默認(rèn) double:123.5、123.5e10
Float 用 f 或者 F 標(biāo)記: 123.5f
代碼示例:
>>> 1234.5 1234.5 >>> 1234.5::class class kotlin.Double >>> 1234.5::class.java double >>> 12.3e10 1.23E11 >>> 12.3e10::class class kotlin.Double >>> 456.7f 456.7 >>> 456.7f::class class kotlin.Float >>> 456.7f::class.java float
我們也可以使用數(shù)字字面值中的下劃線(自 1.1 起),使數(shù)字常量更易讀:
>>> 1_000_000 1000000 >>> 1234_5678_9012_3456L 1234567890123456 >>> 0xFF_EC_DE_5E 4293713502 >>> 0b11010010_01101001_10010100_10010010 3530134674
在 Java 平臺(tái)數(shù)字是物理存儲(chǔ)為 JVM 的原生類(lèi)型,除非我們需要一個(gè)可空的引用(如 Int?)或泛型。
后者情況下會(huì)把數(shù)字裝箱。
由于不同的表示方式,值范圍較小類(lèi)型并不是較大類(lèi)型的子類(lèi)型,是不能隱式轉(zhuǎn)換的。
代碼示例:
>>> val a: Int? = 1 >>> val b: Long? = a error: type mismatch: inferred type is Int? but Long? was expected val b: Long? = a ^ >>> val b: Byte = 1 >>> val i: Int = b error: type mismatch: inferred type is Byte but Int was expected val i: Int = b ^
這意味著在不進(jìn)行顯式轉(zhuǎn)換的情況下我們不能把 Int 型值賦給一個(gè) Long 變量。也不能把 Byte 型值賦給一個(gè) Int 變量。
我們可以顯式轉(zhuǎn)換來(lái)拓寬數(shù)字
>>> val i: Int = b.toInt() // OK: 顯式拓寬
每個(gè)數(shù)字類(lèi)型都繼承Number抽象類(lèi),其中定義了如下的轉(zhuǎn)換函數(shù):
toDouble(): Double toFloat(): Float toLong(): Long toInt(): Int toChar(): Char toShort(): Short toByte(): Byte
所以,在數(shù)字之間的轉(zhuǎn)換,我們直接調(diào)用上面的這些轉(zhuǎn)換函數(shù)即可。
運(yùn)算符+重載缺乏隱式類(lèi)型轉(zhuǎn)換并不顯著,因?yàn)轭?lèi)型會(huì)從上下文推斷出來(lái),而算術(shù)運(yùn)算會(huì)有重載做適當(dāng)轉(zhuǎn)換,例如:
val l = 1L + 3 // Long + Int => Long
這個(gè)是通過(guò)運(yùn)算符+重載實(shí)現(xiàn)的。我們可以在Long類(lèi)的源代碼中看到這個(gè)plus 運(yùn)算符函數(shù)的定義:
public operator fun plus(other: Byte): Long public operator fun plus(other: Short): Long public operator fun plus(other: Int): Long public operator fun plus(other: Long): Long public operator fun plus(other: Float): Float public operator fun plus(other: Double): Double
也就是說(shuō), 編譯器會(huì)把1L + 3 翻譯成 1L.plus(3),然后這個(gè)傳入的參數(shù)類(lèi)型必須是Byte、Short、Int、Long、Float、Double中的一種。例如,我們傳入一個(gè)字符Char參數(shù),編譯器就會(huì)直接拋錯(cuò):
>>> "a" a >>> "a"::class class kotlin.Char >>> "a"::class.java char >>> 1L+"a" error: none of the following functions can be called with the arguments supplied: public final operator fun plus(other: Byte): Long defined in kotlin.Long public final operator fun plus(other: Double): Double defined in kotlin.Long public final operator fun plus(other: Float): Float defined in kotlin.Long public final operator fun plus(other: Int): Long defined in kotlin.Long public final operator fun plus(other: Long): Long defined in kotlin.Long public final operator fun plus(other: Short): Long defined in kotlin.Long 1L+"a" ^運(yùn)算
Kotlin支持?jǐn)?shù)字運(yùn)算的標(biāo)準(zhǔn)集,運(yùn)算被定義為相應(yīng)的類(lèi)成員(但編譯器會(huì)將函數(shù)調(diào)用優(yōu)化為相應(yīng)的指令)。
對(duì)于位運(yùn)算,沒(méi)有特殊字符來(lái)表示,而只可用中綴方式調(diào)用命名函數(shù)(infix fun),例如:
val x = (1 shl 2) and 0x000FF000
這是完整的位運(yùn)算列表(只用于 Int 和 Long):
shl(bits) – 有符號(hào)左移 (Java 的 <<)
shr(bits) – 有符號(hào)右移 (Java 的 >>)
ushr(bits) – 無(wú)符號(hào)右移 (Java 的 >>>)
and(bits) – 位與
or(bits) – 位或
xor(bits) – 位異或
inv() – 位非
4.4.2 Char: 字符(Character)類(lèi)型與轉(zhuǎn)義符(Escape character)字符用 Char 類(lèi)型表示。它們不能直接當(dāng)作數(shù)字
fun check(c: Char) { if (c == 1) { // 錯(cuò)誤:類(lèi)型不兼容 // …… } }
字符字面值用 單引號(hào) 括起來(lái): "1"。
特殊字符可以用反斜杠轉(zhuǎn)義。
Kotlin支持如下轉(zhuǎn)義字符:
` " $
編碼其他字符要用 Unicode 轉(zhuǎn)義序列語(yǔ)法,例如:"uFF00"。
Char類(lèi)的函數(shù)接口定義如下:
public class Char private constructor() : Comparable{ /** * Compares this value with the specified value for order. * Returns zero if this value is equal to the specified other value, a negative number if it"s less than other, * or a positive number if it"s greater than other. */ public override fun compareTo(other: Char): Int /** Adds the other Int value to this value resulting a Char. */ public operator fun plus(other: Int): Char /** Subtracts the other Char value from this value resulting an Int. */ public operator fun minus(other: Char): Int /** Subtracts the other Int value from this value resulting a Char. */ public operator fun minus(other: Int): Char /** Increments this value. */ public operator fun inc(): Char /** Decrements this value. */ public operator fun dec(): Char /** Creates a range from this value to the specified [other] value. */ public operator fun rangeTo(other: Char): CharRange /** Returns the value of this character as a `Byte`. */ public fun toByte(): Byte /** Returns the value of this character as a `Char`. */ public fun toChar(): Char /** Returns the value of this character as a `Short`. */ public fun toShort(): Short /** Returns the value of this character as a `Int`. */ public fun toInt(): Int /** Returns the value of this character as a `Long`. */ public fun toLong(): Long /** Returns the value of this character as a `Float`. */ public fun toFloat(): Float /** Returns the value of this character as a `Double`. */ public fun toDouble(): Double }
我們來(lái)用代碼示例這些函數(shù)的使用:
如果兩個(gè)字符相等:
>>> "a".compareTo("a") 0
如果兩個(gè)字符不相等:
>>> "a".compareTo("b") -1 >>> "a".compareTo("c") -1 >>> "b".compareTo("a") 1 >>> "c".compareTo("a") 1
Char字符只重載了加上Int類(lèi)型的數(shù)字的+運(yùn)算符:
>>> "a"+1 b >>> "a"+1L error: the integer literal does not conform to the expected type Int "a"+1L
所以,當(dāng)我們把一個(gè)Char類(lèi)型值和不是Int類(lèi)型的值相加,就報(bào)錯(cuò)了。
相減:
>>> "a"-1 ` >>> "c"-"a" 2
自增計(jì)算:
>>> var a="a" >>> val b=a++ >>> a b >>> b a >>> val c=++a >>> c c
我們不能在字符的字面量上直接使用++:
>>> "a"++ error: variable expected "a"++ ^ >>> ++"a" error: variable expected ++"a" ^
范圍
>>> "a".rangeTo("z") a..z >>> for(c in "a".."z") {print(c)} abcdefghijklmnopqrstuvwxyz
Char的顯式類(lèi)型轉(zhuǎn)換函數(shù)如下:
/** Returns the value of this character as a `Byte`. */ public fun toByte(): Byte /** Returns the value of this character as a `Char`. */ public fun toChar(): Char /** Returns the value of this character as a `Short`. */ public fun toShort(): Short /** Returns the value of this character as a `Int`. */ public fun toInt(): Int /** Returns the value of this character as a `Long`. */ public fun toLong(): Long /** Returns the value of this character as a `Float`. */ public fun toFloat(): Float /** Returns the value of this character as a `Double`. */ public fun toDouble(): Double
例如,我們顯式把字符轉(zhuǎn)換為 Int 數(shù)字:
fun decimalDigitValue(c: Char): Int { if (c !in "0".."9") throw IllegalArgumentException("Out of range") return c.toInt() - "0".toInt() // 顯式轉(zhuǎn)換為數(shù)字 }
測(cè)試代碼:
>>> decimalDigitValue("a") java.lang.IllegalArgumentException: Out of range at Line24.decimalDigitValue(Unknown Source) >>> decimalDigitValue("1") 14.4.3 Boolean: 布爾類(lèi)型
Kotlin的布爾類(lèi)型用 Boolean 類(lèi)來(lái)表示,它有兩個(gè)值:true 和 false。
>>> true::class class kotlin.Boolean >>> true::class.java boolean
對(duì)應(yīng)Java中的boolean類(lèi)型。
其源碼定義如下:
package kotlin /** * Represents a value which is either `true` or `false`. On the JVM, non-nullable values of this type are * represented as values of the primitive type `boolean`. */ public class Boolean private constructor() : Comparable{ /** * Returns the inverse of this boolean. */ public operator fun not(): Boolean /** * Performs a logical `and` operation between this Boolean and the [other] one. */ public infix fun and(other: Boolean): Boolean /** * Performs a logical `or` operation between this Boolean and the [other] one. */ public infix fun or(other: Boolean): Boolean /** * Performs a logical `xor` operation between this Boolean and the [other] one. */ public infix fun xor(other: Boolean): Boolean public override fun compareTo(other: Boolean): Int }
從上面我們可以看出,Boolean類(lèi)的內(nèi)置的布爾運(yùn)算有:
! 邏輯非 not()
&& 短路邏輯與 and()
|| 短路邏輯或or()
xor 異或(相同false,不同true)
另外,Boolean還繼承實(shí)現(xiàn)了Comparable的compareTo()函數(shù)。
代碼示例:
>>> !true false >>> true.not() false >>> true && true true >>> true.and(false) false >>> true || false true >>> false.or(false) false >>> true xor true false >>> true xor false true >>> false xor false false >>> true > false true >>> true < false false >>> true.compareTo(false) 1 >>> true.compareTo(false) 1 >>> true.compareTo(true) 0 >>> false.compareTo(true) -14.4.4 String: 字符串類(lèi)型
Kotlin的字符串用 String 類(lèi)型表示。對(duì)應(yīng)Java中的java.lang.String。字符串是不可變的。
>>> "abc"::class class kotlin.String >>> "abc"::class.java class java.lang.String
另外,在Kotlin中,String同樣是final不可繼承的。
代碼示例:
>>> class MyString:String error: this type is final, so it cannot be inherited from class MyString:String ^索引運(yùn)算符 s[i]
字符串的元素——字符可以使用索引運(yùn)算符 s[i]來(lái)訪問(wèn)。
>>> val s="abc" >>> s abc >>> s[0] a
當(dāng)我們下標(biāo)越界時(shí),會(huì)拋越界錯(cuò)誤:
>>> s[-1] java.lang.StringIndexOutOfBoundsException: String index out of range: -1 at java.lang.String.charAt(String.java:646) >>> s[3] java.lang.StringIndexOutOfBoundsException: String index out of range: 3 at java.lang.String.charAt(String.java:646)
從出錯(cuò)信息,我們可以看出,索引運(yùn)算符 s[i]會(huì)被翻譯成java.lang.String.charAt(), 背后調(diào)用的是Java的String類(lèi)。其調(diào)用的方法是:
public char charAt(int index) { if ((index < 0) || (index >= value.length)) { throw new StringIndexOutOfBoundsException(index); } return value[index]; }for 循環(huán)迭代字符串
我們可以用 for 循環(huán)迭代字符串:
>>> for(c in "abc") { println(c) } a b c
關(guān)于字符串String類(lèi)的完整的操作方法,我們可以看下源碼:
public class String : Comparable, CharSequence { companion object {} /** * Returns a string obtained by concatenating this string with the string representation of the given [other] object. */ public operator fun plus(other: Any?): String public override val length: Int public override fun get(index: Int): Char public override fun subSequence(startIndex: Int, endIndex: Int): CharSequence public override fun compareTo(other: String): Int }
類(lèi)似的,字符串有一個(gè)length屬性:
>>> "abc".length 3重載+操作符
字符串類(lèi)重載了+操作符,作用對(duì)象可以是任何對(duì)象,包括空引用:
>>> "abc".plus(true) abctrue >>> "abc"+false abcfalse >>> "abc"+1 abc1 >>> "abc"+1.20 abc1.2 >>> "abc"+100L abc100 >>> "abc"+"cdef" abccdef >>> "abc"+null abcnull >>> "abc"+"z" abcz >>> "abc"+arrayOf(1,2,3,4,5) abc[Ljava.lang.Integer;@3d6f0054
截取字符串的子串:
>>> "abc".subSequence(0,1) a >>> "abc".subSequence(0,2) ab >>> "abc".subSequence(0,3) abc >>> "abc".subSequence(0,4) java.lang.StringIndexOutOfBoundsException: String index out of range: 4 at java.lang.String.substring(String.java:1951) at java.lang.String.subSequence(String.java:1991)字符串字面值
字符串的字面值,可以包含原生字符串可以包含換行和任意文本,也可以是帶有轉(zhuǎn)義字符(Escape Charactor)的轉(zhuǎn)義字符串。
>>> val s = "Hello,World! " >>> s Hello,World! >>>
轉(zhuǎn)義采用傳統(tǒng)的反斜杠方式。
原生字符串 使用三個(gè)引號(hào)(""")分界符括起來(lái),內(nèi)部沒(méi)有轉(zhuǎn)義并且可以包含換行和任何其他字符:
>>> val text = """ ... for (c in "abc") ... print(c) ... """ >>> text for (c in "foo") print(c) >>>
另外,在package kotlin.text下面的Indent.kt代碼中,Kotlin還定義了String類(lèi)的擴(kuò)展函數(shù):
fun String.trimMargin(marginPrefix: String = "|"): String fun String.trimIndent(): String
我們可以使用trimMargin()、trimIndent() 裁剪函數(shù)來(lái)去除前導(dǎo)空格。可以看出,trimMargin()函數(shù)默認(rèn)使用 "|" 來(lái)作為邊界字符:
>>> val text = """ ... |理論是你知道是這樣,但它卻不好用。 ... |實(shí)踐是它很好用,但你不知道是為什么。 ... |程序員將理論和實(shí)踐結(jié)合到一起: ... |既不好用,也不知道是為什么。 ... """ >>> text.trimMargin() 理論是你知道是這樣,但它卻不好用。 實(shí)踐是它很好用,但你不知道是為什么。 程序員將理論和實(shí)踐結(jié)合到一起: 既不好用,也不知道是為什么。
默認(rèn) | 用作邊界前綴,但你可以選擇其他字符并作為參數(shù)傳入,比如 trimMargin(">")。
trimIndent()函數(shù),則是把字符串行的左邊空白對(duì)齊切割:
>>> val text=""" ... Hello ... World! ... """ >>> text.trimIndent() Hello World! >>> val text=""" ... Hello, ... World! ... """ >>> text.trimIndent() Hello, World!字符串模板
字符串可以包含模板表達(dá)式 ,即一些小段代碼,會(huì)求值并把結(jié)果合并到字符串中。
模板表達(dá)式以美元符($)開(kāi)頭,由一個(gè)簡(jiǎn)單的名字構(gòu)成:
>>> val h=100 >>> val str = "A hundred is $h" >>> str A hundred is 100
或者用花括號(hào)擴(kuò)起來(lái)的任意表達(dá)式:
>>> val s = "abc" >>> val str = "$s.length is ${s.length}" >>> str abc.length is 3
原生字符串和轉(zhuǎn)義字符串內(nèi)部都支持模板。
>>> val price=9.9 >>> val str="""Price is $$price""" >>> str Price is $9.9 >>> val str="Price is $$price" >>> str Price is $9.9 >>> val quantity=100 >>> val str="Quantity is $quantity" >>> str Quantity is 100 >>> val str="""Quantity is $quantity""" >>> str Quantity is 1004.4.5 Array: 數(shù)組類(lèi)型
數(shù)組在 Kotlin 中使用 Array 類(lèi)來(lái)表示,它定義了 get 和 set 函數(shù)(映射到重載運(yùn)算符 [])和 size 屬性,以及一個(gè)用于變量數(shù)組的iterator()函數(shù):
class Arrayprivate constructor() { val size: Int operator fun get(index: Int): T operator fun set(index: Int, value: T): Unit operator fun iterator(): Iterator // …… }
我們可以使用函數(shù) arrayOf() 來(lái)創(chuàng)建一個(gè)數(shù)組并傳遞元素值給它。這個(gè)函數(shù)簽名如下:
public inline funarrayOf(vararg elements: T): Array
其中,vararg表示是一個(gè)參數(shù)個(gè)數(shù)是一個(gè)變量。
例如, arrayOf(1, 2, 3) 創(chuàng)建了 array [1, 2, 3] :
>>> arrayOf(1,2,3) [Ljava.lang.Integer;@4a37191a >>> arrayOf(1,2,3)::class class kotlin.Array >>> arrayOf(1,2,3)::class.java class [Ljava.lang.Integer;
另外,Kotlin還允許不同類(lèi)型元素放到一個(gè)數(shù)組中,例如:
>>> val arr = arrayOf(1,"2",true) >>> arr [Ljava.lang.Object;@61af1510 >>> arr.forEach{ println(it) } 1 2 true >>> arr.forEach{ println(it::class) } class kotlin.Int class kotlin.String class kotlin.Boolean
Kotlin自動(dòng)把這個(gè)數(shù)組元素的類(lèi)型升級(jí)為java.lang.Object, 同時(shí),由于Kotlin擁有的類(lèi)型推斷的功能,我們?nèi)匀豢梢钥吹矫總€(gè)數(shù)組元素對(duì)應(yīng)的各自的類(lèi)型。
函數(shù) arrayOfNulls() 可以用于創(chuàng)建一個(gè)指定大小、元素都為空的數(shù)組。這個(gè)特殊的空數(shù)組在創(chuàng)建的時(shí)候,我們需要指定元素的類(lèi)型。如果不指定,直接按照下面這樣寫(xiě),會(huì)報(bào)錯(cuò):
>>> arrayOfNulls(10) error: type inference failed: Not enough information to infer parameter T in funarrayOfNulls(size: Int): Array Please specify it explicitly. arrayOfNulls(10) ^
也就是說(shuō),我們要指定
>>> arrayOfNulls(10) [Ljava.lang.Integer;@77c10a5f >>> arrayOfNulls (10).forEach{println(it)} null null null null null null null null null null
數(shù)組Array類(lèi),還提供了一個(gè)構(gòu)造函數(shù):
public inline constructor(size: Int, init: (Int) -> T)
第1個(gè)參數(shù)是數(shù)組大小,第2個(gè)參數(shù)是一個(gè)初始化函數(shù)類(lèi)型的參數(shù)(關(guān)于函數(shù)類(lèi)型,我們將在后面章節(jié)介紹)。
代碼示例:
>>> val square = Array(10, { i -> (i*i)}) >>> square [Ljava.lang.Integer;@6f9e08d4 >>> square.forEach{ println(it) } 0 1 4 9 16 25 36 49 64 81
如上所述,[] 運(yùn)算符代表調(diào)用成員函數(shù) get() 和 set()。
代碼示例:
>>> square[3] 9 >>> square[3]=1000 >>> square.forEach{ println(it) } 0 1 4 1000 16 25 36 49 64 81
與 Java 不同的是,Kotlin 中數(shù)組不是型變的(invariant)。 Kotlin中,我們不能把 Array
代碼示例:
>>> val arrstr = arrayOf原生數(shù)組類(lèi)型("1","2","3") >>> arrstr [Ljava.lang.String;@39374689 >>> var arrany = arrayOf (Any(),Any(),Any()) >>> arrany [Ljava.lang.Object;@156324b >>> arrany = arrstr error: type mismatch: inferred type is Array but Array was expected arrany = arrstr ^
Kotlin 也有無(wú)裝箱開(kāi)銷(xiāo)的專門(mén)的類(lèi)來(lái)表示原生類(lèi)型數(shù)組。這些原生數(shù)組類(lèi)如下:
BooleanArray ByteArray CharArray ShortArray IntArray LongArray FloatArray DoubleArray BooleanArray
這些類(lèi)和 Array 并沒(méi)有繼承關(guān)系,但它們有同樣的函數(shù)和屬性集。它們也都有相應(yīng)的工廠方法:
/** * Returns an array containing the specified [Double] numbers. */ public fun doubleArrayOf(vararg elements: Double): DoubleArray /** * Returns an array containing the specified [Float] numbers. */ public fun floatArrayOf(vararg elements: Float): FloatArray /** * Returns an array containing the specified [Long] numbers. */ public fun longArrayOf(vararg elements: Long): LongArray /** * Returns an array containing the specified [Int] numbers. */ public fun intArrayOf(vararg elements: Int): IntArray /** * Returns an array containing the specified characters. */ public fun charArrayOf(vararg elements: Char): CharArray /** * Returns an array containing the specified [Short] numbers. */ public fun shortArrayOf(vararg elements: Short): ShortArray /** * Returns an array containing the specified [Byte] numbers. */ public fun byteArrayOf(vararg elements: Byte): ByteArray /** * Returns an array containing the specified boolean values. */ public fun booleanArrayOf(vararg elements: Boolean): BooleanArray
代碼示例:
>>> val x: IntArray = intArrayOf(1, 2, 3) >>> x[0] 14.5 Any?可空類(lèi)型(Nullable Types)
可空類(lèi)型是Kotlin類(lèi)型系統(tǒng)的一個(gè)特性,主要是為了解決Java中的令人頭疼的
NullPointerException問(wèn)題。
我們知道,在Java中如果一個(gè)變量可以是null,來(lái)那么使用它調(diào)用一個(gè)方法就是不安全的,因?yàn)樗鼤?huì)導(dǎo)致:NullPointerException 。
Kotlin把可空性(nullability)作為類(lèi)型系統(tǒng)的一部分,Kotlin編譯器可以直接在編譯過(guò)程中發(fā)現(xiàn)許多可能的錯(cuò)誤,并減少在運(yùn)行時(shí)拋出異常的可能性。
Kotlin的類(lèi)型系統(tǒng)和Java相比,首要的區(qū)別就是Kotlin對(duì)可空類(lèi)型的顯式支持。
在本節(jié)中,我們將討論Kotlin中的可空類(lèi)型。
4.5.1 null 是什么對(duì)于Java程序員來(lái)說(shuō),null是令人頭痛的東西。我們時(shí)常會(huì)受到空指針異常(NPE)的騷擾。就連Java的發(fā)明者都承認(rèn)這是他的一項(xiàng)巨大失誤。Java為什么要保留null呢?null出現(xiàn)有一段時(shí)間了,并且我認(rèn)為Java發(fā)明者知道null與它解決的問(wèn)題相比帶來(lái)了更多的麻煩,但是null仍然陪伴著Java。
我們通常把null理解為編程語(yǔ)言中定義特殊的0, 把我們初始化的指針指向它,以防止“野指針”的惡果。在Java中,null是任何引用類(lèi)型的默認(rèn)值,不嚴(yán)格的說(shuō)是所有Object類(lèi)型的默認(rèn)值。
這里的null既不是對(duì)象也不是一種類(lèi)型,它僅是一種特殊的值,我們可以將其賦予任何引用類(lèi)型,也可以將null轉(zhuǎn)化成任何類(lèi)型。在編譯和運(yùn)行時(shí)期,將null強(qiáng)制轉(zhuǎn)換成任何引用類(lèi)型都是可行的,在運(yùn)行時(shí)期都不會(huì)拋出空指針異常。注意,這里指的是任何Java的引用類(lèi)型。在遇到基本類(lèi)型int long float double short byte 等的時(shí)候,情況就不一樣了。而且還是個(gè)坑。編譯器不會(huì)報(bào)錯(cuò),但是運(yùn)行時(shí)會(huì)拋NPE??罩羔槷惓?。這是Java中的自動(dòng)拆箱導(dǎo)致的。代碼示例:
Integer nullInt = null; // this is ok int anotherInt = nullInt; // 編譯器允許這么賦值, 但是在運(yùn)行時(shí)拋 NullPointerException
所以,我們寫(xiě)Java代碼的時(shí)候,要時(shí)刻注意這一點(diǎn):Integer的默認(rèn)值是null而不是0。當(dāng)把null值傳遞給一個(gè)int型變量的時(shí)候,Java的自動(dòng)裝箱將會(huì)返回空指針異常。
4.5.2 Kotlin中的null在Kotlin中,針對(duì)Java中的null的雜亂局面,進(jìn)行了整頓,作了清晰的界定,并在編譯器級(jí)別強(qiáng)制規(guī)范了可空null變量類(lèi)型的使用。
我們來(lái)看一下Kotlin中關(guān)于null的一些有趣的運(yùn)算。
null跟null是相等的:
>>> null==null true >>> null!=null false
null這個(gè)值比較特殊,null 不是Any類(lèi)型
>>> null is Any false
但是,null是Any?類(lèi)型:
>>> null is Any? true
我們來(lái)看看null對(duì)應(yīng)的類(lèi)型到底是什么:
>>> var a=null >>> a null >>> a=1 error: the integer literal does not conform to the expected type Nothing? a=1 ^
從報(bào)錯(cuò)信息我們可以看出,null的類(lèi)型是Nothing?。關(guān)于Nothing?我們將會(huì)在下一小節(jié)中介紹。
我們可以對(duì)null進(jìn)行加法運(yùn)算:
>>> "1"+null 1null >>> null+20 null20
對(duì)應(yīng)的重載運(yùn)算符的函數(shù)定義在kotlin/Library.kt里面:
package kotlin import kotlin.internal.PureReifiable /** * Returns a string representation of the object. Can be called with a null receiver, in which case * it returns the string "null". */ public fun Any?.toString(): String /** * Concatenates this string with the string representation of the given [other] object. If either the receiver * or the [other] object are null, they are represented as the string "null". */ public operator fun String?.plus(other: Any?): String ...
但是,反過(guò)來(lái)就不行了:
>>> 1+null error: none of the following functions can be called with the arguments supplied: public final operator fun plus(other: Byte): Int defined in kotlin.Int public final operator fun plus(other: Double): Double defined in kotlin.Int public final operator fun plus(other: Float): Float defined in kotlin.Int public final operator fun plus(other: Int): Int defined in kotlin.Int public final operator fun plus(other: Long): Long defined in kotlin.Int public final operator fun plus(other: Short): Int defined in kotlin.Int 1+null ^
這是因?yàn)镮nt沒(méi)有重載傳入null參數(shù)的plus()函數(shù)。
4.5.3 可空類(lèi)型String?與安全調(diào)用?.我們來(lái)看一個(gè)例子。下面是計(jì)算字符串長(zhǎng)度的簡(jiǎn)單Java方法:
public static int getLength1(String str) { return str.length(); }
我們已經(jīng)習(xí)慣了在這樣的Java代碼中,加上這樣的空判斷處理:
public static int getLength2(String str) throws Exception { if (null == str) { throw new Exception("str is null"); } return str.length(); }
而在Kotlin中,當(dāng)我們同樣寫(xiě)一個(gè)可能為null參數(shù)的函數(shù)時(shí):
fun getLength1(str: String): Int { return str.length }
當(dāng)我們傳入一個(gè)null參數(shù)時(shí):
@Test fun testGetLength1() { val StringUtilKt = StringUtilKt() StringUtilKt.getLength1(null) }
編譯器就直接編譯失?。?/p>
e: /Users/jack/easykotlin/chapter4_type_system/src/test/kotlin/com/easy/kotlin/StringUtilKtTest.kt: (15, 33): Null can not be a value of a non-null type String :compileTestKotlin FAILED FAILURE: Build failed with an exception. * What went wrong: Execution failed for task ":compileTestKotlin". > Compilation error. See log for more details
如果我們使用IDEA,會(huì)在編碼時(shí)就直接提示錯(cuò)誤了:
這樣通過(guò)編譯時(shí)強(qiáng)制排除空指針的錯(cuò)誤,大大減少了出現(xiàn)NPE的可能。
另外,如果我們確實(shí)需要傳入一個(gè)可空的參數(shù),我們可以使用可空類(lèi)型String?來(lái)聲明一個(gè)可以指向空指針的變量。
可空類(lèi)型可以用來(lái)標(biāo)記任何一個(gè)變量,來(lái)表明這個(gè)變量是可空的(Nullable)。例如:
Char?, Int?, MineType?(自定義的類(lèi)型)等等。
我們用示例代碼來(lái)更加簡(jiǎn)潔的說(shuō)明:
>>> var x:String="x" >>> x=null error: null can not be a value of a non-null type String x=null ^ >>> var y:String?="y" >>> y=null >>> y null
我們可以看出:普通String類(lèi)型,是不允許指向null的;而可空String?類(lèi)可以指向null。
下面我們來(lái)嘗試使用一個(gè)可空變量來(lái)調(diào)用函數(shù):
>>> fun getLength2(str: String?): Int? = str.length error: only safe (?.) or non-null asserted (!!.) calls are allowed on a nullable receiver of type String? fun getLength2(str: String?): Int? = str.length ^
編譯器直接報(bào)錯(cuò),告訴我們,變量str: String?是可空的類(lèi)型,調(diào)用只能通過(guò)安全調(diào)用?. 或者 非空斷言調(diào)用!!. 。
另外,如果不需要捕獲異常來(lái)處理,我們可以使用Kotlin里面的安全調(diào)用符?. 。
代碼示例:
fun getLength2(str: String?): Int? { return str?.length }
測(cè)試代碼:
@Test fun testGetLength2() { val StringUtilKt = StringUtilKt() println(StringUtilKt.getLength2(null)) //null Assert.assertTrue(3 == StringUtilKt.getLength2("abc")) }
我們可以看出,當(dāng)我們使用安全調(diào)用?. , 代碼安靜的執(zhí)行輸出了null。
如果,我們確實(shí)想寫(xiě)一個(gè)出現(xiàn)空指針異常的代碼,那就使用可能出現(xiàn)空指針的斷言調(diào)用符!!. 。
代碼示例:
fun getLength3(str: String?): Int? { return str!!.length }
測(cè)試代碼:
@Test fun testGetLength3() { val StringUtilKt = StringUtilKt() println(StringUtilKt.getLength3(null)) Assert.assertTrue(3 == StringUtilKt.getLength3("abc")) }
上面的代碼就跟Java里面差不多了,運(yùn)行會(huì)直接拋出空指針異常:
kotlin.KotlinNullPointerException at com.easy.kotlin.StringUtilKt.getLength3(StringUtilKt.kt:16) at com.easy.kotlin.StringUtilKtTest.testGetLength3(StringUtilKtTest.kt:28)
這里的KotlinNullPointerException 是KotlinNullPointerException.java代碼,繼承了Java中的java.lang.NullPointerException, 它的源代碼如下:
package kotlin; public class KotlinNullPointerException extends NullPointerException { public KotlinNullPointerException() { } public KotlinNullPointerException(String message) { super(message); } }
另外,如果異常需要捕獲到進(jìn)行特殊處理的場(chǎng)景,在Kotlin中仍然使用 try ... catch 捕獲并處理異常。
4.5.4 可空性的實(shí)現(xiàn)原理我們來(lái)看一段Kotlin的可空類(lèi)型的示例代碼如下:
fun testNullable1(x: String, y: String?): Int { return x.length } fun testNullable2(x: String, y: String?): Int? { return y?.length } fun testNullable3(x: String, y: String?): Int? { return y!!.length }
我們來(lái)使用IDEA的Kotlin插件來(lái)看下可空類(lèi)型的安全調(diào)用的等價(jià)Java代碼。
打開(kāi)IDEA的 Tools > Kotlin > Show Kotlin Bytecode
然后,點(diǎn)擊Decompile , 我們可以得到反編譯的Java代碼
public final class NullableTypesKt { public static final int testNullable1(@NotNull String x, @Nullable String y) { Intrinsics.checkParameterIsNotNull(x, "x"); return x.length(); } @Nullable public static final Integer testNullable2(@NotNull String x, @Nullable String y) { Intrinsics.checkParameterIsNotNull(x, "x"); return y != null?Integer.valueOf(y.length()):null; } @Nullable public static final Integer testNullable3(@NotNull String x, @Nullable String y) { Intrinsics.checkParameterIsNotNull(x, "x"); if(y == null) { Intrinsics.throwNpe(); } return Integer.valueOf(y.length()); } }
在不可空變量調(diào)用函數(shù)之前,都檢查了是否為空, 使用的是kotlin.jvm.internal.Intrinsics這個(gè)Java類(lèi)里面的checkParameterIsNotNull方法。如果是null就拋出異常:
public static void checkParameterIsNotNull(Object value, String paramName) { if (value == null) { throwParameterIsNullException(paramName); } }
同時(shí),我們可以看出在Kotlin中函數(shù)的入?yún)⒙暶?/p>
fun testNullable(x: String, y: String?)
反編譯成等價(jià)的Java代碼是
public static final void testNullable(@NotNull String x, @Nullable String y)
我們可以看出,這里使用注解@NotNull標(biāo)注不可空的變量,使用注解@Nullable標(biāo)注一個(gè)變量可空。
可空變量的安全調(diào)用符y?.length 等價(jià)的Java代碼就是:
y != null?Integer.valueOf(y.length()):null
可空變量的斷言調(diào)用y!!.length等價(jià)的Java代碼是:
if(y == null) { Intrinsics.throwNpe(); } return Integer.valueOf(y.length());4.5.5 可空類(lèi)型層次體系
就像Any是在非空類(lèi)型層次結(jié)構(gòu)的根,
Any?是可空類(lèi)型層次的根。
由于Any?是Any的超集,所以,Any?是Kotlin的類(lèi)型層次結(jié)構(gòu)的最頂端。
代碼示例:
>>> 1 is Any true >>> 1 is Any? true >>> null is Any false >>> null is Any? true >>> Any() is Any? true4.6 kotlin.Unit類(lèi)型
Kotlin也是面向表達(dá)式的語(yǔ)言。在Kotlin中所有控制流語(yǔ)句都是表達(dá)式(除了變量賦值、異常等)。
Kotlin中的Unit類(lèi)型實(shí)現(xiàn)了與Java中的void一樣的功能。不同的是,當(dāng)一個(gè)函數(shù)沒(méi)有返回值的時(shí)候,我們用Unit來(lái)表示這個(gè)特征,而不是null。
大多數(shù)時(shí)候,我們并不需要顯式地返回Unit,或者聲明一個(gè)函數(shù)的返回類(lèi)型為Unit。編譯器會(huì)推斷出它。
代碼示例:
>>> fun unitExample(){println("Hello,Unit")} >>> val helloUnit = unitExample() Hello,Unit >>> helloUnit kotlin.Unit >>> println(helloUnit) kotlin.Unit
下面幾種寫(xiě)法是等價(jià)的:
@RunWith(JUnit4::class) class UnitDemoTest { @Test fun testUnitDemo() { val ur1 = unitReturn1() println(ur1) // kotlin.Unit val ur2 = unitReturn2() println(ur2) // kotlin.Unit val ur3 = unitReturn3() println(ur3) // kotlin.Unit } fun unitReturn1() { } fun unitReturn2() { return Unit } fun unitReturn3(): Unit { } }
總的來(lái)說(shuō),這個(gè)Unit類(lèi)型并沒(méi)有什么特別之處。它的源碼是:
package kotlin /** * The type with only one value: the Unit object. This type corresponds to the `void` type in Java. */ public object Unit { override fun toString() = "kotlin.Unit" }
跟任何其他類(lèi)型一樣,它的父類(lèi)型是Any。如果是一個(gè)可空的Unit?,它的父類(lèi)型是Any?。
4.7 kotlin.Nothing類(lèi)型Kotlin中沒(méi)有類(lèi)似Java和C中的函數(shù)沒(méi)有返回值的標(biāo)記void,但是擁有一個(gè)對(duì)應(yīng)Nothing。在Java中,返回void的方法,其返回值void是無(wú)法被訪問(wèn)到的:
public class VoidDemo { public void voidDemo() { System.out.println("Hello,Void"); } }
測(cè)試代碼:
@org.junit.runner.RunWith(org.junit.runners.JUnit4.class) public class VoidDemoTest { @org.junit.Test public void testVoid() { VoidDemo voidDemo = new VoidDemo(); void v = voidDemo.voidDemo(); // 沒(méi)有void變量類(lèi)型,無(wú)法訪問(wèn)到void返回值 System.out.println(voidDemo.voidDemo()); // error: "void" type not allowed here } }
在Java中,void不能是變量的類(lèi)型。也不能被當(dāng)做值打印輸出。但是,在Java中有個(gè)包裝類(lèi)Void是 void 的自動(dòng)裝箱類(lèi)型。如果你想讓一個(gè)方法返回類(lèi)型 永遠(yuǎn)是 null 的話, 可以把返回類(lèi)型置為這個(gè)大寫(xiě)的V的Void類(lèi)型。
代碼示例:
public Void voidDemo() { System.out.println("Hello,Void"); return null; }
測(cè)試代碼:
@org.junit.runner.RunWith(org.junit.runners.JUnit4.class) public class VoidDemoTest { @org.junit.Test public void testVoid() { VoidDemo voidDemo = new VoidDemo(); Void v = voidDemo.voidDemo(); // Hello,Void System.out.println(v); // null } }
這個(gè)Void就是Kotlin中的Nothing?。它的唯一可被訪問(wèn)到的返回值也是null。
在Kotlin類(lèi)型層次結(jié)構(gòu)的最底層就是類(lèi)型Nothing。
正如它的名字Nothing所暗示的,Nothing是沒(méi)有實(shí)例的類(lèi)型。
代碼示例:
>>> Nothing() is Any error: cannot access "": it is private in "Nothing" Nothing() is Any ^
注意:Unit與Nothing之間的區(qū)別: Unit類(lèi)型表達(dá)式計(jì)算結(jié)果的返回類(lèi)型是Unit。Nothing類(lèi)型的表達(dá)式計(jì)算結(jié)果是永遠(yuǎn)不會(huì)返回的(跟Java
中的void相同)。
例如,throw關(guān)鍵字中斷的表達(dá)式的計(jì)算,并拋出堆棧的功能。所以,一個(gè)throw Exception 的代碼就是返回Nothing的表達(dá)式。代碼示例:
fun formatCell(value: Double): String = if (value.isNaN()) throw IllegalArgumentException("$value is not a number") // Nothing else value.toString()
再例如, Kotlin的標(biāo)準(zhǔn)庫(kù)里面的exitProcess函數(shù):
@file:kotlin.jvm.JvmName("ProcessKt") @file:kotlin.jvm.JvmVersion package kotlin.system /** * Terminates the currently running Java Virtual Machine. The * argument serves as a status code; by convention, a nonzero status * code indicates abnormal termination. * * This method never returns normally. */ @kotlin.internal.InlineOnly public inline fun exitProcess(status: Int): Nothing { System.exit(status) throw RuntimeException("System.exit returned normally, while it was supposed to halt JVM.") }
Nothing?可以只包含一個(gè)值:null。代碼示例:
>>> var nul:Nothing?=null >>> nul = 1 error: the integer literal does not conform to the expected type Nothing? nul = 1 ^ >>> nul = true error: the boolean literal does not conform to the expected type Nothing? nul = true ^ >>> nul = null >>> nul null
從上面的代碼示例,我們可以看出:Nothing?它唯一允許的值是null,被用作任何可空類(lèi)型的空引用。
綜上所述,我們可以看出Kotlin有一個(gè)簡(jiǎn)單而一致的類(lèi)型系統(tǒng)。Any?是整個(gè)類(lèi)型體系的頂部,Nothing是底部。如下圖所示:
4.8 類(lèi)型檢測(cè)與類(lèi)型轉(zhuǎn)換 4.8.1 is,!is運(yùn)算符is運(yùn)算符可以檢查對(duì)象是否與特定的類(lèi)型兼容(“兼容”的意思是:此對(duì)象是該類(lèi)型,或者派生于該類(lèi)型)。
is運(yùn)算符用來(lái)檢查對(duì)象(變量)是否屬于某數(shù)據(jù)類(lèi)型(如Int、String、Boolean等)。C#里面也有這個(gè)運(yùn)算符。
is運(yùn)算符類(lèi)似Java的instanceof:
@org.junit.runner.RunWith(org.junit.runners.JUnit4.class) public class TypeSystemDemo { @org.junit.Test public void testVoid() { if ("abc" instanceof String) { println("abc is instanceof String"); } else { println("abc is not instanceof String"); } } void println(Object obj) { System.out.println(obj); } }
在Kotlin中,我們可以在運(yùn)行時(shí)通過(guò)使用 is 操作符或其否定形式 !is 來(lái)檢查對(duì)象是否符合給定類(lèi)型:
>>> "abc" is String true >>> "abc" !is String false >>> null is Any false >>> null is Any? true
代碼示例:
@RunWith(JUnit4::class) class ASOperatorTest { @Test fun testAS() { val foo = Foo() val goo = Goo() println(foo is Foo) //true 自己 println(goo is Foo)// 子類(lèi) is 父類(lèi) = true println(foo is Goo)//父類(lèi) is 子類(lèi) = false println(goo is Goo)//true 自己 } } open class Foo class Goo : Foo()類(lèi)型自動(dòng)轉(zhuǎn)換
在Java代碼中,當(dāng)我們使用str instanceof String來(lái)判斷其值為true的時(shí)候,我們想使用str變量,還需要顯式的強(qiáng)制轉(zhuǎn)換類(lèi)型:
@org.junit.runner.RunWith(org.junit.runners.JUnit4.class) public class TypeSystemDemo { @org.junit.Test public void testVoid() { Object str = "abc"; if (str instanceof String) { int len = ((String)str).length(); // 顯式的強(qiáng)制轉(zhuǎn)換類(lèi)型為String println(str + " is instanceof String"); println("Length: " + len); } else { println(str + " is not instanceof String"); } boolean is = "1" instanceof String; println(is); } void println(Object obj) { System.out.println(obj); } }
而大多數(shù)情況下,我們不需要在 Kotlin 中使用顯式轉(zhuǎn)換操作符,因?yàn)榫幾g器跟蹤不可變值的 is-檢查,并在需要時(shí)自動(dòng)插入(安全的)轉(zhuǎn)換:
@Test fun testIS() { val len = strlen("abc") println(len) // 3 val lens = strlen(1) println(lens) // 1 } fun strlen(ani: Any): Int { if (ani is String) { return ani.length } else if (ani is Number) { return ani.toString().length } else if (ani is Char) { return 1 } else if (ani is Boolean) { return 1 } print("Not A String") return -1 }4.8.2 as運(yùn)算符
as運(yùn)算符用于執(zhí)行引用類(lèi)型的顯式類(lèi)型轉(zhuǎn)換。如果要轉(zhuǎn)換的類(lèi)型與指定的類(lèi)型兼容,轉(zhuǎn)換就會(huì)成功進(jìn)行;如果類(lèi)型不兼容,使用as?運(yùn)算符就會(huì)返回值null。
代碼示例:
>>> open class Foo >>> class Goo:Foo() >>> val foo = Foo() >>> val goo = Goo() >>> foo as Goo java.lang.ClassCastException: Line69$Foo cannot be cast to Line71$Goo >>> foo as? Goo null >>> goo as Foo Line71$Goo@73dce0e6
我們可以看出,在Kotlin中,父類(lèi)是禁止轉(zhuǎn)換為子類(lèi)型的。
按照Liskov替換原則,父類(lèi)轉(zhuǎn)換為子類(lèi)是對(duì)OOP的嚴(yán)重違反,不提倡、也不建議。嚴(yán)格來(lái)說(shuō),父類(lèi)是不能轉(zhuǎn)換為子類(lèi)的,子類(lèi)包含了父類(lèi)所有的方法和屬性,而父類(lèi)則未必具有和子類(lèi)同樣成員范圍,所以這種轉(zhuǎn)換是不被允許的,即便是兩個(gè)具有父子關(guān)系的空類(lèi)型,也是如此。
本章小結(jié)在本章中,我們停下腳步,仔細(xì)深入地去探討了Kotlin語(yǔ)言中最重要的部分之一的:類(lèi)型系統(tǒng)。
與Java相比,Kotlin的類(lèi)型系統(tǒng)更加簡(jiǎn)單一致,同時(shí)引入了一些新的特性,這些特性對(duì)于提高代碼的安全性、可靠性至關(guān)重要。例如:可空類(lèi)型和只讀集合。關(guān)于只讀集合類(lèi),我們將在下一章中介紹。
我們下一章的主題是:Kotlin的集合類(lèi)和泛型。
本章示例代碼工程:https://github.com/EasyKotlin...
參考資料1.https://jetbrains.github.io/k...
2.http://natpryce.com/articles/...
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://hztianpu.com/yun/67463.html
摘要:下一代服務(wù)端開(kāi)發(fā)下一代服務(wù)端開(kāi)發(fā)第部門(mén)快速開(kāi)始第章快速開(kāi)始環(huán)境準(zhǔn)備,,快速上手實(shí)現(xiàn)一個(gè)第章企業(yè)級(jí)服務(wù)開(kāi)發(fā)從到語(yǔ)言的缺點(diǎn)發(fā)展歷程的缺點(diǎn)為什么是產(chǎn)生的背景解決了哪些問(wèn)題為什么是的發(fā)展歷程容器的配置地獄是什么從到下一代企業(yè)級(jí)服務(wù)開(kāi)發(fā)在移動(dòng)開(kāi)發(fā)領(lǐng)域 《 Kotlin + Spring Boot : 下一代 Java 服務(wù)端開(kāi)發(fā) 》 Kotlin + Spring Boot : 下一代 Java...
摘要:是一門(mén)最近比較流行的靜態(tài)類(lèi)型編程語(yǔ)言,而且和一樣同屬系。這個(gè)生成的構(gòu)造函數(shù)是合成的,因此不能從或中直接調(diào)用,但可以使用反射調(diào)用。 showImg(https://segmentfault.com/img/remote/1460000012958496); Kotlin是一門(mén)最近比較流行的靜態(tài)類(lèi)型編程語(yǔ)言,而且和Groovy、Scala一樣同屬Java系。Kotlin具有的很多靜態(tài)語(yǔ)言...
摘要:第章元編程與注解反射反射是在運(yùn)行時(shí)獲取類(lèi)的函數(shù)方法屬性父類(lèi)接口注解元數(shù)據(jù)泛型信息等類(lèi)的內(nèi)部信息的機(jī)制。本章介紹中的注解與反射編程的相關(guān)內(nèi)容。元編程本質(zhì)上是一種對(duì)源代碼本身進(jìn)行高層次抽象的編碼技術(shù)。反射是促進(jìn)元編程的一種很有價(jià)值的語(yǔ)言特性。 第12章 元編程與注解、反射 反射(Reflection)是在運(yùn)行時(shí)獲取類(lèi)的函數(shù)(方法)、屬性、父類(lèi)、接口、注解元數(shù)據(jù)、泛型信息等類(lèi)的內(nèi)部信息的機(jī)...
摘要:的語(yǔ)言的動(dòng)態(tài)性意味著我們可以使用以上種數(shù)據(jù)類(lèi)型表示變換過(guò)渡動(dòng)畫(huà)實(shí)現(xiàn)案例前端掘金以下所有效果的實(shí)現(xiàn)方式均為個(gè)人見(jiàn)解,如有不對(duì)的地方還請(qǐng)一一指出。 讀 zepto 源碼之工具函數(shù) - 掘金Zepto 提供了豐富的工具函數(shù),下面來(lái)一一解讀。 源碼版本 本文閱讀的源碼為 zepto1.2.0 $.extend $.extend 方法可以用來(lái)擴(kuò)展目標(biāo)對(duì)象的屬性。目標(biāo)對(duì)象的同名屬性會(huì)被源對(duì)象的屬性...
閱讀 3765·2021-09-22 10:52
閱讀 1784·2021-09-09 09:34
閱讀 2109·2021-09-09 09:33
閱讀 875·2019-08-30 15:54
閱讀 2822·2019-08-29 11:15
閱讀 802·2019-08-26 13:37
閱讀 1756·2019-08-26 12:11
閱讀 3070·2019-08-26 12:00