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
小节