JavaSE学习笔记(十六)—— List集合
一、List接口概述
1.1 List概述
有序(存储和取出的元素一致)的 collection(也称为序列)。此接口的用户可以对列表中每个元素的插入位置进行精确地控制。用户可以根据元素的整数索引(在列表中的位置)访问元素,并搜索列表中的元素。与 set 不同,列表通常允许重复的元素。
1.2 List案例
【List集合存储字符串并遍历】
public class ListDemo { public static void main(String[] args) { // 创建集合对象 List list = new ArrayList(); // 创建字符串并添加字符串 list.add("hello"); list.add("world"); list.add("java"); // 遍历集合 Iterator it = list.iterator(); while (it.hasNext()) { String s = (String) it.next(); System.out.println(s); } } }
【存储自定义对象并遍历】
public class ListDemo { public static void main(String[] args) { // 创建集合对象 List list = new ArrayList(); // 创建学生对象 Student s1 = new Student("白骨精", 30); Student s2 = new Student("蜘蛛精", 40); Student s3 = new Student("观音姐姐", 22); // 把学生对象添加到集合对象中 list.add(s1); list.add(s2); list.add(s3); // 遍历 Iterator it = list.iterator(); while (it.hasNext()) { Student s = (Student) it.next(); System.out.println(s.getName() + "---" + s.getAge()); } } }
二、List的特有功能
2.1 基本功能
【添加功能】
void add(int index,Object element):在指定位置添加元素
【获取功能】
Object get(int index):获取指定位置的元素
【列表迭代器】
ListIterator listIterator():List集合特有的迭代器
【删除功能】
Object remove(int index):根据索引删除元素,返回被删除的元素
【修改功能】
Object set(int index,Object element):根据索引修改元素,返回被修饰的元素
public class ListDemo { public static void main(String[] args) { // 创建集合对象 List list = new ArrayList(); // 添加元素 list.add("hello"); list.add("world"); list.add("java"); // void add(int index,Object element):在指定位置添加元素 // list.add(1, "android");//没有问题 // IndexOutOfBoundsException // list.add(11, "javaee");//有问题 // list.add(3, "javaee"); //没有问题 // list.add(4, "javaee"); //有问题 // Object get(int index):获取指定位置的元素 // System.out.println("get:" + list.get(1)); // IndexOutOfBoundsException // System.out.println("get:" + list.get(11)); // Object remove(int index):根据索引删除元素,返回被删除的元素 // System.out.println("remove:" + list.remove(1)); // IndexOutOfBoundsException // System.out.println("remove:" + list.remove(11)); // Object set(int index,Object element):根据索引修改元素,返回被修饰的元素 System.out.println("set:" + list.set(1, "javaee")); System.out.println("list:" + list); } }
2.2 遍历功能
【使用普通for循环]
List集合的特有遍历功能:size()和get()方法结合使用
public class ListDemo { public static void main(String[] args) { // 创建集合对象 List list = new ArrayList(); // 添加元素 list.add("hello"); list.add("world"); list.add("java"); for (int x = 0; x < list.size(); x++) { String s = (String) list.get(x); System.out.println(s); } } }
存储自定义对象并遍历,用普通for循环。(size()和get()结合)
public class ListDemo3 { public static void main(String[] args) { // 创建集合对象 List list = new ArrayList(); // 创建学生对象 Student s1 = new Student("林黛玉", 18); Student s2 = new Student("刘姥姥", 88); Student s3 = new Student("王熙凤", 38); // 把学生添加到集合中 list.add(s1); list.add(s2); list.add(s3); // 遍历 // 迭代器遍历 Iterator it = list.iterator(); while (it.hasNext()) { Student s = (Student) it.next(); System.out.println(s.getName() + "---" + s.getAge()); } System.out.println("--------"); // 普通for循环 for (int x = 0; x < list.size(); x++) { Student s = (Student) list.get(x); System.out.println(s.getName() + "---" + s.getAge()); } } }
【使用列表迭代器】
ListIterator listIterator():List集合特有的迭代器。该迭代器继承了Iterator迭代器,所以,就可以直接使用hasNext()和next()方法。
特有功能:
- Object previous():获取上一个元素
- boolean hasPrevious():判断是否有元素
public class ListIteratorDemo { public static void main(String[] args) { // 创建List集合对象 List list = new ArrayList(); list.add("hello"); list.add("world"); list.add("java"); // 迭代器 Iterator it = list.iterator(); while (it.hasNext()) { String s = (String) it.next(); System.out.println(s); } System.out.println("----------"); // ListIterator listIterator() ListIterator lit = list.listIterator(); while (lit.hasPrevious()) { String s = (String) lit.previous(); System.out.println(s); } } }
注意:ListIterator可以实现逆向遍历,但是必须先正向遍历,才能逆向遍历,所以一般无意义,不使用。
三、List的并发修改异常
问题:我有一个集合,如下,请问,我想判断里面有没有"world"这个元素,如果有,我就添加一个"javaee"元素,请写代码实现。
public class ListIteratorDemo { public static void main(String[] args) { // 创建List集合对象 List list = new ArrayList(); // 添加元素 list.add("hello"); list.add("world"); list.add("java"); // 迭代器遍历 Iterator it = list.iterator(); while (it.hasNext()) { String s = (String) it.next(); if ("world".equals(s)) { list.add("javaee"); } } } }
运行上述代码时,出现了这样的异常:
ConcurrentModificationException:当方法检测到对象的并发修改,但不允许这种修改时,抛出此异常。
【产生的原因】
迭代器是依赖于集合而存在的,在判断成功后,集合中新添加了元素,而迭代器却不知道,所以就报错了,这个错叫并发修改异常。
其实这个问题描述的是:迭代器遍历元素的时候,通过集合是不能修改元素的。
【解决方法】
- 方式一:迭代器迭代元素,迭代器修改元素(元素是跟在刚才迭代的元素后面的)
public class ListIteratorDemo { public static void main(String[] args) { // 创建List集合对象 List list = new ArrayList(); // 添加元素 list.add("hello"); list.add("world"); list.add("java"); // 方式1:迭代器迭代元素,迭代器修改元素 // 而Iterator迭代器却没有添加功能,所以我们使用其子接口ListIterator ListIterator lit = list.listIterator(); while (lit.hasNext()) { String s = (String) lit.next(); if ("world".equals(s)) { lit.add("javaee"); } } System.out.println("list:" + list);//[hello, world, javaee, java] } }
- 方式2:集合遍历元素,集合修改元素(元素在最后添加的)
public class ListIteratorDemo { public static void main(String[] args) { // 创建List集合对象 List list = new ArrayList(); // 添加元素 list.add("hello"); list.add("world"); list.add("java"); // 方式2:集合遍历元素,集合修改元素(普通for) for (int x = 0; x < list.size(); x++) { String s = (String) list.get(x); if ("world".equals(s)) { list.add("javaee"); } } System.out.println("list:" + list);//[hello, world, java, javaee] } }
四、List接口的实现类
4.1 ArrayList
【ArrayList类概述】
底层数据结构是数组,查询快,增删慢
线程不安全,效率高
【ArrayList案例】
存储字符串并遍历
public class ArrayListDemo { public static void main(String[] args) { // 创建集合对象 ArrayList array = new ArrayList(); // 创建元素对象,并添加元素 array.add("hello"); array.add("world"); array.add("java"); // 遍历 Iterator it = array.iterator(); while (it.hasNext()) { String s = (String) it.next(); System.out.println(s); } System.out.println("-----------"); for (int x = 0; x < array.size(); x++) { String s = (String) array.get(x); System.out.println(s); } } }
ArrayList存储自定义对象并遍历
public class ArrayListDemo2 { public static void main(String[] args) { // 创建集合对象 ArrayList array = new ArrayList(); // 创建学生对象 Student s1 = new Student("武松", 30); Student s2 = new Student("鲁智深", 40); Student s3 = new Student("林冲", 36); Student s4 = new Student("杨志", 38); // 添加元素 array.add(s1); array.add(s2); array.add(s3); array.add(s4); // 遍历 Iterator it = array.iterator(); while (it.hasNext()) { Student s = (Student) it.next(); System.out.println(s.getName() + "---" + s.getAge()); } System.out.println("----------------"); for (int x = 0; x < array.size(); x++) { // ClassCastException 注意,千万要搞清楚类型 // String s = (String) array.get(x); // System.out.println(s); Student s = (Student) array.get(x); System.out.println(s.getName() + "---" + s.getAge()); } } }
去除集合中字符串的重复值(字符串的内容相同)
/* * 分析: * A:创建集合对象 * B:添加多个字符串元素(包含内容相同的) * C:创建新集合 * D:遍历旧集合,获取得到每一个元素 * E:拿这个元素到新集合去找,看有没有 * 有:不搭理它 * 没有:就添加到新集合 * F:遍历新集合 */ public class ArrayListDemo { public static void main(String[] args) { // 创建集合对象 ArrayList array = new ArrayList(); // 添加多个字符串元素(包含内容相同的) array.add("hello"); array.add("world"); array.add("java"); array.add("world"); array.add("java"); array.add("world"); array.add("world"); array.add("world"); array.add("world"); array.add("java"); array.add("world"); // 创建新集合 ArrayList newArray = new ArrayList(); // 遍历旧集合,获取得到每一个元素 Iterator it = array.iterator(); while (it.hasNext()) { String s = (String) it.next(); // 拿这个元素到新集合去找,看有没有 if (!newArray.contains(s)) { newArray.add(s); } } // 遍历新集合 for (int x = 0; x < newArray.size(); x++) { String s = (String) newArray.get(x); System.out.println(s); } } }
去除集合中字符串的重复值(字符串的内容相同),要求:不能创建新的集合,就在以前的集合上做。
public class ArrayListDemo2 { public static void main(String[] args) { // 创建集合对象 ArrayList array = new ArrayList(); // 添加多个字符串元素(包含内容相同的) array.add("hello"); array.add("world"); array.add("java"); array.add("world"); array.add("java"); array.add("world"); array.add("world"); array.add("world"); array.add("world"); array.add("java"); array.add("world"); // 由选择排序思想引入,我们就可以通过这种思想做这个题目 // 拿0索引的依次和后面的比较,有就把后的干掉 // 同理,拿1索引... for (int x = 0; x < array.size() - 1; x++) { for (int y = x + 1; y < array.size(); y++) { if (array.get(x).equals(array.get(y))) { array.remove(y); y--; } } } // 遍历集合 Iterator it = array.iterator(); while (it.hasNext()) { String s = (String) it.next(); System.out.println(s); } } }
去除集合中自定义对象的重复值(对象的成员变量值都相同)
/* * 我们按照和字符串一样的操作,发现出问题了。 * 为什么呢? * 我们必须思考哪里会出问题? * 通过简单的分析,我们知道问题出现在了判断上。 * 而这个判断功能是集合自己提供的,所以我们如果想很清楚的知道它是如何判断的,就应该去看源码。 * contains()方法的底层依赖的是equals()方法。 * 而我们的学生类中没有equals()方法,这个时候,默认使用的是它父亲Object的equals()方法 * Object()的equals()默认比较的是地址值,所以,它们进去了。因为new的东西,地址值都不同。 * 按照我们自己的需求,比较成员变量的值,重写equals()即可。 * 自动生成即可。 */ public class ArrayListDemo3 { public static void main(String[] args) { // 创建集合对象 ArrayList array = new ArrayList(); // 创建学生对象 Student s1 = new Student("林青霞", 27); Student s2 = new Student("林志玲", 40); Student s3 = new Student("凤姐", 35); Student s4 = new Student("芙蓉姐姐", 18); Student s5 = new Student("翠花", 16); Student s6 = new Student("林青霞", 27); Student s7 = new Student("林青霞", 18); // 添加元素 array.add(s1); array.add(s2); array.add(s3); array.add(s4); array.add(s5); array.add(s6); array.add(s7); // 创建新集合 ArrayList newArray = new ArrayList(); // 遍历旧集合,获取得到每一个元素 Iterator it = array.iterator(); while (it.hasNext()) { Student s = (Student) it.next(); // 拿这个元素到新集合去找,看有没有 if (!newArray.contains(s)) { newArray.add(s); } } // 遍历新集合 for (int x = 0; x < newArray.size(); x++) { Student s = (Student) newArray.get(x); System.out.println(s.getName() + "---" + s.getAge()); } } }
4.2 Vector
【Vector类概述】
底层数据结构是数组,查询快,增删慢
线程安全,效率低
注:Vector很少用了,现在ArrayList基本上取代了Vector。
【Vector类特有功能】
添加功能:public void addElement(Object obj) -- add()
获取功能:
public Object elementAt(int index) -- get()
public Enumeration elements() -- Iterator iterator()
boolean hasMoreElements() hasNext()
Object nextElement() next()
public class VectorDemo { public static void main(String[] args) { // 创建集合对象 Vector v = new Vector(); // 添加功能 v.addElement("hello"); v.addElement("world"); v.addElement("java"); // 遍历 for (int x = 0; x < v.size(); x++) { String s = (String) v.elementAt(x); System.out.println(s); } System.out.println("------------------"); Enumeration en = v.elements(); // 返回的是实现类的对象 while (en.hasMoreElements()) { String s = (String) en.nextElement(); System.out.println(s); } } }
4.3 LinkedList
【LinkedList类概述】
底层数据结构是链表,查询慢,增删快
线程不安全,效率高
【LinkedList类特有功能】
A:添加功能
public void addFirst(Object e)
public void addLast(Object e)
B:获取功能
public Object getFirst()
public Obejct getLast()
C:删除功能
public Object removeFirst()
public Object removeLast()
public class LinkedListDemo { public static void main(String[] args) { // 创建集合对象 LinkedList link = new LinkedList(); // 添加元素 link.add("hello"); link.add("world"); link.add("java"); // public void addFirst(Object e) // link.addFirst("javaee"); // public void addLast(Object e) // link.addLast("android"); // public Object getFirst() // System.out.println("getFirst:" + link.getFirst()); // public Obejct getLast() // System.out.println("getLast:" + link.getLast()); // public Object removeFirst() System.out.println("removeFirst:" + link.removeFirst()); // public Object removeLast() System.out.println("removeLast:" + link.removeLast()); // 输出对象名 System.out.println("link:" + link); } }
【练习】
请用LinkedList模拟栈数据结构的集合,并测试
注意,题目的意思是:你自己的定义一个集合类,在这个集合类内部可以使用LinkedList模拟。
MyStack.java
/** * 自定义的栈集合 */ public class MyStack { private LinkedList linkedList; public MyStack() { linkedList = new LinkedList(); } public void add(Object obj) { linkedList.addFirst(obj); } public Object get() { return linkedList.removeFirst(); } public boolean isEmpty() { return linkedList.isEmpty(); } }
MyStackDemo.java
/** * MyStack的测试 */ public class MyStackDemo { public static void main(String[] args) { // 创建集合对象 MyStack myStack = new MyStack(); // 添加元素 myStack.add("hello"); myStack.add("world"); myStack.add("java"); while (!myStack.isEmpty()) { System.out.println(myStack.get()); } } }
五、List集合的综合练习
【题目一】
集合的嵌套遍历
需求:
我们班有学生,每一个学生都是一个对象。所以我们可以使用一个集合表示我们班级的学生。ArrayList<Student>
但是呢,我们旁边还有班级,每个班级也是一个ArrayList<Student>。
而我现在有多个ArrayList<Student>。也要用集合存储,怎么办呢?
就是这个样子的:ArrayList<ArrayList<Student>>
public class ArrayListDemo { public static void main(String[] args) { // 创建大集合 ArrayList<ArrayList<Student>> bigArrayList = new ArrayList<>(); // 创建第一个班级的学生集合 ArrayList<Student> firstArrayList = new ArrayList<>(); // 创建学生 Student s1 = new Student("唐僧", 30); Student s2 = new Student("孙悟空", 29); Student s3 = new Student("猪八戒", 28); Student s4 = new Student("沙僧", 27); Student s5 = new Student("白龙马", 26); // 学生进班 firstArrayList.add(s1); firstArrayList.add(s2); firstArrayList.add(s3); firstArrayList.add(s4); firstArrayList.add(s5); // 把第一个班级存储到学生系统中 bigArrayList.add(firstArrayList); // 创建第二个班级的学生集合 ArrayList<Student> secondArrayList = new ArrayList<Student>(); // 创建学生 Student s11 = new Student("诸葛亮", 30); Student s22 = new Student("司马懿", 28); Student s33 = new Student("周瑜", 26); // 学生进班 secondArrayList.add(s11); secondArrayList.add(s22); secondArrayList.add(s33); // 把第二个班级存储到学生系统中 bigArrayList.add(secondArrayList); // 创建第三个班级的学生集合 ArrayList<Student> thirdArrayList = new ArrayList<Student>(); // 创建学生 Student s111 = new Student("宋江", 40); Student s222 = new Student("吴用", 35); Student s333 = new Student("高俅", 30); Student s444 = new Student("李师师", 22); // 学生进班 thirdArrayList.add(s111); thirdArrayList.add(s222); thirdArrayList.add(s333); thirdArrayList.add(s444); // 把第三个班级存储到学生系统中 bigArrayList.add(thirdArrayList); // 遍历集合 for (ArrayList<Student> students : bigArrayList) { for (Student student : students) { System.out.println(student.getName() + "---" + student.getAge()); } } } }
【题目二】
获取10个1-20之间的随机数,要求不能重复
/** * 用数组实现,但是数组的长度是固定的,长度不好确定。所以我们使用集合实现。 * 分析: * A:创建产生随机数的对象 * B:创建一个存储随机数的集合。 * C:定义一个统计变量。从0开始。 * D:判断统计遍历是否小于10 * 是:先产生一个随机数,判断该随机数在集合中是否存在。 * 如果不存在:就添加,统计变量++。 * 如果存在:就不搭理它。 * 否:不搭理它 * E:遍历集合 */ public class RandomDemo { public static void main(String[] args) { // 创建产生随机数的对象 Random random = new Random(); // 创建一个存储随机数的集合 ArrayList<Integer> arrayList = new ArrayList<>(); // 定义一个统计变量。从0开始。 int count = 0; // 判断统计遍历是否小于10 while (count < 10) { //先产生一个随机数 int number = random.nextInt(20) + 1; //判断该随机数在集合中是否存在。 if (!arrayList.contains(number)) { //如果不存在:就添加,统计变量++。 arrayList.add(number); count++; } } // 遍历集合 for (Integer i : arrayList) { System.out.println(i); } } }
【题目三】
键盘录入多个数据,以0结束,要求在控制台输出这多个数据中的最大值
/** * 分析: * A:创建键盘录入数据对象 * B:键盘录入多个数据,我们不知道多少个,所以用集合存储 * C:以0结束,这个简单,只要键盘录入的数据是0,我就不继续录入数据了 * D:把集合转成数组 * E:对数组排序 * F:获取该数组中的最大索引的值 */ public class ArrayListDemo { public static void main(String[] args) { // 创建键盘录入数据对象 Scanner sc = new Scanner(System.in); // 键盘录入多个数据,我们不知道多少个,所以用集合存储 ArrayList<Integer> arrayList = new ArrayList<>(); // 以0结束,这个简单,只要键盘录入的数据是0,我就不继续录入数据了 while (true) { System.out.println("请输入数据:"); int number = sc.nextInt(); if (number != 0) { arrayList.add(number); } else { break; } } // 把集合转成数组 // public <T> T[] toArray(T[] a) Integer[] i = new Integer[arrayList.size()]; arrayList.toArray(i); // 对数组进行排序 Arrays.sort(i); // 获取该数组中的最大索引的值 System.out.println("数组是:" + Arrays.toString(i) + "最大值是:" + i[i.length - 1]); } }