Kotlin系列之數組與集合

今天說說kotlin中的數組和集合。java

咱們前面說過kotlin是一門更加純粹的面向對象的語言,因此kotlin中的數組與集合與Java中會有一些不一樣,可是爲了性能考慮,同時由於kotlin的最終編譯產物是運行在JVM之上的,還爲了保持kotlin與Java良好的互操做性,因此kotlin的最終編譯產物又在儘量接近Java的編譯產物。數組

對象數組

咱們在以前的代碼中看到kotlin的main函數是這樣寫的:bash

fun main(args: Array<String>){

}
複製代碼

咱們對比一下Java中的main函數寫法:ide

public static void main(String[] args) {
        
}
複製代碼

能夠看出kotlin中的對象數組的寫法與泛型的寫法很像。這就是kotlin中對象數組的聲明方式,其實在kotlin中又以下三種方式能夠用來建立對象數組: 1.使用arrayOf函數和指定的數組元素建立數組函數

//Java寫法:
String[] params1 = {"str1", "str2", "str3"};

//kotlin寫法:
val params1 = arrayOf("str1", "str2", "str3")
複製代碼

2.使用arrayOfNulls函數建立一個指定大小的並初始化每一個元素爲null的數組性能

//Java寫法:
String[] params2 = new String[12];

//kotlin寫法:
val params2 = arrayOfNulls<String>(12)
複製代碼

3.Array構造方法指定數組大小和一個生成元素的lambda表達式ui

這種方法建立的數組,其中的每一個元素都是非空的,就像下面這樣:編碼

//kotlin寫法:
val params3 = Array<String>(3){i -> "str" + i }
複製代碼

由於數組的原始大小已經肯定,在lambda表達式中接收數組的下標,並返回一個該下標位置的值。spa

根據kotlin的自動推導,咱們能夠省略調泛型類型,根據lambda內部的it參數名,咱們能夠不用寫出i,根據字符串模板,咱們能夠不使用「+」來鏈接字符串,因此上面的代碼還能夠寫成這樣:線程

val params3 = Array(3){"str$it"}
複製代碼

要注意的是,上面的三種方式建立的數組都是對象數組。好比你建立了Array類型的數組,那麼其中的每一個類型都對應了Java中的Integer類型,編譯後的class文件中對應的數組的每一個元素也是Integer類型。

基本數據類型數組

大部分時候咱們並不想建立對象類型的數組,咱們就想建立像Java中的基本數據類型的數組,這時候kotlin爲咱們提供了下面3種函數能夠建立基本數據類型的數組。 1.形如「xxxArray」這樣的構造方法,每種基本數據類型都有對應的構造函數,這樣的構造函數會接收一個數組大小,並使用基本數據類型的默認值初始化數組,就像下面這樣:

val array1 = IntArray(5)
複製代碼

2.形如「xxxArrayOf」函數來指定數組原來並建立數組,就像下面這樣:

val array2 = intArrayOf(1, 2, 3, 4)
複製代碼

上面這個函數的參數是一個可變參數,相似於Java中的可變長參數,假設咱們想拿一個數組來初始化另外一個數組,那咱們必需要使用「*」來將一個數組進行解包(能夠簡單理解爲展開),而後再賦值給可邊參數,就像下面這樣:

val x = IntArray(3);
val array2 = intArrayOf(*x)
複製代碼

3.接收一個數組大小和lambda來建立數組,這種方式跟咱們上面對象數組的建立方法同樣,直接看下面的示例代碼:

val array3 = IntArray(5){ it * it }
複製代碼

注意,使用上面的方法建立的數組會被編譯爲Java中的基本數據類型的數組,相比於對象類型的數組,少了裝箱和拆箱的步驟,性能上也更好一些。

集合與可空性

前面講過kotlin中的類型又可空類型和非空類型之分,相應的,對於一個集合,咱們也應該考慮到,這個集合中是否能夠存儲null值,對應於有包含可空值的集合和包含非空值的集合,就像下面這樣:

//包含可空值的列表
val list1 = ArrayList<Int?>()
list1.add(null)

val list2 = ArrayList<Int>()
//不容許
//list2.add(null)
複製代碼

上面展現了在kotlin中是支持泛型的類型參數的可空性的。

