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

原文鏈接:https://github.com/EasyKotlin

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

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

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

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

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

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

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

  • 一組基本類型構(gòu)成的PTS(Primary Type Set伐弹,基本類型集合);
  • PTS上定義的一系列組合榨为、運算惨好、轉(zhuǎn)換規(guī)則等。

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

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

4.1 什么是類型分歇?

一切皆是映射

在計算機中,任何數(shù)值都是以一組比特(01)組成的欧漱,硬件無法區(qū)分內(nèi)存地址职抡、腳本、字符误甚、整數(shù)缚甩、以及浮點數(shù)。這個時候窑邦,我們使用類型賦予一組比特以特定的意義擅威。

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

《周易》有云:

易有太極裕寨,是生兩儀,兩儀生四象派继,四象生八卦。(《易傳·系辭上傳》) 捻艳。

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

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

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

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

  • 安全性

編譯器可以使用類型來檢查無意義的,或者是可能無效的代碼衷恭。例如此叠,在強類型的語言中,如果沒有對字符串的+進行重載随珠,那么表達式

"Hello, World" + 3

就會被編譯器檢測出來灭袁,因為不能對字符串加上一個整數(shù)。強類型提供更多的安全性窗看。

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

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

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

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

  • 可讀性

  • 抽象化(或模塊化)

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

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

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

程序中的變量在程序執(zhí)行期間,可能會有不同的取值范圍躏敢,我們可以把變量可取值的最大范圍稱為這個變量的類型闷愤。例如,具有類型Boolean的變量x件余,在程序執(zhí)行期間讥脐,只能取布爾值遭居。指定變量類型的程序設(shè)計語言,稱為類型化的語言(typed language)旬渠。

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

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

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

像C掂摔、C++、Java等語言赢赊,都是顯式類型化的乙漓。而像ML、Haskell释移、Groovy等可以省略類型聲明叭披,它們的類型系統(tǒng)會自動推斷出程序的類型。

4.2 編譯時類型與運行時類型

Koltin是一門強類型的玩讳、靜態(tài)類型涩蜘、支持隱式類型的顯式類型語言。

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

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

  • 弱類型語言
  • 強類型語言

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

但是另一方面毒费,強和弱只是相對的吭服。

Kotlin是強類型語言。

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

checked language)

類型檢查可發(fā)生在編譯時期(靜態(tài)檢查)或運行時期(動態(tài)檢查)蝗罗。這樣我們將編程語言分為

  • 靜態(tài)類型語言
  • 動態(tài)類型語言

靜態(tài)類型檢查是基于編譯器來分析源碼本身來確保類型安全艇棕。靜態(tài)類型檢查能讓很多bug在編碼早期被捕捉到,并且它也能優(yōu)化運行串塑。因為如果編譯器在編譯時已經(jīng)證明程序是類型安全的沼琉,就不用在運行時進行動態(tài)的類型檢查,編譯過后的代碼會更優(yōu)化桩匪,運行更快打瘪。

動態(tài)類型語言是在運行時期進行類型標(biāo)記的檢查,因為變量所約束的值傻昙,可經(jīng)由運行路徑獲得不同的標(biāo)記闺骚。關(guān)于動態(tài)類型,有個很形象的說法:

當(dāng)看到一只鳥走起來像鴨子妆档、游泳起來像鴨子僻爽、叫起來也像鴨子,那么這只鳥就可以被稱為鴨子贾惦⌒匕穑——詹姆斯·惠特科姆·萊利(James Whitcomb Riley,1849-1916)

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

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

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

  • 顯式類型語言
  • 隱式類型語言

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

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

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

Kotlin 跟Scala類似糜俗,它也使用類型推斷支持隱式類型。但是曲饱,在一些場景下也需要顯式聲明變量的類型悠抹,所以我們可以說,同時也是顯式類型扩淀。

4.3 根類型Any

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

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

也就是說卵凑,Any在運行時庆聘,其類型自動映射成java.lang.Object。我們知道勺卢,在Java中Object類是所有引用類型的父類伙判。但是不包括基本類型:byte int long等,基本類型對應(yīng)的包裝類是引用類型黑忱,其父類是Object宴抚。而在Kotlin中,直接統(tǒng)一——所有類型都是引用類型甫煞,統(tǒng)一繼承父類Any菇曲。

