Java & Groovy & Scala & Kotlin - 13.数组与集合

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 默认是可变的,其本身是一个接口,最常用的是其子类 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,字符串使用 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,41,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 访问数组长度和列表长度分为为 lengthsize(),Scala 访问数组和列表长度都使用 length,Kotlin 则都使用 size() ╮(╯_╰)╭

文章源码见 https://github.com/SidneyXu/JGSK 仓库的 _13_collection 小节

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,324评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,303评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,192评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,555评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,569评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,566评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,927评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,583评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,827评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,590评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,669评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,365评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,941评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,928评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,159评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,880评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,399评论 2 342

推荐阅读更多精彩内容

  • Scala的集合类可以从三个维度进行切分: 可变与不可变集合(Immutable and mutable coll...
    时待吾阅读 5,798评论 0 4
  • 前言 人生苦多,快来 Kotlin ,快速学习Kotlin! 什么是Kotlin? Kotlin 是种静态类型编程...
    任半生嚣狂阅读 26,134评论 9 118
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,580评论 18 139
  • 这篇讲义只讲scala的简单使用,目的是使各位新来的同事能够首先看懂程序,因为 scala 有的语法对于之前使用习...
    MrRobot阅读 2,896评论 0 10
  • 春晚的经典歌曲《时间都去哪儿》令人印象深刻。你有关注过自己时间都花销在哪里了吗?你知道自己的时间黑洞都在哪里吗?最...
    加一的修炼场阅读 1,048评论 5 4