6、Java集合


集合也叫做容器,顾名思义就是用来装一些东西的容器。

Collection 集合

image.png
Interface Collection<E> E-此集合中的元素类型
在 Java 中所有的容器都属于 Collection 接口的子类,就像异常都属于 Throwable 的子类一样。

Java 标准库自带的java.util包提供了集合接口:Collection,它是除Map外所有其他集合类的根接口。Java 的java.util包主要提供了以下三种类型的集合:

  • List:一种有序列表的集合,例如,按索引排列的StudentList
  • Set:一种保证没有重复元素的集合,例如,所有无重复名称的StudentSet
  • Map:一种通过键值(key-value)查找的映射表集合,例如,根据Studentname查找对应StudentMap

Java 集合的设计有几个特点:一是实现了接口和实现类相分离,例如,有序表的接口是List,具体的实现类有ArrayListLinkedList等。二是支持泛型,我们可以限制在一个集合中只能放入同一种数据类型的元素,例如:

List<String> list = new ArrayList<>(); // 只能放入String类型

最后,Java 遍历集合是通过统一的方式——迭代器(Iterator)来实现,它最明显的好处在于无需知道集合内部元素是按什么方式存储的。
由于 Java 的集合设计非常久远,中间经历过大规模改进;

Collection 集合常用方法

image.png
Collection 不能直接实例化,要通过其子类实例,
image.png
案例:创建学生类并添加到 Collection 集合中
image.png

set 集合

Interface Set<E>,Set 是一个接口不能直接使用,我们一般使用HashSetTreeSet
Set 集合特点:

  • 不包含重复元素;
  • 没有索引,不能通过 for 循环遍历,可以用 foreach 或 Iterator 遍历;

**
哈希值:

是 JDK 根据对象的地址或字符串或数字根据哈希算法算出来的 int 类型数值

image.png
image.png
image.png
哈希表
https://www.bilibili.com/video/BV1si4y1b7Rs?from=search&seid=2727536263524650394

HashSet 集合

特点:

  • 底层数据结构是哈希表;
  • 不保证集合的迭代顺序;
  • 此类实现Set接口,不包含重复元素;
  • 没有索引,不能通过 for 循环遍历;

瞎摆->存储的速度快;
继承关系:

java.lang.Object
	java.util.AbstractCollection<E>
		java.util.AbstractSet<E>
			java.util.HashSet<E>

image.png

LinkedHashSet

特点:

  • Hash 表和Set接口的链表实现,具有可预测的迭代顺序;
  • 此实现与HashSet不同之处在于它维护了一个贯穿其所有条目的双向链表, 此链接列表定义迭代排序,即元素插入集合( 插入顺序 ) 的顺序,保证元素有序的;

继承关系:

Class  LinkedHashSet<E>
	java.lang.Object
		java.util.AbstractCollection<E>
			java.util.AbstractSet<E>
				java.util.HashSet<E>
					java.util.LinkedHashSet<E>

image.png

TreeSet

按顺序摆->存储的时候帮你排序, 存储的速度就慢;
继承关系:

java.lang.Object
	java.util.AbstractCollection<E>
		java.util.AbstractSet<E>
			java.util.TreeSet<E>

public class TreeSet<E>
extends AbstractSet<E>
implements NavigableSet<E>, Cloneable, Serializable
//一个NavigableSet实现基于一个TreeMap
//元件使用其有序natural ordering ,或由Comparator集合创建时提供,这取决于所使用的构造方法。

image.png
image.png
案例:对存储在 TreeSet 集合中的学生对象按照年龄排序,若年龄相同,则按名字字母排序

  • Student 类要实现 Comparable 接口,重写 compareTo 方法

    此接口对实现它的每个类的对象强加一个总排序。 这种排序被称为类的自然顺序 ,类的compareTo方法被称为其自然比较方法

SetDemo.java

TreeSet<Student> ts = new TreeSet<>();
Student s1 = new Student("diaocan", 26);
Student s2 = new Student("xisi", 33);
Student s3 = new Student("wangzhaojun", 28);
Student s4 = new Student("yangguifei", 35);

Student s5 = new Student("zhangmanyu", 35);
Student s6 = new Student("lingqqingxia", 35);
Student s7 = new Student("lingqqingxia", 35);
ts.add(s1);
ts.add(s2);
ts.add(s3);
ts.add(s4);
ts.add(s5);
ts.add(s6);
ts.add(s7);
for (Student s:ts
             ) {
            System.out.println("name="+s.getName()+",age="+s.getAge());
        }

Student.java

