黑马程序员全套Java教程_Java基础教程_集合进阶之List(二十五)

2.1 List集合概述和特点

  • List集合概述:
    (1)有序集合(也称为序列),用户可以精确控制列表中每个元素的插入位置。用户可以通过整数索引访问元素,并搜索列表中的元素;
    (2)与Set集合不同,列表通常允许重复的元素。
  • List集合概述:
    (1)有序:存储和取出的元素顺序一致;
    (2)可重复:存储的元素可以重复。
    public static void main(String[] args) {
        List<String> list = new ArrayList<String>();
        list.add("dad");
        list.add("mom");
        list.add("son");
        list.add("son");
        System.out.println(list);//[dad, mom, son, son]

        Iterator<String> it = list.iterator();
        while (it.hasNext()){
            System.out.println(it.next());
            //dad
            //mom
            //son
            //son
        }
    }

2.2 List集合特有方法

方法名说明
void add(int index, Eelement)在此集合中的指定位置插入指定的元素
E remove(int index)删除指定索引处的元素,返回被删除的元素
E set(int index,E element)修改指定索引处的元素,返回被修改的元素
E get(int index)返回指定索引处的元素
    public static void main(String[] args) {
        List<String> list = new ArrayList<>();
        list.add("java");
        list.add("my");
        list.add("love");
        //list.add(4,"love");//IndexOutOfBoundsException
        list.add(3,"love");
        list.add(3,"l");
        System.out.println(list);//[java, my, love, l, love]
        list.set(3,"forver");
        System.out.println(list);//[java, my, love, forver, love]
        System.out.println(list.get(2));//love
        System.out.println("被移除一个元素:" + list.remove(1));//被移除一个元素:my
        System.out.println(list);//[java, love, forver, love]
        System.out.println(list.get(2));//forver


        //List集合的遍历:除了迭代器,还可以
        for (int i = 0; i < list.size(); i++){//java,love,forver,love
            System.out.print(list.get(i));
            if (i != list.size()-1){
                System.out.print(",");
            }
        }
    }

案例:List集合存储学生对象并遍历

  • 需求:创建一个存储学生对象的集合,存储三个学生对象,使用程序实现在控制台遍历该集合
  • 思路:
    (1)定义学生类;
    (2)创建List集合对象;
    (3)创建学生对象;
    (4)把学生添加到集合;
    (5)遍历集合(迭代器、for循环两种方法)。
    public static void main(String[] args) {
        List<Student> list = new ArrayList<>();

        Student s1 = new Student();
        s1.setAge(13);
        s1.setName("liubei");
        list.add(s1);
        Student s2 = new Student("lisi", 12);
        list.add(s2);
        list.add(new Student("zhangsan",11));

        //方法1
        for (int i = 0; i < list.size(); i++){
            System.out.println(list.get(i));
        }

        //方法2
        System.out.println("方法2");
        Iterator<Student> it = list.iterator();
        while (it.hasNext()){
            System.out.println(it.next());
        }
    }

2.3 并发修改异常

  • 有一个List集合,集合中有三个元素liubei,guanyu,zhangfei,遍历集合,看看有没有“zhangfei”这个元素,如果有我们就添加一个“pangfeng”元素,我们可以这样写:
    public static void main(String[] args) {
        List<String> list = new ArrayList<>();
        list.add("liubei");
        list.add("guanyu");
        list.add("zhangfei");

        Iterator<String> it = list.iterator();
        while (it.hasNext()){
            if (it.next() == "zhangfei"){//ConcurrentModificationException
                list.add("panfeng");
            }
        }
    }

