JavaStudy4(集合+泛型)—B站韩顺平

JavaStudy4(集合+泛型)—B站hsp

1.集合

1.1 集合的理解和好处

​ 前面我们保存多个数据使用的是数组,那么数组有不足的地方,我们分析一下

1.1.1 数组

在这里插入图片描述

1.1.2 集合

在这里插入图片描述

1.2 集合的框架体系

Java 的集合类很多,主要分为两大类,如图 :[背下来]

在这里插入图片描述

代码演示:

public class Collection_{
    @SuppressWarnings("all") //注解
    public static void main(String[] args) {
        //老韩解读
        // 1. 集合主要是两组(单列集合 , 双列集合)
        // 2. Collection 接口有两个重要的子接口 List Set , 他们的实现子类都是单列集合
        // 3. Map 接口的实现子类 是双列集合,存放的 Key-Value
        // 4. 把老师梳理的两张图记住

        //Collection
        //Map
        ArrayList arrayList =  new ArrayList();
        arrayList.add("jack");
        arrayList.add("tom");

        HashMap hashMap = new HashMap();
        hashMap.put("no1","北京");
        hashMap.put("no2","上海");
    }
}

1.3 Collection 接口和常用方法

1.3.1 Collection 接口实现类的特点

在这里插入图片描述

在这里插入图片描述

  1. Collection 接口常用方法,以实现子类 ArrayList 来演示. CollectionMethod.java

代码演示:

public class CollectionMethod {
    @SuppressWarnings("all")
    public static void main(String[] args) {
        List list = new ArrayList();

        //1. add:添加单个元素
        list.add("jack");
        list.add(10); //自动装箱,转为Integer 相当于list.add(new Integer(10));
        list.add(true); //自动装箱
        list.add(false); //自动装箱
        System.out.println("list=" + list);
        //2. remove:删除指定元素
        list.remove(0); //删除下标为0的元素
        list.remove(false); //删除"false"
        list.remove(new Integer(10)); //删除数字
        System.out.println("list=" + list); //list=[true]
        //3. contains:查找元素是否存在
        System.out.println(list.contains(false)); //false
        //4. size:获取元素个数
        System.out.println(list.size()); //1
        //5. isEmpty:判断是否为空
        System.out.println(list.isEmpty()); //false
        //6. clear:清空元素
        list.clear();
        System.out.println("list=" + list); //list=[]
        //7. addAll: 添加多个元素
        List list2 = new ArrayList();
        list2.add("三国演义");
        list2.add("红楼梦");
        list2.add("西游记");
        list.addAll(list2);
        System.out.println("list=" + list); //list=[三国演义, 红楼梦, 西游记]
        //8. containsAll:查找多个元素是否都存在
        System.out.println(list.containsAll(list2));//true
        //9. removeAll:删除多个元素
        list.add("聊斋");
        list.removeAll(list2); //删除list集合中存放的list2集合中的多个元素
        System.out.println("list=" + list);//list=[聊斋]
    }
}
1.3.2 Collection 接口遍历元素方式 1-使用 Iterator(迭代器)

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

迭代器的使用案例

看下老师的案例演示 CollectionIterator.java

public class CollectionIterator {
    @SuppressWarnings("all")
    public static void main(String[] args) {

        Collection col = new ArrayList();
        col.add(new Book("三国演义", "罗贯中", 10.1));
        col.add(new Book("小李飞刀", "古龙", 5.1));
        col.add(new Book("红楼梦", "曹雪芹", 34.6));
        // System.out.println("col=" + col);
        //现在希望遍历集合,一个一个输出
        //1. 先得到 col 对应的 迭代器
        //只要实现了Collection接口 的都会实现iterator方法
        Iterator iterator = col.iterator();
        //使用while()循环遍历
        while (iterator.hasNext()){ //判断是否还有数据
            //返回下一个元素,类型是Object  因为集合可以添加任何 Object类型的元素,所以返回Object
            Object obj = iterator.next();
            System.out.println("obj=" + obj); // 编译时为Object类型  运行类型为存放的元素类型  多态
        }
        //集合遍历 快捷键 itit     ctrl+J 可以把所有的快捷键显示出来
//        while (iterator.hasNext()) {
//            Object obj =  iterator.next();
//            System.out.println("obj+" + obj);
//        }
        //3. 当退出while循环后,这是iterator迭代器,只想最后的元素
        //iterator.next();//异常:NosuchElementException
        //4.如果希望再次遍历,需要我们重置迭代器。
        iterator = col.iterator();
        System.out.println("===第二次遍历===");
        while (iterator.hasNext()) {
            Object next =  iterator.next();
            System.out.println("next" + next);
        }
    }
}
class Book{
    private String name;
    private String author;
    private double price;

    public Book(String name, String author, double price) {
        this.name = name;
        this.author = author;
        this.price = price;
    }

    public String getName() {
        return name;
    }

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

    public String getAuthor() {
        return author;
    }

    public void setAuthor(String author) {
        this.author = author;
    }

    public double getPrice() {
        return price;
    }

    public void setPrice(double price) {
        this.price = price;
    }

    @Override
    public String toString() {
        return "Book{" +
                "name='" + name + '\'' +
                ", author='" + author + '\'' +
                ", price=" + price +
                '}';
    }
}

在这里插入图片描述

1.3.3 Collection 接口遍历对象方式 2-for 循环增强

在这里插入图片描述

public class CollectionFor {
    public static void main(String[] args) {
        Collection col = new ArrayList();

        col.add(new Book("三国演义", "罗贯中", 10.1));
        col.add(new Book("小李飞刀", "古龙", 5.1));
        col.add(new Book("红楼梦", "曹雪芹", 34.6));

        //使用增强for循环
        for (Object book : col){
            System.out.println("book=" + book);
        }
        //1.增强for也可以直接用在数组
        //2. 增强for 底层仍然时iterator迭代器
        //3. 增强for,就是简化版的迭代器
        int[] a = {1,2,3,5,44,88};
        for (int k : a){
            System.out.println(k);
        }
//        快捷键   I
        for (Object o : col) {
            System.out.println(o);
        }
    }
}

在这里插入图片描述

public class CollectionExrcise {
    public static void main(String[] args) {
        List list = new ArrayList();

        list.add(new Dog("tom",2));
        list.add(new Dog("jack",3));

        for (Object dog : list) {
            System.out.println("dog=" + dog);
        }

        Iterator iterator = list.iterator();
        while (iterator.hasNext()) {
            Object dogs =  iterator.next();
            System.out.println("iterator dogs=" + dogs);
        }
    }
}
class Dog{
    private String name;
    private int age;