public class Student implements Comparable<Student> {
    private String name;
    private int age;
    public Student() { }

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

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    @Override
    public int compareTo(Student s) {
//        return 0;       //相同,不添加
//        return 1;       //后一个比前一个大,正序
//        return -1;       //后一个比前一个小,倒序

        int num = this.age - s.age;     //this 后一项,s 当前项
        //如果age相同则比较name 否则直接返回num
        return num==0?this.name.compareTo(s.name):num;      //String重写了compareTo方法
    }
}

打印:

name=diaocan,age=26
name=wangzhaojun,age=28
name=xisi,age=33
name=lingqqingxia,age=35
name=yangguifei,age=35
name=zhangmanyu,age=35

image.png

总结:

  • 自然排序:要在要比较的对象类中实现_Comparable_并重写compareTo方法;
  • 比较器排序:在TreeSet构造方法中定义Comparator实例对象;

案例:

TreeSet<StudentScore> ts = new TreeSet<>(new Comparator<StudentScore>() {
    @Override
    public int compare(StudentScore o1, StudentScore o2) {
        Double ret = o1.scoreSum() - o2.scoreSum();
        return ret>0?-1:1;
    }
});

StudentScore s1 = new StudentScore("zhangsan", 76.5,92.6);
StudentScore s2 = new StudentScore("lisi", 79.5,88.9);
StudentScore s3 = new StudentScore("wangwu", 91.6,78.6);
ts.add(s1);
ts.add(s2);
ts.add(s3);
for (StudentScore ss:ts
             ) {
            System.out.println(ss.getName()+":"+ss.scoreSum());
        }

//打印结果
/*
wangwu:170.2
zhangsan:169.1
lisi:168.4
*/

List 列表

两种数据结构:
数组有索引,查询非常快,但是增加删除节点 该位置后面的节点都要移动,效率非常低;
链表是一种增删快的模型(对比数组)
链表是一种查询慢的模型(对比数组),查询每一个元素都要从头开始查

Interface List<E> java.util包
public interface ``List<E> ``extends Collection<E>
List 接口特点:

  • 有序集合,该接口的用户可以精确控制列表中每个元素的插入位置,用户可以通过整数索引(列表中的位置)访问元素,并搜索列表中的元素;
  • 与 Set 集合不同,列表通常允许重复元素;

List 实例方法

  • add(obj) 添加元素
  • get(i) 查看第 i 个元素
  • size() 查看列表中的数据个数
  • isEmpty() 判断是否是空列表
  • indexOf(xxx) 查看 xxx 元素在列表中的位置
  • lastIndexOf(xxx) 查看 xxx 元素在列表中最后一次出现的位置
  • contains(xxx) 判断列表中是否包含了 xxx
  • subList(start. end) 从列表中 start 开始截取, 截取到 end 但不包括 end
  • toArray() 转化成数组
  • remove(obj) 删除某个元素
  • remove(i) 删除某个位置的元素
  • clear() 清空列表
List lst = new ArrayList();
lst.add("美团外卖");
lst.add("饿了么");
lst.add("百度外卖");
lst.add("我不吃");
lst.add("外卖");
System.out.println(lst); // [美团外卖, 饿了么, 百度外卖, 我不吃, 外卖]
System.out.println(lst.get(0)); // 查询第0个元素
System.out.println(lst.get(2)); // 查询第2个元素
System.out.println(lst.size());  // 列表中元素的个数
// 遍历出列表中所有的数据
for(int i = 0 ; i < lst.size(); i++){
    System.out.println(lst.get(i));
}
// 判断列表是否是空列表
System.out.println(lst.isEmpty()); // false
//查看"我不吃"元素在列表中的位置
System.out.println(lst.indexOf("我不吃")); // 3
//查看最后一个"百度外卖"在列表中的位置
System.out.println(lst.lastIndexOf("百度外卖")); // 2
//判断是否包含饿了么
System.out.println(lst.contains("饿了么")); // true
//列表的截取, 从1截取到3  [1,3)区间
System.out.println(lst.subList(1, 3)); //   [饿了么, 百度外卖]
// 转化成数组
System.out.println(lst.toArray());
lst.remove("外卖");  // 删除"外卖"
lst.remove(2); // 删除索引为2的元素

ArrayList

List接口的可调整大小的阵列实现,底层数据结构是数组,查询快,增删慢

根据异常信息分析源码

描述:

一个线程正在迭代 List 而另一个线程去修改 List(Collection 多态),会抛出 ConcurrentModificationException 异常

image.png

//异常
Exception in thread "main" java.util.ConcurrentModificationException
	at java.base/java.util.ArrayList$Itr.checkForComodification(ArrayList.java:1013)
	at java.base/java.util.ArrayList$Itr.next(ArrayList.java:967)
	at com.xjt.myList.ListDemo.main(ListDemo.java:18)

