Overview
本節主要介紹幾種語言中的數組和集合的對應用法。
數組在程序中一般用于表示一段連續的空間。通常來說數組的大小是預先指定的,數組的元素類型也是統一的,所以訪問數組時可以通過偏移量快速訪問任意一個元素。
集合類似數組,很多時候集合也是通過數組實現的,但是集合的長度是可變的,存儲的數據類型也可以不一樣(盡管一般都是存儲同類型的數據)。集合通常有兩種:List 和 Set,前者有序且元素可以重復,后者無序但元素不能重復。
Java 篇
數組
創建數組
定義一個數組的基本語法如下
類型[] 變量名 = new 類型[長度];
也可以在定義的同時指定包含的元素
類型[] 變量名 = {元素..};
例:
int[] arr = new int[3];
int[] arr2 = {1, 2, 3};
基本用法
修改數組
由于數組的長度是固定的,所以無法增加元素也無法刪除元素,只能對某一位置的元素進行修改
arr[0] = 10;
讀取某一位置的元素
System.out.println(arr[0]);
獲得數組長度
System.out.println(arr.length);
遍歷數組
for (int a : arr2) {
System.out.println("Traverse " + a);
}
打印數組
Java 中默認的 toString()
只會打印出數組的地址,要想打印數組的內容需要使用工具類 Arrays
或者將其轉換為 List
。
例:
System.out.println(Arrays.toString(arr2));
集合
List
List 表示列表,是集合中最常用的類型,Java 中的 List 默認是可變的,其本身是一個接口,最常用的是其子類 ArrayList
和 LinkedList
,前者用于隨機訪問,后者用于實現隊列。
創建 List
List<String> list = new ArrayList<String>();
默認創建的列表是空列表,里面沒有任何元素。這種兩邊都要寫明列表中元素類型的語法一直以來都被認為是非常丑陋的,所以 Java 7 以后提供了被稱作 Diamond 的語法,可以省略等式右邊的類型聲明。
List<String> list = new ArrayList<>();
事實上即使這樣比起 Guava 提供的用法 List<String> list = Lists.newArrayList();
還是不太好看
需要注意的是在列表的工具類 Collections
中也有一個可以提供空列表的方法,但那個返回的是繼承自 AbstractList
的 EmptyList
的實例,該實例不支持任何操作,與 new 出來的空列表并不一樣。
List emptyList = Collections.emptyList();
// 試圖調用以下方法會報 UnsupportedOperationException
// emptyList.add(1);
修改 List
添加元素
list.add("Groovy");
也可以使用 Collections
的工具方法一次性添加多個元素
Collections.addAll(list, "Groovy", "Java", "Scala");
修改元素
list.set(0, "Ruby");
這里需要注意的是修改時的第一個參數為索引,索引不能超過該列表的長度,否則會報 IndexOutOfBoundsException
。
刪除元素
// 刪除指定位置
list.remove(1);
// 刪除指定元素
list.remove("Ruby");
訪問元素
list.get(1);
索引只能是大于等于小于列表長度的整數,否則會報ArrayIndexOutOfBoundsException
。
通過已有元素創建 List
List 有兩種方式可以在創建時就指定其存儲的內容
第一種是通過工具類 Arrays
List<String> list = Arrays.asList("Groovy", "Java", "Scala");
這種方式創建的其實是一個定長的列表,不支持任何會影響其長度的方法,比如說添加或者刪除項目均為報 UnsupportedOperationException
。
第二種是通過初始化塊,這種方式創建的是普通的列表,但是不支持 Diamond 語法。
List<String> list = new ArrayList<String>() {{
add("Groovy");
add("Java");
add("Scala");
}};
不可變 List
在將列表作為返回值返回時,通常我們不希望列表內容被隨意修改,這時可以返回一個不可變列表來禁止任何修改行為。
List<String> immuatbleList = Collections.unmodifiableList(list);
以上方法實際是返回了一個 UnmodifiableCollection
的實例,該實例使用委托方式調用傳入的 list
對象,一旦發現執行了任何修改操作就立即拋出異常。
其它操作
獲得 List 的長度
System.out.println(list.size());
列表使用
size()
,數組使用length
,字符串使用length()
,這種設計個人認為是 Java API 中設計的很失敗的一個地方,但居然還有一些公司會拿這個作為面試的考題。
遍歷 List
for (String lang : list) {
System.out.println("Traverse " + lang);
}
Set
Set 最常用的是其子類 HashSet
,本質上其實是一個 key-value 都一致的 HashMap
。由于用法類似,就不詳細舉例了。
Range
Java 不支持 Range
Groovy 篇
數組
創建數組
Groovy 定義數組類似 Java,但是指定具體元素時使用符號 []
而不是 Java 的 {}
// 基本語法
def arr = new int[3]
// 定義時指定內容
def arr2 = [1, 2, 3] as int[]
注意第二個例子后面的
as int[]
,因為在 Groovy 中[]
這種語法默認產生的其實時List
對象,所以需要通過as
語法將其轉換為數組對象。
基本用法
修改元素
arr[0] = 10
讀取元素
println(arr[0])
獲得數組長度
println(arr.length)
遍歷數組
for (int a : arr2) {
println("Traverse " + a)
}
集合
List
Groovy 中的 List 與 Java 中的完全一致。
創建 List
空列表
def emptyList = []
指定元素的列表
def list = ["Groovy", "Java", "Scala"]
以上方式默認創建的是 ArrayList
修改 List
添加元素
list.add("Rust")
除了以上傳統方式,Groovy 還支持使用符號 <<
來追加元素
list << "Kotlin"
修改元素
list[10] = "Ruby"
list.putAt(9, "Python")
與 Java 不同的是,Groovy 中如果指定的索引超過了列表的長度,列表會被自動擴容,所以以上做法在 Java 中是非法的,但是在 Groovy 中是允許的。
刪除元素
與 Java 完全相同
// 刪除指定位置
list.remove(1)
// 刪除自定元素
list.remove("Ruby")
訪問元素
list.get(2)
list[2]
以上兩種方式都可以,其中第二種可以讓數組和列表的訪問擁有統一的方式
同時 Groovy 還支持使用負數從后往前建立索引,或者使用 Range
獲得一個子列表
list[-1]
list[-1..-3]
通過已有元素創建 List
Groovy 可以使用 +
和 -
從現有列表的元素創建新列表,這些方式不會修改原有列表,需要特別注意
def list = ["Groovy", "Java", "Scala", "Kotlin"]
def newList = list - ["Ruby", "Rust", "Kotlin"] + "Swift"
以上操作返回一個包含 Groovy, Java, Scala, Swift
的新列表。
不可變 List
使用方法同 Java 完全一致
展開操作符
Groovy 中可以使用展開操作符 *.
來操作列表中的每一個元素,實際上就相當于函數式編程中的 map
算子
def numbers = [1, 2, 3, 4, 3, 4]
def numbers2 = numbers*.plus(10)
println(numbers) //[1, 2, 3, 4, 3, 4]
println(numbers2) //[11, 12, 13, 14, 13, 14]
其它操作
獲得 List 長度
println(list.size())
遍歷 List
for (lang in list) {
println("Traverse " + lang)
}
Set
同 Java 一致
Range
Range 表示一個連續范圍內的值。Range (范圍) 看起來有些像數組,但是數組只是存儲空間連續,值不需要連續。簡單來說數組可以是 1,10,2,3,399
,而 Range 則必須是 1,2,3,4
或 1,3,5,7
這樣的數列。
定義一個 Range
def rng1 = 1..3
println(rng1) // [1, 2, 3]
Range 不僅可以用在數字上,也可以用在字符上
def rng4 = 'a'..'c'
println(rng4) // [a, b, c]
左閉右開
def rng2 = 1..<3
println(rng2) // [1, 2]
指定步長
def rng3 = (1..5).step(2)
println(rng3) // [1, 3, 5]
Scala 篇
數組
數組在 Scala 中可以分為定長數組 Array 和 變長數組 ArrayBuffer
Array
Array 在 Scala 中屬于傳統的定長數組。Scala 定義數組的方式類似 Java 中的列表,但是指定具體元素時即不是 Groovy 的 []
也不是 Java 的{}
,而是 ()
。
// 基本語法
val arr = new Array[Int](3)
// 定義時指定內容
val arr2 = Array(1, 2, 3)
修改元素
arr(0) = 10
讀取元素
println(arr(0))
獲得數組長度
println(arr2.length)
遍歷數組
for (a <- arr2) {
println(s"Traverse $a")
}
打印數組
默認 toString()
只會打印出數組的地址,要想打印數組的內容需要使用 mkString()
println(arr2.mkString(","))
ArrayBuffer
ArrayBuffer 在 Scala 中屬于變長數組,相比較數組而言其最大的缺點就是刪除元素時的效率較低,使用時相當于 Java 的 ArrayList。
定義 ArrayBuffer
val abuffer = ArrayBuffer[Int]()
修改元素
// 添加單個或多個元素
abuffer += 10
// 添加單個或多個 Array 或 ArrayBuffer
abuffer ++= arr2
// 刪除單個或多個元素
abuffer -= 3
讀取元素
println(abuffer(0))
Array 和 ArrayBuffer 的轉換
Array -> ArrayBuffer
val buffer = arr.toBuffer
ArrayBuffer -> Array
val arr4 = abuffer.toArray
集合
列表
Scala 中列表在語言層面就分為不可變列表和可變列表。不可變列表無法改變列表的內容,是 Scala 默認的列表類型。
不可變 List
定義一個不可變列表
空列表
val empty = List()
println(empty == Nil) // true
Scala 中空列表等于 Nil
。且由于列表不可變,所以該空列表也無法進行任何寫操作
創建時指定元素
val list = List("Groovy", "Java", "Scala")
List 構造器
Scala 的列表結構與其它語言都不一樣。它分為頭部和尾部,頭部就是列表的第一個元素,尾部也是一個列表,它包含列表的其余部分。
val list = List("Groovy", "Java", "Scala")
println(list.head) // Groovy
println(list.tail) // List(Java, Scala)
println(list.tail.head) // Java
由于是不可變列表,所以即無法添加修改元素,也無法刪除元素,但是可以通過符號 ::
結合當前列表返回新列表。
::
是中綴操作符,左邊的操作數為組成新列表的元素,右操作數為當前列表,該操作符具有右結合性,即 A :: B :: C
會被翻譯成 A :: (B :: C)
val list = List("Groovy", "Java", "Scala")
val newList = "Ruby" :: list
// val newList2 = list :: "Ruby"
以上第一個操作會返回包含 Ruby, Groovy, Java, Scala
的新列表。第二個操作則是非法的,因為右操作數 Ruby
不是列表。
掌握了右結合性的特性,我們就可以寫出如下代碼
val days = "Sunday" :: "Monday" :: "Tuesday" :: "Wednesday" :: "Thursday" :: "Friday" :: "Saturday" :: Nil
也可以通過操作符 :::
結合兩個列表的內容產生新列表
val all = list ::: days
訪問元素
println(list(2)) // Scala
可變 List
可變 List 位于 scala.collection.mutable
包下,本質上是 LinkedList
。
創建一個可變 List
var mutableList = new mutable.MutableList[Int]
可變 List 可以使用符號 +=
添加新元素
// 添加單個元素
mutableList += 1
// 添加多個元素
mutableList +=(2, 3, 5)
訪問時與不可變 List 相同
println(mutableList(1))
盡管稱作可變 List,但是其并不支持直接刪除任意元素
ListBuffer
ListBuffer 是另一種可變 List,其本質實際是由 Nil
和 ::
結合不可變 List 來實現的。
創建一個 ListBuffer
var listBuffer = new ListBuffer[Int]
可以使用符號 +=
添加新元素或者符號 -=
刪除元素
// 添加單個元素
listBuffer += 1
// 添加多個元素
listBuffer +=(2, 3, 5)
// 刪除元素
listBuffer -= 2
訪問時與不可變 List 相同
println(listBuffer(1))
List 和 ListBuffer 轉換
List -> ListBuffer
list.toBuffer
ListBuffer -> List
listBuffer.toList
其它操作
獲得 List 長度
println(list.length)
注意,Scala 中列表和數組都統一使用了 length
獲取長度,解決了 Java 中那個糟糕的設計。
遍歷 List
for (lang <- list) {
println(s"Traverse $lang")
}
Set
Set 使用方式類似 List,這里就不細說了。
Range
定義一個 Range
val rng1 = 1 to 3
println(rng1) // Range(1, 2, 3)
Range 不僅可以用在數字上,也可以用在字符上
val rng4 = 'a' to 'c'
println(rng4) // NumericRange(a, b, c)
左閉右開
val rng2 = 1 until 3
println(rng2) // Range(1, 2)
指定步長
val rng3 = 1 to 5 by 2
println(rng3) // Range(1, 3, 5)
除了以上方法,還可以直接通過構造器建立對象,需要注意的是通過構造器建立的范圍是左閉右開的
val rng5 = Range(1, 3)
println(rng5) // Range(1, 2)
val rng6 = Range(1, 5, 2)
println(rng6) // Range(1, 3)
Kotlin 篇
數組
創建數組
// 基本語法
val arr = arrayOfNulls<Int>(3)
// 定義時指定內容
val arr2 = arrayOf(1, 2, 3)
基本用法
修改元素
arr[0] = 10
讀取元素
println(arr[0])
獲得數組長度
println(arr.size
遍歷數組
for (a in arr2) {
println("Traverse $a")
}
集合
列表
Kotlin 同 Scala 一樣 列表在語言層面就分為不可變列表和可變列表。不可變列表無法改變列表的內容,是 Kotlin 默認的列表類型。
不可變列表
定義一個不可變列表
空列表
val empty = emptyList<Int>()
Kotlin 中由于列表不可變,所以該空列表也無法進行任何寫操作
指定元素的列表
val list = listOf("Groovy", "Java", "Scala")
訪問元素
println(list(2)) // Scala
可變 List
老版本的 Kotlin 中可變 List 就是 LinkedList
,但是 1.0 版本變成了 ArrayList
,api 也跟著改變了。
創建一個可變 List
var mutableList = mutableListOf<String>()
可變 List 可以使用方法 add()
添加新元素,使用方法 remove()
刪除元素
mList.add("Ruby")
mList.remove("Java")
訪問時與不可變 List 相同
println(mutableList(1))
其它操作
獲得列表長度
println(list.size)
注意,Kotlin 和 Scala 一樣也統一了用法,但是 Kotlin 使用的是 size()
。
遍歷 List
for (lang in list) {
println("Traverse $lang")
}
Set
Set 使用方式類似 List,這里就不細說了。
Range
定義一個 Range
val rng1 = 1..3
println(rng1) // 1..3
Range 不僅可以用在數字上,也可以用在字符上
val rng4 = 'a'..'c'
println(rng4) // a..c
指定步長
val rng3 = (1..5).step(2)
println(rng3) // 1..5 step 2
Summary
- 除了 Java,其它語言都支持 Range 類型
- Scala 與 Kotlin 中默認列表都是不可變形式
- Scala 有可變數組和不可變數組兩種數組,其中可變數組是通過
ArrayList
實現的 - Scala 有 Mutable List, Immutable List 和 ListBuffer 三種列表,其中 Mutable List 是通過
LinkedList
實現的, ListBuffer 是通過 Immutable List 和 Nil 實現的 - Java 和 Groovy 訪問數組長度和列表長度分為為
length
和size()
,Scala 訪問數組和列表長度都使用length
,Kotlin 則都使用size()
╮(╯_╰)╭
文章源碼見 https://github.com/SidneyXu/JGSK 倉庫的 _13_collection
小節