JavaSE 进阶 - 集合

1 篇文章 0 订阅

集合

集合非常的重要,在开发中使用的非常之多,是重点中的重点。

1、集合概述

1.1、什么是集合

数组其实就是一个集合。集合实际上就是一个容器。可以用来容纳其他类型的数据。

集合在开发中使用的非常多,因为集合本身就是一个容器,是一个载体,可以一次容纳多个对象。在实际开发中,假设连接数据库,数据库当中有10条记录,那么假设把这十条记录查询出来,Java程序会把这十条数据封装到10个Java对象,然后将10个对象放到某个一个集合当中,再将这个集合传到前端,使用遍历的方式将数据展现出来。

集合不能直接存储基本数据类型,另外集合也不能直接存储Java对象,集合当中存储的都是对象的内存地址。(或者说集合中存储的是引用)

list.add(100); // 自动装箱Integer

注意:集合在Java中本身是一个容器,是一个对象,其中的任何存储的都是“引用”。

在Java中每一个不同的集合,底层会对应不同的数据结构。往不同的集合中存储元素,等于将数据放到了不同的数据结构中。什么是数据结构?数据存储的结构就是数据结构,不同的数据结构数据存储的方式不同。例如:数组、二叉树、链表、哈希表……

new ArrayList(); // 创建一个集合,底层是数组
new LinkedList(); // 创建一个集合对象,底层是链表
new TreeSet(); // 创建一个集合对象,底层是二叉树

1.2、在Java中集合分为两大类

一类是以单个方式存储元素

​ 单个方式存储元素,这一类集合中超级父接口:java.util.Collection

一类是以键值对的方式存储元素

​ 以键值对的方式存储元素,这一类集合中超级父接口:java.util.Map

1.3、Java集合继承的体系

Java中所有的集合类和接口都在java.util.*包下。

1.3.1 Collection集合继承结构图:

在这里插入图片描述

1.3.2 Map集合继承结构图:

在这里插入图片描述

1.3.3 实现类总结
  • ArrayList:底层是数组。
  • LinkedList:底层是双向链表。
  • Vector:底层是数组,线程安全的,效率较低,使用较少。
  • HashSet:底层是HashMap,放到HashSet集合中的元素等同于放到HashMap集合key部分了。
  • HashMap:底层是哈希表。
  • Hashtable:底层也是哈希表,只不过线程是安全的,效率较低,使用较少。
  • Properties:是线程安全的,并且key和value只能存储字符串String。
  • TreeMap:底层是二叉树。TreeMap集合的key可以自动按照大小顺序排序。

List集合存储元素的特点:

​ 有序可重复

​ 有序:存进去的顺序和取出来的顺序相同,每一个元素有下标。

​ 可重复:存进去1,可以在存储一个1.

Set(Map)集合存储元素的特定:

​ 无序不可重复

​ 无序:存进去的顺序和取出来的顺序不一定相同。另外Set集合中元素没有下标。

​ 不可重复:存进去1,不能在存储1了。

SortedSet(SortedMap)存元素的特点:

​ 首先是无序不可重复的,但是SortedSet集合中的元素是可排序的。

​ 无序:存进去的顺序和取出来的顺序不一定相同。另外Set集合中元素没有下标。

​ 不可重复:存进去1,不能在存储1了。

​ 可排序:可以按照大小顺序排序。

Map集合的key,就是一个Set集合。

往Set集合中放数据,实际上放到了Map集合的Key部分。

1.4、Collection集合中常用方法

package javase.collection;

import java.util.ArrayList;
import java.util.Collection;

/*
关于java.util.Collection接口中的常用方法。
    1、Collection中能存储的元素:
        没有使用“泛型”之前,Collection中可以存储Object的所有子类型。
        使用了“泛型”之后,Collection中只能存储某个具体的类型(集合后期会讲解“泛型”的语法)。
        Collection中什么都能存储,只要是Object子类型就行(集合中不能直接存储基本数据类型,
        也不能直接存放Java对象,只能存放Java对象的内存地址)。
    2、Collection中的常用方法
        boolean add(Object e)  向集合中添加元素
        int size()  返回次collection中元素的个数
        void clear()  清空collection中所有的元素
        boolean contains(Object o)  判断collection中是否包含对象o,包含返回true
        boolean remove(Object o)  删除collection中的某个元素
        boolean isEmpty()  判断 collection 中的元素是否为 0
        Object[] toArray()  将 collection 转换为Object数组
 */
public class CollectionTest01 {
    public static void main(String[] args) {
        // 创建一个集合对象
        // Collection c =new Collection(); // 接口是抽象类,无法实例化
        // 多态
        Collection c = new ArrayList();
        // 测试Collection接口中的常用方法
        c.add(200);  // 自动装箱(java5新特性),实际上放进去了一个对象的内存地址。即new Integer(200);
        c.add(3.14); // 自动装箱
        c.add(new Object());
        c.add(new Student());
        c.add(true);  // 自动装箱

        // 获取集合中元素的个数
        System.out.println("集合中元素个数是 : " + c.size());  // 5

        // 清空c中的元素
        c.clear();
        System.out.println("集合中元素个数是 : " + c.size());  // 0

        // 向集合中添加元素
        c.add("hello");  // "hello"对象的内存地址放到了集合当中。
        c.add("world");
        c.add("java");
        c.add(1);

        // 判断 c 中是否包含某个对象
        System.out.println(c.contains("java"));  // true
        System.out.println(c.contains("php")); // false
        System.out.println(c.contains(1));  // true

        // 删除 c 中的1
        c.remove(1);
        System.out.println(c.contains(1));  // false

        // 清空元素
        c.clear();
        //添加元素
        c.add("java");
        c.add("php");
        c.add("hello world");
        // 转换为数组(了解,使用不多)
        Object[] objs = c.toArray();
        for (Object obj: objs){
            // 遍历数组
            System.out.println(obj);  // 输出引用时,自动调用toString()方法
        }

    }
}

class Student{}

1.5、集合迭代Iterator

注意:集合结构只要发生改变,迭代器必须重新获取。

image-20211030122000059

package javase.collection;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;

/**
 * 关于集合迭代(遍历)专题。(重点:五颗星*****)
 *
 */
public class CollectionTest02 {
    public static void main(String[] args) {
        // 注意:以下讲解的遍历方式/迭代方式,是所有Collection通用的一种方式,
        // 但是在Map集合中不能使用,在所有的Collection以及子类中使用。
        // 创建集合对象
        // ArrayList 有序可重复
        Collection c = new ArrayList();// 后面的集合无所谓,主要是看前面的Collection接口怎么迭代。
        // 添加元素
        c.add("abc");
        c.add("def");
        c.add(100);
        c.add(new Object());
        // 对集合Collection进行迭代
        // 第一步:获取集合对象的迭代对象Iterator
        Iterator iterator = c.iterator();
        // 第二步:通过以上获取的迭代器对象开始迭代
        /*
        以下两个方法是迭代器对象Iterator中的方法:
            boolean hasNext(); 如果仍有元素可以迭代,则返回 true
            Object next(); 返回迭代的下一个元素
        */
        while (iterator.hasNext()){  // 如果这里iterator.hasNext()写成true会出现:java.util.NoSuchElementException
            // 不管当初存进去的是什么,取出来统一都是Object
            System.out.println(iterator.next());
        }
    }
}

