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