    public Dog(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 String toString() {
        return "Dog{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}

1.4 List 接口和常用方法

1.4.1 List 接口基本介绍

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Y2w4aH0V-1648439966857)(C:\Users\admin\AppData\Roaming\Typora\typora-user-images\image-20220319143336643.png)]

代码演示:

public class list_ {
    @SuppressWarnings({"all"})
    public static void main(String[] args) {
        //1. List 集合类中元素有序(即添加顺序和取出顺序一致)、且可重复 [案例]
        List list = new ArrayList();
        list.add("jack");
        list.add("tom");
        list.add("mary");
        list.add("hsp");
        list.add("tom");
        System.out.println("list=" + list);
        //2. List 集合中的每个元素都有其对应的顺序索引,即支持索引
        // 索引是从 0 开始的
        System.out.println(list.get(3));

    }
}
1.4.2 List 接口的常用方法

在这里插入图片描述

代码演示:

public class ListMethod {
    @SuppressWarnings({"ALL"})
    public static void main(String[] args) {
        List list = new ArrayList();
        list.add("贾宝玉");
        list.add("张三丰");
        //1. void add(int index, Object ele):在 index 位置插入 ele 元素
        //在 index = 1 的位置插入一个对象
        list.add(1,"韩顺平");
        System.out.println("list=" + list); //list=[贾宝玉, 韩顺平, 张三丰]
        //2. boolean addAll(int index, Collection eles):从 index 位置开始将 eles 中的所有元素添加进来
        List list1 = new ArrayList();
        list1.add("jack");
        list1.add("tom");
        list.addAll(1,list1);
        System.out.println("list=" + list); //list=[贾宝玉, jack, tom, 韩顺平, 张三丰]
        //3. Object get(int index):获取指定 index 位置的元素
        System.out.println(list.get(4)); //张三丰
        //4. int indexOf(Object obj):返回 obj 在集合中首次出现的位置
        System.out.println(list.indexOf("tom")); //2
        //5. int lastIndexOf(Object obj):返回 obj 在当前集合中末次出现的位置
        list.add("韩顺平");
        System.out.println("list=" + list); //list=[贾宝玉, jack, tom, 韩顺平, 张三丰, 韩顺平]
        System.out.println(list.lastIndexOf("韩顺平")); //5
        //6. Object remove(int index):移除指定 index 位置的元素,并返回此元素
        list.remove(5);
        list.remove("韩顺平");
        System.out.println("list=" + list); //list=[贾宝玉, jack, tom, 张三丰]
        //7. Object set(int index, Object ele):设置指定 index 位置的元素为 ele , 相当于是替换
        list.set(1,"玛丽");
        System.out.println("list=" + list);//list=[贾宝玉, 玛丽, tom, 张三丰]
        //8.  List subList(int fromIndex, int toIndex):返回从 fromIndex 到 toIndex 位置的子集合
        // 注意返回的子集合 fromIndex <= subList < toIndex  左闭右开
        List returnList = list.subList(1,3);
        System.out.println("returnList=" + returnList); //returnList=[玛丽, tom]

    }
}

1.4.3 List 接口课堂练习

在这里插入图片描述

代码演示:

public class ListExercise {
    public static void main(String[] args) {
        /*添加 10 个以上的元素(比如 String "hello" ),在 2 号位插入一个元素"韩顺平教育",
        获得第 5 个元素,删除第 6 个元素,修改第 7 个元素,在使用迭代器遍历集合
        要求:使用 List 的实现类 ArrayList 完成。
         */
        List list = new ArrayList();
        for (int i = 0; i < 12; i++) {
            list.add("hello"+i);
        }
        System.out.println("list= " + list);
        list.add(1,"韩顺平");
        System.out.println("list= " + list);
        System.out.println("第五个元素:" + list.get(4));
        list.remove(5);
        System.out.println("删除第6个元素list= " + list);
        list.set(7,"哈喽");
        System.out.println("list= " + list);
        for (Object hello : list) {
            System.out.print("hello=" + hello + "\t");
        }

    }
}
1.4.4 List 的三种遍历方式 [ArrayList, LinkedList,Vector]

在这里插入图片描述

public class ListFor {
    public static void main(String[] args) {
        List list = new ArrayList();
        list.add("jack");
        list.add("tom");
        list.add("北京考研");
        list.add("覃波");
        //遍历
        //1. iterator 迭代器
        Iterator iterator = list.iterator();
        while (iterator.hasNext()) {
            Object next = iterator.next();
            System.out.println("next=" + next);
        }
        //2. 增强for
        for (Object o : list) {
            System.out.println("o=" + o);
        }
        //3. 普通for
        for (int i = 0; i < list.size(); i++) {
            System.out.println("list" +list.get(i));
        }
1.4.5 List实现类的课堂练习

在这里插入图片描述

public class ListExercise02 {
    //省略了Book类
    @SuppressWarnings({"all"})
    public static void main(String[] args) {
        List list = new ArrayList();
        list.add(new Book("三国演义",80,"罗贯中"));
        list.add(new Book("红楼梦",100,"曹雪芹"));
        list.add(new Book("西游记",10,"吴承恩"));
        listSort(list);
        for (Object o : list) {
            System.out.println(o);
        }
        /*输出
        Book{name='西游记', price=10.0, author='吴承恩'}
        Book{name='三国演义', price=80.0, author='罗贯中'}
        Book{name='红楼梦', price=100.0, author='曹雪芹'}
         */
    }
    public static void listSort(List list){
        for (int i = 0; i < list.size()-1; i++) {
            for (int j = 0; j < list.size() - 1 - i; j++) {
                //取出Book对象
                Book book1 = (Book)list.get(j);
                Book book2 = (Book)list.get(j+1);
                if (book1.getPrice() > book2.getPrice()){
                    list.set(j,book2);
                    list.set(j+1,book1);

                }
            }
        }
    }
}

1.5 ArrayList 底层结构和源码分析

1.5.1 ArrayList 的注意事项

在这里插入图片描述

1.5.2 ArrayList 的底层操作机制源码分析(重点,难点.)

在这里插入图片描述

  1. ArrayList 中维护了一个Object类型的数组elementData.

    transient Object[] elementData; //transient 表示瞬间的,短暂的,表示该属性不会被序列化;

  2. 当创建ArrayList 对象时,如果使用的是无参构造器,则初始elementData容量为0,第1次添加,则扩容elementData为10,如果需要再次扩容,则扩容elementData为1.5倍

    0 -->10 -->15–> 22–>33

  3. 如果使用的是指定大小的构造器,则初始化elementData容量为指定大小,如果需要扩容,则直接扩容elementData为1.5倍

    比如初始化为8:8–> 12–> 18–>27–> 40

示意图:
在这里插入图片描述

在这里插入图片描述

1.6 Vector 底层结构和源码剖析

1.6.1 Vector 的基本介绍

在这里插入图片描述

在这里插入图片描述

代码演示:

@SuppressWarnings({"all"})
public class Vector_ {
    public static void main(String[] args) {
        //无参构造
        Vector vector = new Vector();
        for (int i = 0; i < 10; i++) {
            vector.add(i);
        }
        vector.add(100);
        System.out.println("vector=" + vector);
        老韩解读源码
        // 1. new Vector() 底层
        /*
            public Vector() {
            this(10);
        }
        补充:如果是 Vector vector = new Vector(8);
            走的方法:
            public Vector(int initialCapacity) {
                this(initialCapacity, 0);
        }
        2. vector.add(i) 2.1
         //下面这个方法就添加数据到 vector 集合
         public synchronized boolean add(E e) {
             modCount++;
             ensureCapacityHelper(elementCount + 1);
              elementData[elementCount++] = e;
              return true;
          }
          2.2 //确定是否需要扩容 条件 : minCapacity - elementData.length>0
          private void ensureCapacityHelper(int minCapacity) {// overflow-conscious code
              if (minCapacity - elementData.length > 0)
              grow(minCapacity); }
          2.3 //如果 需要的数组大小 不够用,就扩容 , 扩容的算法
          //newCapacity = oldCapacity + ((capacityIncrement > 0) ?
          //                                capacityIncrement : oldCapacity);
          //就是扩容两倍.
          private void grow(int minCapacity) {
          // overflow-conscious code
          int oldCapacity = elementData.length;
          int newCapacity = oldCapacity + ((capacityIncrement > 0) ?
                                            capacityIncrement : oldCapacity);
          if (newCapacity - minCapacity < 0)
                newCapacity = minCapacity;
          if (newCapacity - MAX_ARRAY_SIZE > 0)
                newCapacity = hugeCapacity(minCapacity);
          elementData = Arrays.copyOf(elementData, newCapacity);
          }
          */
    }
}

1.7 LinkedList 底层结构

1.7.1 LinkedList 的全面说明
  1. LinkedList底层实现了双向链表双端队列特点
  2. 可以添加任何元素(元素可以重复),包括null
  3. 线程不安全,没有实现同步
1.7.2 LinkedList 的底层操作机制
  1. LinkedList底层维护了一个双向链表
  2. LinkedList中维护了两个属性first和last分别指向首结点和尾节点
  3. 每个节点(Node)对象,里面又维护了prev、next、item三个属性,其中通过prev指向前一个,通过next指向后一个节点。最终实现双向链表
  4. 所以LinkedList的元素添加和删除,不是通过数组完成的,相对来说效率较高
  5. 模拟一个简单的双向链表【代码】
    在这里插入图片描述

代码演示:

public class LinkedList01 {
    public static void main(String[] args) {
        //模拟一个简单的双向链表

        Node jack = new Node("jack");
        Node tom = new Node("tom");
        Node hsp = new Node("老韩");

        //连接三个结点,形成双向链表
        //jack -> tom -> hsp
        jack.next = tom;
        tom.next = hsp;
        //hsp -> tom -> jack
        hsp.pre = tom;
        tom.pre = jack;

        Node first = jack; //让 first 引用指向 jack,就是双向链表的头结点
        Node last = hsp; //让 last 引用指向 hsp,就是双向链表的尾结点

        //演示,从头到尾进行遍历
        System.out.println("===从头到尾进行遍历===");
        while (true) {
            if (first == null) {
                break;
            }
            //输出first信息
            System.out.println(first);
            first = first.next;
        }
        //演示,从尾到头进行遍历
        System.out.println("===从尾到头进行遍历===");
        while (true) {
            if (last == null) {
                break;
            }
            //输出first信息
            System.out.println(last);
            last = last.pre;
        }
        //演示链表的添加对象/数据,是多么的方便
        // 要求,是在 tom --------- 老韩直接,插入一个对象 smith

        //1. 先创建一个 Node 结点,name 就是 smith
        Node smith = new Node("smith");
        //下面就把 smith 加入到双向链表了
        smith.pre = tom;
        smith.next = hsp;
        tom.next = smith;
        hsp.pre = smith;
        //让 first 再次指向 jack
        first = jack;//让 first 引用指向 jack,就是双向链表的头结点
        //演示,从头到尾进行遍历
        System.out.println("===从头到尾进行遍历===");
        while (true) {
            if (first == null) {
                break;
            }
            //输出first信息
            System.out.println(first);
            first = first.next;
        }
        //让 last 再次重新指向 hsp
         last = hsp; //让 last 引用指向 hsp,就是双向链表的尾结点
        //演示,从尾到头进行遍历
        System.out.println("===从尾到头进行遍历===");
        while (true) {
            if (last == null) {
                break;
            }
            //输出first信息
            System.out.println(last);
            last = last.pre;
        }
    }
}


//定义一个 Node 类,Node 对象 表示双向链表的一个结点
class Node {
    public Object item; //真正存放数据
    public Node next; //指向后一个结点
    public Node pre; //指向前一个结点

    public Node(Object name) {
        this.item = name;
    }

    public String toString() {
        return "Node name=" + item;
    }
}

在这里插入图片描述

1.7.3 LinkedList 的增删改查案例

@SuppressWarnings({"all"})
public class LinkedListCRUD {
    public static void main(String[] args) {
        LinkedList linkedList = new LinkedList();
        linkedList.add(1);
        linkedList.add(2);
        linkedList.add(3);
        System.out.println("linkedList=" + linkedList);

        //演示删除一个节点的元素
        linkedList.remove(); // 这里默认删除的是第一个结点
        //linkedList.remove(2);
        System.out.println("linkedList=" + linkedList);
        //修改某个结点对象
        linkedList.set(1, 999);
        System.out.println("linkedList=" + linkedList);
        //得到某个结点对象
        // get(1) 是得到双向链表的第二个对象
        Object o = linkedList.get(1);
        System.out.println(o);//999
        //因为 LinkedList 是 实现了 List 接口, 遍历方式
        System.out.println("===LinkeList 遍历迭代器====");
        Iterator iterator = linkedList.iterator();
        while (iterator.hasNext()) {
            Object next = iterator.next();
            System.out.println("next=" + next);
        }
        //普通for循环遍历
        System.out.println("====普通for循环====");
        for (int i = 0; i < linkedList.size(); i++) {
            System.out.println("linkedList=" + linkedList.get(i));
        }
    }
    }
    //老韩源码阅读.
     /* 1. LinkedList linkedList = new LinkedList();
    public LinkedList() {
     }
     2. 这时 linkeList 的属性 first = null last = null
    3. 执行 添加 public boolean add(E e) {
     linkLast(e);
     return true;
     }
     4.将新的结点,加入到双向链表的最后 void linkLast(E e) {
     final Node<E> l = last;
     final Node<E> newNode = new Node<>(l, e, null);
     last = newNode;
     if (l == null) first = newNode;
     else l.next = newNode; size++; modCount++; } */
    /* 老韩读源码 linkedList.remove();
    // 这里默认删除的是第一个结点
    1. 执行 removeFirst public E remove() {
    return removeFirst();
     }
    */
    /*2. 执行public E removeFirst() {
    final Node<E> f = first;
    if (f == null)
    throw new NoSuchElementException();
    return unlinkFirst(f);
    }
    3. 执行 unlinkFirst, 将 f 指向的双向链表的第一个结点拿掉
    private E unlinkFirst(Node<E> f) {
    // assert f == first && f != null;
    final E element = f.item;
    final Node<E> next = f.next;
    f.item = null; f.next = null;
    // help GC first = next;
    if (next == null) last = null;
    else next.prev = null; size--;
    modCount++; return element; }
    */
}

在这里插入图片描述

1.7.4 ArrayList 和 LinkedList 比较

在这里插入图片描述

1.8 Set 接口和常用方法

1.8.1 Set 接口基本介绍

在这里插入图片描述

1.8.2 Set 接口的常用方法

和 List 接口一样, Set 接口也是 Collection 的子接口,因此,常用方法和 Collection 接口一样.

Set遍历方式:

在这里插入图片描述

@SuppressWarnings({"all"})
public class SetMethod {
    public static void main(String[] args) {
        // 1.以set接口的实现类HashSet 来讲解 接口的方法
        //2. set 接口的实现类对象(Set接口对象),不能放重复的元素,可以添加一个null值
        //3. set 接口对象存放数据是无序的(即添加的顺序和取出的顺序不一致),
        // 4. 注意:但是一直是这个是固定顺序,不会一直改变
        Set set = new HashSet();
        set.add("jhon");
        set.add("lucy");
        set.add("jhon"); //重复
        set.add("jack");
        set.add(null);
        set.add(null); //空
        for (int i = 0; i < 10; i++) {
            System.out.println("set=" + set);
        }
        //遍历,迭代器
        Iterator iterator = set.iterator();
        while (iterator.hasNext()) {
            Object next =  iterator.next();
            System.out.println("set=" + next);
        }
        //增强for循环
        for (Object o :set) {
            System.out.println("set=" + o);
        }
    }
}

1.9 Set 接口实现类-HashSet

1.9.1 HashSet 的全面说明

在这里插入图片描述

代码演示:


@SuppressWarnings({"all"})
public class HashSet_ {
    public static void main(String[] args) {
        /*1.构造器走源码
          public HashSet() {
        map = new HashMap<>();
    }
         2. HashSet 可以存放 null ,但是只能有一个 null,即元素不能重复
         */
        Set hashSet = new HashSet();
        hashSet.add(null);
        hashSet.add(null);
        System.out.println("hashSet=" + hashSet);
    }
}
1.9.2 HashSet 案例说明
@SuppressWarnings({"all"})
public class HashSet01 {
    public static void main(String[] args) {
        Set set = new HashSet();
        //说明
        // 1. 在执行 add 方法后,会返回一个 boolean 值
        // 2. 如果添加成功,返回 true, 否则返回 false
        // 3. 可以通过 remove 指定删除哪个对象
        System.out.println(set.add("john")); //t
        System.out.println(set.add("lucy")); //t
        System.out.println(set.add("john")); //f
        System.out.println(set.add("jack")); //t
        System.out.println(set.add("Rose")); //t
        set.remove("john");
        System.out.println("set=" + set); //set=[Rose, lucy, jack]


        set = new HashSet();
        System.out.println("set=" + set);//0个
        //4 Hashset 不能添加相同的元素/数据?
        set.add("lucy");//添加成功
        set.add("lucy");//加入不了
        set.add(new Dog("tom"));//OK
        set.add(new Dog("tom"));//Ok
        System.out.println("set=" + set);
        
        //在加深一下. 非常经典的面试题.
        //看源码,做分析, 先给小伙伴留一个坑,以后讲完源码,你就了然
        // 去看他的源码,即 add 到底发生了什么?=> 底层机制.
        set.add(new String("hsp"));//ok
        set.add(new String("hsp"));//加入不了.
        System.out.println("set=" + set);
    }
    }

在这里插入图片描述

1.10 HashSet 底层机制说明

1.10 HashSet 底层机制说明

在这里插入图片描述在这里插入图片描述

代码演示:

@SuppressWarnings({"all"})
public class HashSetSource {
    public static void main(String[] args) {
        HashSet hashSet = new HashSet();
        hashSet.add("java");//到此位置,第 1 次 add 分析完毕.
        hashSet.add("php");//到此位置,第 2 次 add 分析完毕
        hashSet.add("java");
        System.out.println("set=" + hashSet);
    }