1.6、contains()

资源地址:https://www.bilibili.com/video/BV1Rx411876f?p=673&spm_id_from=pageDriver

package javase.collection;

import java.util.ArrayList;
import java.util.Collection;

/**
 * 测试contains()方法
 * 结论:放在一个集合中的类型,一定要重写equals()方法。
 */
public class CollectionTest04 {
    public static void main(String[] args) {
        // 创建集合
        Collection users = new ArrayList();

        // 创建两个User对象
        User u1 = new User("jack");
        User u2 = new User("jack");

        // 将 u1 加入users 集合
        users.add(u1);
        // 没有重写equals()方法之前
        // System.out.println(users.contains(u2)); // false

        // 重写equals()方法之后
        // 由于u1和u2的username都是"jack",即它们在堆中保存的内存地址是相同的,由于User类的equals方法已经重写了。equals在判断的时候返回true。
        // 而contains()方法底层调用的是equals,由于equals返回true,故contains()方法也返回true。
        System.out.println(users.contains(u2));  // true

        // 例如
        Integer x = new Integer(1000);
        users.add(x); // 将x加入到users集合中

        Integer y = new Integer(1000);
        // 判断user集合中是否包含y
        System.out.println(users.contains(y));  // true
    }
}

class User{
    private String username;

    public User(){}

    public User(String username){
        this.username = username;
    }

    // 重写equals()方法
    // 将来调用equals()方法的时候,一定是调用这个重写了的equals()方法。
    // 这个equals()方法的比较原理是:只要姓名一样同一个用户。
    @Override
    public boolean equals(Object o) {
        if (o == null) return false;
        if (this == o) return true;
        if (!(o instanceof User)) return false;
        User user = (User) o;
        // username = "jack"  , user.username = "jack"
        // return Objects.equals(username, user.username); // idea 生成
        // 这里调用String中已经重写了的equals()方法
        return this.username.equals(user.username);
    }
}	

image-20211030125307642

1.7、remove()

package javase.collection;

import java.util.ArrayList;
import java.util.Collection;

/**
 * 测试remove()
 * 结论:集合中的元素一定要重写equals()方法
 */
public class CollectionTest05 {
    public static void main(String[] args) {
        Collection cc = new ArrayList();
        // 加入两个"hello"字符串对象
        String s1 = "hello";
        String s2 = "hello";

        cc.add(s1);  // s1加入集合cc
        System.out.println(cc.size());  // 1
        // 这里的s2并没有加入cc中,但是remove()底层还是会调用equals()方法,Java在执行equals()方法的时候认为s1与s2是相同的。
        // 所以在删除s2的时候实际上也将s1删除了。
        cc.remove(s2);  // 删除s2
        System.out.println(cc.size());  // 0
    }
}

1.8、集合中元素的删除

package javase.collection;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;

/**
 * 关于集合元素的remove
 *      重点:当集合的结构发生改变是,迭代器必须重新获取,如果还是使用以前老状态的迭代器,会出现
 *      异常:java.util.ConcurrentModificationException
 *
 *      重点:在迭代集合元素的过程中,不能调用集合对象的remove()方法来删除元素,如:
 *          c.remove(o); 在迭代的过程中不能这样,否则会出现:java.util.ConcurrentModificationException
 *      
 *      注意点:在使用迭代器删除集合中的元素时,一定要使用迭代器Iterator中的remove()方法删除,不能使用集合自带的remove()方法删除元素。
 */
public class CollectionTest06 {
    public static void main(String[] args) {
        // 创建集合
        Collection c = new ArrayList();

        // 注意:此时获取到的迭代器指向的是那时集合中没有元素状态下的迭代器。
        // 一定要注意:集合结构只要发生改变,迭代器必须重新获取。
        // 当集合结构发生了改变,迭代器没有重新获取时,调用next()方法时会出现:java.util.ConcurrentModificationException异常。
        Iterator it = c.iterator();

        // 添加元素
        c.add(1); // Integer类型 , 即“自动装箱”
        c.add(2);
        c.add(3);

        // 遍历
        // 因为这里的it迭代器是在向集合中添加元素之前的状态,然而集合中添加元素之后,集合的状态改变了,但是it并没有更新,所以出现异常。
        /*
        while (it.hasNext()) {
            // 编写代码是next()方法返回值类型必须是Object
            Object obj = it.next();
            System.out.println(obj);  // 抛出异常:java.util.ConcurrentModificationException
        }
        */
        Collection c2 = new ArrayList();
        c2.add("abc");
        c2.add("def");
        c2.add("xyz");

        Iterator it2 = c2.iterator();
        while (it2.hasNext()) {
            Object obj = it2.next();
            // 删除元素
            // 在删除元素之后,集合的结构发生了变化,应该重新获取迭代器,但是循环下一次并没有重新获取迭代器,所以会出现异常。
            // 出现异常的根本原因:集合中元素删除了,但是没有更新迭代器(迭代器不知道集合发生变化了)。
            // c2.remove(); // 直接通过集合去删除元素,没有通知迭代器。

            // 使用迭代器来删除可以吗?
            // 迭代器去删除时,会自动更新迭代器和集合(即也会删除集合中的元素)。
            it2.remove();  // 迭代器删除的一定是迭代器指向的当前元素
            System.out.println(obj);
        }
        System.out.println(c2.size());  // 0
    }
}

2、List

2.1、List常用方法

package javase.List;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

/**
 * 1、List集合存储元素特点:有序可重复。
 *      有序:List集合中的元素有下标
 *      可重复:存储一个1,还可以在存储一个1.
 * 2、List既然是Collection接口的子接口,那么肯定List接口有自己“独一”方法。
 *      以下只列出List接口特有常用的方法:
 *          void add(int index, Object element); 在列表的指定位置插入指定元素
 *          Object get(int index);
 *          int indexOf(Object o);
 *          int lastIndexOf(Object o);
 *          Object remove(int index);   移除列表中指定位置的元素
 *          Object set(int index, Object element); 用指定元素替换列表中指定位置的元素
 *
 *      以上的这些方法只需要编写代码测试一下,不用硬记,以后开发的时候还是需要翻阅帮助文档。
 */
public class ListTest01 {
    public static void main(String[] args) {
        // 创建List类型的集合
        List list = new ArrayList();

        // 添加元素
        // add()方法默认在列表的最后添加元素,这个方法是用的比较多。
        list.add("a");
        list.add("b");
        list.add("c");
        list.add("d");

        // 使用add(int index,  Object element)添加元素,这个方法使用不多,因为对于ArrayList集合来说效率比较低。
        list.add(1, "java"); // 在列表的第二个位置添加字符串"java"
        // 迭代
        Iterator it = list.iterator();
        while (it.hasNext()) {
            System.out.println(it.next());
        }

        // 根据下标获取元素
        System.out.println(list.get(1));  // java
        // 因为有下标,所以List集合有自己比较特殊的遍历方式,通过下标进行遍历。【List集合特有的方式,Set没有】
        for (int i = 0; i < list.size(); i++) {
            Object obj = list.get(i);
            System.out.print(obj + " "); // a java b c d
        }
        System.out.println();

        // 获取指定对象第一次出现处的索引
        System.out.println(list.indexOf("java")); // 1

        // 获取指定对象最后一次出现的索引
        System.out.println(list.lastIndexOf("c"));  // 3

        // 删除集合中的指定元素
        list.remove(1);
        System.out.println(list.size()); // 4

        // 用指定元素替换列表中指定位置的元素
        list.set(1, "JavaSE");
        for (Object o : list) {
            System.out.print(o + " ");
        }
    }
}