同時,還要注意區分「ArrayList<Int?>」和ArrayList?的區別。前面一種表示集合自己是非空類型,可是集合中能夠存儲null值。後面一種表示集合自己是可空的,可是集合中只能存儲非空類型的元素。

只讀集合和可變集合

與Java中不一樣,kotlin中將集合劃分爲只讀集合和可變集合。在kotlin中有兩種集合接口,一種是Collection接口,其中值包含了集合的一些基本屬性,好比大小,判斷某個元素是否在集合中等。另外一個接口MutableCollection繼承自Collection接口,並在其中添加了集合修改相關的方法,好比add、clear、remove等。

它們的繼承關係圖以下:

繼承關係
圖中灰色背景的爲只讀集合,白色背景的爲可變集合。

你會發現上圖中沒有Map,在kotlin中,Map不在上面的繼承關係中。

kotlin爲何要這麼劃分呢,個人理解是,這樣劃分能夠在編寫代碼的時候,若是你定義的函數傳入的參數是一個只讀集合,那麼調用者就會明確知道這個函數並不會修改傳入的集合。下面一個例子來講明一下。

fun main(args: Array<String>) {
    //只讀集合
    val readOnlyCollection = List(10) { "str$it" }
    printList(readOnlyCollection)
}

fun printList(list: Collection<String>){
    for (x in list){
        println("元素 = $x")
    }
}
複製代碼

上面也要注意的一點是,由於只讀集合並不能在後續修改,因此只讀集合必須在聲明時就建立好裏面的元素。 這裏要注意一點,只讀集合並不必定是不可變的。什麼意思呢?咱們看看下面的代碼:

fun main(args: Array<String>) {

    //可變集合
    val mutableList = MutableList(10){it}

    //可變集合賦值給只讀集合
    val readOnlyList: List<Int> = mutableList

    //打印
    printList(readOnlyList)

    //在集合中添加元素
    mutableList.add(10)

    //再次打印
    printList(readOnlyList)
}

/**
 * 打印只讀集合中的元素
 */
fun printList(list: Collection<Int>){
    for (x in list){
        println("元素 = $x")
    }
}
複製代碼

咱們將一個可變集合的引用和只讀集合的引用都指向了同一個集合。儘管咱們的函數的參數是隻讀集合,沒有對集合進行修改,可是咱們在其餘地方修改了這個集合。若是有兩個函數都使用了這個集合,而且他們同時在不一樣的線程運行,那麼其中的一個線程修改了該集合,就會形成另外一個線程中對集合的讀取出現異常。 因此只讀集合只能做爲一種約定,告訴調用方,在個人方法裏面只會對你傳入的集合進行讀取。

下面咱們看看除了上面的那種寫法建立集合外,kotlin爲咱們提供的建立集合的函數。

集合類型 只讀 可變
List listOf mutableListOf arrayListOf
Set setOf mutableSetOf hashSetOf linkedSetOf sortedSetOf
Map mapOf mutableMapOf hashMapOf linkedMapOf sortedMapOf

使用上面的函數,咱們能夠很方便地建立出kotlin中的集合對象。

kotlin集合與java集合互調

kotlin中建立的集合能夠直接傳遞給Java方法進行操做,下面看看kotlin集合與Java集合在互相調用時的一些注意點。 首先看看kotlin中聲明的集合在Java中被使用時要注意的點。 1.只讀集合在Java中沒法被約束 若是你在kotlin中聲明瞭一個只讀的集合,在Java中只會把它看成一個普通的集合,Java能夠對其進行修改,就像下面的代碼同樣:

//Java代碼
import java.util.Collection;

public class Sample {
    public static void modifyCollection(Collection<String> collection){
        //傳入的只讀集合在kotlin中被修改
        if (collection != null){
            collection.add("xxxx");
        }

        for (String x : collection){
            System.out.println(x);
        }
    }
}

//kotlin代碼
fun main(args: Array<String>) {
    //建立一個只讀集合
    val list1 = listOf("str1", "str2", "str3")
    Sample.modifyCollection(list1)
}
複製代碼

2.集合類型參數非空在Java中沒法被約束 在前面說過kotlin的集合能夠在泛型的類型參數上約束該集合中的元素是不是非空的,可是由於Java中並無將類型分爲可空類型和非空類型,因此Java能夠向集合中加入null值,就像下面這樣:

//kotlin代碼
fun main(args: Array<String>) {
    val list1: MutableCollection<String> = mutableListOf("str1", "str2", "str3")
    //報錯,元素必須是非空的
    //list1.add(null)
    Sample.modifyCollection(list1)
}

//Java代碼
import java.util.Collection;

public class Sample {
    public static void modifyCollection(Collection<String> collection){
        if (collection != null){
            //Java能夠向集合中添加null值
            collection.add(null);
        }
    }
}
複製代碼

其實上面也很好理解,kotlin只能在kotlin相關的編譯層面去判斷約束非空性,若是你調用的方法在Java代碼中,那kotlin是無法察覺調用者已經打破了這種約束。

下面看看Java中聲明的集合,在kotlin代碼中被使用時要注意的點。

咱們在前面的文章中說過平臺類型,在這種狀況下也是平臺類型,對於Java中聲明的集合,在kotlin中被視爲平臺類型,能夠是可變集合也能夠是隻讀集合。 咱們舉個簡單的例子說明一下: 假設Java中聲明瞭這樣一個接口:

//Java代碼
import java.util.List;

public interface DataProcessor {
    void process(List<String> list);
}
複製代碼

上面咱們聲明的一個接口中的process方法中的形參是集合類型,咱們看看在kotlin中咱們如何實現這個接口,因爲koltin中將其視爲平臺類型,因此在kotlin中以下的幾種接口實現方式都是被容許的(具體的差別能夠看代碼中的註釋):

1.方式1

class Main : DataProcessor{
    //傳入的集合變量自己是非空的,
    //集合中的每一個元素必須是非空的,
    //集合是可變集合
    override fun process(list: MutableList<String>) {
    }
}
複製代碼

2.方式2

class Main : DataProcessor{
    //傳入的集合變量自己是非空的,
    //集合中的每一個元素必須是非空的,
    //集合是隻讀集合
    override fun process(list: List<String>) {
    }
}
複製代碼

3.方式3

class Main : DataProcessor{
    //傳入的集合變量自己是非空的,
    //集合中的每一個元素是可空的,
    //集合是可變集合
    override fun process(list: MutableList<String?>) {
    }
}
複製代碼

4.方式4

class Main : DataProcessor{
    //傳入的集合變量自己是非空的,
    //集合中的每一個元素是可空的,
    //集合是隻讀集合
    override fun process(list: List<String?>) {
    }
}
複製代碼

5.方式5

class Main : DataProcessor{
    //傳入的集合變量自己是可空的,
    //集合中的每一個元素是非空的,
    //集合是可變集合
    override fun process(list: MutableList<String>?) {
    }
}
複製代碼

6.方式6

class Main : DataProcessor{
    //傳入的集合變量自己是可空的,
    //集合中的每一個元素是非空的,
    //集合是隻讀集合
    override fun process(list: List<String>?) {
    }
}
複製代碼

7.方式7

class Main : DataProcessor{
    //傳入的集合變量自己是可空的,
    //集合中的每一個元素是可空的,
    //集合是可變集合
    override fun process(list: MutableList<String?>?) {
    }
}
複製代碼

8.方式8

class Main : DataProcessor{
    //傳入的集合變量自己是可空的,
    //集合中的每一個元素是可空的,
    //集合是隻讀集合
    override fun process(list: List<String?>?) {
    }
}
複製代碼

因爲變量自己和類型參數有可空和非空之分,集合有可變和只讀之分,因此上面衍生出了8中狀況,在咱們具體的使用中,咱們須要根據實際狀況去選用合適的形式。

集合與數組互相轉化

最後再補充一下,能夠將集合類型和數組類型進行相互,在編碼過程當中咱們常常會用到:

fun main(args: Array<String>) {
    
    //聲明一個數組
    val array1 = arrayListOf("str1", "str2", "str3")

    //將數組轉化爲集合
    val list1 = array1.toList()
    
    //將集合轉化爲數組
    val array2 = list1.toTypedArray()
}
複製代碼

寫在最後

以上就是kotlin中關於集合與數組的相關內容,能夠看出,kotlin仍是充分利用了Java已有的數據接口,並進行了本身的擴展,同時爲了保持良好的互調性,引入了平臺類型,稍微作了一部分的妥協。

相關文章
相關標籤/搜索