    /*老韩对 HashSet 的源码解读
    1. 执行 HashSet() public HashSet() {
    map = new HashMap<>();
    }
    2. 执行 add()
     public boolean add(E e) { //e = "java"
            return map.put(e, PRESENT)==null;//(static) PRESENT = new Object();
        }
        3.执行 put() , 该方法会执行 hash(key) 得到 key 对应的 hash 值 算法 h = key.hashCode()) ^ (h >>> 16)
         public V put(K key, V value) {
        return putVal(hash(key), key, value, false, true);
    }
    4.执行 putVal
    final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
                   boolean evict) {
        Node<K,V>[] tab; Node<K,V> p; int n, i; //定义了辅助变量
        //table 就是 HashMap 的一个数组,类型是 Node[]
        //if 语句表示如果当前 table 是 null, 或者 大小=0
        //就是第一次扩容,到 16 个空间.
        if ((tab = table) == null || (n = tab.length) == 0)
            n = (tab = resize()).length;
            //(1)根据 key,得到 hash 去计算该 key 应该存放到 table 表的哪个索引位置
            //并把这个位置的对象,赋给 p
            //(2)判断 p 是否为 null
            //(2.1) 如果 p 为 null, 表示还没有存放元素, 就创建一个 Node (key="java",value=PRESENT)
            //(2.2) 就放在该位置 tab[i] = newNode(hash, key, value, null)
        if ((p = tab[i = (n - 1) & hash]) == null)
            tab[i] = newNode(hash, key, value, null);
        else {
        //一个开发技巧提示: 在需要局部变量(辅助变量)时候,在创建
            Node<K,V> e; K k;
            //如果当前索引位置对应的链表的第一个元素和准备添加的 key 的 hash 值一样
             //并且满足 下面两个条件之一:
            //(1) 准备加入的 key 和 p 指向的 Node 结点的 key 是同一个对象
            //(2) p 指向的 Node 结点的 key 的 equals() 和准备加入的 key 比较后相同
            //就不能加入
            if (p.hash == hash &&
                ((k = p.key) == key || (key != null && key.equals(k))))
                e = p;
                //再判断 p 是不是一颗红黑树,
                //如果是一颗红黑树,就调用 putTreeVal , 来进行添加
            else if (p instanceof TreeNode)
                e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
            else {
            //如果 table 对应索引位置,已经是一个链表, 就使用 for 循环比较
            //(1) 依次和该链表的每一个元素比较后,都不相同, 则加入到该链表的最后
            // 注意在把元素添加到链表后,立即判断 该链表是否已经达到 8 个结点
            // , 就调用 treeifyBin() 对当前这个链表进行树化(转成红黑树)
            // 注意,在转成红黑树时,要进行判断, 判断条件
            // if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY(64))
            // resize(); // 如果上面条件成立,先 table 扩容.
            // 只有上面条件不成立时,才进行转成红黑树
            //(2) 依次和该链表的每一个元素比较过程中,如果有相同情况,就直接 break
                for (int binCount = 0; ; ++binCount) {
                    if ((e = p.next) == null) {
                        p.next = newNode(hash, key, value, null);
                        if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                            treeifyBin(tab, hash);
                        break;
                    }
                    if (e.hash == hash &&
                        ((k = e.key) == key || (key != null && key.equals(k))))
                        break;
                    p = e;
                }
            }
            if (e != null) { // existing mapping for key
                V oldValue = e.value;
                if (!onlyIfAbsent || oldValue == null)
                    e.value = value;
                afterNodeAccess(e);
                return oldValue;
            }
        }
        ++modCount;
        //size 就是我们每加入一个结点 Node(k,v,h,next), size++
        if (++size > threshold)
            resize();//扩容
        afterNodeInsertion(evict);
        return null;
    }
    */
}
1.10.2 分析HashSet的扩容转成红黑树机制

在这里插入图片描述

代码演示:

@SuppressWarnings({"all"})
public class HashSetIncrement {
    public static void main(String[] args) {
        /*HashSet 底层是 HashMap, 第一次添加时,table 数组扩容到 16,
         临界值(threshold)是 16*加载因子(loadFactor)是 0.75 = 12
         如果 table 数组使用到了临界值 12,就会扩容到 16 * 2 = 32
         新的临界值就是 32*0.75 = 24, 依次类推
         */
        HashSet hashSet = new HashSet();
//        for(int i = 1; i <= 100; i++) {
//            hashSet.add(i);//1,2,3,4,5...100 //
//            }
        /*在 Java8 中, 如果一条链表的元素个数到达 TREEIFY_THRESHOLD(默认是 8 ),
        并且 table 的大小 >= MIN_TREEIFY_CAPACITY(默认 64),就会进行树化(红黑树),
        否则仍然采用数组扩容机制
        */

//        for (int i = 1; i < 12; i++) {
//            hashSet.add(new A(i));
//        }
        System.out.println("hashSet=" + hashSet);
        /* 当我们向 hashset 增加一个元素,-> Node -> 加入 table , 就算是增加了一个 size++ */
        for (int i = 1; i <= 7; i++) {//在 table 的某一条链表上添加了 7 个 A 对象
            hashSet.add(new A(i));
        }

        for (int i = 1; i <= 7; i++) {//在 table 的另外一条链表上添加了 7 个 A 对象
            hashSet.add(new B(i));
        }
    }

}

class B {
    private int n;

    public B(int n) {
        this.n = n;
    }

    @Override
    public int hashCode() {
        return 200;
    }
}

class A {
    private int n;

    public A(int n) {
        this.n = n;
    }

    @Override
    public int hashCode() {
        return 100;
    }
}
1.10.3 HashSet 课堂练习 1

在这里插入图片描述

public class HashSetExercise {
    public static void main(String[] args) {
        /**定义一个 Employee 类,该类包含:private 成员属性 name,age
         * 要求: 创建 3 个 Employee 对象放入 HashSet 中 当 name 和 age 的值相同时,
         * 认为是相同员工, 不能添加到 HashSet 集合中 */
        Set set = new HashSet();
        set.add(new Employee("jack",15));
        set.add(new Employee("rose",15));
        set.add(new Employee("jack",15));
        System.out.println("hash=" + set);

    }
}
class Employee{
    private String name;
    private int 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;
    }

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

    @Override
    public String toString() {
        return "Employee{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
    //如果 name 和 age 值相同,则返回相同的 hash 值


    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Employee employee = (Employee) o;
        return age == employee.age && Objects.equals(name, employee.name);
    }

    @Override
    public int hashCode() {
        return Objects.hash(name, age);
    }
}

HashSet 课堂练习 2
在这里插入图片描述

public class HashSetExercise02 {
    public static void main(String[] args) {
        Set hashSet = new HashSet();
        hashSet.add(new Employee1("jack",15.2,new MyDate("2000","11","5")));
        hashSet.add(new Employee1("smith",15.2,new MyDate("1999","11","5")));
        hashSet.add(new Employee1("jack",15.2,new MyDate("2000","11","5")));

        System.out.println("hashSet=" + hashSet);
    }
}

class Employee1 {
    private String name;
    private double sal;
    private MyDate birthday;

    public Employee1(String name, double sal, MyDate birthday) {
        this.name = name;
        this.sal = sal;
        this.birthday = birthday;
    }

    @Override
    public String toString() {
        return "Employee1{" +
                "name='" + name + '\'' +
                ", sal=" + sal +
                ", birthday=" + birthday +
                '}';
    }

    public String getName() {
        return name;
    }

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

    public double getSal() {
        return sal;
    }

    public void setSal(double sal) {
        this.sal = sal;
    }

    public MyDate getBirthday() {
        return birthday;
    }

    public void setBirthday(MyDate birthday) {
        this.birthday = birthday;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Employee1 employee1 = (Employee1) o;
        return Objects.equals(name, employee1.name) && Objects.equals(birthday, employee1.birthday);
    }

    @Override
    public int hashCode() {
        return Objects.hash(name, birthday);
    }
}

class MyDate {
    String year;
    String month;
    String day;

    public MyDate(String year, String month, String day) {
        this.year = year;
        this.month = month;
        this.day = day;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        MyDate myDate = (MyDate) o;
        return Objects.equals(year, myDate.year) && Objects.equals(month, myDate.month) && Objects.equals(day, myDate.day);
    }

    @Override
    public int hashCode() {
        return Objects.hash(year, month, day);
    }

    @Override
    public String toString() {
        return "MyDate{" +
                "year='" + year + '\'' +
                ", month='" + month + '\'' +
                ", day='" + day + '\'' +
                '}';
    }
}

1.11 Set 接口实现类-LinkedHashSet

1.11.1 LinkedHashSet 的全面说明
  1. LinkedHashSet是HashSet 的子类
  2. LinkedHashSet 底层是一个LinkedHashMap,底层维护了一个数组+双向链表
  3. LinkedHashSet 根据元素的hashCode值来决定元素的存储位置,同时使用链表维护元素的次序,使元素看起来是以插入的元素保存的。、
  4. LinkedHashSet 不允许重复添加元素。
    在这里插入图片描述
1.11.2 LinkedHashSet 源码分析:
@SuppressWarnings({"all"})
public class LinkedHashSet_ {
    public static void main(String[] args) {
        Set set = new LinkedHashSet();
        set.add(new String("AA"));
        set.add(456);
        set.add(456);
        set.add(new Customer("刘",1001));
        set.add(123);
        set.add("HSP");
        System.out.println("set" + set);
        //老韩解读
        //1,LinkedHashSet加入顺序和取出元素/数据的顺序一致
        //2,LinkedHashSet底层维护的是一个LinkedHashMap(是HashMap的子类)
        //3.LinkedHashSet底层结构(数组table+双向链表)
        //4.添加第一次时,直接将数组table扩容到16,存放的结点类型是LinkedHashMap$Entry
        //5,数组是HashMap$Node[]存放的元素/数据是LinkedHashMap$Entry类型
        /*
        //继承关系在内部类完成的
        static class Entry<K,V> extends HashMap.Node<K,V> {
        Entry<K,V> before, after;
        Entry(int hash, K key, V value, Node<K,V> next) {
            super(hash, key, value, next);
        }
    }
         */

    }
}
class Customer{
    private String name;
    private int number;

    public Customer(String name, int number) {
        this.name = name;
        this.number = number;
    }

    public String getName() {
        return name;
    }

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

    public int getNumber() {
        return number;
    }

