成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

《Kotlin 極簡教程 》第4章 基本數(shù)據(jù)類型與類型系統(tǒng)

MoAir / 2971人閱讀

摘要:本章我們來學(xué)習(xí)一下的基本數(shù)據(jù)類型與類型系統(tǒng)。字符串就是一個(gè)抽象數(shù)據(jù)類型。如果程序語言的語法中含有類型標(biāo)記,就稱該語言是顯式類型化的,否則就稱為隱式類型化的。但是,可以把中對(duì)應(yīng)的這幾種基本數(shù)據(jù)類型,理解為的基本類型的裝箱類。

第4章 基本數(shù)據(jù)類型與類型系統(tǒng) 《Kotlin極簡教程》正式上架:
點(diǎn)擊這里 > 去京東商城購買閱讀 
點(diǎn)擊這里 > 去天貓商城購買閱讀 
非常感謝您親愛的讀者,大家請(qǐng)多支持?。?!有任何問題,歡迎隨時(shí)與我交流~

到目前為止,我們已經(jīng)了解了Kotlin的基本符號(hào)以及基礎(chǔ)語法。我們可以看出,使用Kotlin寫的代碼更簡潔、可讀性更好、更富有生產(chǎn)力。

本章我們來學(xué)習(xí)一下Kotlin的基本數(shù)據(jù)類型與類型系統(tǒng)。

道生一,一生二,二生三,三生萬物 (老子《道德經(jīng)》第四十二章)

在計(jì)算機(jī)科學(xué)中,最早的類型系統(tǒng)用來區(qū)別數(shù)字里面的整數(shù)和浮點(diǎn)數(shù)。

在20世紀(jì)五六十年代,這種分類擴(kuò)展到了結(jié)構(gòu)化的數(shù)據(jù)和高階函數(shù)中。

70年代,引入了幾個(gè)更為豐富的概念,例如:參數(shù)化類型,抽象數(shù)據(jù)類型,模塊系統(tǒng),子類型等等,類型系統(tǒng)作為一個(gè)獨(dú)立的領(lǐng)域形成了。

在每一門編程語言中,都有一個(gè)特定的類型系統(tǒng)(Type System)。類型系統(tǒng)是一門編程語言最核心也是最基礎(chǔ)的部分。我們這里說的類型系統(tǒng),可以簡單理解為以下兩個(gè)部分:

一組基本類型構(gòu)成的PTS(Primary Type Set,基本類型集合);

PTS上定義的一系列組合、運(yùn)算、轉(zhuǎn)換規(guī)則等。

這一簡單優(yōu)雅而驚人的世界構(gòu)成觀,貫穿了人類現(xiàn)實(shí)世界和計(jì)算機(jī)編程語言所定義的虛擬世界。或許語言的設(shè)計(jì)者也沒有料想到,但是最終的結(jié)果確實(shí)是有限的設(shè)計(jì)導(dǎo)出了無限的可能性。

本章我們將學(xué)習(xí)Kotlin語言的基本類型,以及簡單介紹Kotlin的類型系統(tǒng)。

4.1 什么是類型?

一切皆是映射

在計(jì)算機(jī)中,任何數(shù)值都是以一組比特(01)組成的,硬件無法區(qū)分內(nèi)存地址、腳本、字符、整數(shù)、以及浮點(diǎn)數(shù)。這個(gè)時(shí)候,我們使用類型賦予一組比特以特定的意義。

類型(Type),本質(zhì)上就是內(nèi)存中的數(shù)值或變量對(duì)象的邏輯映射。

《周易》有云:

易有太極,是生兩儀,兩儀生四象,四象生八卦。(《易傳·系辭上傳》) 。

這里所包含的思想,跟我們這里所說的類型系統(tǒng)的思想有著異曲同工之妙。

類型系統(tǒng)用于定義如何將編程語言中的數(shù)值和表達(dá)式歸類為許多不同的類型,如何操作這些類型,這些類型如何互相作用等。

類型系統(tǒng)在各種語言之間有非常大的不同,主要的差異存在于編譯時(shí)期的語法,以及運(yùn)行時(shí)期的操作實(shí)現(xiàn)方式。

類型系統(tǒng)提供的主要功能有:

安全性

編譯器可以使用類型來檢查無意義的,或者是可能無效的代碼。例如,在強(qiáng)類型的語言中,如果沒有對(duì)字符串的+進(jìn)行重載,那么表達(dá)式