2.2、ArrayList初始化及扩容

建议阅读一下jdk中的源代码。

package javase.ArrayList;

import java.util.ArrayList;
import java.util.List;

/**
 * ArrayList集合:
 *      1、默认初始化容量 10 (底层优先创建了一个长度为0的数组,当添加第一个元素的时候,将容量初始化为10)。
 *      2、集合底层是一个Object[]数组。
 *      3、构造方法:
 *          new ArrayList();
 *          new ArrayList(20); // 这里的20是指定数组的大小
 *          ArrayList(Collection <? extends E> c)
 *          第三种方式:
 *          // 创建一个HashSet集合
 *          Collection c = new HashSet();
 *          c.add(100);
 *          c.add(200);
 *          c.add(300);
 *          // 通过这个构造方法可以将HashSet集合转换成List集合
 *          List list = new ArrayList(c);
 *      4、ArrayList集合的扩容:
 *          增长到原容量的1.5倍。
 *          ArrayList集合底层是数组,怎么优化?
 *              尽可能少的扩容。因为数组扩容的效率比较低,建议在使用ArrayList集合的时候预估计元素的个数,给定一个初始化容量。
 *      5、数组优点:检索效率很高(每个元素占用空间大小相同,内存地址又是连续的,知道首元素内存地址,然后知道下标,通过数据表达式
 *   计算出元素的内存地址,所以检索效率很高)。
 *      6、缺点:
 *          随机增删元素效率较低(末尾处添加或删除元素除外)。
 *          另外数组无法存储大数据量(很难找到一块非常巨大的连续的内存空间)。
 *      7、面试官经常问的一个问题:
 *          这个多的集合中,那个集合使用最多?答:ArrayList集合。
 *          因为往数组末尾添加元素,效率不会受到影响。另外,我们检索某个元素的操作比较多。
 *      8、ArrayList不是线程安全的,效率较高。
 *
 */
public class ArrayListTest01 {
    public static void main(String[] args) {
        // 默认初始化容量是 10
        // 数组的长度是 10
        List list1 = new ArrayList();
        // 集合中的size()方法获取当前集合中元素的个数,不是获取集合的容量。
        System.out.println(list1.size());  // 0

        // 指定初始化容量,数组的长度指定为20
        List list2 = new ArrayList(20);
        System.out.println(list2.size());  // 0
    }
}

2.3、LinkedList

2.3.1、单向链表

在这里插入图片描述

代码实现单向链表:

Link.java

package javase.singleLink;

/*单链表*/
public class Link {
    // 头结点
    private Node header = null;

    // 链表元素的个数
    private int size = 0;

    // 统计列表元素的个数
    public int size(){
        return size;
    }

    // 向链表中添加元素的方法(在末尾添加)
    public void add(Object data){
        // 创建一个新的节点对象,让之前单链表的末尾节点next指向新节点对象。
        // 有可能这个元素是第一个,也可能是第二个,也可能是第三个。
        if (header == null){
            // 说明还没有节点
            // new 一个新的节点对象,作为头节点对象
            header = new Node(100, null);
        } else {
            // 说明头节点不为空,头结点已经存在了。找出当前末尾节点,让当前末尾节点的next是新节点。
            Node currentLastNode = findLast(header);
            currentLastNode.next = new Node(data, null);
        }
        size++;  // 每添加一个元素 size加1
    }

    // 使用递归的方式来查找最后一个节点
    private Node findLast(Node node) {

        if (node.next == null){ // 如果一个节点的next为null,这说明当前这个节点就是最后一个节点。
            return node;
        }
        // 程序能够到这里说明当前的node节点不是最后一个节点。
        return findLast(node.next);
    }
}

// 节点类
// 每一个节点都有两个属性,一个用来保存数据,另一个用来保存下一个节点的内存地址(也是节点)
class Node{
    // 数据
    Object data;

    // 下一个节点
    Node next;

    public Node(){}
    public Node(Object data, Node next){
        this.data = data;
        this.next = next;
    }
}

Test.java

package javase.singleLink;

public class Test {
    public static void main(String[] args) {
        Link link = new Link();
        link.add(100);
        link.add(200);
        link.add(300);
        System.out.println(link.size());  // 3
    }
}
2.3.2、双向链表

视频资源:https://www.bilibili.com/video/BV1Rx411876f?p=686&spm_id_from=pageDriver

image-20211031121106196

2.3.3、LinkedList原理

image-20211031124233281

package javase.LinkedList;

import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;

/**
 * 链表的优点:
 *      由于链表上的元素在空间存储上内存地址不连续,所有随机增删元素的时候不会有大量元素位移,因此随机增删效率较高。
 *      在以后开发中,如果遇到随机增删集合中元素的业务比较多时,建议使用LinkedList。
 *
 * 链表的缺点:
 *      不能通过数据表达式计算被查找元素的内存地址,每一次查找都是从头开始遍历,知道找到为止。
 *      所以LinkedList集合检索的效率比较低。
 * ArrayList:把检索发挥到极致,由于加元素大多数情况都是王末尾添加,所以ArrayList用的比LinkedList多。
 * LinkedList:把随机增删发挥到极致。
 *
 */
public class LinkedListTest01 {
    public static void main(String[] args) {
        // LinkedList集合底层也是有下标的。
        // 注意:ArrayList之所以检索效率标记高,不是单纯因为下标的原因,还有底层数组发挥的主要作用。
        // LinkedList集合照样有下标,但是检索某个元素的时候效率比较低,因为只能从头节点开始一个一个遍历。
        List list = new LinkedList();
        list.add("a");
        list.add("b");
        list.add("c");
        list.add("d");

        // 通过下标遍历LinkedList中的元素
        for (int i = 0; i < list.size(); i++) {
            Object o = list.get(i);
            System.out.print(o + " ");
        }
        System.out.println();

        // LinkedList集合有初始化容量吗?答案:没有。
        // 最初这个链表中没有任何元素,first和last引用都是null,不管是LinkedList还是ArrayList,以后写代码时不需要关心具体是那个集合。
        // 因为我们是面向接口编程,调用的方法都是接口中的方法。
        // List list1 = new ArrayList();  // 这样写表示底层使用了数组。
        List list1 = new LinkedList();  // 这样写表示底层使用了双向链表。
        list1.add("abc");
        list1.add("def");
        list1.add("xyz");
        list1.add("java");
        for (int i = 0; i < list1.size(); i++) {
            System.out.print(list1.get(i) + " ");
        }
        System.out.println();
    }
}

2.4、Vector

java.util.Collections包下面有一个synchronizedList()方法可以将非线程安全的ArrayList集合转变为线程安全的ArrayList集合。

package javase.Vector;