    public void setNumber(int number) {
        this.number = number;
    }
}

在这里插入图片描述

1.11.3 LinkedHashSet 课后练习题 LinkedHashSetExercise.java

在这里插入图片描述

代码演示:

@SuppressWarnings({"all"})
public class LinkedHashSetExercise01 {
    public static void main(String[] args) {
        LinkedHashSet linkedHashSet = new LinkedHashSet();
        linkedHashSet.add(new Car("奥拓", 1000));//OK
        linkedHashSet.add(new Car("奥迪", 300000));//OK
         linkedHashSet.add(new Car("法拉利", 10000000));//OK
         linkedHashSet.add(new Car("奥迪", 300000));//加入不了
         linkedHashSet.add(new Car("保时捷", 70000000));//OK
         linkedHashSet.add(new Car("奥迪", 300000));//加入不了
        System.out.println("linkedHashSet=" + linkedHashSet);
    }
        }
        /* Car 类(属性:name,price), 如果 name 和 price 一样,
        * 则认为是相同元素,就不能添加。 5min */

class Car {
    private String name;
    private double price;

    public Car(String name, double price) {
        this.name = name;
        this.price = price;
    }

    public String getName() {
        return name;
    }

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

    public double getPrice() {
        return price;
    }

    public void setPrice(double price) {
        this.price = price;
    }

    @Override
    public String toString() {
        return "\nCar{" +
                "name='" + name + '\'' +
                ", price=" + price +
                '}';
    }
    //重写 equals 方法 和 hashCode
    // 当 name 和 price 相同时, 就返回相同的 hashCode 值, equals 返回 t

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Car car = (Car) o;
        return Double.compare(car.price, price) == 0 && Objects.equals(name, car.name);
    }

    @Override
    public int hashCode() {
        return Objects.hash(name, price);
    }
}

1.12 Map 接口和常用方法

1.12.1 Map 接口实现类的特点 [很实用]

在这里插入图片描述

代码演示:

@SuppressWarnings({"all"})
public class Map_ {
    public static void main(String[] args) {
        //老韩解读 Map 接口实现类的特点, 使用实现类 HashMap
        // 1. Map 与 Collection 并列存在。用于保存具有映射关系的数据:Key-Value(双列元素)
        // 2. Map 中的 key 和 value 可以是任何引用类型的数据,会封装到 HashMap$Node 对象中
        // 3. Map 中的 key 不允许重复,原因和 HashSet 一样,前面分析过源码.
        // 4. Map 中的 value 可以重复
        // 5. Map 的 key 可以为 null, value 也可以为 null ,
        // 注意 key 为 null,,只能有一个,value 为 null ,可以多个
        Map map = new HashMap();
        map.put("no1", "韩顺平");//k-v
        map.put("no2", "张无忌");//k-v
        map.put("no1", "张三丰");//有相同的key,相当于替换
        map.put("no3", "张三丰");//k-v
        map.put(null,null);//k-v
        map.put(null, "abc"); //等价替换
        map.put("no4", null); //k-v
        map.put("no5", null); //k-v
        map.put(1, "赵敏");//k-v ,key可以是Object
        map.put(new Object(), "金毛狮王");//k-v
        // 通过 get 方法,传入 key ,会返回对应的 value
        System.out.println(map.get(1)); //赵敏


        System.out.println("map=" + map);
        //map={no2=张无忌, null=abc, no1=张三丰, 1=赵敏, no4=null,
        // no3=张三丰, no5=null, java.lang.Object@1b6d3586=金毛狮王}
    }
}

在这里插入图片描述

public class MapSrouse_ {
    public static void main(String[] args) {
        Map map = new HashMap();
        map.put("no1","韩顺平");//k-v
        map.put("no2","张无忌");//k-v
        map.put(new Car(),new Person());//k-v

        //老汉解读
        //1. k-v最后是 HashMap$Node node = newNode(hash,key,value,null)
        //2. k-v为了方便程序员的遍历,还会创建EntrySet 集合,该集合存放的元素的类型 Entry,而一个Entry
        // 对象就有k-v EntrySet<Entry<K,V>>,即:transient Set<Map.Entry<K,V>> entrySet;
        //3. entrySet中,定义的类型是Map.Entry,但实际上存放的还是HashMap$Node
        // 这是因为 static class Node<K,V> implement Map.Entry<K,V>
        //4. 当把HashMap$Node 对象存放到 entrySet 就方便我们的遍历,因为Map.Entry提供了重要方法
        //K getKev():V getValue():
        Set set = map.entrySet();
        System.out.println(set.getClass()); // HashMap$EntrySet
        for (Object obj : set) {
            //System.out.println(obj.getclass());//HashMap$Node
            //为了从HashMap$Node取出k-v
            //1.先做一个向下转型
            Map.Entry entry = (Map.Entry) obj;
            System.out.println(entry.getKey() + "-" + entry.getValue());
        }

    }
}
class Car{}
class Person{}

K-V值是在HashMap$Node中的,NodeHashMap内部类

EntrySet集合里面的元素为Entry一个Entry里面就有一个k-v EntrySet<Entry<K,V>>,

entrySet中定义的类型是Map.Entry,但实际上存放的还是HashMap$Node。

因为Node实现了接口Map.Entry<K,V>。

HashMap$Node 对象存放到 entrySet 就方便我们的遍历,因为Map.Entry提供了重要方法。getKey()和getValue()。

在这里插入图片描述

1.12.2 Map 接口常用方法
@SuppressWarnings({"all"})
public class MapMethod {
    public static void main(String[] args) {
        Map map = new HashMap();
        map.put("邓超", new Book("", 100));
        map.put("邓超", "孙俪");//替换-> 一会分析源码
        map.put("王宝强", "马蓉");
        map.put("宋喆", "马蓉");
        map.put("刘令博", null);
        map.put(null, "刘亦菲");
        map.put("鹿晗", "关晓彤");
        map.put("hsp", "hsp 的老婆");
        System.out.println("map=" + map);
        //remove:根据键删除映射关系
        map.remove(null);
        System.out.println("map=" + map);
        //get:根据键获取值
        map.get("鹿晗");
        System.out.println("map=" + map);
        //size:获取元素个数
        System.out.println("k-v=" + map.size());
        //isEmpty:判断个数是否为 0
        System.out.println(map.isEmpty()); //F
        //clear:清除 k-v
//        map.clear();
//        System.out.println("map=" + map);
        //containsKey:查找键是否存在
        System.out.println(map.containsKey("hsp"));//Ture
        System.out.println(map.containsValue("hsp"));


    }
}

class Book {
    private String name;
    private int num;

    public Book(String name, int num) {
        this.name = name;
        this.num = num;
    }
1.12.3 Map 接口遍历方法

在这里插入图片描述

在这里插入图片描述

代码演示:

@SuppressWarnings({"all"})
public class MapFor {
    public static void main(String[] args) {
        Map map = new HashMap();
        map.put("邓超", "孙俪");
        map.put("王宝强", "马蓉");
        map.put("宋喆", "马蓉");
        map.put("刘令博", null);
        map.put(null, "刘亦菲");
        map.put("鹿晗", "关晓彤");
        //第一组: 先取出 所有的 Key , 通过 Key 取出对应的 Value
        Set keySet = map.keySet();
        //(1)增强for
        System.out.println("----第一种方式增强for----");
        for (Object key : keySet) {
            System.out.println(key + "-" + map.get(key));
        }
        //(2) 迭代器
        System.out.println("----第二种方式迭代器--------");
        Iterator iterator = keySet.iterator();
        while (iterator.hasNext()) {
            Object key = iterator.next();
            System.out.println(key + "-" + map.get(key));
        }
        //第二组: 把所有的 values 取出
        Collection values = map.values();
        //这里可以使用所有的 Collections 使用的遍历方法
        //这里可以使用所有的 Collections 使用的遍历方法
        // (1) 增强 for
        System.out.println("---取出所有的 value 增强 for----");
        for (Object value : values) {
            System.out.println(value);
        }

        //(2) 迭代器
        System.out.println("---取出所有的 value 迭代器----");
        Iterator iterator2 = values.iterator();
        while (iterator2.hasNext()) {
            Object value = iterator2.next();
            System.out.println(value);
        }
        //第三组: 通过 EntrySet 来获取 k-v
        Set entrySet = map.entrySet();// EntrySet<Map.Entry<K,V>>
        //增强for循环
        System.out.println("----使用 EntrySet 的 for 增强(第 3 种)----");
        for (Object entry : entrySet) {
            //将entry 转成 Map.Entry
            Map.Entry m = (Map.Entry) entry;
            System.out.println(m.getKey() + "-" + m.getValue());
        }
        //(2) 迭代器
        System.out.println("----使用 EntrySet 的 迭代器(第 4 种)----");
        Iterator iterator3 = entrySet.iterator();
        while (iterator3.hasNext()) {
            Object entry = iterator3.next();
            //System.out.println(next.getClass());//HashMap$Node -实现-> Map.Entry (getKey,getValue)
            // 向下转型 Map.Entry
            
            Map.Entry m = (Map.Entry)entry;
            System.out.println(m.getKey() + "-" + m.getValue());
        }
    }
}
1.12.4 Map 接口课堂练习

在这里插入图片描述

代码演示:

@SuppressWarnings({"all"})
public class MapExercise {
    public static void main(String[] args) {
        Map hashMap = new HashMap();
        hashMap.put(1,new Emp("jack",30000,1));
        hashMap.put(2,new Emp("tom",21000,2));
        hashMap.put(3,new Emp("milan",12000,3));

        //遍历 2 种方式
        //并遍历显示工资>18000 的员工(遍历方式最少两种)
        //1.使用keyset 增强for
        Set keySet = hashMap.keySet();
        for (Object key : keySet) {
            Emp emp = (Emp)hashMap.get(key);
            if (emp.getSal()>18000) {
                System.out.println(emp);
            }
        }
        //2. 使用 EntrySet -> 迭代器
        // 体现比较难的知识点
        // 慢慢品,越品越有味道.
        Set entrySet = hashMap.entrySet();
        Iterator iterator = entrySet.iterator();
        while (iterator.hasNext()) {
            Map.Entry entry =  (Map.Entry)iterator.next(); //iterator.next();这里取出来就是一个Entry,
            //通过 entry 取得 key 和 value
            Emp emp = (Emp) entry.getValue();
            if (emp.getSal()>18000){
                System.out.println(emp);
            }

        }


    }
}
/*** 使用 HashMap 添加 3 个员工对象,要求
 * 键:员工 id
 * 值:员工对象
 * 并遍历显示工资>18000 的员工(遍历方式最少两种)
 * 员工类:姓名、工资、员工 id
 */
class Emp{
    private String name;
    private double sal;
    private int id;

    public Emp(String name, double sal, int id) {
        this.name = name;
        this.sal = sal;
        this.id = id;
    }

    public String getName() {
        return name;
    }

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

    public double getSal() {
        return sal;
    }