"Hello, World" + 3

就會(huì)被編譯器檢測出來,因?yàn)椴荒軐?duì)字符串加上一個(gè)整數(shù)。強(qiáng)類型提供更多的安全性。

但是,為了讓程序員可以寫出極簡的代碼,很多語言都提供了操作符重載的機(jī)制。比如說,在Scala中,上面的代碼是可以被正確執(zhí)行的(重載了+操作符)

scala> "Hello,World"+1
res15: String = Hello,World1

scala> 1+"Hello,World"
res16: String = 1Hello,World

但是在Kotlin中, 由于Int類型沒有對(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)類型檢查可提供有用的信息給編譯器。編譯器可以使用更有效率的機(jī)器指令,實(shí)現(xiàn)編譯器優(yōu)化。

可讀性

抽象化(或模塊化)

類型本質(zhì)上是對(duì)較低層次的邏輯單元進(jìn)行高層次的邏輯抽象。這樣我們就可以直接使用類型在較高層次的方式思考,而不是繁重的低層次實(shí)現(xiàn)。

例如,我們可以將字符串想成一個(gè)值,以此取代僅僅是字節(jié)的數(shù)組。字符串就是一個(gè)抽象數(shù)據(jù)類型。

從01到類型,從類型到接口API,再到軟件服務(wù),都可以看做是廣義的“類型”范疇。

程序中的變量在程序執(zhí)行期間,可能會(huì)有不同的取值范圍,我們可以把變量可取值的最大范圍稱為這個(gè)變量的類型。例如,具有類型Boolean的變量x,在程序執(zhí)行期間,只能取布爾值。指定變量類型的程序設(shè)計(jì)語言,稱為類型化的語言(typed language)。

如果一個(gè)語言,不限制變量的取值,稱為無類型語言(untyped language),我們既可以說它不具有類型,也可以說它具有一個(gè)通用類型,這個(gè)類型的取值范圍是程序中所有可能的值。

類型系統(tǒng)是類型化語言的一個(gè)組成部分,它用來計(jì)算和跟蹤程序中所有表達(dá)式的類型,從而判斷某段程序是否表現(xiàn)良好(well behaved)。

如果程序語言的語法中含有類型標(biāo)記,就稱該語言是顯式類型化的(explicitly typed),否則就稱為隱式類型化的(implicitly typed)。

像C、C++、Java等語言,都是顯式類型化的。而像ML、Haskell、Groovy等可以省略類型聲明,它們的類型系統(tǒng)會(huì)自動(dòng)推斷出程序的類型。

4.2 編譯時(shí)類型與運(yùn)行時(shí)類型

Koltin是一門強(qiáng)類型的、靜態(tài)類型、支持隱式類型的顯式類型語言。

4.2.1 弱類型(Weakly checked language)與強(qiáng)類型(Strongly checked language)

類型系統(tǒng)最主要的作用是,通過檢查類型的運(yùn)算和轉(zhuǎn)換過程,來減少類型錯(cuò)誤的發(fā)生。如果一個(gè)語言的編譯器引入越多的類型檢查的限制,就可以稱這個(gè)語言的類型檢查越強(qiáng),反之越弱。根據(jù)類型檢查的強(qiáng)弱,我們把編程語言分為

弱類型語言

強(qiáng)類型語言

弱類型語言在運(yùn)行時(shí)會(huì)隱式做數(shù)據(jù)類型轉(zhuǎn)換。
強(qiáng)類型語言在運(yùn)行時(shí)會(huì)確保不會(huì)發(fā)生未經(jīng)明確轉(zhuǎn)換(顯式調(diào)用)的類型轉(zhuǎn)換。

但是另一方面,強(qiáng)和弱只是相對(duì)的。

Kotlin是強(qiáng)類型語言。

4.2.2 靜態(tài)類型(Statically checked language)與動(dòng)態(tài)類型(Dynamically

checked language)

類型檢查可發(fā)生在編譯時(shí)期(靜態(tài)檢查)或運(yùn)行時(shí)期(動(dòng)態(tài)檢查)。這樣我們將編程語言分為

靜態(tài)類型語言

動(dòng)態(tài)類型語言

靜態(tài)類型檢查是基于編譯器來分析源碼本身來確保類型安全。靜態(tài)類型檢查能讓很多bug在編碼早期被捕捉到,并且它也能優(yōu)化運(yùn)行。因?yàn)槿绻幾g器在編譯時(shí)已經(jīng)證明程序是類型安全的,就不用在運(yùn)行時(shí)進(jìn)行動(dòng)態(tài)的類型檢查,編譯過后的代碼會(huì)更優(yōu)化,運(yùn)行更快。