分析源码:
异常信息要从下往上读
1、at com.xjt.myList.ListDemo.main(ListDemo.java:18),在myList.ListDemo.main函数第 18 行
2、at java.base/java.util.ArrayList$Itr.next(ArrayList.java:967),ArrayList.java中第 967 行
image.png

执行itr.next()方法时,会先去执行 checkForComodification``() 方法

3、at java.base/java.util.ArrayList$Itr.checkForComodification(ArrayList.java:1013) 在ArrayList.java文件的 1013 行
image.png
判断 modCount != expectedModCount ,如果不相等 抛出 ConcurrentModificationException异常
image.png
当执行arrayList.add("python") modCount+1,下次循环进来的时候 expectedModCount 和 modCount 就不相等了。

那么 modCount 和 expectedModCount 又是什么东东呢?
我们可以发现在   类 Itr 里面定义了:
image.png
ArrayList.java 中 Ctrl+F 搜索发现 modCount 没有定义,是直接调用的,所有我们去它父类中找
image.png
image.png
问题找到了,那么我们怎么在遍历 List 时改变它的内容呢?

//for循环遍历
for (int i = 0; i < arrayList.size(); i++) {
    if(arrayList.get(i).equals("java")){
        arrayList.add("python");
    }
}

注意:增强 for(foreach)内部其实就是 Iterator
image.png
通过 ArrayList 实例的ListIterator实现遍历
ListIterator:列表的迭代器,允许程序员在任一方向上遍历列表,在迭代期间修改列表,并获取迭代器在列表中的当前位置。

ListIterator<String> lit = arrayList.listIterator();
while (lit.hasNext()) {
    String s = lit.next();
    if (s.equals("java")) {
        arrayList.add("python");
    }
}

这么多种遍历 List 方式我们怎么选择呢?

  • 只是取 List 每一项值,不需要修改时,使用 foreach 最方便
  • 需要获取索引值或需要修改 List 项的值时,选择 for 循环
  • ListIterator 可以往前遍历往后遍历或从某一项索引开始遍历,且能修改 List,使用最灵活
  • Iterator 遍历 List 可以删除/获取 List 每一项

LinkedList

双链表实现了ListDeque接口,底层数据结构是链表,查询慢,增删快

public class LinkedList<E>  extends AbstractSequentialList<E>
implements List<E>, Deque<E>, Cloneable, Serializable

image.png

ArrayList 和 LinkedList 的区别对比:

功能 ArrayList LinkedList
获取指定元素 速度很快 需要从头开始查找元素,慢
添加元素到末尾 速度很快 速度很快
在指定位置添加/删除 该位置后面的所有元素都要移动 不需要移动元素
内存占用 较大

ArrayList 的优势是查询;
LinkedList 的优势是删除和添加;

List.of()创建列表

除了使用ArrayListLinkedList,我们还可以通过List接口提供的of()方法,根据给定元素快速创建List

List<Integer> list = List.of(1, 2, 5);

但是List.of()方法不接受null值,如果传入null,会抛出NullPointerException异常。

Map 集合

image.png
image.png
image.png

Map 实例的基本方法

  • put(key,value) 向映射中添加一个键值对,相同的 key 会把前面保存的数据顶掉;
  • get(key) 使用 key 查询 value;
  • isEmpty() 判断 Map 是否是空的;
  • size() 返回 key, value 键值对的个数;
  • containsKey(key) 判断是否存在 key 返回布尔值;
  • containsValue(Object value) 判断映射中是否包含值==value 返回布尔值
  • remove(key) 根据 key 删除信息;
  • keySet() 获取到 map 中 key 的集合 set;
  • map.values() 获取到 map 中所有 value 值;
  • map1.equals(map2) 判断两个 Set 集合的元素是否相同;

用法示例:

//map实例化
Map<String,String> map = new HashMap<>();
////基本方法
//添加元素
map.put("name","xiong");
map.put("gender","男");
Map<String,String> map2 = new HashMap<>();
map2.put("2", "B");
map2.put("3", "C");
map.putAll(map2);       //把一个map集合合并到另一个map集合里
System.out.println(map);
//修改元素
map.remove("2","2B");
//删除元素
map.remove("3");
//查询元素
System.out.println("name="+map.get("name"));
//获取集合大小
System.out.println(map.size());
//获取集合所有值/所有键
System.out.println(map.keySet());   //所有键
System.out.println(map.values());   //所有值
//清空集合
//map.clear();
//获得map中一组组键值对的集合
System.out.println(map.entrySet());     //[2=B, gender=男, name=xiong]
//原始方式遍历
for (Map.Entry<String,String> entry:map.entrySet()){
    System.out.println("key:"+entry.getKey()+";value:"+entry.getValue());
}
//map的forEach方式(java8新特性)
map.forEach((k,v) ->
            System.out.println("key:"+k+";value:"+v)
           );