在这里插入图片描述
然而却发现程序报ConcurrentModificationException的运行时异常,此即为并发修改异常。查看API文档的解释是:当不允许这样的修改时,可以通过检测到对象的并发修改来抛出此异常。那么为什么会产生这个异常呢?
通过at itheima6.ListDemo.main(ListDemo.java:22)我们可以知道异常是在next()方法出现的问题。
通过at java.util.ArrayList I t r . n e x t ( A r r a y L i s t . j a v a : 851 ) 我 们 可 以 知 道 n e x t ( ) 为 为 j a v a . u t i l 包 下 A r r a y L i s t 类 里 面 的 内 部 类 I t r 类 的 方 法 。 通 过 a t j a v a . u t i l . A r r a y L i s t Itr.next(ArrayList.java:851)我们可以知道next()为为java.util包下ArrayList类里面的内部类Itr类的方法。 通过at java.util.ArrayList Itr.next(ArrayList.java:851)next()java.utilArrayListItratjava.util.ArrayListItr.checkForComodification(ArrayList.java:901)我们最终定位到next()内调用的checkForComodification(),此为并发修改异常产生的原因。

  • 并发修改异常的源码分析:
    在mian()的第一行“List list = new ArrayList<>();”可以知道程序首先定义了一个List集合,并且后面还调用了List接口的add()和Iterator(),因为List为接口,这两个方法并没有直接在List接口中实现。我们最终创建的是ArrayList类的对象,而ArrayList类实现了List接口,所以在List类中没有实现的两个方法也是在ArrayList类中实现的。在iterator()内,还new了Itr类的对象,通过上文控制台输出的错误信息我们也知道异常是在内部类Itr的next()调用其方法checkForComodification()时产生,因此我们将内部类Itr的相关源码的也拿过来。
public interface List<E> {
	boolean add(E e);
	Iterator<E> iterator();
}

public class ArrayList<E> extends AbstractList<E> implements List<E>{
	public boolean add(E e) {
        modCount++;
        elementData[size++] = e;
        return true;
    }
    
    public Iterator<E> iterator() {
        return new Itr();
    }

	private class Itr implements Iterator<E> {
        int expectedModCount = modCount;

        @SuppressWarnings("unchecked")
        public E next() {
            checkForComodification();
            int i = cursor;
            if (i >= size)
                throw new NoSuchElementException();
            Object[] elementData = ArrayList.this.elementData;
            if (i >= elementData.length)
                throw new ConcurrentModificationException();
            cursor = i + 1;
            return (E) elementData[lastRet = i];
        }
        
        final void checkForComodification() {
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
        }
    }
}

我们定位到checkForComodification(),发现其对modCount和expectedModCount两个值进行了比较,其中modCount为我们对集合实际修改的次数,expectedModCount为预期修改集合的次数。如果这两个值不相等就会抛出并发修改异常。在内部类开头将实际修改次数modCount赋值给了预期修改次数expectedModCount,而modCount值来自ArrayList类继承的父类AbstractList。

public abstract class AbstractList<E> {
	protected transient int modCount = 0;
}

可以知道,modCount和expectedModCount两个值一开始都为0,那么我们什么时候改变某个值,导致出现异常呢?我们看到ArrayList类的add方法,发现其在第一行modCount++。综上,我们创建好迭代器it,在遍历到zhangfei并add(“panfeng”)后,再回到while循环,此时modCount发生了改变而expectedModCount却没有改变,这就是异常产生的根本原因。

  • 解决办法:使用for循环配合get()进行代码的编写(因为get()并没有对modCount和expectedModCount两个值进行修改)
    public static void main(String[] args) {
        List<String> list = new ArrayList<>();
        list.add("liubei");
        list.add("guanyu");
        list.add("zhangfei");

        for (int i = 0; i < list.size(); i++){
            if (list.get(i) == "zhangfei"){
                list.add("panfeng");
            }
            System.out.println(list.get(i));
        }
    }
  • 总结:
    (1)并发修改异常:ConcurrentModificationException;
    (2)产生原因:迭代器遍历过程中,通过集合对象修改了集合中元素的长度,造成了迭代器获取元素中判断预期修改值和实际值不一致;
    (3)解决方案:用for循环遍历,然后用集合对象做对应的操作即可。

2.4 ListIterator

  • 列表迭代器ListIterator:
    (1)继承自Iterator,通过List集合的listIterator()得到,所以说它是list集合特有的迭代器。
    (2)用于允许程序员沿任一方向遍历列表的列表迭代器,在迭代期间修改列表,并获取列表中迭代器的当前位置。
  • ListIterator中的常用方法:
方法说明
E next()返回迭代中的下一个元素
boolean hasNext()如果迭代具有更多元素,则返回true
E previous()返回列表中的上一个元素
boolean hasPrevious()如果此列表迭代器在相反方向遍历列表时具有更多元素,则返回true
void add(E e)将指定的元素插入列表
    public static void main(String[] args) {
        List<String> list = new ArrayList<>();
        list.add("caocao");
        list.add("xunyu");
        list.add("guojia");

        ListIterator<String> lt = list.listIterator();
        //正向遍历
        while (lt.hasNext()){
            System.out.print(lt.next() + " ");
        }
        System.out.println();
        //逆向遍历 - 少用
        while (lt.hasPrevious()){
            System.out.print(lt.previous() + " ");
        }
        System.out.println();
        //添加元素
        while (lt.hasNext()){
            String s = lt.next();
            if (s.equals("guojia")){
                //list.add("lidian");//ConcurrentModificationException
                lt.add("lidian");//不报错
            }
        }
        System.out.println(list);//[caocao, xunyu, guojia, lidian]
    }

lt.add(“lidian”)不报错的原因:方法在执行完毕之后,重新将modCount值赋值给了expectedModCount。

2.5 增强for循环

  • 增强for循环:
    (1)用于简化数组和Collection集合的遍历;
    (2)查看API文档,Collection继承了Iterable接口,而实现Iterable接口的类的对象可以成为增强型for语句的目标(如ArrayList,另也包括数组),我们知道Collection在内部实现了Iterator类;
    (3)它是JDK5之后出现的,其内部原理是一个Iterator迭代器。
  • 增强for循环的格式:
for(元素数据类型 变量名:数组或者Collection集合){
	//在此处使用变量即可,该变量就是元素
}

范例:

int[] arr = {1,2,3,4,5};
for(int i : arr){
	System.out.println(i);
}
    public static void main(String[] args) {
        int[] arr = {1,2,3,4,5};
        for (int i : arr){
            System.out.print(i);//12345
        }
        System.out.println();

        String[] s = {"sunquan", "sunce", "zhouyu"};
        for (String ss : s){
            System.out.print(ss);//sunquansuncezhouyu
        }
        System.out.println();

        List<String> list = new ArrayList<>();
        list.add("liubei");
        list.add("guanyu");
        list.add("zhangfei");
        for (String s2 : list){
            System.out.print(s2);//liubeiguanyuzhangfei
        }

        //验证其内部原理为迭代器
        //for (String s3 :list){
        //    if (s3.equals("zhangfei")){
        //        list.add("zhugeliang");//ConcurrentModificationException
        //    }
        //}
    }

案例:List集合存储学生对象用三种方式遍历

  • 需求:创建一个存储学生对象的集合,存储3个学生对象,使用程序实现在控制台遍历该集合。
  • 思路:
    (1)定义学生类;
    (2)创建List集合对象;
    (3)创建学生对象;
    (4)把学生添加到集合;
    (5)用三种方式遍历集合:迭代器(集合特有的遍历方式)、普通for(带有索引的遍历方式)、增强for(最方便的遍历方式)。
public class Student {
    private String name;
    private int age;

    public Student() {
    }

    public Student(String name, int age) {
        this.name = name;
        this.age = age;
    }

    @Override
    public String toString() {
        return "Student{" +
                "name=" + name +
                ", age=" + age +
                '}';
    }
    ............................
    public static void main(String[] args) {
        List<Student> list = new ArrayList<>();
        list.add(new Student("liubei",11));
        list.add(new Student("guanyu",12));
        list.add(new Student("zhangfei",13));

        System.out.println("方式1:");
        for (int i = 0; i < list.size(); i++){
            System.out.print(list.get(i) + " ");
        }
        System.out.println();

        System.out.println("方式2:");
        Iterator<Student> it = list.iterator();
        while (it.hasNext()){
            System.out.print(it.next() + " ");
        }
        System.out.println();

        System.out.println("方式3:");
        for (Student s : list){
            System.out.print(s + " ");
        }
    }

2.6 数据结构