動(dòng)態(tài)類型語言是在運(yùn)行時(shí)期進(jìn)行類型標(biāo)記的檢查,因?yàn)樽兞克s束的值,可經(jīng)由運(yùn)行路徑獲得不同的標(biāo)記。關(guān)于動(dòng)態(tài)類型,有個(gè)很形象的說法:

當(dāng)看到一只鳥走起來像鴨子、游泳起來像鴨子、叫起來也像鴨子,那么這只鳥就可以被稱為鴨子?!材匪埂せ萏乜颇贰とR利(James Whitcomb Riley,1849-1916)

Kotlin是靜態(tài)類型語言。

4.2.3 顯式類型(Explicitly typed language)與隱式類型(Implicitly typed language)

還有一種區(qū)分方法是,根據(jù)變量名是否需要顯式給出類型的聲明,來將語言分為

顯式類型語言

隱式類型語言

前者需要在定義變量時(shí)顯式給出變量的類型,而后者可以使用類型推論來確定變量的類型。

大多數(shù)靜態(tài)類型語言,例如 Java、C/C++ 都是顯式類型語言。但是有些則不是,如 Haskell、ML 等,它們可以基于變量的操作來推斷其類型;

Scala 是靜態(tài)類型語言,它使用類型推斷功能來支持隱式類型。

Kotlin 跟Scala類似,它也使用類型推斷支持隱式類型。但是,在一些場景下也需要顯式聲明變量的類型,所以我們可以說,同時(shí)也是顯式類型。

4.3 根類型Any

Kotlin 中所有類都有一個(gè)共同的超類 Any ,如果類聲明時(shí)沒有指定超類,則默認(rèn)為 Any 。我們來看一段代碼:

>>> val any = Any()
>>> any
java.lang.Object@2e377400
>>> any::class
class kotlin.Any
>>> any::class.java
class java.lang.Object

也就是說,Any在運(yùn)行時(shí),其類型自動(dòng)映射成java.lang.Object。我們知道,在Java中Object類是所有引用類型的父類。但是不包括基本類型:byte int long等,基本類型對(duì)應(yīng)的包裝類是引用類型,其父類是Object。而在Kotlin中,直接統(tǒng)一——所有類型都是引用類型,統(tǒng)一繼承父類Any。

Any是Java的等價(jià)Object類。但是跟Java不同的是,Kotlin中語言內(nèi)部的類型和用戶定義類型之間,并沒有像Java那樣劃清界限。它們是同一類型層次結(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 基本類型(Primitive Types)

本節(jié)我們來探討學(xué)習(xí):Kotlin的基礎(chǔ)類型:數(shù)字、字符、布爾和數(shù)組等。

我們知道Java的類型分成兩種:一種是基本類型,一種是引用類型。它們的本質(zhì)區(qū)別是:

基本類型是在堆棧處分配空間存“值”,而引用類型是在堆里面分配空間存“值”。

Java的基本類型有: byte、int、short、long、float、double、char、boolean,這些類都有對(duì)應(yīng)的裝箱類(引用類型)。

另外,void也可以算是一種特殊的基本類型,它也有一個(gè)裝箱類Void(跟我們后文講到的Unit、Nothing相關(guān))。因?yàn)?,Void是不能new出來的,也就是不能在堆里面分配空間存對(duì)應(yīng)的值。所以,Void是一開始在堆棧處分配好空間。所以,將Void歸成基本類型。

在Kotlin中,一切皆是對(duì)象。所有類型都是引用類型。沒有類似Java中的基本類型。但是,可以把Kotlin中對(duì)應(yīng)的這幾種基本數(shù)據(jù)類型,理解為Java的基本類型的裝箱類。

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類型:

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
    }
    ...
}

我們通過Java的Integer封裝類,跟Kotlin的Int類的定義可以看出兩者的思想上的同源性。

Kotlin的基本類型的類圖結(jié)構(gòu)如下圖所示

4.4.1 數(shù)字(Number)類型

Kotlin 提供了如下的內(nèi)置類型來表示數(shù)字(與 Java 很相近):

類型 寬度(Bit)
Double 64
Float 32
Long 64
Int 32
Short 16
Byte 8