//迭代器方式遍历
System.out.println(map);    //{2=B, gender=男, name=xiong}
System.out.println(map.entrySet());     //[2=B, gender=男, name=xiong]
Iterator it = map.entrySet().iterator(); // 拿到map内部的entry,逗号分隔的每一项都是一个entry
while(it.hasNext()){
    Map.Entry entry = (Map.Entry) it.next();
    System.out.println(entry.getKey()+"=>"+entry.getValue());
}

image.png
image.png
示例:
image.png
示例 2:
image.png
示例 3:
image.png
示例 4:
image.png

ArrayList<HashMap<String, String>> arrayList = new ArrayList<>();
HashMap<String, String> hm1 = new HashMap<>();
hm1.put("令狐冲","任盈盈");
hm1.put("岳不群","宁中则");
hm1.put("林平之","岳灵珊");
arrayList.add(hm1);

HashMap<String, String> hm2 = new HashMap<>();
hm2.put("杨过","小龙女");
hm2.put("郭靖","黄蓉");
arrayList.add(hm2);

HashMap<String, String> hm3 = new HashMap<>();
hm3.put("张无忌","赵敏");
hm3.put("宋青书","周芷若");
hm3.put("张无忌","小昭");
arrayList.add(hm3);

for (HashMap<String, String> hm:arrayList) {
    //Set<String> keys = hm.keySet();
    //for (String key:keys) {
    	//System.out.println(key+"="+hm.get(key));
    //}

    //HashSet集合的第二种遍历方法
    Set<Map.Entry<String, String>> entries = hm.entrySet();
    for (Map.Entry<String, String> entry:entries
        ) {
        String key = entry.getKey();
        String val = entry.getValue();
        System.out.println(key+"="+val);
    }
}

示例 5:
image.png

HashMap<String, ArrayList<String>> stringArrayListHashMap = new HashMap<>();
ArrayList<String> arr1 = new ArrayList<>();
arr1.add("张无忌");
arr1.add("赵敏");
arr1.add("小昭");
arr1.add("周芷若");
stringArrayListHashMap.put("倚天屠龙记",arr1);

ArrayList<String> arr2 = new ArrayList<>();
arr2.add("杨过");
arr2.add("小龙女");
arr2.add("李莫愁");
arr2.add("金轮法王");
stringArrayListHashMap.put("神雕侠侣",arr2);

ArrayList<String> arr3 = new ArrayList<>();
arr3.add("郭靖");
arr3.add("黄蓉");
arr3.add("杨康");
arr3.add("穆念慈");
arr3.add("欧阳克");
stringArrayListHashMap.put("射雕英雄传",arr3);

Set<Map.Entry<String, ArrayList<String>>> entries = stringArrayListHashMap.entrySet();
for (Map.Entry<String, ArrayList<String>> entry:entries) {
    String key = entry.getKey();
    ArrayList<String> values = entry.getValue();
    System.out.println(key+":");
    for (String value:values
        ) {
        System.out.println("\t"+value);
    }
}

示例 6:
image.png

HashMap<Character, Integer> hm = new HashMap<>();

System.out.println("请输入一串字符:");
Scanner sc = new Scanner(System.in);
String line = sc.nextLine();
for (int i = 0; i < line.length(); i++) {
    char key = line.charAt(i);
    Integer value = hm.get(key);
    if(value == null){
        hm.put(key,1);
    }else{
        value++;
        hm.put(key,value);
    }
}
StringBuilder sb = new StringBuilder();
Set<Map.Entry<Character, Integer>> entries = hm.entrySet();
for (Map.Entry<Character, Integer> entry:entries) {
    Character key = entry.getKey();
    Integer value = entry.getValue();
    sb.append(key).append("(").append(value).append(")");
}
System.out.println(sb.toString());

Collections 工具类

image.png
Collections.sort()
Collections.reverse()
Collections.shuffle()
这些都是静态方法,通过类 Collections 直接调用
示例:
image.png
示例:用 Collection.sort()方法对学生年龄排序,若年龄相同按学生姓名排序
image.png
案例:模拟斗地主
image.png