Any是Java的等價Object類。但是跟Java不同的是抚吠,Kotlin中語言內(nèi)部的類型和用戶定義類型之間常潮,并沒有像Java那樣劃清界限。它們是同一類型層次結(jié)構(gòu)的一部分埃跷。

Any 只有 equals() 蕊玷、 hashCode() 和 toString() 三個方法。其源碼是

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 對象相等性

從Any的源碼注釋中弥雹,我們可以看到垃帅,判斷兩個對象是否相等,需要滿足以下條件:

  • 自反性:對于任何非空引用值x剪勿,x.equals(x) 應(yīng)返回true贸诚。
  • 對稱性:對于任何非空引用值x和y,x.equals(y) 應(yīng)返回true當(dāng)且僅當(dāng)y.equals(x) 返回true厕吉。
  • 傳遞性:對于任何非空引用值x酱固,y,z头朱,如果x.equals(y) 返回true运悲,y.equals(z) 返回true,那么x.equals(z) 應(yīng)返回true
  • 一致性:對于任何非空引用值x和y项钮,多次調(diào)用x.equals(y) 始終返回true或者始終返回false班眯。

另外,在Kotlin中烁巫,操作符==會被編譯器翻譯成調(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泥张,這些類都有對應(yīng)的裝箱類(引用類型)。

另外鞠值,void也可以算是一種特殊的基本類型媚创,它也有一個裝箱類Void(跟我們后文講到的Unit、Nothing相關(guān))彤恶。因為钞钙,Void是不能new出來的,也就是不能在堆里面分配空間存對應(yīng)的值声离。所以芒炼,Void是一開始在堆棧處分配好空間。所以术徊,將Void歸成基本類型本刽。

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

Integer.java

public final class Integer extends Number implements Comparable<Integer> {
    /**
     * A constant holding the minimum value an {@code int} can
     * have, -2<sup>31</sup>.
     */
    @Native public static final int   MIN_VALUE = 0x80000000;

    /**
     * A constant holding the maximum value an {@code int} can
     * have, 2<sup>31</sup>-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<Integer>  TYPE = (Class<Integer>) Class.getPrimitiveClass("int");

    ...

}

Kotlin中的Int類型:

public class Int private constructor() : Number(), Comparable<Int> {
    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)如下圖所示

Kotlin極簡教程
Kotlin極簡教程

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ù)類型嗤瞎,都繼承了NumberComparable類墙歪。例如,Byte類型的聲明:

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

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

注意在 Kotlin 中字符Char不是數(shù)字掉瞳。這些基本數(shù)據(jù)類型毕源,會在運行時自動優(yōu)化為Java的double浪漠、float、long霎褐、int址愿、short、byte冻璃。

字面常量值(literal constant values)

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

  • 十進制: 123
  • Long 類型用大寫 L 標(biāo)記: 123L
  • 十六進制: 0x0F
  • 二進制: 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)我們賦值超過變量的類型的取值范圍時,編譯器會直接拋錯省艳。

注意: 不支持八進制

Kotlin 同樣支持浮點數(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 平臺數(shù)字是物理存儲為 JVM 的原生類型跋炕,除非我們需要一個可空的引用(如 Int?)或泛型赖晶。
后者情況下會把數(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
             ^

這意味著在不進行顯式轉(zhuǎn)換的情況下我們不能把 Int 型值賦給一個 Long 變量纠修。也不能把 Byte 型值賦給一個 Int 變量胳嘲。

我們可以顯式轉(zhuǎn)換來拓寬數(shù)字

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

每個數(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ù)即可。

運算符+重載

缺乏隱式類型轉(zhuǎn)換并不顯著德召,因為類型會從上下文推斷出來白魂,而算術(shù)運算會有重載做適當(dāng)轉(zhuǎn)換,例如:

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

這個是通過運算符+重載實現(xiàn)的上岗。我們可以在Long類的源代碼中看到這個plus 運算符函數(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

也就是說福荸, 編譯器會把1L + 3 翻譯成 1L.plus(3),然后這個傳入的參數(shù)類型必須是Byte肴掷、Short敬锐、Int、Long呆瞻、Float台夺、Double中的一種。例如痴脾,我們傳入一個字符Char參數(shù)颤介,編譯器就會直接拋錯:

>>> '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'
  ^

運算

Kotlin支持?jǐn)?shù)字運算的標(biāo)準(zhǔn)集,運算被定義為相應(yīng)的類成員(但編譯器會將函數(shù)調(diào)用優(yōu)化為相應(yīng)的指令)。

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

val x = (1 shl 2) and 0x000FF000

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

  • shl(bits) – 有符號左移 (Java 的 <<)
  • shr(bits) – 有符號右移 (Java 的 >>)
  • ushr(bits) – 無符號右移 (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) { // 錯誤:類型不兼容
        // ……
    }
}

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

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

\t
\b
\n
\r
\`
\"
\\
\$

編碼其他字符要用 Unicode 轉(zhuǎn)義序列語法移宅,例如:'\uFF00'归粉。

Char類的函數(shù)接口定義如下:

public class Char private constructor() : Comparable<Char> {
    /**
     * 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ù)的使用:

如果兩個字符相等:

>>> 'a'.compareTo('a')
0

如果兩個字符不相等:

>>> 'a'.compareTo('b')
-1
>>> 'a'.compareTo('c')
-1
>>> 'b'.compareTo('a')
1
>>> 'c'.compareTo('a')
1

Char字符只重載了加上Int類型的數(shù)字的+運算符:

>>> 'a'+1
b

>>> 'a'+1L
error: the integer literal does not conform to the expected type Int
'a'+1L

所以,當(dāng)我們把一個Char類型值和不是Int類型的值相加吞杭,就報錯了盏浇。

相減:

>>> 'a'-1
`
>>> 'c'-'a'
2

自增計算:

>>> 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 類來表示芽狗,它有兩個值:truefalse

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

對應(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<Boolean> {
    /**
     * 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)置的布爾運算有:

  • ! 邏輯非 not()

  • && 短路邏輯與 and()

  • || 短路邏輯或or()

  • xor 異或(相同false程腹,不同true)

另外济锄,Boolean還繼承實現(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 類型表示枫绅。對應(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
               ^

索引運算符 s[i]

字符串的元素——字符可以使用索引運算符 s[i]來訪問。

>>> val s="abc"
>>> s
abc
>>> s[0]
a

當(dāng)我們下標(biāo)越界時动羽,會拋越界錯誤:

>>> 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)

從出錯信息包帚,我們可以看出,索引運算符 s[i]會被翻譯成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<String>, 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
}

類似的,字符串有一個length屬性:

>>> "abc".length
3

重載+操作符

字符串類重載了+操作符拘哨,作用對象可以是任何對象谋梭,包括空引用:

>>> "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!\n\n\n"
>>> s
Hello,World!

>>> 

轉(zhuǎn)義采用傳統(tǒng)的反斜杠方式。

原生字符串使用三個引號(""")分界符括起來,內(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類的擴展函數(shù):

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

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

>>> val text = """
... |理論是你知道是這樣吼鱼,但它卻不好用蓬豁。
... |實踐是它很好用,但你不知道是為什么菇肃。
... |程序員將理論和實踐結(jié)合到一起:
... |既不好用地粪,也不知道是為什么。
...     """
>>> text.trimMargin()
理論是你知道是這樣琐谤,但它卻不好用蟆技。
實踐是它很好用,但你不知道是為什么斗忌。
程序員將理論和實踐結(jié)合到一起:
既不好用质礼,也不知道是為什么。

默認(rèn) | 用作邊界前綴织阳,但你可以選擇其他字符并作為參數(shù)傳入眶蕉,比如 trimMargin(">")

trimIndent()函數(shù)唧躲,則是把字符串行的左邊空白對齊切割:

>>> val text="""
...              Hello
...                    World!
... """
>>> text.trimIndent()
Hello
      World!
>>> val text="""
...             Hello,
...         World!
... """
>>> text.trimIndent()
    Hello,
World!

字符串模板

字符串可以包含模板表達式造挽,即一些小段代碼,會求值并把結(jié)果合并到字符串中弄痹。
模板表達式以美元符($)開頭饭入,由一個簡單的名字構(gòu)成:

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

或者用花括號擴起來的任意表達式:

>>> 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ù)(映射到重載運算符 [])和 size 屬性圣拄,以及一個用于變量數(shù)組的iterator()函數(shù):

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

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

public inline fun <reified @PureReifiable T> arrayOf(vararg elements: T): Array<T>

其中毁欣,vararg表示是一個參數(shù)個數(shù)是一個變量庇谆。

例如, 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還允許不同類型元素放到一個數(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自動把這個數(shù)組元素的類型升級為java.lang.Object, 同時执解,由于Kotlin擁有的類型推斷的功能寞肖,我們?nèi)匀豢梢钥吹矫總€數(shù)組元素對應(yīng)的各自的類型纲酗。

函數(shù) arrayOfNulls() 可以用于創(chuàng)建一個指定大小、元素都為空的數(shù)組新蟆。這個特殊的空數(shù)組在創(chuàng)建的時候觅赊,我們需要指定元素的類型。如果不指定琼稻,直接按照下面這樣寫吮螺,會報錯:

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

arrayOfNulls(10)
^

也就是說,我們要指定

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

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

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

第1個參數(shù)是數(shù)組大小鸠补,第2個參數(shù)是一個初始化函數(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

如上所述紫岩,[] 運算符代表調(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<String> 賦值給 Array<Any>揩晴。這地方Kotlin類型檢查的限制強于Java的數(shù)組類型梨与。

代碼示例:

>>> val arrstr = arrayOf<String>("1","2","3")
>>> arrstr
[Ljava.lang.String;@39374689
>>> var arrany = arrayOf<Any>(Any(),Any(),Any())
>>> arrany
[Ljava.lang.Object;@156324b
>>> arrany = arrstr
error: type mismatch: inferred type is Array<String> but Array<Any> 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)的一個特性缘挽,主要是為了解決Java中的令人頭疼的 NullPointerException 問題瞄崇。

我們知道,在Java中如果一個變量可以是null壕曼,來那么使用它調(diào)用一個方法就是不安全的苏研,因為它會導(dǎo)致:NullPointerException

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

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

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

4.5.1 null 是什么

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

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

這里的null既不是對象也不是一種類型瞳抓,它僅是一種特殊的值,我們可以將其賦予任何引用類型伏恐,也可以將null轉(zhuǎn)化成任何類型孩哑。在編譯和運行時期,將null強制轉(zhuǎn)換成任何引用類型都是可行的翠桦,在運行時期都不會拋出空指針異常横蜒。注意,這里指的是任何Java的引用類型销凑。在遇到基本類型int long float double short byte 等的時候丛晌,情況就不一樣了。而且還是個坑斗幼。編譯器不會報錯澎蛛,但是運行時會拋NPE⊥闪空指針異常谋逻。這是Java中的自動拆箱導(dǎo)致的。代碼示例:

Integer nullInt = null; // this is ok
int anotherInt = nullInt; // 編譯器允許這么賦值, 但是在運行時拋 NullPointerException

所以桐经,我們寫Java代碼的時候毁兆,要時刻注意這一點:Integer的默認(rèn)值是null而不是0。當(dāng)把null值傳遞給一個int型變量的時候阴挣,Java的自動裝箱將會返回空指針異常气堕。

4.5.2 Kotlin 中的 null

在Kotlin中,針對Java中的null的雜亂局面畔咧,進行了整頓茎芭,作了清晰的界定,并在編譯器級別強制規(guī)范了可空null變量類型的使用盒卸。

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

nullnull是相等的:

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

null這個值比較特殊,null 不是Any類型

>>> null is Any
false

但是蔽介,nullAny?類型:

>>> null is Any?
true

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

>>> var a=null
>>> a
null
>>> a=1
error: the integer literal does not conform to the expected type Nothing?
a=1
  ^

從報錯信息我們可以看出摘投,null的類型是Nothing?煮寡。關(guān)于Nothing?我們將會在下一小節(jié)中介紹。

我們可以對null進行加法運算:

>>> "1"+null
1null

>>> null+20
null20

對應(yīng)的重載運算符的函數(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
 ^

這是因為Int沒有重載傳入null參數(shù)的plus()函數(shù)幸撕。

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

我們來看一個例子。下面是計算字符串長度的簡單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)我們同樣寫一個可能為null參數(shù)的函數(shù)時:

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

當(dāng)我們傳入一個null參數(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宋光,會在編碼時就直接提示錯誤了:

Kotlin極簡教程
Kotlin極簡教程

這樣通過編譯時強制排除空指針的錯誤貌矿,大大減少了出現(xiàn)NPE的可能。

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

可空類型可以用來標(biāo)記任何一個變量赘艳,來表明這個變量是可空的(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

下面我們來嘗試使用一個可空變量來調(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
                                        ^

編譯器直接報錯掰曾,告訴我們旭蠕,變量str: String?是可空的類型,調(diào)用只能通過安全調(diào)用?. 或者 非空斷言調(diào)用!!. 旷坦。

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

Kotlin極簡教程
Kotlin極簡教程

代碼示例:

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讶坯。

如果番电,我們確實想寫一個出現(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里面差不多了漱办,運行會直接拋出空指針異常:

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);
    }
}

另外婉烟,如果異常需要捕獲到進行特殊處理的場景娩井,在Kotlin中仍然使用 try ... catch 捕獲并處理異常。

4.5.4 可空性的實現(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)用的等價Java代碼似袁。

打開IDEA的 Tools > Kotlin > Show Kotlin Bytecode

Kotlin極簡教程
Kotlin極簡教程

然后洞辣,點擊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這個Java類里面的checkParameterIsNotNull方法。如果是null就拋出異常:

public static void checkParameterIsNotNull(Object value, String paramName) {
    if (value == null) {
        throwParameterIsNullException(paramName);
    }
}

同時扬霜,我們可以看出在Kotlin中函數(shù)的入?yún)⒙暶?/p>

fun testNullable(x: String, y: String?) 

反編譯成等價的Java代碼是

public static final void testNullable(@NotNull String x, @Nullable String y) 

我們可以看出定鸟,這里使用注解@NotNull標(biāo)注不可空的變量,使用注解@Nullable標(biāo)注一個變量可空著瓶。

可空變量的安全調(diào)用符y?.length 等價的Java代碼就是:

y != null?Integer.valueOf(y.length()):null

可空變量的斷言調(diào)用y!!.length等價的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)的最頂端。

Kotlin極簡教程
Kotlin極簡教程

代碼示例:

>>> 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也是面向表達式的語言余蟹。在Kotlin中所有控制流語句都是表達式(除了變量賦值卷胯、異常等)。

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

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

代碼示例:

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

下面幾種寫法是等價的:

@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 {
    }
}

總的來說吓著,這個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绑莺。如果是一個可空的Unit?暖眼,它的父類型是Any?

Kotlin極簡教程
Kotlin極簡教程

4.7 kotlin.Nothing類型

Kotlin中沒有類似Java和C中的函數(shù)沒有返回值的標(biāo)記void纺裁,但是擁有一個對應(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中有個包裝類Voidvoid 的自動裝箱類型嫩絮。如果你想讓一個方法返回類型 永遠是 null 的話, 可以把返回類型置為這個大寫的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
    }
}

這個Void就是Kotlin中的Nothing?围肥。它的唯一可被訪問到的返回值也是null

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

Kotlin極簡教程
Kotlin極簡教程

正如它的名字Nothing所暗示的虐先,Nothing是沒有實例的類型。

代碼示例:

>>> Nothing() is Any
error: cannot access '<init>': it is private in 'Nothing'
Nothing() is Any
^

注意:Unit與Nothing之間的區(qū)別: Unit類型表達式計算結(jié)果的返回類型是Unit派敷。Nothing類型的表達式計算結(jié)果是永遠不會返回的(跟Java中的void相同)蛹批。

例如,throw關(guān)鍵字中斷的表達式的計算篮愉,并拋出堆棧的功能腐芍。所以,一個throw Exception 的代碼就是返回Nothing的表達式试躏。代碼示例:

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?可以只包含一個值: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極簡教程
Kotlin極簡教程

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

Kotlin極簡教程
Kotlin極簡教程

4.8 類型檢測與類型轉(zhuǎn)換

4.8.1 is,!is運算符

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

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

is運算符類似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中藕畔,我們可以在運行時通過使用 is 操作符或其否定形式 !is 來檢查對象是否符合給定類型:

>>> "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()

類型自動轉(zhuǎn)換

在Java代碼中马僻,當(dāng)我們使用str instanceof String來判斷其值為true的時候,我們想使用str變量注服,還需要顯式的強制轉(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();  // 顯式的強制轉(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)換操作符,因為編譯器跟蹤不可變值的 is-檢查祠汇,并在需要時自動插入(安全的)轉(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運算符

as運算符用于執(zhí)行引用類型的顯式類型轉(zhuǎn)換。如果要轉(zhuǎn)換的類型與指定的類型兼容熄诡,轉(zhuǎn)換就會成功進行可很;如果類型不兼容,使用as?運算符就會返回值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)換為子類是對OOP的嚴(yán)重違反瓣窄,不提倡、也不建議纳鼎。嚴(yán)格來說俺夕,父類是不能轉(zhuǎn)換為子類的,子類包含了父類所有的方法和屬性贱鄙,而父類則未必具有和子類同樣成員范圍劝贸,所以這種轉(zhuǎn)換是不被允許的,即便是兩個具有父子關(guān)系的空類型逗宁,也是如此映九。

本章小結(jié)

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

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

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

本章示例代碼工程:https://github.com/EasyKotlin/chapter4_type_system

參考資料

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市穿铆,隨后出現(xiàn)的幾起案子您单,更是在濱河造成了極大的恐慌,老刑警劉巖荞雏,帶你破解...
    沈念sama閱讀 206,839評論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件虐秦,死亡現(xiàn)場離奇詭異,居然都是意外死亡凤优,警方通過查閱死者的電腦和手機悦陋,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,543評論 2 382
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來筑辨,“玉大人俺驶,你說我怎么就攤上這事」髟” “怎么了暮现?”我有些...
    開封第一講書人閱讀 153,116評論 0 344
  • 文/不壞的土叔 我叫張陵还绘,是天一觀的道長。 經(jīng)常有香客問我栖袋,道長拍顷,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,371評論 1 279
  • 正文 為了忘掉前任塘幅,我火速辦了婚禮昔案,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘晌块。我一直安慰自己爱沟,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 64,384評論 5 374
  • 文/花漫 我一把揭開白布匆背。 她就那樣靜靜地躺著呼伸,像睡著了一般。 火紅的嫁衣襯著肌膚如雪钝尸。 梳的紋絲不亂的頭發(fā)上括享,一...
    開封第一講書人閱讀 49,111評論 1 285
  • 那天,我揣著相機與錄音珍促,去河邊找鬼铃辖。 笑死,一個胖子當(dāng)著我的面吹牛猪叙,可吹牛的內(nèi)容都是我干的娇斩。 我是一名探鬼主播,決...
    沈念sama閱讀 38,416評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼穴翩,長吁一口氣:“原來是場噩夢啊……” “哼犬第!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起芒帕,我...
    開封第一講書人閱讀 37,053評論 0 259
  • 序言:老撾萬榮一對情侶失蹤歉嗓,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后背蟆,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體鉴分,經(jīng)...
    沈念sama閱讀 43,558評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,007評論 2 325
  • 正文 我和宋清朗相戀三年带膀,在試婚紗的時候發(fā)現(xiàn)自己被綠了志珍。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,117評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡垛叨,死狀恐怖伦糯,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤舔株,帶...
    沈念sama閱讀 33,756評論 4 324
  • 正文 年R本政府宣布,位于F島的核電站还棱,受9級特大地震影響载慈,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜珍手,卻給世界環(huán)境...
    茶點故事閱讀 39,324評論 3 307
  • 文/蒙蒙 一办铡、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧琳要,春花似錦寡具、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,315評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至课幕,卻和暖如春厦坛,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背乍惊。 一陣腳步聲響...
    開封第一講書人閱讀 31,539評論 1 262
  • 我被黑心中介騙來泰國打工杜秸, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人润绎。 一個月前我還...
    沈念sama閱讀 45,578評論 2 355
  • 正文 我出身青樓撬碟,卻偏偏與公主長得像,于是被迫代替她去往敵國和親莉撇。 傳聞我的和親對象是個殘疾皇子呢蛤,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,877評論 2 345

推薦閱讀更多精彩內(nèi)容