從上面的Kotlin的基本類型的類的結(jié)構(gòu)圖,我們可以看出這些內(nèi)置的數(shù)據(jù)類型,都繼承了Number Comparable類。例如,Byte類型的聲明:

public class Byte private constructor() : Number(), Comparable {
    ...
}

Kotlin 的數(shù)字類型跟 Java基本相同。有一點(diǎn)不同的是,Kotlin對(duì)于數(shù)字沒有隱式拓寬轉(zhuǎn)換(如 Java 中 int 可以隱式轉(zhuǎn)換為long)。

注意在 Kotlin 中字符Char不是數(shù)字。這些基本數(shù)據(jù)類型,會(huì)在運(yùn)行時(shí)自動(dòng)優(yōu)化為Java的double、float、long、int、short、byte。

字面常量值(literal constant values)

數(shù)值常量字面值有以下幾種:

十進(jìn)制: 123

Long 類型用大寫 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)我們賦值超過變量的類型的取值范圍時(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 的原生類型,除非我們需要一個(gè)可空的引用(如 Int?)或泛型。
后者情況下會(huì)把數(shù)字裝箱。

顯式轉(zhuǎn)換

由于不同的表示方式,值范圍較小類型并不是較大類型的子類型,是不能隱式轉(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)換來拓寬數(shù)字

>>> val i: Int = b.toInt() // OK: 顯式拓寬

每個(gè)數(shù)字類型都繼承Number抽象類,其中定義了如下的轉(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)算符+重載

缺乏隱式類型轉(zhuǎn)換并不顯著,因?yàn)轭愋蜁?huì)從上下文推斷出來,而算術(shù)運(yùn)算會(huì)有重載做適當(dāng)轉(zhuǎn)換,例如:

val l = 1L + 3 // Long + Int => Long

這個(gè)是通過運(yùn)算符+重載實(shí)現(xiàn)的。我們可以在Long類的源代碼中看到這個(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

也就是說, 編譯器會(huì)把1L + 3 翻譯成 1L.plus(3),然后這個(gè)傳入的參數(shù)類型必須是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)的類成員(但編譯器會(huì)將函數(shù)調(diào)用優(yōu)化為相應(yīng)的指令)。

對(duì)于位運(yùn)算,沒有特殊字符來表示,而只可用中綴方式調(diào)用命名函數(shù)(infix fun),例如:

val x = (1 shl 2) and 0x000FF000

這是完整的位運(yùn)算列表(只用于 IntLong):

shl(bits) – 有符號(hào)左移 (Java 的 <<)

shr(bits) – 有符號(hào)右移 (Java 的 >>)

ushr(bits) – 無符號(hào)右移 (Java 的 >>>)

and(bits) – 位與

or(bits) – 位或

xor(bits) – 位異或

inv() – 位非

4.4.2 Char: 字符(Character)類型與轉(zhuǎn)義符(Escape character)

字符用 Char 類型表示。它們不能直接當(dāng)作數(shù)字

fun check(c: Char) {
    if (c == 1) { // 錯(cuò)誤:類型不兼容
        // ……
    }
}

字符字面值用 單引號(hào) 括起來: "1"
特殊字符可以用反斜杠轉(zhuǎn)義。

Kotlin支持如下轉(zhuǎn)義字符:

	