/**
 * Vector:
 *      1、底层也是一个数组
 *      2、初始化容量:10
 *      3、怎么扩容?
 *          扩容之后是原容量的两倍。 10 -> 20 -> 40
 *      4、ArrayList集合扩容特点:ArrayList集合扩容是原容量的1.5倍。10 -> 15
 *      5、Vector中所有的方法都是线程同步的,都带有synchronized关键字,是线程安全的,效率比较低,使用较少。
 *      6、怎么将一个线程不安全的ArrayList集合转换为线程安全的?
 *          使用结合工具类:
 *              java.util.Collections
 *              java.util.Collection 是集合接口
 *              java.util.Collections 是集合工具类
 */

import java.util.*;

public class VectorTest01 {
    public static void main(String[] args) {
        // 创建Vector对象
        List list = new Vector();

        // 添加元素
        // vector初始化的默认容量是:10
        list.add(1);
        list.add(2);
        list.add(3);
        list.add(4);
        list.add(5);
        list.add(6);
        list.add(7);
        list.add(8);
        list.add(9);
        list.add(10);

        // vector扩容,扩容之后是:20
        list.add(11);

        // Vector遍历
        Iterator iterator = list.iterator();
        while (iterator.hasNext()) {
            System.out.print(iterator.next() + " "); // 1 2 3 4 5 6 7 8 9 10 11
        }
        System.out.println();

        // 将一个ArrayList非线程安全的转换为Vector线程安全的?可以使用java.util.Collections类中的方法进行转换。
        // 创建ArrayList对象
        List list1 = new ArrayList();  // 非线程安全的。


        // 将ArrayList转为Vector,变成线程安全的。
        Collections.synchronizedList(list1);
        // list1集合就是线程安全的了。
        list1.add(10);
        list1.add(20);
        list1.add(30);
        list1.add(40);
    }
}

3、泛型

泛型属于编译阶段的功能,与运行阶段的关系不大。

3.1、泛型的概述

package javase.generic;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

/**
 * 1、JDK5.0之后推出的新特性:泛型
 * 2、泛型这种语法机制,只在程序编译阶段起作用,只是给编译器参考的。(运行阶段泛型作用不大)
 * 3、泛型的好处:
 *      第一:集合中存储的元素类型统一。
 *      第二:从集合中取出的元素类型都是泛型指定的类型,不需要进行大量的“向下转型”。
 * 4、泛型的缺点:
 *      导致集合中存储的元素缺乏多样性!
 *      但实际上在大多数业务中,集合中元素的类型还是统一的。所有这种泛型特性还是被很多人所认可的。
 */
public class GenericTest01 {
    public static void main(String[] args) {
        // 未使用“泛型”
        /*
        // 创建集合
        List animals = new ArrayList();

        // 创建动物
        Cat cat = new Cat();
        Bird bird = new Bird();

        // 将动物添加到集合中
        animals.add(cat);
        animals.add(bird);

        // 使用迭代器遍历集合,输出猫爪老鼠与鸟儿在飞翔!
        Iterator it = animals.iterator();
        while (it.hasNext()) {
            // 迭代器中取出来的元素只能是Object对象(但是可以使用向下转型)
            Object o = it.next();
            if (o instanceof Cat){
                ((Cat) o).catchMouse();
            } else if (o instanceof Bird){
                ((Bird)o).fly();
            }
        }
        */

        // 使用JDK5.0之后的新特性“泛型机制”后,代码变得更为直观
        // 使用泛型List<Animal>之后,表示List集合中只运行存储Animal类型的数据。
        // 用泛型来指定集合中存储的数据类型。
        List<Animal> animals = new ArrayList<Animal>();

        // 指定List集合中指定存储Animal,如果存储其他类型如String的编译器就会报错。
        // 这样用了泛型之后,集合中元素的数据类型更加统一了。

        Cat cat = new Cat();
        Bird bird = new Bird();

        // 指定了泛型的集合中,可以存储器类型的子类。
        animals.add(cat);
        animals.add(bird);

        // 获取迭代器,迭代器也可以指定其迭代的类型。
        // 这个表示迭代器迭代的是Animal类型。如果不指定,则返回的默认还是“Object”类型。
        Iterator<Animal> it = animals.iterator();
        while (it.hasNext()) {
            // 使用泛型之后,每一次迭代返回的数据都是Animal类型
            Animal animal = it.next();
            // 这里不需要进行强制类型转换了,可以直接使用。
            animal.move();  // 动物在移动!

            // 但是这里如果是调用子类型中特有的方法时还是需要向下转型的。
            if (animal instanceof Cat){
                ((Cat) animal).catchMouse();  // 猫爪老鼠!
            }
        }
    }
}

class Animal{
    public void move(){
        System.out.println("动物在移动!");
    }
}

class Cat extends Animal{
    public void catchMouse(){
        System.out.println("猫抓老鼠!");
    }
}

class Bird extends Animal{
    public void fly(){
        System.out.println("鸟儿飞翔!");
    }
}

3.2、钻石表达式

package javase.generic;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

/**
 * JDK8之后引入了:自动类型推断的机制(又称为“钻石表达式”)。
 */
public class GenericTest02 {
    public static void main(String[] args) {
        // ArrayList<这里的类型可以自动推断>();前提是JDK8之后才运行的。
        // 自动类型推断
        List<String> myList = new ArrayList<>(); // ArrayList<>这里尖括号里面的类型可以不用写了(JDK8之后)。
        myList.add("java");
        myList.add("php");
        myList.add("python");

        // 不允许添加其他类型的数据
        // myList.add(new Animal());  // 报错

        // 获取迭代器对象,并指定迭代器类型为String
        Iterator<String> iterator = myList.iterator();
        while (iterator.hasNext()) {
            System.out.print(iterator.next() + " ");  // java php python
        }
        System.out.println();
    }
}

3.3、自定义泛型

package javase.generic;

/**
 * 如何自定义泛型?
 *      自定义泛型的时候,<>尖括号中的是一个标识符,可以随便写。
 *      java源代码中经常出现的是:
 *          <E>和<T>
 *      E是element单词首字母。
 *      T是type单词首字母。
 */
// 这里的<Test>是随便写的,只要符合标识符命名规则即可。
public class GenericTest03<Test> {

    public void doSome(Test e){  // 这里的e也只是一个表标识符而已
        System.out.println(e);
    }

    public static void main(String[] args) {
        // new对象的时候确定了泛型的对象,如:String类型
        GenericTest03<String> gt = new GenericTest03<>();

        // 上面指定了gt泛型的类型为String类型,如果我们传入其他类型编译器会报错。如:int
        // gt.doSome(100);  // 类型不匹配

        // 传入一个String类型的数据,编译器不会报错
        gt.doSome("java is the best language!");

        // 再如:这里指定泛型的类型为Integer的类型。
        GenericTest03<Integer> igt = new GenericTest03<>();

        // 传入一个100(会自动装箱)编译器不会报错。
        igt.doSome(100);

        // 传入一个字符串类型编译器会报错
        // igt.doSome("java");  // 类型不匹配

        // 如果不使用泛型,默认就是Object类型
        // GenericTest03 igt2 = new GenericTest03();
        // igt2.doSome(new Object());
    }
}

// 自定义一个类,并指定为泛型
class MyGeneric<T>{
    public T getType(T j){
        return null;
    }
}