public static void main(String[] args) {
    //1.创建一副牌
    //♦ 3 4 5 6 7 8 9 10 J Q K A 2
    //♣ 3 4 5 6 7 8 9 10 J Q K A 2
    //♥ 3 4 5 6 7 8 9 10 J Q K A 2
    //♠ 3 4 5 6 7 8 9 10 J Q K A 2
    List<String> pokerList = new ArrayList<>();
    //1.1创建花色和数字列表
    String[] colors = {"♦","♣","♥","♠"};
    String[] nums = {"3","4", "5", "6", "7", "8", "9", "10", "J", "Q", "K", "A", "2"};
    for (String color:colors
        ) {
        for (String num:nums
            ) {
            pokerList.add(color+num);
        }
    }
    pokerList.add("大王");
    pokerList.add("小王");
    //1.2洗牌
    Collections.shuffle(pokerList);
    System.out.println(pokerList);

    //2.创建3个玩家列表和一个底牌列表
    ArrayList<String> playerAList = new ArrayList<>();
    ArrayList<String> playerBList = new ArrayList<>();
    ArrayList<String> playerCList = new ArrayList<>();
    ArrayList<String> dipaiList = new ArrayList<>();
    //3.发牌
    for (int i = 0; i < pokerList.size(); i++) {
        if(i>=pokerList.size()-3){
            dipaiList.add(pokerList.get(i));
        }else if(i%3 == 0){
            playerAList.add(pokerList.get(i));
        }else if(i%3 == 1){
            playerBList.add(pokerList.get(i));
        }else if(i%3 == 2){
            playerCList.add(pokerList.get(i));
        }
    }
    //4.三位玩家看牌,并亮出底牌
    showPocker("张三丰",playerAList);
    showPocker("张无忌",playerBList);
    showPocker("张翠山",playerCList);
    showPocker("底牌",dipaiList);

}
public static void showPocker(String name, ArrayList<String> array){
    System.out.println("玩家-"+name+"手里的牌是:");
    for (String pocker:array
        ) {
        System.out.print(pocker+" ");       //System.out.println()这个是在输出内容之后换行,而 System.out.print()输出内容之后不换行
    }
    System.out.println();       //空行
}

打印:
image.png
image.png
image.png

public static void main(String[] args) {
    //1.创建一副牌
    //♦ 3 4 5 6 7 8 9 10 J Q K A 2
    //♣ 3 4 5 6 7 8 9 10 J Q K A 2
    //♥ 3 4 5 6 7 8 9 10 J Q K A 2
    //♠ 3 4 5 6 7 8 9 10 J Q K A 2
    //1.1HashMap键值0 1 2 3 ...52 值对应的牌♦3 ♦4...
    HashMap<Integer, String> pockerHM = new HashMap<>();
    ArrayList<Integer> array = new ArrayList<>();    //存储牌的键值
    Integer index = 0;

    //1.1创建花色和数字列表
    String[] colors = {"♦","♣","♥","♠"};
    String[] nums = {"3","4", "5", "6", "7", "8", "9", "10", "J", "Q", "K", "A", "2"};
    for (String color:colors
        ) {
        for (String num:nums
            ) {
            array.add(index);
            pockerHM.put(index,color+num);
            index++;
        }
    }
    array.add(index);
    pockerHM.put(index,"大王");
    index++;
    array.add(index);
    pockerHM.put(index,"小王");
    //1.2洗牌
    Collections.shuffle(array);
    //        System.out.println(array);
    //        System.out.println(pockerHM);

    //2.创建3个玩家列表和一个底牌列表(存储的HashMap键值)
    TreeSet<Integer> playerATreeSet = new TreeSet<>();
    TreeSet<Integer> playerBTreeSet = new TreeSet<>();
    TreeSet<Integer> playerCTreeSet = new TreeSet<>();
    TreeSet<Integer> dipaiTreeSet = new TreeSet<>();

    //3.发牌
    for (int i = 0; i < pockerHM.size(); i++) {
        Integer idx = array.get(i);
        if(i>=pockerHM.size()-3){
            dipaiTreeSet.add(idx);
        }else if(i%3 == 0){
            playerATreeSet.add(idx);
        }else if(i%3 == 1){
            playerBTreeSet.add(idx);
        }else if(i%3 == 2){
            playerCTreeSet.add(idx);
        }
    }
    //4.三位玩家看牌,并亮出底牌
    showPocker("张三丰",playerATreeSet,pockerHM);
    showPocker("张无忌",playerBTreeSet,pockerHM);
    showPocker("张翠山",playerCTreeSet,pockerHM);
    showPocker("底牌",dipaiTreeSet,pockerHM);

}
public static void showPocker(String name, TreeSet<Integer> array,HashMap<Integer,String> playerHashMap){
    System.out.println(name+"手里的牌是:");
    for (Integer i:array
        ) {
        System.out.print(playerHashMap.get(i)+" ");       //System.out.println()这个是在输出内容之后换行,而 System.out.print()输出内容之后不换行
    }
    System.out.println();       //空行
}

打印:
image.png
作业:

  1. 有字符串”k:1,k1:2,k2:3,k3:4” 处理成 Map: {‘k’=1,’k1’=2….}