    public void setSal(double sal) {
        this.sal = sal;
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    @Override
    public String toString() {
        return "Emp{" +
                "name='" + name + '\'' +
                ", sal=" + sal +
                ", id=" + id +
                '}';
    }
}

在这里插入图片描述

1.13 Map 接口实现类-HashMap

1.13.1 HashMap 小结

在这里插入图片描述

1.13.2 HashMap 底层机制及源码剖析

在这里插入图片描述

1.13.3 HashMap 底层机制及源码剖析

在这里插入图片描述

源码演示讲解:

@SuppressWarnings({"all"})
public class HashMapSource1 {
    public static void main(String[] args) {
        HashMap map = new HashMap();
        map.put("java", 10);//ok
        map.put("php", 10);//ok
        map.put("java", 20);//替换 value
        System.out.println("map=" + map);//

        /*老韩解读 HashMap 的源码+图解
        1. 执行构造器 new HashMap()
        初始化加载因子 loadfactor = 0.75
         HashMap$Node[] table = null
        2. 执行 put 调用 hash 方法,计算 key 的 hash 值 (h = key.hashCode()) ^ (h >>> 16)
         public V put(K key, V value) {//K = "java" value = 10
         return putVal(hash(key), key, value, false, true);
         }

         3.执行 putVal
         final V putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict) {
        Node<K,V>[] tab; Node<K,V> p; int n, i; //辅助变量
        //如果底层的 table 数组为 null, 或者 length =0 , 就扩容到 16
        if ((tab = table) == null || (n = tab.length) == 0)
            n = (tab = resize()).length;
            //取出 hash 值对应的 table 的索引位置的 Node, 如果为 null, 就直接把加入的 k-v
            //, 创建成一个 Node ,加入该位置即可
        if ((p = tab[i = (n - 1) & hash]) == null)
            tab[i] = newNode(hash, key, value, null);
        else {
            Node<K,V> e; K k;;//辅助变量
            // 如果 table 的索引位置的 key 的 hash 相同和新的 key 的 hash 值相同,
            // 并 满足(table 现有的结点的 key 和准备添加的 key 是同一个对象 || equals 返回真)
            // 就认为不能加入新的 k-v
            if (p.hash == hash &&
                ((k = p.key) == key || (key != null && key.equals(k))))
                e = p;
            else if (p instanceof TreeNode)//如果当前的 table 的已有的 Node 是红黑树,就按照红黑树的方式处
                e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
            else {
            //如果找到的结点,后面是链表,就循环比较
                for (int binCount = 0; ; ++binCount) {//死循环
                    if ((e = p.next) == null) {{//如果整个链表,没有和他相同,就加到该链表的最后
                        p.next = newNode(hash, key, value, null);
                        //加入后,判断当前链表的个数,是否已经到 8 个,到 8 个,后
                        //就调用 treeifyBin 方法进行红黑树的转换
                        if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                            treeifyBin(tab, hash);
                        break;
                    }
                    if (e.hash == hash &&    //如果在循环比较过程中,发现有相同,就 break,就只是替换 value
                        ((k = e.key) == key || (key != null && key.equals(k))))
                        break;
                    p = e;
                }
            }
            if (e != null) { // existing mapping for key
                V oldValue = e.value;
                if (!onlyIfAbsent || oldValue == null)
                    e.value = value;
                afterNodeAccess(e);
                return oldValue;
            }
        }
        ++modCount;//每增加一个 Node ,就 size++
        if (++size > threshold)
            resize();
        afterNodeInsertion(evict);
        return null;
    }

    5. 关于树化(转成红黑树)
    //如果 table 为 null ,或者大小还没有到 64,暂时不树化,而是进行扩容.
    //否则才会真正的树化 -> 剪枝
    final void treeifyBin(Node<K,V>[] tab, int hash) {
    int n, index; Node<K,V> e;
    if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY)
    resize();
    }
         */
    }
}

在这里插入图片描述

1.13.4 HashMap出发扩容、树化情况。

在这里插入图片描述

代码演示:

public class HashMapSource2 {
    public static void main(String[] args) {
        HashMap hashMap = new HashMap();
        for (int i = 1; i <= 12; i++) {
            hashMap.put(new A(i),"hello");
        }
        System.out.println("hashMap=" + hashMap); //12个k-v
        //布置一个任务,自己设计代码去验证,table 的扩容
        // 0 -> 16(12) -> 32(24) -> 64(64*0.75=48)-> 128 (96) ->
        // 自己设计程序,验证-》 增强自己阅读源码能力. 看别人代码。
    }

}
class A{
    private int num;

    public A(int num) {
        this.num = num;
    }
    @Override
    public String toString() {
        return "\nA{" +
                "num='" + num + '\'' +
                '}';
    }
    //所有的HashCode都一样了
    @Override
    public int hashCode() {
        return 100;
    }
}

1.14 Map 接口实现类-Hashtable

1.14.1 HashTable 的基本介绍

在这里插入图片描述

public class HashTableExercise {
    public static void main(String[] args) {
        Hashtable table = new Hashtable();
        table.put("john",100);//ok
        table.put(null,100);//NullPointerException异常
        table.put("john",null);//NullPointerException异常
        table.put("lucy",100);//ok
        table.put("lic",100);//ok
        table.put("lic",88);//替换
        System.out.println(table);

        //1,底层有数组Hashtable$Entry[]初始化大小为11
        //2.临界值threshold 8=11*0.75;
        //3。扩容:按照自己的扩容机制来进行即可,
        //4.执行方法addEntry(hash,key,vaue,index); 添加K-V封装到Entry
        //5.当if(count >= threshold)满足时,就进行扩容
        //6.按照int newCapacity = (oldCapacity < 1)+1;的大小扩容。
    }
}
1.14.2 Hashtable 和 HashMap 对比

在这里插入图片描述

1.15 Map 接口实现类-Properties

1.15.1 基本介绍

在这里插入图片描述

1.15.2 基本使用
public class Properties_ {
    public static void main(String[] args) {
        //老韩解读
        //1. Properties 继承 Hashtable
        //2. 可以通过 k-v 存放数据,当然 key 和 value 不能为 null
        //增加
        Properties properties = new Properties();
        //properties.put(null, "abc");//抛出 空指针异常
        //properties.put("abc", null); //抛出 空指针异常
        properties.put("john", 100);//k-v
        properties.put("lucy", 100);
        properties.put("lic", 100);
        properties.put("lic", 88);//如果有相同的 key , value 被替换
        System.out.println("properties=" + properties);
        //通过 k 获取对应值 
        System.out.println(properties.get("lic"));//88 
        //删除 
        properties.remove("lic");
        System.out.println("properties=" + properties); 
        //修改
        properties.put("john", "约翰");
        System.out.println("properties=" + properties);
    }
}

1.16 总结-开发中如何选择集合实现类(记住)

在这里插入图片描述

手敲:在开发中,选择什么集合实现类,主要取决于业务操作特点,然后根据集合实现类特性进行选择,分析如下

  1. 先判断存储类型(一组对象[单列]或一组键值对[双列])

  2. 一组对象[单列]:Collection接口

    允许重复:list

    ​ 增删多:LinkedList(底层维护了一个双向链表)

    ​ 改查多:ArrayList(底层维护Object类型的可变数组)

    不允许重复:Set

    ​ 无序:HashSet(底层是HashMap,维护了一个哈希表 即(数组+链表+红黑树))

    ​ 排序:TreeSet (可以使用内部类自定义排序)

    ​ 插入和取出顺序一致:LinkedHashSet,维护数组+双向链表

  3. 一组键值对[双列]:Map

    键无序:HashMap [底层是:哈希表 jdk7:数组+链表,jdk8:数组+链表+红黑树]

    键排序:TreeMap[]

    键插入和取出顺序一致:LinkedHashMap

    读取文件:Perproties

1.17 TreeSet和TreeMap

1.17.1 TreeSet源码讲解
@SuppressWarnings({"all"})
public class TreeSet_ {
    public static void main(String[] args) {
        //老韩解读
        // 1. 当我们使用无参构造器,创建 TreeSet 时,仍然是无序的(没有按照插入顺序),这里是按照ASCII码进行自动排序
        // 2. 老师希望添加的元素,按照字符串大小来排序
        // 3. 使用 TreeSet 提供的一个构造器,可以传入一个比较器(匿名内部类) 并指定排序规则
        // 4,简单看看源码
        //老韩解读
        /*
        1. 构造器把传入的比较器对象,赋给了 TreeSet 的底层的 TreeMap 的属性 this.comparator
        public TreeSet(Comparator<? super E> comparator) {
                this(new TreeMap<>(comparator));
            }
        2.在 调用 treeSet.add("tom"), 在底层会执行到
        if (cpr != null) { //cpr 就是我们的匿名内部类(对象)
            do {
                parent = t;
                //动态绑定到我们的匿名内部类(对象)compare
                cmp = cpr.compare(key, t.key);
                if (cmp < 0)
                    t = t.left;
                else if (cmp > 0)
                    t = t.right;
                else//如果相等,即返回 0,这个 Key 就没有加入
                    return t.setValue(value);
            } while (t != null);
        }
            */
        TreeSet treeSet = new TreeSet(new Comparator() {
            @Override
            public int compare(Object o1, Object o2) {
//                return ((String)o1).compareTo((String) o2);正序
               // return ((String)o2).compareTo((String) o1);//倒序
                return ((String)o2).length() - ((String)o1).length(); //按照长度大小排序
            }
        });
        //添加数据
        treeSet.add("jack");
        treeSet.add("tom");
        treeSet.add("hsp");
        treeSet.add("a");
        treeSet.add("tom");


        System.out.println("treeSet=" + treeSet);
    }
}
1.17.2 TreeMap源码讲解
@SuppressWarnings({"all"})
public class TreeMap_ {
    public static void main(String[] args) {
        //使用默认的构造器,创建 TreeMap, 是无序的(也没有排序)
        //仍然是无序的(没有按照插入顺序),这里是按照ASCII码进行自动排序
        /*
        老韩要求:按照传入的 k(String) 的大小进行排序
         */
//        TreeMap treeMap = new TreeMap();
        TreeMap treeMap = new TreeMap(new Comparator() {
            //按照传入的 k(String) 的大小进行排序
            // 按照 K(String) 的长度大小排序
            @Override

            public int compare(Object o1, Object o2) {
                //return ((String)o2).compareTo((String)o1) ;
                return ((String)o2).length() - ((String)o1).length();
            }
        });
        treeMap.put("jack", "杰克");
        treeMap.put("tom", "汤姆");
        treeMap.put("kristina", "克瑞斯提诺");
        treeMap.put("smith", "斯密斯");
        treeMap.put("hsp", "韩顺平");//加入不了

        System.out.println(treeMap);
        /*
        1. 构造器. 把传入的实现了 Comparator 接口的匿名内部类(对象), 传给给 TreeMap 的 comparator
        public TreeMap(Comparator<? super K> comparator) {
         this.comparator = comparator;
         }
         \2. 调用 put 方法

        2.1 第一次添加, 把 k-v 封装到 Entry 对象,放入 root
        Entry<K,V> t = root;
        if (t == null) {
            compare(key, key); // type (and possibly null) check
            root = new Entry<>(key, value, null);
            size = 1;
            modCount++;
            return null;
        }
        2.2 以后添加
        Comparator<? super K> cpr = comparator;
            if (cpr != null) {
                do { //遍历所有的 key , 给当前 key 找到适当位置
                parent = t;
                因为传入的o1 o2,比较的时候o1-o2那就是从小到大,o2-o1就是从大到小,可以带数进去试一试,这里很巧秒的
                cmp = cpr.compare(key, t.key);//动态绑定到我们的匿名内部类的 compare
              if (cmp < 0)
                t = t.left;
              else if (cmp > 0)
                t = t.right;
                else //如果遍历过程中,发现准备添加 Key 和当前已有的 Key 相等,就不添加
                return t.setValue(value);
                }
                while (t != null);
                }
                 */
    }
}

如何比较大小:因为传入的o1 o2,比较的时候o1-o2那就是从小到大,o2-o1就是从大到小,可以带数进去试一试,这里很巧秒的
在这里插入图片描述

TreeMap底层源码

在这里插入图片描述

1.18 Collections 工具类

1.18.1 Collections 工具类介绍

在这里插入图片描述

1.18.2 Collections 方法

排序操作:(均为 static 方法)

在这里插入图片描述

查找、替换

在这里插入图片描述

代码演示:

public class Collections_ {
    public static void main(String[] args) {

        //创建 ArrayList 集合,用于测试.
        List list = new ArrayList();
        list.add("tom");
        list.add("smith");
        list.add("king");
        list.add("milan");
        list.add("tom");

        //reverse(List):反转 List 中元素的顺序
        Collections.reverse(list);
        System.out.println("list=" + list);
        //shuffle(List):对 List 集合元素进行随机排序
        System.out.println("====shuffle(List)===");
        for (int i = 0; i < 5; i++) {
            Collections.shuffle(list);
            System.out.println("list=" + list);
        }

        //sort(List):根据元素的自然顺序对指定(ASCII) List 集合元素按升序排序
        Collections.sort(list);
        System.out.println("===自然排序后===");
        System.out.println("list=" + list);
        //sort(List,Comparator):根据指定的 Comparator 产生的顺序对 List 集合元素进行排序
        //我们希望按照 字符串的长度大小排序
        Collections.sort(list, new Comparator() {
            @Override
                public int compare(Object o1,Object o2){
                    //可以加入校验代码.
                return ((String)o2).length() - ((String)o1).length();
            }
        });
        System.out.println("====字符串的长度大小排序====");
        System.out.println("list=" + list);

        //swap(List,int, int):将指定 list 集合中的 i 处元素和 j 处元素进行交换
        Collections.swap(list,0,1);
        System.out.println("元素进行交换 :list=" + list);

        //Object max(Collection):根据元素的自然顺序,返回给定集合中的最大元素
        System.out.println("====集合中的最大元素====");
        System.out.println(Collections.max(list));
        //Object max(Collection,Comparator):根据 Comparator 指定的顺序,返回给定集合中的最大元素
        // 比如,我们要返回长度最大的元素
        Object max = Collections.max(list, new Comparator() {
            @Override
            public int compare(Object o1, Object o2) {
                return ((String)o1).length() - ((String)o2).length();

            }
        });
        System.out.println("==长度最大的元素==");
        System.out.println(max);
        //Object min(Collection)
        // Object min(Collection,Comparator)
        // 上面的两个方法,参考 max 即可

        //int frequency(Collection,Object):返回指定集合中指定元素的出现次数
        System.out.println("tom 出现的次数=" + Collections.frequency(list, "tom"));

        //void copy(List dest,List src):将 src 中的内容复制到 dest 中
        ArrayList dest = new ArrayList();
        //为了完成一个完整拷贝,我们需要先给 dest 赋值,大小和 list.size()一样
        for (int i = 0; i < list.size(); i++) {
            dest.add("");
        }
        //拷贝
        Collections.copy(dest,list);
        System.out.println("===将 src 中的内容复制到 dest 中===");
        System.out.println(dest);

        //boolean replaceAll(List list,Object oldVal,Object newVal):使用新值替换 List 对象的所有旧值
        //如果 list 中,有 tom 就替换成 汤姆
        Collections.replaceAll(list, "tom", "汤姆");
        System.out.println("list 替换后=" + list);
    }
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-cMrcSA9G-1648439966876)(C:\Users\admin\AppData\Roaming\Typora\typora-user-images\image-20220324113312982.png)]

1.19 本章作业

1.封装一个新闻类,包含标题和内容属性,

在这里插入图片描述

代码演示:

public class HomeWork01 {
    /*
        按要求实现:
        (1)封装一个新闻类,包含标题和内容属性,提供get、set方法,重写toString方法,打印对擦
        时只打印标题;
        (2)只提供一个带参数的构造器,实例化对象时,只初始化标题:并且实例化两个对象:
        新闻一:新冠确诊病例超干万,数百万印度教信徒赴恒河“圣浴”引民众担忧
        新闻二:男子突然想起2个月前钓的鱼还在网兜里,捞起一看赶紧放生
        (3)将新闻对象添加到ArrayList:集合中,并且进行倒序遍历;
        (4)在遍历集合过程中,对新闻标题进行处理,超过15字的只保留前15个,然后在后边加
        (5)在控制台打印遍历出经过处理的新闻标题;
     */
    public static void main(String[] args) {
        List list = new ArrayList();
        News news1 = new News("新闻一:新冠确诊病例超干万,数百万印度教信徒赴恒河“圣浴”引民众担忧");
        News news2 = new News("新闻二:男子突然想起2个月前钓的鱼还在网兜里,捞起一看赶紧放生");
        list.add(news1);
        list.add(news2);
        for (int i = list.size() - 1; i >= 0; i--) {
            News news = (News) list.get(i);
            System.out.println(processTitle(processTitle(news.getTitle())));
        }
    }