3.4、增强版for循环

package javase.generic;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

// JDK5.0之后推出一个新特性,叫做增强for循环,或叫做foreach循环
public class ForeachTest01 {
    public static void main(String[] args) {
        // 定义一个数组
        int[] array = {1, 2, 4, 6, 7};

        // 普通for循环的遍历方式
        for (int i = 0; i < array.length; i++) {
            System.out.print(array[i] + " ");
        }
        System.out.println();

        // 增强版for循环(又称为“foreach”循环)
        // foreach有个小小的缺点:就是没有下标。
        for (int i : array) {
            System.out.print(i + " ");
        }
        System.out.println();

        // 创建集合
        List<String> strings = new ArrayList<>();
        strings.add("hello");
        strings.add("world");
        strings.add("javaSE");

        // 集合的几种遍历方式
        // 第一种使用迭代器遍历
        Iterator<String> iterator = strings.iterator();
        while (iterator.hasNext()) {
            System.out.print(iterator.next() + " "); // hello world javaSE
        }
        System.out.println();

        // 第二种使用下标(针对有下标的集合)
        for (int i = 0; i < strings.size(); i++) {
            System.out.print(strings.get(i) + " "); // hello world javaSE
        }

        // 第三种是有foreach
        for (String string : strings) {  // 这里为什么是String string?因为我们上面定义strings的泛型类型为String类型。
            System.out.print(string + " ");  // // hello world javaSE
        }
    }
}

3.5、HashSet与TreeSet简单演示

HashSet:

package javase.set;

import java.util.HashSet;

/**
 * HashSet集合:
 *      无序不可重复。
 */
public class HashSetTest01 {
    public static void main(String[] args) {
        // HashSet集合特点
        HashSet<String> strs = new HashSet<>();

        // 添加元素
        strs.add("hello2");
        strs.add("hello1");
        strs.add("hello3");
        strs.add("hello3");
        strs.add("hello3");

        // 遍历
        for (String str : strs) {
            // 输出:hello1 hello2 hello3
            // 1、存储时顺序和取出的顺序不同
            // 2、不可重复
            // 3、放到HashSet集合中的元素实际上是放到HashMap结合的key部分了。
            System.out.print(str + " ");
        }
    }
}

TreeSet:

package javase.set;

import java.util.Set;
import java.util.TreeSet;

/**
 * TreeSet集合存储元素特点:
 *      1、无序不可重复,但是存储的元素可以自动按照从小到大顺序排序!
 *      称为:可排序集合。
 *      2、无序:这里的无序指的是存进去的顺序和取出来的顺序不同,并且没有下标。
 */
public class TreeSetTest01 {
    public static void main(String[] args) {
        // 创建集合对象
        Set<String> strs = new TreeSet<>();
        // 添加元素
        strs.add("a");
        strs.add("b");
        strs.add("z");
        strs.add("y");
        strs.add("k");
        strs.add("z");
        // 遍历
        // 输出:a b k y z , 从小到大自动排序
        for (String str : strs) {
            System.out.print(str + " ");
        }
    }
}

4、Map集合

4.1、Map集合常用方法

package javase.map;

import java.util.Collection;
import java.util.HashMap;
import java.util.Map;

/**
 * java.util.Map接口:
 *      1、Map和Collection没有继承关系。
 *      2、Map集合以key和value的方式存储数据,即键值对。
 *          key和value都是引用数据类型。
 *          key和value都是存储对象的内存地址。
 *          key起到主导的地位,value是key的一个附属品。
 *      3、Map接口中常用的方法:
 *          V put(K key, V value) 向Map集合中添加键值对
 *          V get(Object key) 通过key获取value
 *          void clear() 清空Map集合
 *          boolean containsKey(Object key) 判断Map中是否包含某个key
 *          boolean containsValue(Object value) 判断Map中是否包含某个Value
 *          boolean isEmpty() 判断Map中的元素是否为 0
 *          Set<K> keySet() 获取Map集合所有的key(所有的键是一个集合)
 *          V remove(Object key) 通过key删除键值对
 *          int size()  获取Map集合中键值对的个数
 *          Collection<V> values() 获取Map集合中所有的value,返回一个Collection
 *          Set<Map.Entry<K,V>> entrySet()
 *              将Map集合转换为Set集合
 *              假设现在有一个Map集合,选下所示:
 *                  map1集合对象
 *                  key             value
 *                  ---------------------
 *                  1               zhangsan
 *                  2               lisi
 *                  3               wangwu
 *
 *                  Set set = map1.entrySet();
 *                  set集合对象
 *                  1=zhangsan 【注意:Map集合通过entrySet()方法转换成的这个Set集合,Set集合中的元素的类型是Map.Entry<K,V>】
 *                  2=lisi 【MapEntry和String一样,都是一种类型的名字,只不过:Map.Entry是静态内部类,是Map中的静态内部类】
 *                  3=wangwu --> 这个东西是啥?答案:Map.Entry
 *
 *      4、关于内部类可以看这个包下面的MyClass.java类.
 */
public class MapTest01 {

    public static void main(String[] args) {
        Map<Integer, String> map = new HashMap<>();
        // 向Map集合中添加键值对
        map.put(1, "龙小萌");  // 这里的 1 会自动装箱,和new Integer(1);是一样的。
        map.put(2, "小萌狗");
        map.put(3, "龙萌萌");

        // 通过key获取value以及Map的遍历
        for (Integer integer : map.keySet()) {
            System.out.println(integer + ":" + map.get(integer));
            /*
            输出:
                1:龙小萌
                2:小萌狗
                3:龙萌萌
            */
        }

        // 获取Map集合中键值对的数量
        System.out.println("键值对的数量: " + map.size()); // 3

        // 通过key删除value
        map.remove(3);
        System.out.println("键值对的数量: " + map.size());  // 2

        // 获取所有的value
        Collection<String> values = map.values();
        for (String value : values) {
            System.out.print(value + " ");
        }
        System.out.println();

        // 判断是否包含某个key
        // 需要注意的是,contains()方法底层调用的都是equals()方法进行比较的,所以自定义类都需要重写equals()方法。
        System.out.println(map.containsKey(2)); // true
        // 判断是否包含某个value
        System.out.println(map.containsValue("小萌狗")); // true

        // 清空Map集合
        map.clear();
        System.out.println("键值对的数量: " + map.size()); // 0

        // 判断Map集合中的键值对是否为0
        System.out.println(map.isEmpty());  // true
    }
}

4.2、Map集合的遍历

常用两种方法进行遍历。

package javase.map;

import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;

/**
 * Map集合的遍历【非常重要】
 */