package com.xjt.homework;
import java.util.HashMap;
import java.util.Map;
public class Work1 {
    public static void main(String[] args) {
        /*1. 有字符串”k:1,k1:2,k2:3,k3:4” 处理成Map: {‘k’=1,’k1’=2….}
         */
        String str = "k:1,k1:2,k2:3,k3:4";
        Map<String,Integer> mymap = new HashMap<String,Integer>();
        String[] lst = str.split(",");
        for (String item:lst
             ) {
            String StrKey = item.split(":")[0];
            String StrValue = item.split(":")[1];
            mymap.put(StrKey,Integer.parseInt(StrValue));
        }
        System.out.println(mymap);
    }
}
  1. 元素分类, 有如下值 int[] li= {11,22,33,44,55,66,77,88,99,90},将所有大于 等于 66 的值保存至 Map 的第一个 key 中,将小于 66 的值保存至第二个 key 的值中。
    即: {‘k1’: 大于 66 的所有值列表, ‘k2’: 小于 66 的所有值列表}
package com.xjt.homework;
import java.util.*;
public class Work2 {
    public static void main(String[] args) {
        int[] li= {11,22,33,44,55,66,77,88,99,90};
        //定义一个有泛型映射
        Map<String, List<Integer>> map = new HashMap<String,List<Integer>>();
        //定义两个有泛型的列表
        List<Integer> lst1 = new ArrayList<>();
        List<Integer> lst2 = new ArrayList<>();
        for (Integer item:li
             ) {
            if(item>=66){
                lst1.add(item);
            }else{
                lst2.add(item);
            }
        }
        map.put("k1",lst1);
        map.put("k2",lst2);
        System.out.println(map);    //{k1=[66, 77, 88, 99, 90], k2=[11, 22, 33, 44, 55]}
    }
}

作业参考答案:

  1. 给定一组连续的整数,例如:10,11,12,……,20,但其中缺失一个数字,试找出缺失的数字:
package com.xjt.homework;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class Test1 {
    static int findMissingNumber(int start, int end, List<Integer> list) {
        int ret=0;
        for (int i = start; i <= end; i++) {
            if (!list.contains(i)) {
                ret = i;
            }
        }
        return ret;
    }
    public static void main(String[] args) {
        // 构造从start到end的序列:
        final int start = 10;
        final int end = 20;
        List<Integer> list = new ArrayList<>();
        for (int i = start; i <= end; i++) {
            list.add(i);
        }
        // 洗牌算法shuffle可以随机交换List中的元素位置:
        Collections.shuffle(list);
        // 随机删除List中的一个元素:
        int removed = list.remove((int) (Math.random() * list.size()));
        int found = findMissingNumber(start, end, list);
        System.out.println(list.toString());
        System.out.println("missing number: " + found);
        System.out.println(removed == found ? "测试成功" : "测试失败");
    }
}

Iterator 迭代器

我们可以使用 get() size() 搭配 for 循环来完成遍历,但是想一个问题. set 好像没有 get()方法. 那 set 集合怎么遍历啊….这就要用到咱们本节课讲的这个叫 Iterator 的东西. 这个东西叫迭代器。

啥是迭代器. 想一个事儿. 我们村以前有那种乡村诊所. 病人来了之后呢就在门口一站. 然后大夫在诊治的时候会喊一嗓子~ 下一个. 下一个人就进来了. 大夫不关心外面多少人, 也不关心外面的病人是谁. 他只关心”下一个”. OK. 咱们今天要说的这个叫迭代器的东西, 和乡村诊所里的大夫是一样一样的~~.

先看用法:

Set set = new HashSet();
set.add("王大锤");
set.add("二麻子");
set.add("三愣子");
set.add("哈利路呀大大大锤");
// 创建迭代器对象, 相当于创建一个医生
Iterator it = set.iterator();
// 吼一嗓子, 进来一个人
String s1 = (String) it.next();
System.out.println(s1);
// 吼一嗓子, 进来一个人
String s2 = (String) it.next();
System.out.println(s2);
// 吼一嗓子, 进来一个人
String s3 = (String) it.next();
System.out.println(s3);
// 吼一嗓子, 进来一个人
String s4 = (String) it.next();
System.out.println(s4);
// 没人了. 还吼, 吼破了喉咙也不会有人理你的.  报错
String s5 = (String) it.next();     //Exception in thread "main" java.util.NoSuchElementException
System.out.println(s5);

next() 会自动帮我们获取到元素,并且指针指向下一个元素。
但是呢,这时候我们发现迭代器, 太容易出错了,我也不知道啥时候还有没有元素了。怎么解决呢? 在 next()之前, 我们可以做一个判断, 判断是否还有下一个元素。
set 集合使用迭代器遍历

Set set = new HashSet();
set.add("王大锤");
set.add("王小锤");
set.add("王不大不小锤");
set.add("哈利路呀大大大锤");
// 拿到迭代器, 相当于创建一个医生
Iterator it = set.iterator();		//集合迭代器
while(it.hasNext()){ // 判断是否还有下一个元素
    String s = (String) it.next();
    System.out.println(s);
}

