JGSK - 13.数组,集合与范围

Table Of Contents

共通篇

数组在程序中用于表示一组特定的值,通常来说数组的大小是预先指定的,数组的元素类型也是统一的,所以访问数组时可以通过偏移量快速访问任意一个元素。

集合类似数组,很多时候集合也是通过数组实现的,但是集合的长度是可变的,存储的数据类型也可以不一样(尽管一般都是存储同类型的数据)。集合通常有两种: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);
}

打印数组

默认 toString() 只会打印出数组的地址,要想打印数组的内容需要使用工具类 Arrays

System.out.println(Arrays.toString(arr2));

集合

List

List 表示列表,是集合中最常用的类型,Java 中的 List 默认是可变的,其本身是一个接口,最常用的是其子类 ArrayListLinkedList,前者用于随机访问,后者用于实现队列。

定义一个 List
List<String> list = new ArrayList<String>();

默认创建的列表是空列表,里面没有任何元素。这种两边都要写明列表中元素类型的语法一直以来都被认为是非常愚蠢的,所以 Java 7 以后提供了被称作 Diamond 的语法,可以省略等式右边的类型声明。

List<String> list = new ArrayList<>();

实际上这样也没有进步多少,Guava 包提供的语法还是更加简单 List<String> list = Lists.newArrayList();

需要注意的是在列表的工具类 Collections 中也有一个可以提供空列表的方法,但那个返回的是继承自 AbstractListEmptyList 的实例,该实例不支持任何操作,与 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,这种设计一直都是 Java 程序员的槽点之一。

遍历 List

for (String lang : list) {
    System.out.println("Traverse " + lang);
}

Set

Set 最常用的是其子类 HashSet,本质上其实是一个 key-value 都一致的 HashMap。由于用法类似,本系列就不详细举例了。

Range

Java 不支持 Range

Groovy 篇

数组

Groovy 定义数组类似 Java,但是指定具体元素时 Groovy 使用 []而不是 {}

//	基本语法
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")
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] 
建立新列表

Groovy 可以使用 +- 从现有列表的元素创建新列表,这些方式不会修改原有列表,需要特别注意

def list = ["Groovy", "Java", "Scala", "Kotlin"]
def newList = list - ["Ruby", "Rust", "Kotlin"] + "Swift"

以上操作返回一个包含 Groovy, Java, Scala, Swift 的新列表。

不可变List

需要使用 Java 的方法

展开操作符

Groovy 中可以使用展开操作符 *. 来操作列表中的每一个元素。

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 可以表示一个连续范围内的值,可以使 for 循环变得更加简单。

定义一个 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 篇

数组

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

集合

List

Scala 列表在语言层面就分为不可变列表和可变列表。不可变列表无法改变列表的内容,是 Scala 默认的列表类型。

定义一个不可变列表

空列表

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

集合

List

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

其它操作

获得 kotlin 长度

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

总结

  • 除了 Java,其它语言都支持 Range 类型
  • Scala 与 Kotlin 默认集合为不可变形式
  • Scala 有可变数组和不可变数组两种数组
  • Scala 有 Mutable List, Immutable List 和 ListBuffer 三种列表
  • Java 和 Groovy 访问数组长度和列表长度分为为 lengthsize(),Scala 访问数组和列表长度都使用 length,Kotlin 都使用 size() ╮(╯_╰)╭

项目源码见 JGSK/_13_collection