public class MapTest02 {
    public static void main(String[] args) {
        // 第一种方式,获取所有的key,通过遍历key来遍历value
        Map<Integer, String> map = new HashMap<>();
        map.put(1, "zhangsan");
        map.put(2, "lisi");
        map.put(3, "wangwu");
        map.put(4, "zhaoliu");

        // 遍历Map集合
        // 获取所有的key,所有的key是一个Set集合
        Set<Integer> keySet = map.keySet();
        // 遍历key,通过key获取value
        // 使用迭代器来实现
        Iterator<Integer> iterator = keySet.iterator();
        while (iterator.hasNext()) {
            // 取出其中一个key
            Integer key = iterator.next();
            // 通过key获取value
            String value = map.get(key);
            System.out.println(key + "=" + value);
        }

        // foreach来实现
        for (Integer key : keySet) {
            System.out.println(key + " = " + map.get(key));
        }
        /*
        输出:
            1 = zhangsan
            2 = lisi
            3 = wangwu
            4 = zhaoliu
        */

        // 第二种方式,Set<Map.Entry<K,V>> entrySet()
        // 以上这个方法是把Map集合直接全部转换成Set集合,Set集合中元素的类型是:Map.Entry
        Set<Map.Entry<Integer, String>> entrySet = map.entrySet();

        // 遍历Set集合,每一次取出一个Node
        // 这里使用迭代器来遍历
        Iterator<Map.Entry<Integer, String>> it2 = entrySet.iterator();
        while (it2.hasNext()) {
            Map.Entry<Integer, String> node = it2.next();
            Integer key = node.getKey();
            String value = node.getValue();
            System.out.println(key + " = " + value);
        }

        // 增强for循环
        // [这一种方式的效率要高一点],适用有大数据量的情况
        for (Map.Entry<Integer, String> stringEntry : entrySet) {
            System.out.println(stringEntry.getKey() + " = " + stringEntry.getValue());
        }

        /*
        输出:
            1 = zhangsan
            2 = lisi
            3 = wangwu
            4 = zhaoliu
        */
    }
}

4.3、HashMap中两个重要的方法

map.put(k,v);

v = map.get(k);

这两个方法应该重点掌握其原理。

image-20211101182101254

package javase.map.HashMap;

import java.util.HashMap;
import java.util.Map;
import java.util.Set;

/**
 * 1、HashMap集合底层是哈希表/散列表的数据结构。
 * 2、哈希表示一个怎样的数据结构?
 *      哈希表是一个数组和单向链表的结合体。
 *      数组:在查询方面效率很高,随机删除方面效率很低。
 *      单向链表:在随机增删方面效率较高,在查询方面效率很低。
 *      哈希表将以上的两种数据结构融合在一起,充分发挥了它们各自的优点。
 * 3、HashMap集合底层的源代码:
 *      public class HashMap<K,V>{
 *          // HashMap底层实际上就是一个数组(一维数组)。
 *          Node<K,V>[] table;
 *
 *          // 静态内部类Hash.Node
 *          static class Node<K,V>{
 *              final int hash; // 哈希值(哈希值是key的hashCode()方法执行结果。hash值通过哈希函数/算法,可以转换存储成数组的下标。
 *              final K key; // 存储到Map集合中的那个key
 *              V value; // 存储到Map集合中的那个value
 *              Node<K,V> next; // 下一个节点的内存地址
 *      }
 *      哈希表/散列表:是一个一维数组,这个数组中每一个元素是一个单向链表(数组和链表的结合体)。
 * 4、重点掌握:
 *      map.put(K, V);
 *      v = map.get(K);
 *      以上这两个方法的实现原理,是必须掌握的。
 *
 * 5、HashMap集合的key部分特点:
 *      无序,不可重复。
 *      为什么无序?答案:因为不一定挂到那个单向链表上。
 *      不可重复是怎么保证的?答案:equals方法来保证HashMap集合的key不可重复,如果key重复了,value会覆盖。
 *
 *      放在HashMap集合key部分的元素其实就是放到HashSet集合中了。
 *      所以HashSet集合中的元素也需要同时重写hashCode()+equals()方法。
 *
 * 6、哈希表HashMap使用不当时无法发挥性能!
 *      假设将所有的hashCode()方法返回值固定为某个值,那么会导致底层哈希表变成了纯单向链表。这种情况我们称为:散列分布不均匀。
 *      什么是散列分布均匀?
 *          假设有100个元素和10个单向链表,那么如果每个单向链表上有10个节点,这是最好的,这时也称为:散列分布均匀。
 *
 *      假设将所有的hashCode()方法返回值都设定为不一样的值,又会有什么问题?
 *          这样会导致底层哈希表变成一个纯一维数组,没有链表这个概念。也是散列分布不均匀。
 *
 *      所以散列分布均匀就需要在重写hashCode()方法的时候有一定的技巧。
 *
 * 7、重点:放在HashMap集合key部分的元素,以及放在HashSet集合中的元素,需要同时重写hashCode()和equals()方法。
 * 8、HashMap集合的默认初始化容量是16,默认加载因子是0.75(了解)
 *      这个默认加载因子是当HashMap集合底层数据容量达到75%的时候,数组开始扩容。
 *
 *      重点记住:HashMap集合初始化容量必须是2的倍数,这也是官方推荐的。这也是达到散列均匀,为了提高HashMap集合的存取效率所必须的。
 *
 */
public class HashMapTest01 {
    public static void main(String[] args) {
        // 测试Hashmap集合key部分的元素特点
        // Integer是key,它的hashCode和equals方法都已经重写了
        Map<Integer, String> map = new HashMap<>();
        map.put(1111, "zhangsan");
        map.put(2222, "lisi");
        map.put(3333, "wangwu");
        map.put(4444, "zhaoliu");
        map.put(4444, "zh0u");  // key重复的时候value会自动覆盖

        System.out.println(map.size());  // 4

        // 遍历Map集合
        Set<Map.Entry<Integer, String>> entrySet = map.entrySet();
        for (Map.Entry<Integer, String> entry : entrySet) {
            // 验证结果:HashMap集合key部分元素:无序不可重复。
            System.out.println(entry.getKey() + " -> " + entry.getValue());
        }
    }
}

4.4、重写hashCode()方法

package javase.map.HashMap;

import java.util.HashSet;
import java.util.Objects;
import java.util.Set;

/*
1、向Map集合中存、取元素时,都是先调用key的hashCode()方法,然后在调用equals()方法!
equals()方法有可能调用,也有可能不会调用。
    拿put(k,v)举例:什么时候equals()不会调用?
        k.hashCode()方法返回哈希值,当哈希值经过哈希算法转换成数组下标,数组下标位置上如果是null,equals()方法不需要执行。
    拿get(k)举例:什么时候equals()不会调用?
        k.hashCode()方法返回哈希值,当哈希值经过哈希算法转换成数组下标,数组下标位置上如果是null,equals()方法不需要执行。

2、注意:如果一个类的equals()方法重写了,那么hashCode()方法必须重写,并且equals()方法如果返回true,hashCode()方法返回的值必须是一样的。

3、hashCode()方法和equals()方法不用手写了,直接使用idea工具生成,但是需要注意的是这两个方法应该同时生成。

4、最后总结:
    放在HashMap集合key部分,已经放在HashSet集合中的元素,需要同时重写hashCode()和equals()方法。
*/
public class HastMapTest02 {
    public static void main(String[] args) {
        Student s1 = new Student("zhangsan");
        Student s2 = new Student("zhangsan");

        // 重写equals()方法之前
        // System.out.println(s1.equals(s2));  // false

        // 重写equals()方法之后
        System.out.println(s1.equals(s2)); // true

        System.out.println("s1的hashCode = " + s1.hashCode());// 1163157884  (hashCode()方法重写之后:-1432604525)
        System.out.println("s2的hashCode = " + s2.hashCode());// 1956725890  (hashCode()方法重写之后:-1432604525)

        // s1.equals(s2);结果已经是true了,表示s1和s2是一样的,相同的,那么往HashSet集合中放元素,理论上来说这里应该只能放进去一个,
        // HashSet集合的特点:无序不可重复。
        Set<Student> students = new HashSet<>();
        students.add(s1);
        students.add(s2);
        // hashCode()方法重写之前,理论上来说这里输出的应该是1,但是结果去输出的是2,显然不符合HashSet集合存储元素的特点。
        System.out.println(students.size()); 

        // 重写Student类当中的hashCode()方法之后
        System.out.println(students.size());  // 1

    }
}