    //专门写一个方法,处理现实新闻标题process处理
    public static String processTitle(String title) {
        if (title == null) {
            return "";
        }
        if (title.length() > 15) {
            return title.substring(0, 15) + "..."; //左闭右开
        } else {
            return title;
        }
    }
}

class News {
    private String title;
    private String content;

    public News(String title) {
        this.title = title;
    }

    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }

    public String getContent() {
        return content;
    }

    public void setContent(String content) {
        this.content = content;
    }

    @Override
    public String toString() {
        return "News{" +
                "title='" + title + '\'' +
                '}';
    }
}
2.使用ArrayList完成对对象Car{name,price}的各种操作

在这里插入图片描述

代码演示:

@SuppressWarnings({"all"})
public class HomeWork02 {
    public static void main(String[] args) {
        ArrayList arrayList = new ArrayList();
        Car car1=new Car("宝马",400000);
        Car car2=new Car("宾利",5000000);
        arrayList.add(car1);
        arrayList.add(car2);
        //2.remove:删除指定元素
        arrayList.remove(0);
        System.out.println("2arrayList=" + arrayList);
        arrayList.add(car1); //重新添加
        // 3.contains:查找元素是否存在
        System.out.println("3contains:查找元素是否存在:" + arrayList.contains(car1));
        //4.size获取元素个数
        System.out.println("4size获取元素个数:" + arrayList.size());
        //5.isEmpty:判断是香为空
        System.out.println("5isEmpty:判断是香为空:" + arrayList.isEmpty());
        //6.clear:清空
        //arrayList.clear();
        //System.out.println("arrayList=" + arrayList);
        //7.addAll::添加多个元素
        List list = new ArrayList();
        Car car3 = new Car("吉利",20000);
        Car car4 = new Car("奔驰",60000);
        list.add(car3);
        list.add(car4);
        arrayList.addAll(list);
        System.out.println("7添加多个元素arrayList=" + arrayList);
        //8.containsAll:查找多个元素是否都存在
        System.out.println("8查找多个元素是否都存在" + arrayList.contains(list));
        //9.removeAll:删除多个元素
        arrayList.removeAll(list);
        System.out.println("9删除多个元素arrayList=" + arrayList);
        //使用增强for和迭代器来遍历所有的car,需要重写Car的toString.方法
        Iterator iterator = arrayList.iterator();
        System.out.println("迭代器来遍历");
        while (iterator.hasNext()) {
            Object car = iterator.next();
            System.out.println(car);
        }
        System.out.println("增强for");
        for (Object car : arrayList) {
            System.out.println(car);
        }

    }
}
class Car {
    private String name;
    private double price;

    public Car(String name, double price) {
        this.name = name;
        this.price = price;
    }

    public String getName() {
        return name;
    }

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

    public double getPrice() {
        return price;
    }

    public void setPrice(double price) {
        this.price = price;
    }

    @Override
    public String toString() {
        return "Car{" +
                "name='" + name + '\'' +
                ", price=" + price +
                '}';
    }
}
3. 使用HashMap类实例化一个Map类型的对象m,键(String)和值(int)

在这里插入图片描述

public class HomeWork03 {
    public static void main(String[] args) {
        /*
        按要求完成下列任务
        1.使用HashMap类实例化一个Map类型的对象m,键(String)和值(int)分别用于存储员
        工的姓名和工资,存入数据如下:jack—650元;tom一1200元;smith—2900元;
        2.将jack的工资更改为2600元
        3.为所有员工工资加薪100元:
        4.遍历集合中所有的员工
        遍历集合中所有的工资
         */
        HashMap hashMap = new HashMap();
        hashMap.put("jack",650); //in -> Integer
        hashMap.put("tom",1200);//in -> Integer
        hashMap.put("smith",2900);//in -> Integer
        System.out.println("hashMap =" + hashMap);
        //2.将jack的工资更改为2600元
        hashMap.put("jack",2600);
        System.out.println("将jack的工资更改为2600元hashMap=" + hashMap);
        //3.为所有员工工资加薪100元:
       /*自己的写法
       Set entrySet = hashMap.entrySet();
        Iterator iterator = entrySet.iterator();
        while (iterator.hasNext()) {
            Map.Entry entry = (Map.Entry)iterator.next();
            int s =((int)(entry.getValue()))+100;
            hashMap.put(entry.getKey(),s);
        }
        System.out.println(hashMap);
*/
        //韩老师写法
        Set keySet = hashMap.keySet();
        for (Object key : keySet) {
            //更新
            hashMap.put(key,(Integer)hashMap.get(key)+100);  //hashMap.get(key)取出来是Object类
        }
        //迭代器
        Set keySet1 = hashMap.keySet();
        Iterator iterator = keySet.iterator();
        System.out.println("==迭代器==");
        while (iterator.hasNext()) {
            Object key =  iterator.next();
            System.out.println(key + "-" + hashMap.get(key));
        }
        //通过 EntrySet 来获取 k-v
        Set entrySet = hashMap.entrySet();// EntrySet<Map.Entry<K,V>>
        for (Object entry :entrySet) {
            Map.Entry m = (Map.Entry) entry;
            System.out.println(m.getKey() + "-"+ m.getValue());
        }
        //遍历集合中所有的工资
        for (Object entry : entrySet) {
            Map.Entry m = (Map.Entry) entry;
            System.out.println("遍历集合中所有的工资" + m.getValue());
        }

    }
}

4.试分析HashSeti和TreeSet:分别如何实现去重的

在这里插入图片描述

5.

在这里插入图片描述

public class HomeWork05 {
    public static void main(String[] args) {
        TreeSet treeSet = new TreeSet();
        //分析源码
        //add方法,因为TreeSet()构造器没有传入Comparator接口的匿名内部类
        //所以在底层Comparable<? super K>k=(Comparable<?super K>)key;
        //即把Person转成Comparable类型
        treeSet.add(new Person());//ClassCastException.
        treeSet.add(new Person());//ClassCastException.
        treeSet.add(new Person());//ClassCastException.
        System.out.println("treeSet" + treeSet); //只有一个,因为compareTo返回0
    }
}
class Person implements Comparable{

    @Override
    public int compareTo(Object o) {
        return 0;
    }
}
6.

在这里插入图片描述

 */
public class HomeWork06 {
    public static void main(String[] args) {
        HashSet set  = new HashSet();//ok
        Person p1 = new Person(1001,"AA");//ok
        Person p2 = new Person(1002,"BB");//ok
        set.add(p1);//ok
        set.add(p2);//ok
        p1.name = "CC";
        set.remove(p1); //删除不了,
        System.out.println(set);//2
        set.add(new Person(1001,"CC"));//ok
        System.out.println(set);//3
        set.add(new Person(1001,"AA"));//ok
        System.out.println(set);//4个对象
    }
}
class Person{
     String name;
     int id;

    public Person(int id,String name) {
        this.name = name;
        this.id = id;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Person person = (Person) o;
        return id == person.id && Objects.equals(name, person.name);
    }

    @Override
    public int hashCode() {
        return Objects.hash(name, id);
    }

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", id=" + id +
                '}';
    }
}

在这里插入图片描述

7. 试写出Vector和ArrayList的比较?
底层结构版本线程安全(同步)效率扩容倍数
ArrayList可变数组jdk1.2不安全,高效无参构造是初始第一次扩容是10,然后第二次1.5倍扩容。初始(有参)有长度就直接1.5倍扩容
Vector可变数组Object[]jdk1.0安全,不高效如果是无参,默认为10,默认按2倍扩容。如果是指定大小创建Vector每次按照两倍扩容
在这里插入图片描述

2 .泛型

2.1 泛型的理解和好处

2.1.1 看一个需求

在这里插入图片描述

public class Generic {
    public static void main(String[] args) {
        //使用传统的方法来解决
        ArrayList arrayList = new ArrayList();
        arrayList.add(new Dog("旺财",10));
        arrayList.add(new Dog("来福",15));
        arrayList.add(new Dog("小黄",5));
        //添加猫猫
        arrayList.add(new Cat("招财",5));
        for (Object o :arrayList) {
            //.ClassCastException
            Dog dog = (Dog)o;
            System.out.println(dog.name+dog.age);
        }

    }
}
/*
请编写程序,在ArrayList中,添加3个Dog对象
Dog对象含有name 和 age,并输出name 和 age(要求使用getXxx())
 */
