传统意义上的集合主要是List和Set,再广泛一点的话,像Map这样的键值对数据结构也可以包含进来。List,Set和Map再Java中都是接口,List主要的实现类是ArrayList和LinkedList,Set的主要实现类是HashSet,Map的主要实现类的HashMap。
比如现在创建一个包含许多水果名称的集合,在java中我们会首先创建一个ArrayList的实例,然后将水果的名称一个个添加到集合中。在kotlin也可以这么做。
fun main(){
val list &#61; ArrayList<String>()
list.add("Apple")
list.add("Banana")
list.add("Orange")
list.add("Pear")
list.add("Grape")
}
但是这种初始化集合的方式比较繁琐&#xff0c;为此Kotlin专门提供了一个内置的listOf()函数来简化初始化集合的写法&#xff0c;如下所示
val list &#61; listOf("Apple", "Banana", "Pear", "Grape")
在循环语句中&#xff0c;for-in循环不仅可以用来遍历区间&#xff0c;还可以用来遍历集合。现在我们就尝试一下使用for-in循环来遍历这个水果集合。
fun main(){
val list &#61; listOf("Apple", "Banana", "Pear", "Grape")
for(fruit in list){
println(fruit)
}
}
不过需要注意的是&#xff0c;listOf()函数创建的是一个不可变的集合&#xff0c;不可变集合指的就是该集合只能用于读取&#xff0c;我们无法对集合进行添加、修改或删除操作。
当我们需要一个可变的集合&#xff0c;就可以使用mutableListOf()函数&#xff0c;示例如下&#xff1a;
fun main(){
val list &#61; mutableListOf("Apple", "Banana", "Pear", "Grape")
list.add("orange")
for(fruit in list){
println(fruit)
}
}
Set集合和List集合的用法几乎是一模一样&#xff0c;只是将创建集合的方式换成了setOf()和mutableSetOf()函数而已。
val set &#61; setOf("Apple", "Banana", "Pear", "Grape","orange")
for(fruit in set){
println(fruit)
}
需要注意的是&#xff0c;Set集合底层是使用hash映射机制存放数据的&#xff0c;因此集合中的元素无法保证有序&#xff0c;这是和List集合最大的不同之处。
最后再来看一下Map集合。Map是一种键值对形式的数据结构&#xff0c;Map用法是先创建一个HashMap的实例&#xff0c;然后将一个个键值对数据添加到Map中。比如这里我们给每种水果设置一个对应的编号&#xff0c;可以这样写
val map &#61; HashMap<String, Int>()
map.put("Apple",1)
map.put("Banana",2)
map.put("Orange",3)
map.put("Pear",4)
for(fruit in map){
println(fruit)
}
因为这种写法和Java语法是比较相似的&#xff0c;因此可能比较好理解&#xff0c;但是其实在Kotlin中并不建议使用put()和get()方法来对Map进行添加和读取数据操作&#xff0c;而是推荐使用一种类似于数组下标的语法结构&#xff0c;比如Map中添加一条数据就可以这么写&#xff1a;
map[“Apple”] &#61; 1
而从Map中读取一条数据就可以这么写
val number &#61; map[“Apple”]
经过上面的代码优化后可以这么写
val map &#61; HashMap<String, Int>()
map["Apple"] &#61; 1
map["Banana"] &#61; 2
map["Orange"] &#61; 3
map["Pear"]&#61;4
这还不是最简便的写法&#xff0c;因为Kotlin给我们提供了一对mapOf()和mutableMapOf()函数来继续简化Map的用法。在mapOf()函数中&#xff0c;我们可以直接传入初始化的键值对组合来完成对Map集合的创建&#xff1a;
var map&#61; mapOf("Apple" to 1,"Banana" to 2,"orange" to 3,"Pear" to 4)
这个to不是关键&#xff0c;涉及到了infix函数。
var map&#61; mapOf("Apple" to 1,"Banana" to 2,"orange" to 3,"Pear" to 4)
for((fruit,number) in map ){
println("fruit is $fruit"&#43;" ,number is $number")
}
在for in循环中可以将Map键值对变量一起声明到了一对括号里面&#xff0c;这样当进行循环遍历时&#xff0c;每次遍历的结果就会赋值给这两个键值对变量&#xff0c;最后将他们的值打印出来。
在水果集合里面找到单词最长的那个水果&#xff0c;如果不用Lambda表达式&#xff0c;你可能会这么写
val list &#61; listOf("Apple", "Banana", "Pear", "Grape", "orange")
var maxLengthFruit&#61;""
for (fruit in list){
if(fruit.length>maxLengthFruit.length){
maxLengthFruit&#61;fruit
}
}
println("max length Fruit is $maxLengthFruit")
如果我们使用集合的函数式API
val maxLengthFruit &#61; list.maxBy { it.length }
println("max length Fruit is $maxLengthFruit")
这里是借助了Lambda表达式
Lambda表达式的语法结构为&#xff1a;&#xff5b;参数名1&#xff1a;参数类型&#xff0c;参数名2&#xff1a;参数类型->函数体&#xff5d;
首先最外层是一对大括号&#xff0c;如果有参数传入到Lambda表达式的话&#xff0c;我们还需要声明参数列表&#xff0c;参数列表的结尾使用一个->符号&#xff0c;表示参数列表的结束以及函数体的开始&#xff0c;函数体中可以编写任意行代码&#xff0c;并且最后一行代码会自动作为Lambda表达式的返回值。
maxBy函数就是一个普通函数&#xff0c;只不过它接收的是一个Lambda类型的参数&#xff0c;并且会在遍历集合时将每次遍历的值作为参数传递给Lambda表达式。maxBy函数的工作原理时将根据我们传入的条件来遍历集合&#xff0c;从而找到该条件下的最大值&#xff0c;比如说想要找到单词最长的水果&#xff0c;那传入的条件就是单词的长度了。
知道了maxBy函数的工作原理后&#xff0c;我们就可以套用刚才的Lambda表达式的语法结构&#xff0c;并将它传入到maxBy函数中
val lambda&#61;{fruit:String ->fruit.length}
val maxLengthFruit&#61;list.maxBy(lambda)//按照lambda表达式的条件进行查找最大长度
println(maxLengthFruit)
可以指导maxBy函数实质上就是接收了一个lambda参数而已&#xff0c;并且这个Lambda参数是完全按照语法结构&#xff1a;参数名&#xff1a;参数类型->函数体来定义的。
这种写法还是可以再进行简化的&#xff0c;可以直接将Lambda表达式传入maxBy函数中
val maxLengthFruit&#61;list.maxBy({fruit:String->fruit.length})
Kotlin规定&#xff0c;当Lambda参数是函数最后一个参数时&#xff0c;可以将Lambda表达式移到函数括号的外面
val maxLengthFruit&#61;list.maxBy(){fruit:String->fruit.length}
接下来&#xff0c;如果Lambda参数是函数的唯一一个参数的话&#xff0c;还可以将函数的括号省略
val maxLengthFruit&#61;list.maxBy{fruit:String->fruit.length}
接下来&#xff0c;还可以继续进行简化&#xff0c;由于Kotlin拥有出色的类型推导机制&#xff0c;Lambda表达式中的参数列表其实在大多数情况下不必要声明参数类型&#xff0c;因此代码进一步简化为
val maxLengthFruit&#61;list.maxBy{fruit->fruit.length}
最后当Lambda表达式的参数列表中只有一个参数时&#xff0c;也不必要声明参数名&#xff0c;而是可以使用it关键字来代替
val maxLengthFruit&#61;list.maxBy{it.length}
通过推导过程&#xff0c;得到了和开始的那一段函数API一模一样的写法。
集合中的map函数也是一种最常用的一种函数API&#xff0c;它用于将集合中的每个元素都映射成一个另外的值&#xff0c;映射规则在Lambda表达式中指定&#xff0c;最终生成一个新的集合。比如这里将所有水果名都变成大写模式&#xff0c;可以这样写
fun main(){
val list &#61; listOf("Apple", "Banana", "Pear", "Grape", "orange")
val newList &#61; list.map { it.toUpperCase() }
for (fruit in newList){
println(fruit)
}
}
可以看到&#xff0c;我们在map函数的Lambda表达式中指定将单词转换成了大写模式&#xff0c;然后遍历这个新的集合。
map的功能很强大&#xff0c;可以根据我们的需求对集合中的元素进行任意的映射转换&#xff0c;除此之外&#xff0c;还可以将水果名全部转换为小写&#xff0c;或者只取单词的首字母&#xff0c;甚至是转换成单词长度这样的数字集合&#xff0c;只需在Lambda表达式中编写需要的逻辑即可。
例如&#xff1a;只取单词首字母
val newList &#61; list.map { it[0]}
for (fruit in newList){
println(fruit)
}
接下来是另外一个比较常用的函数API–filter函数。顾名思义&#xff0c;filter函数是用来过滤集合中的数据的&#xff0c;它可以单独使用&#xff0c;也可以配合刚才的map函数一起使用。
比如我们只想保留5个字母以内的水果&#xff0c;就可以借助filter函数来实现
fun main(){
val list &#61; listOf("Apple", "Banana", "Pear", "Grape", "orange")
val newList &#61; list.filter { it.length<&#61;5 }.map { it.toUpperCase() }
for (fruit in newList){
println(fruit)
}
}
可以看到同时使用了filter和map函数&#xff0c;并通过Lambda表达式将水果单词长度限制在5个单词以内并将水果名映射为大写字母
上述代码中&#xff0c;我们是先调用filter函数再调用map函数&#xff0c;如果改成先调用map函数再调用filter函数&#xff0c;效率会差很多&#xff0c;因为你这样相当于先将所有元素映射转换后再进行过滤&#xff0c;这是完全不必要的。先将所有元素过滤后再进行映射转换&#xff0c;这样明显高效许多。
接下来还有两个比较常用的函数式API–any和all函数。其中any函数用于判断集合中是否至少存在一个元素满足指定条件&#xff0c;all函数用于判断集合中是否所有元素满足指定条件
fun main(){
val list &#61; listOf("Apple", "Banana", "Pear", "Grape", "orange")
val anyResult &#61; list.any { it.length <&#61; 5 }
val allResult &#61; list.all { it.length <&#61; 5 }
println("anyResult is "&#43;anyResult&#43;", allResult is "&#43;allResult)
}
此时any函数表示集合中是否存在5个字母以内的单词&#xff0c;而all函数表示集合中是否所有单词都在5个字母以内。
Kotlin中调用Java方法时也可以使用函数式API&#xff0c;只不过这是有一定条件限制的。具体来讲&#xff0c;如果我们在Kotlin代码中调用了一个Java方法&#xff0c;并且该方法接收一个Java单抽象方法接口参数&#xff0c;就可以使用函数式API&#xff0c;Java单抽象方法接口指的是接口中只有一个待实现方法&#xff0c;如果接口中有多个待实现方法&#xff0c;则无法使用函数式API
例如&#xff1a;Java原生API中有一个最为常见的抽象方法接口–Runnable接口。这个接口中只有一个待实现的run()方法&#xff0c;定义如下&#xff1a;
public interface Runnable{
void run();
}
根据前面的定义&#xff0c;对应任何一个Java方法&#xff0c;只要它接收Runnable参数&#xff0c;就可以使用函数式API。那么什么Java方法接收了Runnable参数呢&#xff1f;这就很多了&#xff0c;不过Runnable接口主要还是结合线程一起使用的。
Thread类的构造方法中接收一个Runnable参数&#xff0c;我们可以使用Java代码创建并执行一个子线程&#xff1a;
new Thread(new Runnable(){
&#64;Override
public void run(){
System.out.println("Thread is running");
}
}).start();
这里使用匿名内部类的写法&#xff0c;我们创建了一个Runnable接口的匿名类实例&#xff0c;并将它给了Thread类的构造方法&#xff0c;最后调用Thread类的start()方法执行这个线程。
如果将这断代码翻译为Kotlin版本&#xff0c;写法如下&#xff1a;
Thread(object:Runnable{
override fun run() {
println("Thread is running")
}
}).start()
Kotlin中匿名内部类的写法和Java有一点区别&#xff0c;由于Kotlin完全舍弃了new关键字&#xff0c;因此创建匿名内部类实例的时候就不能再使用new了&#xff0c;而是改用了object关键字。
还有目前Thread类的构造方法时符合Java函数式API的使用条件的&#xff0c;下面我们就看看如何对代码进行精简
Thread(Runnable { println("Thread is running") }).start()
因为Runnable类中只有一个待实现的方法&#xff0c;即使这里没有显式地重写run()方法&#xff0c;Kotlin也能自动明白Runnable后面的Lambda表达式就是run()方法中要实现的内容。
另外&#xff0c;如果一个Java方法的参数列表中不存在一个以上Java单抽象方法接口参数&#xff0c;我们还可以将接口名进行省略。&#xff08;也就是说Thread方法只需要Runnable一个接口&#xff09;
Thread( { println("Thread is running") }).start()
当Lambda表达式还是方法的最后一个参数时&#xff0c;可以将Lambda表达式移到方法括号的外面。同时&#xff0c;如果Lambda表达式还是方法的唯一一个参数&#xff0c;还可以将方法的括号省略。
Thread{ println("Thread is running") }.start()
当我们在Kotlin中调用Android SDK接口时&#xff0c;就很有可能会用到这种Java函数式API写法
举个例子&#xff1a;Android中有一个经常用到的点击事件接口OnClickListener&#xff0c;其定义如下&#xff1a;
public interface OnClickListener{
void onClick(View v);
}
可以看到这又是一个单抽象方法接口。假设我们拥有一个按钮button实例&#xff0c;然后使用java代码去注册这个按钮的点击事件
button.setOnClickListener(new View.OnClickListener(){
&#64;Override
public void onClick(View v){
}
});
而用Kotlin代码实现同样的功能&#xff0c;就可以使用函数式API的写法来对代码进行简化&#xff0c;结果如下&#xff1a;
button.setOnClickListener{
}
Java函数式API的使用都限定于从Kotlin中调用Java方法&#xff0c;并且抽象方法接口也必须是用Java语言定义的。
例如&#xff1a;ListView中的点击事件方法setOnItemClickListener接收一个OnItemClickListener接口对象
/**
* Register a callback to be invoked when an item in this AdapterView has
* been clicked.
*
* &#64;param listener The callback that will be invoked.
*/
public void setOnItemClickListener(&#64;Nullable OnItemClickListener listener) {
mOnItemClickListener &#61; listener;
}
OnItemClickListener 接口
public interface OnItemClickListener {
/**
* Callback method to be invoked when an item in this AdapterView has
* been clicked.
*
* Implementers can call getItemAtPosition(position) if they need
* to access the data associated with the selected item.
*
* &#64;param parent The AdapterView where the click happened.
* &#64;param view The view within the AdapterView that was clicked (this
* will be a view provided by the adapter)
* &#64;param position The position of the view in the adapter.
* &#64;param id The row id of the item that was clicked.
*/
void onItemClick(AdapterView<?> parent, View view, int position, long id);
}
因为setOnItemClickListener方法接收一个Java单抽象方法接口参数&#xff0c;就可以使用函数式API&#xff0c;由于onItemClick接口中含有四个参数(一个以上)&#xff0c;使用时简化为
list_view.setOnItemClickListener { parent, view, position, id ->
}
当如果只使用到position这个参数的时候&#xff0c;kotlin允许我们将没有用到的参数使用下划线来替代&#xff0c;因此可以写成下面这种写法
list_view.setOnItemClickListener { _, _, position, _ ->
}