今天說說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中被使用時要注意的點。 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已有的數據接口,並進行了本身的擴展,同時爲了保持良好的互調性,引入了平臺類型,稍微作了一部分的妥協。