JAVASE进阶:Collection高级(1)——源码分析contains方法、lambda遍历集合

‍作者简介:一位大四、研0学生,正在努力准备大四暑假的实习
上期文章:JAVASE进阶:函数式编程——lambda表达式替代匿名内部类
订阅专栏:JAVASE进阶
希望文章对你们有所帮助

打算法竞赛的时候用的C++,为了方便敲代码基本上都不怎么用iterator来遍历集合,都是直接使用的下标。
但在JAVA,下标遍历的方式不适合所有的集合,因为集合中还包含了Set,其没有索引的概念。又因为Java具有泛型编程,所以通用的iterator遍历是很重要的,除此之外还有其他两种遍历方式,当然其底层肯定还是用的迭代器。
其实使用起来都是很容易的,学了就会,但是还是要扒一扒源码才有意思。

源码分析contains方法、函数式编程实现遍历

  • Collection体系结构
    • contains()底层原理
  • 遍历Collection的三种通用方式
    • 迭代器方式
    • 增强for方式
    • lambda表达式方式

Collection体系结构

JAVASE进阶:Collection高级(1)——源码分析contains方法、lambda遍历集合_第1张图片
其中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方法,可以看到这是一个通用的抽象方法:
JAVASE进阶:Collection高级(1)——源码分析contains方法、lambda遍历集合_第2张图片
2、返回,选中contains并右键点击Go To再进入Implemention,找到ArrayList的具体实现类:
JAVASE进阶:Collection高级(1)——源码分析contains方法、lambda遍历集合_第3张图片
3、contains传入了泛型对象o,然后将o再放入indexOf函数,indexOf将o、开始下标0、结束下标size放入indexOfRange函数并返回:
JAVASE进阶:Collection高级(1)——源码分析contains方法、lambda遍历集合_第4张图片
4、跟踪indexOfRange函数,可以发现,底层会遍历集合,并使用equals函数判断对象和集合中的对象是否相同:
JAVASE进阶:Collection高级(1)——源码分析contains方法、lambda遍历集合_第5张图片
5、跟踪进入equals方法,可以发现其比较对象是否相等的方式是==
JAVASE进阶:Collection高级(1)——源码分析contains方法、lambda遍历集合_第6张图片

所以,第一个案例中,全部都是字符串常量,来源于常量池,比较的时候两个字符串对象指向了常量池中的同一个字符串,因此比较会成功。
而第二个案例中是自定义对象,其创建的过程是在堆之中的,且每次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 action) 结合lambda遍历集合

可以稍微扒一扒forEach的底层源码:
1、找到forEach方法,其主要的意思就是遍历集合,依次得到容器中的每一个元素,并传递给action.accept方法,剩下的处理就交给accept了:
JAVASE进阶:Collection高级(1)——源码分析contains方法、lambda遍历集合_第7张图片
2、跟踪进入accept方法,可以看到其所在的Consumer接口上的@FunctionalInterface注解,那就说明可以使用函数式编程了:
JAVASE进阶:Collection高级(1)——源码分析contains方法、lambda遍历集合_第8张图片
若不考虑函数式编程,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));

你可能感兴趣的:(JAVASE进阶,java,jvm,lambda,面试)