  • 数据结构是计算机存储、组织数据的方式。是指相互之间存在一种或者多种特定关系的数据元素的集合。通常情况下,精心选择的数据结构可以带来更高的运行或者存储效率。
  • 常见数据结构之一——栈
    (1)数据进入栈模型的过程称为压/进栈
    (2)数据离开栈模型的过程称为弹/出栈
    (3)栈是一种先进后出的模型。
    在这里插入图片描述
  • 常见的数据结构之二——队列
    (1)数据从后端进入队列模型的过程称为:入队列
    (2)数据从前端离开队列模型的过程称为:出队列
    (3)队列是一种数据先进先出的模型。
    在这里插入图片描述
  • 常见的数据结构之三——数组
    (1)查询数据通过索引定位,查询任意数据耗时相同,查询效率高
    (2)删除数据时,要将原始数据删除,同时后面每个数据前移,删除效率低
    (3)删除数据时,添加位置后的每个数据后移,再添加元素,删除效率极低
    (4)总之,数组是一张查询快,增删慢的模型。
    在这里插入图片描述
  • 常见的数据结构之四——链表
    (1)结点的组成:
    在这里插入图片描述
    (2)头结点的组成:
    在这里插入图片描述
    (3)链表元素的添加1:
    在这里插入图片描述
    (4)链表元素的添加2:
    在这里插入图片描述
    (5)链表元素的删除:
    在这里插入图片描述
    (6)链表元素的查询:
    在这里插入图片描述
    (7)总结:链表是一种增删快、查询慢的模型(对比数组)。

2.7 List集合子类特点

  • List集合常用子类:
    (1)ArrayList:底层数据结构是数组,查询快,增删慢;
    (2)LinkedList:底层数据结构是链表,查询慢,增删快。

面试题:关于ArrayList和LinkedList两个类,下列描述错误的是?

  • ArrayList和LinkedList均实现了List接口。
    正确。集合中单列数据通过Collection集合存储,双列数据通过Map集合存储。Collection集合中,List集合可以存储重复元素,Set集合不可以存储重复元素。而Collection、Map、List、Set都只是接口,他们还有具体的实现类,ArrayList集合和LinkedList集合即为List集合的实现类。
  • ArrayList的访问速度比LinkedList快。
    正确。ArrayList集合底层数据结构是数组,查询快(通过索引定位,查询任意数据耗时相同,效率高),增删慢(增加/删除时,要将所增加/删除元素对应的索引位置之后的元素往后/前移动);LinkedList集合的底层数据结构是链表(首先要知道结点由三个部分组成:结点的存储位置即地址、结点存储的具体数据以及下一个结点的地址),其特点是查询慢(查询某个数据,必须从head结点开始查询),增删快(增加时比如说在数据AC之间添加一个数据B,要先将数据B对应的下一个数据地址指向C,再将数据A对应的下一个数据地址指向B;删除时比如说在删除数据B、D之间的数据C,要先将数据B对应的下一个数据地址指向数据D,再将数据C删除)。
  • 添加和删除元素时,ArrayList的表现更佳。
    错误。

2.8 LinkedList集合的特有功能

方法名说明
public void addFirst(E e)在该列表开头插入指定的元素
public void addLast(E e)将指定的元素追加到此列表的末尾
public E getFirst()返回此列表中的第一个元素
public E getLast()返回此列表中的最后一个元素
public E removeFirst()从此列表中删除并返回第一个元素
public E removeLast()从此列表中删除并返回最后一个元素
    public static void main(String[] args) {
        LinkedList<String> linkedList = new LinkedList<>();
        linkedList.add("zhongli");
        linkedList.add("youla");
        linkedList.add("hutao");
        System.out.println(linkedList);//[zhongli, youla, hutao]

        linkedList.addFirst("gongzi");
        System.out.println(linkedList);//[gongzi, zhongli, youla, hutao]

        linkedList.addLast("keli");
        System.out.println(linkedList);//[gongzi, zhongli, youla, hutao, keli]

        System.out.println(linkedList.getFirst());//gongzi
        System.out.println(linkedList.getLast());//keli

        System.out.println(linkedList.removeFirst());//gongzi
        System.out.println(linkedList);//[zhongli, youla, hutao, keli]

        System.out.println(linkedList.removeLast());//keli
        System.out.println(linkedList);//[zhongli, youla, hutao]
    }
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值