👨🎓作者简介:一位大四、研0学生,正在努力准备大四暑假的实习
🌌上期文章:JAVASE进阶:函数式编程——lambda表达式替代匿名内部类
📚订阅专栏:JAVASE进阶
希望文章对你们有所帮助
打算法竞赛的时候用的C++,为了方便敲代码基本上都不怎么用iterator来遍历集合,都是直接使用的下标。
但在JAVA,下标遍历的方式不适合所有的集合,因为集合中还包含了Set,其没有索引的概念。又因为Java具有泛型编程
,所以通用的iterator遍历是很重要的,除此之外还有其他两种遍历方式,当然其底层肯定还是用的迭代器。
其实使用起来都是很容易的,学了就会,但是还是要扒一扒源码才有意思。
源码分析contains方法、函数式编程实现遍历
- Collection体系结构
- contains()底层原理
- 遍历Collection的三种通用方式
- 迭代器方式
- 增强for方式
- lambda表达式方式
Collection体系结构
其中vector已经被淘汰了。
可以看到,Collection除了线性的List,还有不存在索引概念的Set。他们的特点各不相同:
List集合的特点:元素有序、可重复、有索引
Set集合的特点:元素无序、不重复、无索引
因为Java推荐使用泛型编程,等式左边声明的对象一般都是泛型的Collection,所以需要学会通用的遍历方式。
Collection常用通用方法:add()、clear()、remove()、contains()、isEmpty()、size()。除了contains(),其他方法的底层都是很容易的,但是contains()使用不慎就会出错。
contains()底层原理
在了解底层原理的时候,可以先看下面的语句:
Collection<String> coll = new ArrayList<>();
coll.add("aaa");
coll.add("bbb");
coll.add("ccc");
boolean result = coll.contains("aaa");
System.out.println(result);
这里集合中包含"aaa",因此执行结果是true
。
再看下面语句,将学生对象加入容器再执行contains方法:
Collection<Student> coll = new ArrayList<>();
Student s1 = new Student("zhangsan", 20);
Student s2 = new Student("lisi", 21);
Student s3 = new Student("wangwu", 22);
coll.add(s1);
coll.add(s2);
coll.add(s3);
Student s4 = new Student("wangwu", 22);
boolean result = coll.contains(s4);
System.out.println(result);
这里最终返回的结果是false
。
这是为什么?这需要查看contains方法的源码了:
1、跟踪contains方法,可以看到这是一个通用的抽象方法:
2、返回,选中contains并右键点击Go To
再进入Implemention
,找到ArrayList的具体实现类:
3、contains传入了泛型对象o,然后将o再放入indexOf函数,indexOf将o、开始下标0、结束下标size放入indexOfRange函数并返回:
4、跟踪indexOfRange函数,可以发现,底层会遍历集合,并使用equals函数
判断对象和集合中的对象是否相同:
5、跟踪进入equals方法,可以发现其比较对象是否相等的方式是==
:
所以,第一个案例中,全部都是字符串常量
,来源于常量池,比较的时候两个字符串对象指向了常量池中的同一个字符串,因此比较会成功。
而第二个案例中是自定义对象
,其创建的过程是在堆之中的,且每次new出来的都是不一样的,引用对象的==
比较的是地址值,地址值显然是不一样的,因此会返回false。
可以得出结论:contains方法底层使用equals方法判断对象是否在容器中,因此默认只能使用字符串才能正常判断
。
想要解决,需要在自定义的Javabean中重写equals方法,将其重写为判断元素是否完全相等即可。
遍历Collection的三种通用方式
迭代器方式
方法名称 | 说明 |
---|---|
boolean hasNext() | 判断当前位置是否有元素,有元素返回true,没有返回false |
E next() | 获取当前位置的元素,并将迭代器对象移向下一个位置 |
Iterator<String> it = list.iterator();
boolean flag = it.hasNext();
String str = it.next();
System.out.println(str);
需要注意一些细节:
1、遍历完了再访问会报错NoSuchElementException
2、迭代器遍历完毕,指针不会复位
3、循环中只能使用一次next方法
4、迭代器遍历时,不能用集合的方法进行增加或者删除
增强for方式
相关知识:
1、增强for的底层就是迭代器,它是为了简化迭代器的代码书写的。
2、它是JDK5之后出现的,其内部原理就是一个Iterator迭代器。
3、所有的单列集合和数组才能用增强for进行遍历
格式:
for(元素数据类型:变量名 数组或集合){
}
lambda表达式方式
lambda表达式实现遍历提供了一种更简单直接的遍历集合的方式,其遍历其实是使用了forEach函数:
方法名称 | 说明 |
---|---|
default void forEach(Consumer<? super T> action) | 结合lambda遍历集合 |
可以稍微扒一扒forEach的底层源码:
1、找到forEach方法,其主要的意思就是遍历集合,依次得到容器中的每一个元素,并传递给action.accept方法,剩下的处理就交给accept了:
2、跟踪进入accept方法,可以看到其所在的Consumer接口上的@FunctionalInterface
注解,那就说明可以使用函数式编程了:
若不考虑函数式编程,forEach的使用可以采用匿名内部类,即new出一个匿名内部类对象,并重写其中的accept方法,表示接收并编写处理的方式(在这里处理方式为打印):
public static void main(String[] args) {
Collection<String> coll = new ArrayList<>();
coll.add("aaa");
coll.add("bbb");
coll.add("ccc");
coll.forEach(new Consumer<String>() {
@Override
public void accept(String s) {
System.out.println(s);
}
});
}
若使用函数式编程,代码可以简化为:
coll.forEach((String o) -> {
System.out.println(o);
});
又根据函数式编程的简化规则(若不了解看上一篇文章),可以将代码进一步简化为:
coll.forEach(o -> System.out.println(o));