List 使用迭代器遍历

package com.xyq.collection;
import java.util.*;
public class TestIter {
    public static void main(String[] args) {
        List list = new ArrayList();
        list.add("武大郎");
        list.add("武二郎");
        list.add("武三郎");
        Iterator it = list.iterator();	//列表迭代器
        while(it.hasNext()){
            System.out.println(it.next());
        }
    }
}

Map 使用迭代器进行遍历

Map map = new HashMap();
map.put("浪里白条", "张顺");
map.put("及时雨", "松江");
map.put("行者", "悟空");
Iterator it = map.keySet().iterator();	//map映射迭代器,要对map映射的所有键进行迭代
while(it.hasNext()){
    Object key = it.next();
    Object value = map.get(key); // 通过key获取到value
    System.out.println(key+"=>"+value);
}

Map 集合还可以这样遍历.

Map map = new HashMap();
map.put("浪里白条", "张顺");
map.put("及时雨", "松江");
map.put("行者", "悟空");
Iterator it = map.entrySet().iterator(); // 拿到map内部的entry,逗号分隔的每一项都是一个entry
while(it.hasNext()){
    Map.Entry entry = (Map.Entry) it.next();
    System.out.println(entry.getKey()+"=>"+entry.getValue());
}

迭代器存在的意义:
一是可以让不同的数据类型拥有相同的遍历方式,List, Set, Map,完全不同的三种数据类型,但是都可以使用 Iterator 进行遍历;
二是:使用迭代器遍历比 for 循环遍历效率要高;

1.for each == iterator

有童鞋可能觉得使用Iterator访问List的代码比使用索引更复杂。但是,要记住,通过Iterator遍历List永远是最高效的方式。并且,由于Iterator遍历是如此常用,所以,Java 的for each循环本身就可以帮我们使用Iterator遍历。把上面的代码再改写如下:

List<String> list = List.of("apple", "pear", "banana");
for (String s : list) {
    System.out.println(s);
}

上述代码就是我们编写遍历List的常见代码。
实际上,只要实现了Iterable接口的集合类都可以直接用for each循环来遍历,Java 编译器本身并不知道如何遍历集合对象,但它会自动把for each循环变成Iterator的调用,原因就在于Iterable接口定义了一个Iterator iterator()方法,强迫集合类必须返回一个Iterator实例。

2.List 和 Array 转换

List变为Array有三种方法,第一种是调用toArray()方法直接返回一个Object[]数组:

List<String> list = List.of("apple", "pear", "banana");
Object[] array = list.toArray();
for (Object s : array) {
    System.out.println(s);
}

这种方法会丢失类型信息,所以实际应用很少。
第二种方式是给toArray(T[])传入一个类型相同的ArrayList内部自动把元素复制到传入的Array中:

List<Integer> list = List.of(12, 34, 56);
Integer[] array = list.toArray(new Integer[3]);
for (Integer n : array) {
    System.out.println(n);
}

注意到这个toArray(T[])方法的泛型参数并不是List接口定义的泛型参数,所以,我们实际上可以传入其他类型的数组,例如我们传入Number类型的数组,返回的仍然是Number类型:

List<Integer> list = List.of(12, 34, 56);
Number[] array = list.toArray(new Number[3]);
for (Number n : array) {
    System.out.println(n);
}

但是,如果我们传入类型不匹配的数组,例如,String[]类型的数组,由于List的元素是Integer,所以无法放入String数组,这个方法会抛出ArrayStoreException
如果我们传入的数组大小和List实际的元素个数不一致怎么办?根据List 接口的文档,我们可以知道:
如果传入的数组不够大,那么List内部会创建一个新的刚好够大的数组,填充后返回;如果传入的数组比List元素还要多,那么填充完元素后,剩下的数组元素一律填充null
实际上,最常用的是传入一个“恰好”大小的数组:

Integer[] array = list.toArray(new Integer[list.size()]);

最后一种更简洁的写法是通过List接口定义的T[] toArray(IntFunction generator)方法:

Integer[] array = list.toArray(Integer[]::new);

这种函数式写法我们会在后续讲到。
反过来,把Array变为List就简单多了,通过List.of(T...)方法最简单:

Integer[] array = { 1, 2, 3 };
List<Integer> list = List.of(array);

对于 JDK 11 之前的版本,可以使用Arrays.asList(T...)方法把数组转换成List
要注意的是,返回的List不一定就是ArrayList或者LinkedList,因为List只是一个接口,如果我们调用List.of(),它返回的是一个只读List

List<Integer> list = List.of(12, 34, 56);		//返回一个只读的List
list.add(999); // UnsupportedOperationException