`
"

$

編碼其他字符要用 Unicode 轉(zhuǎn)義序列語法,例如:"uFF00"。

Char類的函數(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


}

我們來用代碼示例這些函數(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類型的數(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類型值和不是Int類型的值相加,就報(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的顯式類型轉(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ù)字
}

測試代碼:

>>> decimalDigitValue("a")
java.lang.IllegalArgumentException: Out of range
    at Line24.decimalDigitValue(Unknown Source)

>>> decimalDigitValue("1")
1
4.4.3 Boolean: 布爾類型

Kotlin的布爾類型用 Boolean 類來表示,它有兩個(gè)值:truefalse。

>>> true::class
class kotlin.Boolean
>>> true::class.java
boolean

對(duì)應(yīng)Java中的boolean類型。

其源碼定義如下:

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類的內(nèi)置的布爾運(yùn)算有:

! 邏輯非 not()

&& 短路邏輯與 and()

|| 短路邏輯或or()

xor 異或(相同false,不同true)

另外,Boolean還繼承實(shí)現(xiàn)了ComparablecompareTo()函數(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)
-1
4.4.4 String: 字符串類型

Kotlin的字符串用 String 類型表示。對(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]來訪問。

>>> 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類。其調(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類的完整的操作方法,我們可以看下源碼:

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
}

類似的,字符串有一個(gè)length屬性:

>>> "abc".length
3
重載+操作符

字符串類重載了+操作符,作用對(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)(""")分界符括起來,內(nè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類的擴(kuò)展函數(shù):

fun String.trimMargin(marginPrefix: String = "|"): String
fun String.trimIndent(): String

我們可以使用trimMargin()trimIndent() 裁剪函數(shù)來去除前導(dǎo)空格。可以看出,trimMargin()函數(shù)默認(rèn)使用 "|" 來作為邊界字符:

>>> 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á)式以美元符($)開頭,由一個(gè)簡單的名字構(gòu)成:

>>> val h=100
>>> val str = "A hundred is $h"
>>> str
A hundred is 100

或者用花括號(hào)擴(kuò)起來的任意表達(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 100


4.4.5 Array: 數(shù)組類型

數(shù)組在 Kotlin 中使用 Array 類來表示,它定義了 getset 函數(shù)(映射到重載運(yùn)算符 [])和 size 屬性,以及一個(gè)用于變量數(shù)組的iterator()函數(shù):

class Array private constructor() {
    val size: Int
    operator fun get(index: Int): T
    operator fun set(index: Int, value: T): Unit
    operator fun iterator(): Iterator
    // ……
}

我們可以使用函數(shù) arrayOf() 來創(chuàng)建一個(gè)數(shù)組并傳遞元素值給它。這個(gè)函數(shù)簽名如下:

public inline fun  arrayOf(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還允許不同類型元素放到一個(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ù)組元素的類型升級(jí)為java.lang.Object, 同時(shí),由于Kotlin擁有的類型推斷的功能,我們?nèi)匀豢梢钥吹矫總€(gè)數(shù)組元素對(duì)應(yīng)的各自的類型。

函數(shù) arrayOfNulls() 可以用于創(chuàng)建一個(gè)指定大小、元素都為空的數(shù)組。這個(gè)特殊的空數(shù)組在創(chuàng)建的時(shí)候,我們需要指定元素的類型。如果不指定,直接按照下面這樣寫,會(huì)報(bào)錯(cuò):

>>> arrayOfNulls(10)
error: type inference failed: Not enough information to infer parameter T in fun  arrayOfNulls(size: Int): Array
Please specify it explicitly.

arrayOfNulls(10)
^

也就是說,我們要指定

>>> arrayOfNulls(10)
[Ljava.lang.Integer;@77c10a5f
>>> arrayOfNulls(10).forEach{println(it)}
null
null
null
null
null
null
null
null
null
null

數(shù)組Array類,還提供了一個(gè)構(gòu)造函數(shù):

public inline constructor(size: Int, init: (Int) -> T)

第1個(gè)參數(shù)是數(shù)組大小,第2個(gè)參數(shù)是一個(gè)初始化函數(shù)類型的參數(shù)(關(guān)于函數(shù)類型,我們將在后面章節(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 賦值給 Array。這地方Kotlin類型檢查的限制強(qiáng)于Java的數(shù)組類型。

代碼示例:

>>> val arrstr = arrayOf("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
         ^
原生數(shù)組類型

Kotlin 也有無裝箱開銷的專門的類來表示原生類型數(shù)組。這些原生數(shù)組類如下:

BooleanArray
ByteArray
CharArray
ShortArray
IntArray
LongArray
FloatArray
DoubleArray
BooleanArray

這些類和 Array 并沒有繼承關(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]
1
4.5 Any?可空類型(Nullable Types)

可空類型是Kotlin類型系統(tǒng)的一個(gè)特性,主要是為了解決Java中的令人頭疼的
NullPointerException問題。

我們知道,在Java中如果一個(gè)變量可以是null,來那么使用它調(diào)用一個(gè)方法就是不安全的,因?yàn)樗鼤?huì)導(dǎo)致:NullPointerException 。

Kotlin把可空性(nullability)作為類型系統(tǒng)的一部分,Kotlin編譯器可以直接在編譯過程中發(fā)現(xiàn)許多可能的錯(cuò)誤,并減少在運(yùn)行時(shí)拋出異常的可能性。

Kotlin的類型系統(tǒng)和Java相比,首要的區(qū)別就是Kotlin對(duì)可空類型的顯式支持。

在本節(jié)中,我們將討論Kotlin中的可空類型。

4.5.1 null 是什么

對(duì)于Java程序員來說,null是令人頭痛的東西。我們時(shí)常會(huì)受到空指針異常(NPE)的騷擾。就連Java的發(fā)明者都承認(rèn)這是他的一項(xiàng)巨大失誤。Java為什么要保留null呢?null出現(xiàn)有一段時(shí)間了,并且我認(rèn)為Java發(fā)明者知道null與它解決的問題相比帶來了更多的麻煩,但是null仍然陪伴著Java。

我們通常把null理解為編程語言中定義特殊的0, 把我們初始化的指針指向它,以防止“野指針”的惡果。在Java中,null是任何引用類型的默認(rèn)值,不嚴(yán)格的說是所有Object類型的默認(rèn)值。

這里的null既不是對(duì)象也不是一種類型,它僅是一種特殊的值,我們可以將其賦予任何引用類型,也可以將null轉(zhuǎn)化成任何類型。在編譯和運(yùn)行時(shí)期,將null強(qiáng)制轉(zhuǎn)換成任何引用類型都是可行的,在運(yùn)行時(shí)期都不會(huì)拋出空指針異常。注意,這里指的是任何Java的引用類型。在遇到基本類型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

所以,我們寫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變量類型的使用。

我們來看一下Kotlin中關(guān)于null的一些有趣的運(yùn)算。

nullnull是相等的:

>>> null==null
true
>>> null!=null
false

null這個(gè)值比較特殊,null 不是Any類型

>>> null is Any
false

但是,nullAny?類型:

>>> null is Any?
true

我們來看看null對(duì)應(yīng)的類型到底是什么:

>>> 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的類型是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

...

但是,反過來就不行了:

>>> 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沒有重載傳入null參數(shù)的plus()函數(shù)。

4.5.3 可空類型String?與安全調(diào)用?.

我們來看一個(gè)例子。下面是計(jì)算字符串長度的簡單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)我們同樣寫一個(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)
    }

編譯器就直接編譯失敗:

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ò)誤了:

這樣通過編譯時(shí)強(qiáng)制排除空指針的錯(cuò)誤,大大減少了出現(xiàn)NPE的可能。

另外,如果我們確實(shí)需要傳入一個(gè)可空的參數(shù),我們可以使用可空類型String?來聲明一個(gè)可以指向空指針的變量。

可空類型可以用來標(biāo)記任何一個(gè)變量,來表明這個(gè)變量是可空的(Nullable)。例如:
Char?, Int?, MineType?(自定義的類型)等等。

我們用示例代碼來更加簡潔的說明:

>>> 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類型,是不允許指向null的;而可空String?類可以指向null

下面我們來嘗試使用一個(gè)可空變量來調(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?是可空的類型,調(diào)用只能通過安全調(diào)用?. 或者 非空斷言調(diào)用!!. 。

另外,如果不需要捕獲異常來處理,我們可以使用Kotlin里面的安全調(diào)用符?. 。

代碼示例:

   fun getLength2(str: String?): Int? {
        return str?.length
    }

測試代碼:

    @Test fun testGetLength2() {
        val StringUtilKt = StringUtilKt()
        println(StringUtilKt.getLength2(null)) //null
        Assert.assertTrue(3 == StringUtilKt.getLength2("abc"))
    }

我們可以看出,當(dāng)我們使用安全調(diào)用?. , 代碼安靜的執(zhí)行輸出了null。

如果,我們確實(shí)想寫一個(gè)出現(xiàn)空指針異常的代碼,那就使用可能出現(xiàn)空指針的斷言調(diào)用符!!. 。

代碼示例:

    fun getLength3(str: String?): Int? {
        return str!!.length
    }

測試代碼:

    @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)行特殊處理的場景,在Kotlin中仍然使用 try ... catch 捕獲并處理異常。

4.5.4 可空性的實(shí)現(xiàn)原理

我們來看一段Kotlin的可空類型的示例代碼如下:

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
}

我們來使用IDEA的Kotlin插件來看下可空類型的安全調(diào)用的等價(jià)Java代碼。

打開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類里面的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 可空類型層次體系

就像Any是在非空類型層次結(jié)構(gòu)的根,
Any?是可空類型層次的根。
由于Any?是Any的超集,所以,Any?是Kotlin的類型層次結(jié)構(gòu)的最頂端。

代碼示例:

>>> 1 is Any
true

>>> 1 is Any?
true

>>> null is Any
false

>>> null is Any?
true


>>> Any() is Any?
true
4.6 kotlin.Unit類型

Kotlin也是面向表達(dá)式的語言。在Kotlin中所有控制流語句都是表達(dá)式(除了變量賦值、異常等)。

Kotlin中的Unit類型實(shí)現(xiàn)了與Java中的void一樣的功能。不同的是,當(dāng)一個(gè)函數(shù)沒有返回值的時(shí)候,我們用Unit來表示這個(gè)特征,而不是null。

大多數(shù)時(shí)候,我們并不需要顯式地返回Unit,或者聲明一個(gè)函數(shù)的返回類型為Unit。編譯器會(huì)推斷出它。

代碼示例:

>>> fun unitExample(){println("Hello,Unit")}
>>> val helloUnit = unitExample()
Hello,Unit
>>> helloUnit
kotlin.Unit
>>> println(helloUnit)
kotlin.Unit

下面幾種寫法是等價(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 {
    }
}

總的來說,這個(gè)Unit類型并沒有什么特別之處。它的源碼是:

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"
}

跟任何其他類型一樣,它的父類型是Any。如果是一個(gè)可空的Unit?,它的父類型是Any?。

4.7 kotlin.Nothing類型

Kotlin中沒有類似Java和C中的函數(shù)沒有返回值的標(biāo)記void,但是擁有一個(gè)對(duì)應(yīng)Nothing。在Java中,返回void的方法,其返回值void是無法被訪問到的:

public class VoidDemo {
    public void voidDemo() {
        System.out.println("Hello,Void");
    }
}

測試代碼:

@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(); // 沒有void變量類型,無法訪問到void返回值
        System.out.println(voidDemo.voidDemo()); // error: "void" type not allowed here
    }
}

在Java中,void不能是變量的類型。也不能被當(dāng)做值打印輸出。但是,在Java中有個(gè)包裝類Voidvoid 的自動(dòng)裝箱類型。如果你想讓一個(gè)方法返回類型 永遠(yuǎn)是 null 的話, 可以把返回類型置為這個(gè)大寫的V的Void類型。

代碼示例:

    public Void voidDemo() {
        System.out.println("Hello,Void");
        return null;
    }

測試代碼:

@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?。它的唯一可被訪問到的返回值也是null。

在Kotlin類型層次結(jié)構(gòu)的最底層就是類型Nothing

正如它的名字Nothing所暗示的,Nothing是沒有實(shí)例的類型。

代碼示例:

>>> Nothing() is Any
error: cannot access "": it is private in "Nothing"
Nothing() is Any
^

注意:Unit與Nothing之間的區(qū)別: Unit類型表達(dá)式計(jì)算結(jié)果的返回類型是Unit。Nothing類型的表達(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)庫里面的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,被用作任何可空類型的空引用。

綜上所述,我們可以看出Kotlin有一個(gè)簡單而一致的類型系統(tǒng)。Any?是整個(gè)類型體系的頂部,Nothing是底部。如下圖所示:

4.8 類型檢測與類型轉(zhuǎn)換 4.8.1 is,!is運(yùn)算符

is運(yùn)算符可以檢查對(duì)象是否與特定的類型兼容(“兼容”的意思是:此對(duì)象是該類型,或者派生于該類型)。

is運(yùn)算符用來檢查對(duì)象(變量)是否屬于某數(shù)據(jù)類型(如Int、String、Boolean等)。C#里面也有這個(gè)運(yùn)算符。

is運(yùn)算符類似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í)通過使用 is 操作符或其否定形式 !is 來檢查對(duì)象是否符合給定類型:

>>> "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)// 子類 is 父類 = true
        println(foo is Goo)//父類 is 子類 = false
        println(goo is Goo)//true 自己
    }
}


open class Foo
class Goo : Foo()
類型自動(dòng)轉(zhuǎn)換

在Java代碼中,當(dāng)我們使用str instanceof String來判斷其值為true的時(shí)候,我們想使用str變量,還需要顯式的強(qiáng)制轉(zhuǎn)換類型:

@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)換類型為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í)行引用類型的顯式類型轉(zhuǎn)換。如果要轉(zhuǎn)換的類型與指定的類型兼容,轉(zhuǎn)換就會(huì)成功進(jìn)行;如果類型不兼容,使用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中,父類是禁止轉(zhuǎn)換為子類型的。

按照Liskov替換原則,父類轉(zhuǎn)換為子類是對(duì)OOP的嚴(yán)重違反,不提倡、也不建議。嚴(yán)格來說,父類是不能轉(zhuǎn)換為子類的,子類包含了父類所有的方法和屬性,而父類則未必具有和子類同樣成員范圍,所以這種轉(zhuǎn)換是不被允許的,即便是兩個(gè)具有父子關(guān)系的空類型,也是如此。

本章小結(jié)

在本章中,我們停下腳步,仔細(xì)深入地去探討了Kotlin語言中最重要的部分之一的:類型系統(tǒng)。

與Java相比,Kotlin的類型系統(tǒng)更加簡單一致,同時(shí)引入了一些新的特性,這些特性對(duì)于提高代碼的安全性、可靠性至關(guān)重要。例如:可空類型和只讀集合。關(guān)于只讀集合類,我們將在下一章中介紹。

我們下一章的主題是:Kotlin的集合類和泛型。

本章示例代碼工程: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://systransis.cn/yun/67463.html

相關(guān)文章

  • Kotlin + Spring Boot : 下一代 Java 服務(wù)端開發(fā) 》

    摘要:下一代服務(wù)端開發(fā)下一代服務(wù)端開發(fā)第部門快速開始第章快速開始環(huán)境準(zhǔn)備,,快速上手實(shí)現(xiàn)一個(gè)第章企業(yè)級(jí)服務(wù)開發(fā)從到語言的缺點(diǎn)發(fā)展歷程的缺點(diǎn)為什么是產(chǎn)生的背景解決了哪些問題為什么是的發(fā)展歷程容器的配置地獄是什么從到下一代企業(yè)級(jí)服務(wù)開發(fā)在移動(dòng)開發(fā)領(lǐng)域 《 Kotlin + Spring Boot : 下一代 Java 服務(wù)端開發(fā) 》 Kotlin + Spring Boot : 下一代 Java...

    springDevBird 評(píng)論0 收藏0
  • 初探Kotlin+SpringBoot聯(lián)合編程

    摘要:是一門最近比較流行的靜態(tài)類型編程語言,而且和一樣同屬系。這個(gè)生成的構(gòu)造函數(shù)是合成的,因此不能從或中直接調(diào)用,但可以使用反射調(diào)用。 showImg(https://segmentfault.com/img/remote/1460000012958496); Kotlin是一門最近比較流行的靜態(tài)類型編程語言,而且和Groovy、Scala一樣同屬Java系。Kotlin具有的很多靜態(tài)語言...

    xiaokai 評(píng)論0 收藏0
  • 12 元編程注解、反射 《Kotlin 項(xiàng)目實(shí)戰(zhàn)開發(fā)》

    摘要:第章元編程與注解反射反射是在運(yùn)行時(shí)獲取類的函數(shù)方法屬性父類接口注解元數(shù)據(jù)泛型信息等類的內(nèi)部信息的機(jī)制。本章介紹中的注解與反射編程的相關(guān)內(nèi)容。元編程本質(zhì)上是一種對(duì)源代碼本身進(jìn)行高層次抽象的編碼技術(shù)。反射是促進(jìn)元編程的一種很有價(jià)值的語言特性。 第12章 元編程與注解、反射 反射(Reflection)是在運(yùn)行時(shí)獲取類的函數(shù)(方法)、屬性、父類、接口、注解元數(shù)據(jù)、泛型信息等類的內(nèi)部信息的機(jī)...

    joyqi 評(píng)論0 收藏0
  • 基礎(chǔ) - 收藏集 - 掘金

    摘要:的語言的動(dòng)態(tài)性意味著我們可以使用以上種數(shù)據(jù)類型表示變換過渡動(dòng)畫實(shí)現(xiàn)案例前端掘金以下所有效果的實(shí)現(xiàn)方式均為個(gè)人見解,如有不對(duì)的地方還請(qǐng)一一指出。 讀 zepto 源碼之工具函數(shù) - 掘金Zepto 提供了豐富的工具函數(shù),下面來一一解讀。 源碼版本 本文閱讀的源碼為 zepto1.2.0 $.extend $.extend 方法可以用來擴(kuò)展目標(biāo)對(duì)象的屬性。目標(biāo)對(duì)象的同名屬性會(huì)被源對(duì)象的屬性...

    wuaiqiu 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

最新活動(dòng)
閱讀需要支付1元查看
<