class Dog{
    public String name;
    public int age;

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

    @Override
    public String toString() {
        return "Dog{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}
class Cat{
    public String name;
    public int age;

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

    @Override
    public String toString() {
        return "Cat{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}

在这里插入图片描述

2.1.2使用传统方法的问题分析

在这里插入图片描述

2.1.3 泛型快速体验-用泛型来解决前面的问题

代码演示:

@SuppressWarnings({"all"})
public class Generic02 {
    public static void main(String[] args) {
        //使用传统的方法来解决==>泛型
        //老汉解读
        //1.当我ArrayList<Dog> 表示存放到ArrayList 集合中的元素是Dog类型
        //2. 如果编译器发现添加的类型,不满足要求,就会报错
        //3. 在遍历的时候可以直接取出 Dog 类型而不是 Object 类型
        ArrayList<Dog> arrayList = new ArrayList<Dog>();
        arrayList.add(new Dog("旺财",10));
        arrayList.add(new Dog("来福",15));
        arrayList.add(new Dog("小黄",5));
        //不小心添加猫猫
        //arrayList.add(new Cat("招财",5));
        System.out.println("==使用泛型==");
        for (Dog dog :arrayList) {
            System.out.println(dog.name+dog.age);
        }
    }
}
/*
请编写程序,在ArrayList中,添加3个Dog对象
Dog对象含有name 和 age,并输出name 和 age(要求使用getXxx())
//用泛型来完成
 */
class Dog{
    public String name;
    public int age;

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

    @Override
    public String toString() {
        return "Dog{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}
class Cat{
    public String name;
    public int age;

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

    @Override
    public String toString() {
        return "Cat{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}

2.2 泛型的理解和好处

2.2.1 泛型的好处

在这里插入图片描述

2.3 泛型介绍

在这里插入图片描述

代码演示:

public class Generic03 {
    public static void main(String[] args) {
        //注意,特别强调: E 具体的数据类型在定义 Person 对象的时候指定,即在编译期间,就确定 E 是什么类型
        Person<String> stringPerson = new Person<String>("hsp");
        stringPerson.show();

      //可以这样理解,上面的Person类
            /*
            class Person<E> {
    E s;

    public Person(E s) { //传入参数类型为E
        this.s = s;//E 表示 s 的数据类型, 该数据类型在定义 Person 对象的时候指定,即在编译期间,就确定 E 是什么类型
    }
    public E f(){//返回类型使用E
        return s;
    }
    public void show(){
        System.out.println(s.getClass());//显示s的运行类型
    }
}
             */
        Person<Integer> integerPerson = new Person<>(100);
        integerPerson.show();
        /*
        class Person<E> {
    E s;

    public Person(E s) { //传入参数类型为E
        this.s = s;//E 表示 s 的数据类型, 该数据类型在定义 Person 对象的时候指定,即在编译期间,就确定 E 是什么类型
    }
    public E f(){//返回类型使用E
        return s;
    }
    public void show(){
        System.out.println(s.getClass());//显示s的运行类型
    }
}
         */
    }
}
//泛型的作用是:可以在类声明时通过一个标识表示类中某个属性的类型,
//或者是某个方法的返回值的类型,或者是参数类型
class Person<E> {
    E s;

    public Person(E s) { //传入参数类型为E
        this.s = s;//E 表示 s 的数据类型, 该数据类型在定义 Person 对象的时候指定,即在编译期间,就确定 E 是什么类型
    }
    public E f(){//返回类型使用E
        return s;
    }
    public void show(){
        System.out.println(s.getClass());//显示s的运行类型
    }
}

2.4 泛型的语法

2.4.1 泛型的声明

在这里插入图片描述

2.4.2 泛型的实例化

在这里插入图片描述

2.4.3 泛型使用举例

在这里插入图片描述

代码演示:

/** 创建 3 个学生对象 * 放入到 HashSet 中学生对象,
 * 使用 放入到 HashMap 中,要求 Key 是 String name,
 * Value 就是 学生对象 * 使用两种方式遍历 */
public class GenericExercise01 {
    public static void main(String[] args) {
        HashSet<Student> hashSet = new HashSet<Student>();
        //使用泛型方式给 HashSet 放入 3 个学生对象
        hashSet.add(new Student("jack",18));
        hashSet.add(new Student("tom",28));
        hashSet.add(new Student("mary",19));
        //遍历
        Iterator<Student> iterator = hashSet.iterator();
        while (iterator.hasNext()) {
            Student student = iterator.next();
            System.out.println("student=" + student);
        }
        System.out.println("=========hashSet增强for:======");
        for (Student student : hashSet) {
            System.out.println(student.getName() + "-" + student.getAge());
        }


        使用泛型方式给 HashMap 放入 3 个学生对象
        HashMap<String,Student> hashMap = new HashMap<String, Student>();
        hashMap.put("milan",new Student("milan",18));
        hashMap.put("smith",new Student("smith",28));
        hashMap.put("hsp",new Student("hsp",25));
        //EntrySet 迭代器
        /*public Set<Map.Entry<K,V>> entrySet() {
            Set<Map.Entry<K,V>> es;
            return (es = entrySet) == null ? (entrySet = new EntrySet()) : es;
        }*/
        Set<Map.Entry<String, Student>> entries = hashMap.entrySet();
        /*
        public final Iterator<Map.Entry<K,V>> iterator() {
         return new EntryIterator();
         }
         */
        System.out.println("==============================");
        Iterator<Map.Entry<String, Student>> iterator1 = entries.iterator();

        while (iterator1.hasNext()) {
            Map.Entry<String, Student> studentEntry = iterator1.next();
            System.out.println(studentEntry.getKey() + "-" + studentEntry.getValue());
        }

    }
}

class Student{
    private String name;
    private int age;

    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 String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}
2.4.4 泛型使用的注意事项和细节

在这里插入图片描述

public class GenericDetail {
    public static void main(String[] args) {
        //1.给泛型指向数据类型是,要求是引用类型,不能是基本数据类型
        List<Integer> list = new ArrayList<Integer>(); //OK
        //List<int> list2 = new ArrayList<int>();//错误

        //2. 说明
        // 因为 E 指定了 A 类型, 构造器传入了 new A()
        // 在给泛型指定具体类型后,可以传入该类型或者其子类类型
        Pig<A> aPig = new Pig<A>(new A());
        aPig.f();
        Pig<A> aPig2 = new Pig<A>(new B());
        aPig2.f();

        //3. 泛型的使用形式
        ArrayList<Integer> list1 = new ArrayList<Integer>();
        List<Integer> integers = new ArrayList<Integer>();
        //在实际开发中,我们往往简写
        // 编译器会进行类型推断, 老师推荐使用下面写法
        ArrayList<Integer> lists = new ArrayList<>();
        List<Integer> integers1 = new ArrayList<>();
        ArrayList<Pig> pigs = new ArrayList<>();
        
    }
}
class A{}
class B extends A{}
class Pig<E>{
    E e;

    public Pig(E e) {
        this.e = e;
    }
    public void f(){
        System.out.println(e.getClass());
    }
}

2.5 泛型课堂类型

2.5.1 泛型课堂练习题

在这里插入图片描述

代码演示:

/**定义 Employee 类
 * 1) 该类包含:private 成员变量 name,sal,birthday,其中 birthday 为 MyDate 类的对象;
 * 2) 为每一个属性定义 getter, setter 方法;
 * 3) 重写 toString 方法输出 name, sal, birthday
 * 4) MyDate 类包含: private 成员变量 month,day,year;并为每一个属性定义 getter, setter 方法;
 * 5) 创建该类的 3 个对象,并把这些对象放入 ArrayList 集合中(ArrayList 需使用泛型来定义),对集合中的元素进
 行排序,并遍历输出:
 *
 * 排序方式: 调用 ArrayList 的 sort 方法 ,
 * 传入 Comparator 对象[使用泛型],先按照 name 排序,如果 name 相同,则按生日日期的先后排序。【即:定制排序】
 * 有一定难度 15min , 比较经典 泛型使用案例 GenericExercise02.java
 */
public class GenericExercise02 {
    public static void main(String[] args) {
        ArrayList<Employee> employees = new ArrayList<>();
        employees.add(new Employee("jack",2000.0,new MyDate(2000,11,14)));
        employees.add(new Employee("tom",3000.2,new MyDate(1999,11,24)));
        employees.add(new Employee("mary",2050.0,new MyDate(2000,04,29)));
        //迭代器
        System.out.println("====迭代器====");
        Iterator<Employee> iterator = employees.iterator();
        while (iterator.hasNext()) {
            Employee employee = iterator.next();
            System.out.println(employee);
        }
        employees.sort(new Comparator<Employee>() {
            @Override
            public int compare(Employee employee1, Employee employee2) {
                //先按照 name 排序,如果 name 相同,则按生日日期的先后排序。【即:定制排序】
                //先对传入的参数进行验证
                if (!(employee1 instanceof Employee && employee2 instanceof Employee)){
                    System.out.println("类型不正确");
                    return 0;
                    //我觉得这里没有必要,因为compare(Employee employee1, Employee employee2)
                    // 这里只能传入这个类型或者他的子类
                }
                int i = employee1.getName().compareTo(employee2.getName());
                if (i != 0){
                    return i;
                }
                /*
                //如果name相同 比较生日birthday -year
                int yearMinus = employee1.getBirthday().getYear() - employee2.getBirthday().getYear();
                if (yearMinus != 0){
                    return yearMinus;
                }
                //如果year相同 比较生日birthday -month
                int monthMinus = employee1.getBirthday().getMonth() - employee2.getBirthday().getMonth();
                if (monthMinus != 0){
                    return monthMinus;
                }
                //如果year相同 比较生日birthday -month
                return employee1.getBirthday().getDay() - employee2.getBirthday().getDay();
                */

                //上面是对 birthday 的比较,因此,我们最好把这个比较,放在 MyDate 类完成
                //封装后,将来可维护性和复用性,就大大增强
                //这里秩序返回比较的结果
                return employee1.getBirthday().compareTo(employee2.getBirthday());
            }
        });
        System.out.println("对员工进行排序");
        System.out.println(employees);
    }
}

class Employee{
    private String name;
    private double sal;
    private MyDate birthday;

    public Employee(String name, double sal, MyDate birthday) {
        this.name = name;
        this.sal = sal;
        this.birthday = birthday;
    }

    public String getName() {
        return name;
    }

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

    public double getSal() {
        return sal;
    }

    public void setSal(double sal) {
        this.sal = sal;
    }

    public MyDate getBirthday() {
        return birthday;
    }

    public void setBirthday(MyDate birthday) {
        this.birthday = birthday;
    }

    @Override
    public String toString() {
        return "\nEmployee{" +
                "name='" + name + '\'' +
                ", sal=" + sal +
                ", birthday=" + birthday +
                '}';
    }
}

class MyDate implements Comparable<MyDate>{
    private int year;
    private int month;
    private int day;

    public MyDate(int year, int month, int day) {
        this.year = year;
        this.month = month;
        this.day = day;
    }

    public int getYear() {
        return year;
    }

    public void setYear(int year) {
        this.year = year;
    }

    public int getMonth() {
        return month;
    }

    public void setMonth(int month) {
        this.month = month;
    }

    public int getDay() {
        return day;
    }

    public void setDay(int day) {
        this.day = day;
    }

    @Override
    public String toString() {
        return "MyDate{" +
                "year=" + year +
                ", month=" + month +
                ", day=" + day +
                '}';
    }

    @Override
    public int compareTo(MyDate o) {
        //把对year-month-day的比较放在这里
        int yearMinus = year - o.getYear();
        if (yearMinus != 0){
            return yearMinus;
        }
        //如果year相同 比较生日birthday -month
        int monthMinus = month - o.getMonth();
        if (monthMinus != 0){
            return monthMinus;
        }
        //如果year相同 比较生日birthday -month
        return day - o.getDay();
    }
}

在这里插入图片描述

2.6 自定义泛型

2.6.1 自定义泛型类 (难度)

在这里插入图片描述

在这里插入图片描述

代码演示:

public class CustomGeneric_ {

}
    //老韩解读
    //1. Tiger 后面泛型,所以我们把 Tiger 就称为自定义泛型类
    //2, T, R, M 泛型的标识符, 一般是单个大写字母
    //3. 泛型标识符可以有多个.
    //4. 普通成员可以使用泛型 (属性、方法)
    //5. 使用泛型的数组,不能初始化
    //6. 静态方法中不能使用类的泛型
class Tiger<T,R,M>{
    String name;
    T t; //属性使用到泛型
    R r;
    M m;
    //定义数组不能定义数组空间 ,因为数组在 new 不能确定 T 的类型,就无法在内存开空间
    T[] ts;
    //构造器使用泛型(方法)
    public Tiger(String name, T t, R r, M m) {
        this.name = name;
        this.t = t;
        this.r = r;
        this.m = m;
    }
    //因为静态是和类相关的,在类加载时,对象还没有创建
    // 所以,如果静态方法和静态属性使用了泛型,JVM 就无法完成初始化
    //static R r2;
    //public static void m1(M m){}

    public String getName() {//方法使用泛型
        return name;//返回类型使用泛型
    }

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

    public T getT() {
        return t;
    }

    public void setT(T t) {
        this.t = t;
    }

    public R getR() {
        return r;
    }

    public void setR(R r) {
        this.r = r;
    }

    public M getM() {
        return m;
    }

    public void setM(M m) {
        this.m = m;
    }
}

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

public class CustomGeneric_ {
    public static void main(String[] args) {
        //T=Double, R=String, M=Integer
        Tiger<Double, String, Integer> g = new Tiger<>("john");
        g.setT(10.9); //OK
        // g.setT("yy"); //错误,类型不对
        System.out.println(g);
        Tiger g2 = new Tiger("john~~");//OK  T=Object R = Object M=Object
        g2.setT("yy"); //OK ,因为 T=Object "yy"=String 是 Object 子类
        System.out.println("g2="+g2);
    }
    }

//老韩解读
//1. Tiger 后面泛型,所以我们把 Tiger 就称为自定义泛型类
//2, T, R, M 泛型的标识符, 一般是单个大写字母
//3. 泛型标识符可以有多个.
//4. 普通成员可以使用泛型 (属性、方法
//5. 使用泛型的数组,不能初始化
//6. 静态方法中不能使用类的泛型
class Tiger<T, R, M> {
    String name;
    T t; //属性使用到泛型
    R r;
    M m;
    //定义数组不能定义数组空间 ,因为数组在 new 不能确定 T 的类型,就无法在内存开空间
    T[] ts;

    public Tiger(String name) {
        this.name = name;
    }

    //构造器使用泛型(方法)
    public Tiger(String name, T t, R r, M m) {
        this.name = name;
        this.t = t;
        this.r = r;
        this.m = m;
    }
    //因为静态是和类相关的,在类加载时,对象还没有创建
    // 所以,如果静态方法和静态属性使用了泛型,JVM 就无法完成初始化
    //static R r2;
    //public static void m1(M m){}

    public String getName() {//方法使用泛型
        return name;//返回类型使用泛型
    }

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

    public T getT() {
        return t;
    }

    public void setT(T t) {
        this.t = t;
    }

    public R getR() {
        return r;
    }

    public void setR(R r) {
        this.r = r;
    }

    public M getM() {
        return m;
    }

    public void setM(M m) {
        this.m = m;
    }

}
2.6.2 自定义泛型接口

在这里插入图片描述

代码演示:

public class CustomInterfaceGeneric {
    public static void main(String[] args) {

    }
}
/**
 * 泛型接口使用的说明 *
 * 1. 接口中,静态成员也不能使用泛型 *
 * 2. 泛型接口的类型, 在继承接口或者实现接口时确定 *
 * 3. 没有指定类型,默认为 Object
 */
//在继承接口 指定泛型接口的类型
interface IA extends IUsb<String, Double>{
}
//当我们去实现 IA 接口时,因为 IA 在继承 IUsu 接口时,指定了 U 为 String R 为 Double
// ,在实现 IUsu 接口的方法时,使用 String 替换 U, 是 Double 替换 R
class AA implements IA{
    @Override
    public Double get(String s) {
        return null;
    }
    @Override
    public void hi(Double aDouble) {

    }
    @Override
    public void run(Double r1, Double r2, String u1, String u2) {

    }
}
//实现接口时,直接指定泛型接口的类型
// 给 U 指定 String 给 R 指定了 Integer
// 所以,当我们实现 IUsb 方法时,会使用 String 替换 U, 使用 Integer 替换 R
class BB implements IUsb<String,Integer>{
    @Override
    public Integer get(String s) {
        return null;
    }
    @Override
    public void hi(Integer integer) {
    }
    @Override
    public void run(Integer r1, Integer r2, String u1, String u2) {
    }
}
//没有指定类型,默认为 Object
// 建议直接写成 IUsb<Object,Object>

class CC implements IUsb{// 等价class CC implements IUsb<Object,Object> {
    @Override
    public Object get(Object o) {
        return null;
    }
    @Override
    public void hi(Object o) {
    }
    @Override
    public void run(Object r1, Object r2, Object u1, Object u2) {
    }
}
interface IUsb<U,R>{
    int n = 10;
    //U name; 不能这样使用,因为还不是知道U是什么类型
    //普通方法中,可以使用接口泛型
    R get(U u);
    void hi(R r);
    void run(R r1,R r2, U u1, U u2);
    //再jdk8中,可以在接口中,使用默认方法;
    default R method(R r){
        return null;
    }
}
2.6.3 自定义泛型方法

在这里插入图片描述

代码演示:

public class CustomMethodGeneric {
    public static void main(String[] args) {
        Car car = new Car();
        car.fly("宝马",100);//当调用方法时,传入参数,编译器,就会确定类型
        car.fly(100,200);//当调用方法时,传入参数,编译器,就会确定类型
    }
}

//泛型方法,可以定义在普通类中, 也可以定义在泛型类中
class Car {//普通类

    public void run() { //普通方法

    }

    //说明 泛型方法
    // 1. <T,R> 就是泛型
    // 2. 是提供给 fly 使用的
    public <T, R> void fly(T t, R r) { //泛型方法
        System.out.println(t.getClass());
        System.out.println(r.getClass());

        //测试
        // T->String, R-> ArrayList
        Fish<String, ArrayList> fish = new Fish<>();
        fish.hello(new ArrayList(), 11.3f);
    }
}

class Fish<T, R> {//泛型类

    public void run() {//普通方法
    }

    public <U, M> void eat(U u, M m) {//泛型方法
    }
    //说明
    // 1. 下面 hi 方法不是泛型方法
    // 2. 是 hi 方法使用了类声明的 泛型
    public void hi(T t) {

    }
    //泛型方法,可以使用类声明的泛型,也可以使用自己声明泛型
    public <K> void hello(R r, K k) {
        System.out.println(r.getClass());
        System.out.println(k.getClass());
    }
    }

自定义泛型方法练习

在这里插入图片描述

代码演示

public class CustomMethodGenericExercise {
    public static void main(String[] args) {
        //T->String, R->Integer, M->Double
        Apple<String, Integer, Double> apple = new Apple<>();
        apple.fly(10);//10 会被自动装箱 Integer10, 输出 Integer
        apple.fly(new Bird());//Bird
    }
}
class Apple<T, R, M> {//自定义泛型类
    public <E> void fly(E e) { //泛型方法
    System.out.println(e.getClass().getSimpleName());
    }
    //public void eat(U u) {}//错误,因为 U 没有声明
    public void run(M m) { } //ok
}
class Bird { }

2.7 泛型的继承和通配符

2.7.1 泛型的继承和通配符说明 GenericExtends

在这里插入图片描述

代码演示:

public class GenericExtends {
    public static void main(String[] args) {
        Object o = new String("xx");
        //泛型没有继承性
        //List<Object> list = new ArrayList<String>();

        //举例说明下面三个方法的使用
        List<Object> list1 = new ArrayList<>();
        List<String> list2 = new ArrayList<>();
        List<AA> list3 = new ArrayList<>();
        List<BB> list4 = new ArrayList<>();
        List<CC> list5 = new ArrayList<>();

        //如果是 List<?> c ,可以接受任意的泛型类型
        printCollection1(list1);
        printCollection1(list2);
        printCollection1(list3);
        printCollection1(list4);
        printCollection1(list5);

        //List<? extends AA> c: 表示 上限,可以接受 AA 或者 AA 子类
        //printCollection2(list1);//x Object 是父类
       // printCollection2(list2);//x String 和AA没有关系
        printCollection2(list3);//√
        printCollection2(list4);//√
        printCollection2(list5);//√

        printCollection3(list1);//√
        //printCollection3(list2);//x String 和AA没有关系
        printCollection3(list3);//√
        //printCollection3(list4);//x AA的子类
        //printCollection3(list5);//x AA的子类

    }

    //说明: List<?> 表示 任意的泛型类型都可以接受
    public static void printCollection1(List<?> c) {
        for (Object object : c) { // 通配符,取出时,就是 Object
            System.out.println(object);
        }
    }

    // ? extends AA 表示 上限,可以接受 AA 或者 AA 子类
    public static void printCollection2(List<? extends AA> c) {
        for (Object object : c) {
            System.out.println(object);
        }
    }

    // ? super 子类类名 AA:支持 AA 类以及 AA 类的父类,不限于直接父类,
    // 规定了泛型的下限
    public static void printCollection3(List<? super AA> c) {
        for (Object object : c) {
            System.out.println(object);
        }
    }
}

class AA {
}

class BB extends AA {
}

class CC extends BB {
}

2.8 本章作业

在这里插入图片描述

public class HomeWork01 {
    public static void main(String[] args) {

    }
    @Test
    public void testList(){
        //说明,我们这里给T 指定的泛型就是User
        DAO<User> userDAO = new DAO<>();
        userDAO.save( "11",new User(11,12,"jack"));
        userDAO.save( "12",new User(12,15,"tom"));
        System.out.println("使用save()后的userDAO.map:" + userDAO.map);
        System.out.println("使用get()后" + userDAO.get("11"));
        userDAO.update("11",new User(13,22,"mary"));
        System.out.println("使用update后的userDAO.map:" + userDAO.map);
        System.out.println("使用list()后:" + userDAO.list());
        userDAO.delete("12");
        System.out.println("使用delete()后的userDAO.map:" + userDAO.map);
    }
}

class DAO<T> {
    Map<String, T> map = new HashMap<>();

    public void save(String id, T entry) {
        map.put(id, entry);
    }

    public T get(String id) {
        return map.get(id);
    }

    public void update(String id, T entry) {
        map.put(id, entry);
    }
    //返回map中存放的所有T对象
    //遍历map[k-v],将map的所有valve(T entity),封装到ArrayList返回即可
    public List<T> list() {
        List<T> list = new ArrayList();
        Set set = map.keySet();
        for (Object key: set) {
            list.add(map.get(key));
        }
        return list;
    }

    public void delete(String id) {
        map.remove(id);
    }
}

class User {
    private int id;
    private int age;
    private String name;

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

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public int getAge() {
        return age;
    }

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

    public String getName() {
        return name;
    }

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

    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", age=" + age +
                ", name='" + name + '\'' +
                '}';
    }
}

2.9 JUnit

2.9.1 为什么需要 JUnit

在这里插入图片描述

2.9.2 基本介绍

在这里插入图片描述

2.9.3 使用步骤,看老师演示 JUnit_.java

在这里插入图片描述

我亦无他,惟手熟尔

坦克大战!!!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

别来无恙blwy

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值