对只读List调用add()remove()方法会抛出UnsupportedOperationException

泛型

泛型概述

前面我们说了三种容器. 这三种容器有一个共同的特点, 就是所有存放在容器中的内容最终都被当成了 Object 来保存。当前, 我们能理解为了通用性把所有的元素向上转型成 Object。
但是这样存储是有问题的。比如,酱油瓶里就应该装酱油,醋瓶就应该装醋。混着装不是不能装,是不能用 对吧。所以呢,我们要对容器进行规范,不能乱七八糟的乱装,这就需要用到泛型。通过泛型可以强制规定容器能装什么东西。就好比,给你家里的瓶瓶罐罐贴上标签来说明这个瓶子是用来装什么的。

规范集合存储数据类型,取数据时不需要强转了,在以后的代码中, 如果没有特殊要求,推荐使用泛型

image.png
image.png

List<String> list = new ArrayList<String>(); // 强制规定这个列表只能装字符串,第二个尖括号里面的String可以省略
// list.add(1);// 报错. 只能装字符串
list.add("NB"); // OK
// 好处是, 从list中获取到的数据不需要强转了.
String s = list.get(0) ;// 这个列表只能装字符串, 拿出来也一定是字符串
System.out.println(s);

创建一个只能装歌手的列表(自定义泛型类)

package com.xjt.fanxing;
import java.util.ArrayList;
import java.util.List;
public class Test1 {
    public static void main(String[] args) {
        List<Singer> singerList = new ArrayList<Singer>();  // 只能装歌手类型
        Singer singer1 = new Singer("周杰伦",45);
        singerList.add(singer1); // 添加歌手
        Singer singer2 = new Singer("王菲",50);
        singerList.add(singer2); // 添加歌手
        Singer ss = singerList.get(0); // 查询歌手
        System.out.println(ss);
    }
}
class Singer{
    String name;
    int age;
    @Override
    public String toString() {
        return "Singer{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
    public Singer(String name, Integer age) {
        this.name = name;
        this.age = age;
    }
}

Set 集合和 List 的泛型是一样的,强制规定集合中元素的类型

Set<String> set = new HashSet<>();
set.add("apple");
set.add("banana");
set.add("pear");
set.add("orange");
for (String s : set) {
    System.out.println(s);
}
List<String> list = new ArrayList<>();
list.add("apple"); // size=1
list.add("pear"); // size=2
list.add("apple"); // 允许重复添加元素,size=3
System.out.println(list.size());

Map 集合的泛型比较特殊. 因为这个鬼东西里面有 key 和 value.两项内容.

Map<String, String> map = new HashMap<String, String>();
map.put("萝莉", "小蔡");
map.put("御姐", "苏打强");
//        map.put(1, 2) ; // 报错, 只能是字符串
String v = map.get("萝莉");
System.out.println(v);

定义泛型类

image.png

泛型方法

  • 在调用该方法时决定传入什么类型参数

image.png

public class GenericDemo1 {
    public static void main(String[] args) {
        //调用普通方法
        Generic g1 = new Generic();
        g1.show("hello");
        g1.show(111);
        g1.show(true);

        //泛型类的方法调用
        Generic2<String> g21 = new Generic2<String>();
        g21.show("hello");

        Generic2<Integer> g22 = new Generic2<Integer>();
        g22.show(123);

        Generic2<Boolean> g23 = new Generic2<Boolean>();
        g23.show(true);

        //泛型方法调用(终极版)
        Generic3 g3 = new Generic3();
        g3.show("hello");
        g3.show(111);
        g3.show(true);
        g3.show(12.34);
    }
}

class Generic{
    //普通方法
    public void show(String s){
        System.out.println(s);
    }
    public void show(Integer s){
        System.out.println(s);
    }
    public void show(Boolean s){
        System.out.println(s);
    }
}

//泛型类改进
class Generic2<T>{
    public void show(T t){
        System.out.println(t);
    }
}

//泛型方法改进
class Generic3{
    public <T> void show(T t){
        System.out.println(t);
    }
}

泛型接口

image.png
image.png
具体可参考 Collection 接口及其实现类 ArrayList

Interface Collection<E>		//接口定义的是泛型

public interface List<E>		//List接口继承的仍然是泛型
extends Collection<E>

public class ArrayList<E>		//最终实现类
extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, Serializable

//方法仍然是泛型
void add(int index, E element)   //将指定元素插入此列表中的指定位置。
boolean add(E e) 	//将指定的元素追加到此列表的末尾。

类型通配符

image.png
image.png

可变参数

image.png

image.png

第一个确定的参数必须放置前面,后面不确定个数的参数封装到 a 集合里了

可变参数的使用

image.png
image.png


文章作者: CoderXiong
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 CoderXiong !
  目录