// 学生类
class Student{
    private String username;

    public Student() {
    }

    public Student(String username) {
        this.username = username;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (!(o instanceof Student)) return false;
        Student student = (Student) o;
        return Objects.equals(getUsername(), student.getUsername());
    }

    @Override
    public int hashCode() {
        return Objects.hash(getUsername());
    }
}

4.5、补充

package javase.map.HashMap;

import java.util.HashMap;
import java.util.Map;

/*
HashMap集合的key可以为null吗?
    答案:可以为null。但是需要注意,key值为null的只能有一个(因为key不可重复)。
    有可能面试的时候会遇到,开发用不到。
 */
public class HashMapTest03 {
    public static void main(String[] args) {
        Map map = new HashMap();
        map.put(null, null);
        // 通过key获取到value
        System.out.println(map.get(null)); // null
		// 输出map中元素的个数
        System.out.println(map.size());  // 1

        // 值覆盖
        map.put(null, 100);
        System.out.println(map.get(null));  // 100
    }
}

4.6、Hashtable

package javase.map.Hashtable;

import java.util.Hashtable;
import java.util.Map;

/**
 * 1、Hashtable的key可以为null吗?
 *      Hashtable的key和value都是不能为null的。
 *      HashMap集合的key和value都是可以为null的。
 *
 * 2、Hashtable和HashMap一样,底层都是哈希表数据结构。
 * 3、Hashtable的初始化容量是11,默认加载因子是:0.75f
 * 4、Hashtable的扩容是:原容量 * 2 + 1
 */
public class HashtableTest01 {
    public static void main(String[] args) {
        Map map = new Hashtable();

        // map.put(null, 123);  // java.lang.NullPointerException
        // map.put(1, null);  // java.lang.NullPointerException
    }
}

4.7、Properties

package javase.map.Hashtable;

import java.util.Properties;

/**
 * 目前只需要掌握Properties属性类对象的相关方法即可。
 * Properties是一个Map集合,继承Hashtable,Properties的key和value都是String类型。
 * Properties被称为属性类对象。
 * Properties是线程安全的。
 */
public class PropertiesTest01 {
    public static void main(String[] args) {
        // 创建一个Properties对象
        Properties properties = new Properties();

        // 需要掌握Properties的两个方法,一个存,一个取。
        properties.setProperty("url", "jdbc:mysql://localhost:3306/zh0u");
        properties.setProperty("driver", "com.mysql.jdbc.Driver");
        properties.setProperty("username", "root");
        properties.setProperty("password", "123");

        // 通过key获取value
        String url = properties.getProperty("url");
        String driver = properties.getProperty("driver");
        String username = properties.getProperty("username");
        String password = properties.getProperty("password");
 
        System.out.println(url);  // jdbc:mysql://localhost:3306/zh0u
        System.out.println(driver); // com.mysql.jdbc.Driver
        System.out.println(username); // root
        System.out.println(password); // 123
    }
}

4.8、TreeSet

package javase.map.TreeSet;

import java.util.TreeSet;

/**
 * 1、TreeSet集合底层实际上是一个TreeMap。
 * 2、TreeMap集合底层是一个二叉树。
 * 3、放到TreeSet集合中的元素,实际上是放到TreeMap集合key部分了。
 * 4、TreeSet集合中的元素:无序不可重复,但是可以按照元素的大小顺序自动排序。称为:可排序集合。
 */
public class TreeSetTest01 {
    public static void main(String[] args) {
        // 创建一个TreeSet集合
        TreeSet<String> ts = new TreeSet<>();
        // i添加字符串
        ts.add("zhangsan");
        ts.add("lisi");
        ts.add("wangwu");
        ts.add("zhangsi");
        ts.add("wangliu");

        // 遍历
        for (String t : ts) {
            // 按照字典顺序,升序
            System.out.println(t);
        }
        /*
        输出:
            lisi
            wangliu
            wangwu
            zhangsan
            zhangsi
        */

        // 创建一个Integer类型的TreeSet集合
        TreeSet<Integer> integers = new TreeSet<>();
        integers.add(30);
        integers.add(20);
        integers.add(3);
        integers.add(60);
        integers.add(1);

        // 遍历
        for (Integer integer : integers) {
            // 升序
            System.out.println(integer);
        }
        /*
        输出:
            1
            3
            20
            30
            60
        */
    }
}

4.9、自定义类型实现Comparable接口

package javase.map.TreeSet;

import java.util.TreeSet;

/*
对自定义的类型来说,TreeSet可以排序吗?
    以下程序中对于Person类型来说,无法排序。因为没有指定Person对象之间的比较规整。
    谁大谁小并没有说明。

    以下程序运行的时候出现了这个异常:
        java.lang.ClassCastException:
            javase.map.TreeSet.Person cannot be cast to java.lang.Comparable
    出现这个异常的原因是:
        Person类没有实现java.lang.Comparable接口。

 */
public class TreeSetTest02 {
    public static void main(String[] args) {
        Person p1 = new Person(32);
        Person p2 = new Person(20);
        Person p3 = new Person(30);
        Person p4 = new Person(24);

        // 创建集合
        TreeSet<Person> personTreeSet = new TreeSet<>();
        personTreeSet.add(p1);
        personTreeSet.add(p2);
        personTreeSet.add(p3);
        personTreeSet.add(p4);

        // 遍历
        for (Person person : personTreeSet) {
            // 输出引用数据类型时,会自动调用toString()方法。
            System.out.println(person);
        }
    }
}

// 放在TreeSet集合汇总的元素需要实现java.lang.Comparable接口
// 并且实现compareTo()方法。equals()方法可以不写。
class Person implements Comparable<Person> {
    private int age;

    public Person(){

    }
    public Person(int age){
        this.age = age;
    }

    @Override
    public String toString() {
        return "Person{" +
                "age=" + age +
                '}';
    }

    // 需要在这个方法中编写比较的逻辑,或者说是比较规则,按照什么原理进行比较!
    // k.compareTo(t.key);
    // 拿着参数k和集合中的每一个k进行比较,返回值可能是大于零、小于零、等于零。
    // 比较规则最终还是由程序员指定的:例如这里按照年龄升序。
    @Override
    public int compareTo(Person o) {
        return this.age - o.age; // 升序
    }

}

4.10 自定义排序规则

package javase.map.TreeSet;

import java.util.TreeSet;

// 先按照年龄升序,如果年龄一样的在按照姓名升序。
public class TreeSetTest03 {
    public static void main(String[] args) {
        TreeSet<Vip> vips = new TreeSet<>();
        vips.add(new Vip("zhangsi", 20));
        vips.add(new Vip("zhangsan", 20));
        vips.add(new Vip("king", 18));
        vips.add(new Vip("ying", 17));

        for (Vip vip : vips) {
            System.out.println(vip);
        }
        /*
        从这里可以看出,这里就是先按照年龄进行升序,如果年龄相同,在按照字典顺序进行排序。
            Vip{username='ying', age=17}
            Vip{username='king', age=18}
            Vip{username='zhangsan', age=20}
            Vip{username='zhangsi', age=20}
         */
    }
}

class Vip implements Comparable<Vip> {
    private String username;
    private int age;

    public Vip(){}
    public Vip(String username, int age){
        this.username = username;
        this.age = age;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public int getAge() {
        return age;
    }

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

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

    // 这里的排序规则为:先按照年龄进行排序,如果年龄一样。在按照姓名字典进行排序。
    /*
    compareTo()方法的返回值很重要:
        返回0表示相同,value会进行覆盖。
        返回>0,会继续在右子树上找。【10-9=1>0,说明自编的这个数字比较大,所以回到右子树上去找】
        返回<0,会继续在左子树上找。
     */
    @Override
    public int compareTo(Vip o) {
        // 这里编写的就是放入Set集合中时,排序的规则。
        if (this.age == o.age){
            // String类型与Integer类型中的compareTo()方法已经实现了重写直接调用即可。
            return this.username.compareTo(o.username);
        } else {
            return this.age - o.age;
        }
    }
}

4.11、自平衡二叉树数据结构

image-20211103093116827

4.12、实现比较器接口

package javase.map.TreeSet;

import java.util.Comparator;
import java.util.TreeSet;

/*
TreeSet集合中元素可排序的第二种方式:使用比较器的方式。
重要结论:
    放到TreeSet或者TreeMap集合key部分的元素要想做到排序,包括两种方式:
        第一种:放在集合中元素实现java.lang.Comparable接口。
        第二种;在构造TreeSet或者TreeMap集合的时候给它传一个比较器对象。
    Comparable和Comparator怎么选择?
        当比较规则不会发生改变的时候,或者说当比较规则只有一个的时候,建议实现comparable接口。
        如果比较规则有多个,并且需要多个比较规则之间频繁切换,建议使用comparator接口,comparator的设计符合OCP原则。
 */
public class TreeSetTest04 {
    public static void main(String[] args) {
        // 创建TreeSet集合的时候,需要使用这个比较器。
        // TreeSet<WuGui> wuGuis = new TreeSet<>(); // 这样不行,没有通过构造方法传递比较器

        // 方式一:使用匿名内部类,直接new接口,然后在接口的后面加上{}
        TreeSet<WuGui> wuGuis = new TreeSet<>(new Comparator<WuGui>() {
            @Override
            public int compare(WuGui o1, WuGui o2) {
                return o1.getAge() - o2.getAge();
            }
        });

        // 方式二:给构造方法传递一个比较器
        // TreeSet<WuGui> wuGuis = new TreeSet<>(new WuGuiComparator());

        // 添加元素
        wuGuis.add(new WuGui(1000));
        wuGuis.add(new WuGui(300));
        wuGuis.add(new WuGui(150));
        wuGuis.add(new WuGui(40));
        wuGuis.add(new WuGui(800));

        // 遍历
        for (WuGui wuGui : wuGuis) {
            System.out.println(wuGui);
        }

    }
}

// 创建乌龟对象
class WuGui{
    private int age;

    public WuGui(int age){
        this.age = age;
    }

    @Override
    public String toString() {
        return "WuGui{" +
                "age=" + age +
                '}';
    }

    public int getAge() {
        return age;
    }

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

// 创建乌龟对象比较器,比较的规则是按照年龄进行升序。
class WuGuiComparator implements Comparator<WuGui>{
    @Override
    public int compare(WuGui o1, WuGui o2) {
        return o1.getAge() - o2.getAge();
    }
}

4.13、Collections工具类

package javase;

import java.util.*;

/*
Collection : java.util.Collection集合接口。
Collections: java.util.Collections 集合工具类。
 */
public class CollectionsTest01 {
    public static void main(String[] args) {
        // ArrayList集合是非线程安全的。
        List<String> stringList = new ArrayList<>();

        // 变成线程安全的
        Collections.synchronizedList(stringList);
        // 添加元素
        stringList.add("abf");
        stringList.add("abx");
        stringList.add("abc");
        stringList.add("abe");

        Collections.sort(stringList);
        for (String s : stringList) {
            System.out.println(s);
        }

        // 自定义数据类型
        List<User> userList = new ArrayList<>();
        userList.add(new User(2, "zh0u"));
        userList.add(new User(1, "margin"));
        userList.add(new User(0, "alice"));
        userList.add(new User(1, "java"));

        // 注意:对List集合中元素进行排序,需要保证List集合中的元素实现了Comparable接口。
        Collections.sort(userList);
        for (User user : userList) {
            System.out.println(user);
        }

        // 对Set集合怎么排序?
        Set<String> stringSet = new HashSet<>();
        stringSet.add("java");
        stringSet.add("php");
        stringSet.add("python");
        stringSet.add("mysql");
        // 先将Set集合转换成List集合
        List<String> stringArrayList = new ArrayList<>(stringSet);
        Collections.sort(stringArrayList);

        // 遍历
        for (String s : stringArrayList) {
            System.out.println(s);
        }

        // 还有一种排序的方式
        // Collections.sort(列表, 比较器);
    }
}

class User implements Comparable<User>{
    private int id;
    private String username;

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

    public int getId() {
        return id;
    }

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

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    // 重写Comparable接口中的compareTo()方法。
    @Override
    public int compareTo(User o) {
        if (this.id == o.id) {
            return this.username.compareTo(o.username);
        } else {
            return this.id - o.id;
        }
    }

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

;
        for (String s : stringList) {
            System.out.println(s);
        }

        // 自定义数据类型
        List<User> userList = new ArrayList<>();
        userList.add(new User(2, "zh0u"));
        userList.add(new User(1, "margin"));
        userList.add(new User(0, "alice"));
        userList.add(new User(1, "java"));

        // 注意:对List集合中元素进行排序,需要保证List集合中的元素实现了Comparable接口。
        Collections.sort(userList);
        for (User user : userList) {
            System.out.println(user);
        }

        // 对Set集合怎么排序?
        Set<String> stringSet = new HashSet<>();
        stringSet.add("java");
        stringSet.add("php");
        stringSet.add("python");
        stringSet.add("mysql");
        // 先将Set集合转换成List集合
        List<String> stringArrayList = new ArrayList<>(stringSet);
        Collections.sort(stringArrayList);

        // 遍历
        for (String s : stringArrayList) {
            System.out.println(s);
        }

        // 还有一种排序的方式
        // Collections.sort(列表, 比较器);
    }
}

class User implements Comparable<User>{
    private int id;
    private String username;

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

    public int getId() {
        return id;
    }

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

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    // 重写Comparable接口中的compareTo()方法。
    @Override
    public int compareTo(User o) {
        if (this.id == o.id) {
            return this.username.compareTo(o.username);
        } else {
            return this.id - o.id;
        }
    }

    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", username='" + username + '\'' +
                '}';
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值