Java 泛型&集合


集合

集合简介

数组和集合的区别:

  • 相同点:

    • 都是容器,可以存储多个数据。
  • 不同点:

    • 存储长度:数组长度在初始化时指定,意味着只能保存定长的数据;而集合可以保存数量不确定的数据。

    • 存储类型:数组可以存基本数据类型和引用数据类型;集合只能存引用数据类型(实际上只是保存对象的引用变量),而如果要存基本数据类型,则需要存对应的包装类。

集合体系结构

Java 集合, 也叫作容器,主要是由两大接口派生而来:一个是 Collection 接口,主要用于存放单一元素;另一个是 Map 接口,主要用于存放键值对。

对于 Collection 接口,下面又有三个主要的子接口:List、Set 和 Queue。

  1. List(对付顺序的好帮手): 存储的元素是有序的、可重复的。
  2. Set(注重独一无二的性质): 存储的元素是无序的、不可重复的。
  3. Queue(实现排队功能的叫号机): 按特定的排队规则来确定先后顺序,存储的元素是有序的、可重复的。
  4. Map(用 key 来搜索的专家): 使用键值对(key-value)存储,类似于数学上的函数 y=f(x),"x" 代表 key,"y" 代表 value 。
    • key 是无序的、不可重复的
    • value 是无序的、可重复的
    • 每个键最多映射到一个值

image

图中只列举了主要的继承派生关系,并没有列举所有关系。比方省略了 AbstractList、NavigableSet 等抽象类以及其他的一些辅助类。
实际常用:ArrayList、LinkedList、HashSet、HashTable、HashMap

集合实现类特征

image


泛型

泛型概述

  • 泛型是 JDK5 中引入的特性,它提供了编译时的类型安全检测机制。

  • 泛型其实就是一种参数化的集合,它限制了你添加进集合的类型。泛型的设计之处就是希望对象或方法具有最广泛的表达能力。

  • 多态也可以看作是泛型的机制。一个类继承了父类,那么就能通过它的父类找到对应的子类,但是不能通过其他类来找到具体要找的这个类。

  • 泛型就是允许类、方法、接口对类型进行抽象,在允许向目标中传递多种数据类型的同时限定数据类型,确保数据类型的唯一性。这在集合类型中是非常常见的。

泛型的定义格式:

  • <类型>:指定一种类型的格式。尖括号里面可以任意书写,一般只写一个字母。例如:<E>、<T>
  • <类型1, 类型2, …>:指定多种类型的格式,多种类型之间用逗号隔开。例如:<E,T>、<K,V>
// 示例:<String> 表示该容器只能存储字符串类型的数据
ArrayList<String> list = new ArrayList<>();

// 其余写法
ArrayList<String> list = new ArrayList<String>();
// 以下两种写法主要是为了新老版本的兼容性问题
ArrayList list = new ArrayList<String>();
ArrayList<String> list = new ArrayList();

注意:

  1. 泛型没有多态的概念,左右两边的数据类型必须要一致,或者只是写一边的泛型类型。
  2. 在泛型中不能使用基本数据类型。如果需要使用基本数据类型,那么就要使用基本数据类型所对应的包装类型。

示例:不加泛型,则默认是 Object 类型

image

示例:加泛型

image

泛型的好处:

  1. 解决获取数据元素时,需要注意强制类型转换的问题。

  2. 泛型提供了编译期的类型安全,确保只能把正确类型的对象放入集合中,避免了在运行时出现 ClassCastException。

  3. 把方法写成泛型 <T>,这样就不用针对不同的数据类型(例如 int、double、float)分别写方法,只要写一个方法就可以了,提高了代码的复用性。

  4. 编译时检查类型安全:泛型的主要目标是提高 Java 程序的类型安全,确保只能把正确类型的对象放入集合中,避免了在运行时出现 ClassCastException。

  5. 消除强制类型转换:泛型的一个附带好处是,消除源代码中的许多强制类型转换(所有的强制转换都是自动和隐式的,提高代码的重用率),这样就不用针对不同的数据类型(例如 int、double、float)分别写方法,只要写一个方法就可以了,这使得代码更加可读,并且减少了出错机会。

自定义泛型:

  • 自定义泛类,就是一个数据类型的占位符或者是一个数据类型的变量。
  • 自定义泛型只要符合标识符的命名规则即可。一般习惯使用大写字母 T(type)或 E(element)。

泛型类

定义格式:

修饰符 class 类名<类型> {}

泛型类的注意事项:

  1. 在类上自定义泛型的具体数据类型,是在创建实例对象的时候确定的。
  2. 如果在类上已经声明了自定义泛型,那么在创建实例对象的时候,如果没有指定泛型的具体数据类型,则默认为 Object 类型。
  3. 在类上自定义泛型不能作用于静态方法。如果静态方法需要使用自定义泛型,只能在方法上自定义泛型。

示例代码:

  • 泛型类:
public class Generic<T> {
    private T t;

    public T getT() {
        return t;
    }

    public void setT(T t) {
        this.t = t;
    }
}
  • 测试类:
public class GenericDemo {
    public static void main(String[] args) {
        Generic<String> g1 = new Generic<String>();
        g1.setT("杨幂");
        System.out.println(g1.getT());

        Generic<Integer> g2 = new Generic<Integer>();
        g2.setT(30);
        System.out.println(g2.getT());

        Generic<Boolean> g3 = new Generic<Boolean>();
        g3.setT(true);
        System.out.println(g3.getT());
    }
}

泛型方法

定义格式:

修饰符 <类型> 返回值类型 方法名(类型 变量名) {}

注意:在方法上自定义泛型,这个自定义泛型的具体数据类型是在调用该方法的时候传入实参确定的。

示例:

  • 带有泛型方法的类:
public class Generic {
    public <T> void show(T t) {
        System.out.println(t);
    }
}
  • 测试类:
public class GenericDemo {
    public static void main(String[] args) {
        Generic g = new Generic();
        g.show("小丸子");
        g.show(30);
        g.show(true);
        g.show(12.34);
    }
}

泛型接口

定义格式:

修饰符 interface 接口名<类型> {}

泛型接口的注意事项:

  1. 接口上自定义的泛型的具体数据类型,是在实现一个接口的时候指定的。
  2. 在接口上自定义的泛型如果在实现接口的时候没有指定具体的数据类型,那么默认为 Object 类型。

示例代码:

  • 泛型接口:
public interface Generic<T> {
    void show(T t);
}
  • 泛型接口实现类 方式一:定义实现类时和接口相同泛型,在创建实现类对象时再明确泛型的具体类型。
public class GenericImpl1<T> implements Generic<T> {
    @Override
    public void show(T t) {
        System.out.println(t);
    }
}
  • 泛型接口实现类 方式二:定义实现类时直接明确泛型的具体类型。
public class GenericImpl2 implements Generic<Integer> {
    @Override
    public void show(Integer t) {
        System.out.println(t);
    }
}
  • 测试类:
public class GenericDemo {
    public static void main(String[] args) {
        GenericImpl1<String> g1 = new GenericImpl<String>();
        g1.show("林青霞");
        GenericImpl1<Integer> g2 = new GenericImpl<Integer>();
        g2.show(30);

        GenericImpl2 g3 = new GenericImpl2();
        g3.show(10);
    }
}


类型通配符

类型通配符:<?>

  • ArrayList<?>:表示元素类型未知的 ArrayList,它的元素可以匹配任何的类型。
  • 但是并不能把元素添加到 ArrayList 中了,获取出来的也是父类类型。

类型通配符上限:<? extends 类型>

  • ArrayListList <? extends Number>:它表示的类型是 Number 或者其子类型。

类型通配符下限:<? super 类型>

  • ArrayListList <? super Number>:它表示的类型是 Number 或者其父类型。

泛型通配符的使用:

public class GenericDemo4 {
    public static void main(String[] args) {
        ArrayList<Integer> list1 = new ArrayList<>();
        ArrayList<String> list2 = new ArrayList<>();
        ArrayList<Number> list3 = new ArrayList<>();
        ArrayList<Object> list4 = new ArrayList<>();

        method(list1);
        method(list2);
        method(list3);
        method(list4);

        getElement1(list1);
        getElement1(list2);  // 报错
        getElement1(list3);
        getElement1(list4);  // 报错

        getElement2(list1);  // 报错
        getElement2(list2);  // 报错
        getElement2(list3);
        getElement2(list4);
    }

    // 泛型通配符: 此时的泛型可以是任意类型
    public static void method(ArrayList<?> list){}
    // 泛型的上限: 此时的泛型必须是 Number 类型或者 Number 类型的子类
    public static void getElement1(ArrayList<? extends Number> list){}
    // 泛型的下限: 此时的泛型必须是 Number 类型或者 Number 类型的父类
    public static void getElement2(ArrayList<? super Number> list){}

}

Collection

Collection 概述

  • Collection 接口是 Set、Queue、List 的父接口,即是单列集合的顶层接口,它表示一组对象,这些对象也称为 Collection 的元素。
  • JDK 不提供此接口的任何直接实现,它提供更具体的子接口(如 Set 和 List)实现。

Collection 常用方法

方法名 说明
boolean add(E e) 添加元素
boolean addAll(Collection<E> c) 将另一个集合(c)的元素全部添加到本集合中
boolean remove(Object o) 从集合中移除指定的元素
boolean removeIf(Predicate<? super E> filter) 根据条件进行移除
void clear() 清空集合中的元素
boolean contains(Object o) 判断集合中是否存在指定的元素
boolean containsAll(Collection<E> c) 判断一个集合是否包含另一个集合(c)的所有元素(与元素顺序无关,与元素内的元素顺序有关)
boolean isEmpty() 判断集合是否为空
int size() 集合的长度,也就是集合中元素的个数
Object[] toArray() 把集合中的元素全部存储到一个 Object 的数组中返回
void forEach() 实现集合类的遍历

toArray() 示例:

public static void main(String[] args) {

     Collection c = new ArrayList();
     c.add(new Person(110, "狗娃"));
     c.add(new Person(111, "狗剩"));

     // 从Object数组中取出的元素只能使用Object类型声明变量接收
     // 如果需要其他的类型则需要先进行强制类型转换
     Object[] arr = c.toArray();
     // 需求:把编号是110的人的信息输出
     for(int i=0; i<arr.length; i++){  // 遍历数组
         Person p = (Person) arr[i];
         if(p.id==110){
              System.out.println(p);
         }
     }
}

Collection 遍历

迭代器

迭代器介绍:

  • Iterator(迭代器)是 Collection 集合的超级接口,是 Collection 集合的专用遍历方式。
  • Iterator<E> iterator(): 返回此集合中元素的迭代器,通过集合对象的 iterator() 方法得到。

Iterator 中的常用方法

  • boolean hasNext(): 判断当前位置是否有元素可以被取出。
  • <E> next():获取当前位置的元素,并将迭代器对象移向下一个索引位置。
  • void remove(): 删除迭代器对象当前指向的元素。

注意:

当使用 Iterator 对集合元素进行迭代时,Iterator 并不是把元素对象传给了迭代变量,而是把元素的值传给了迭代变量(即值传递,而不是引用传递),所以修改迭代变量的值对集合元素本身没有任何影响。

示例:Collection 集合的遍历

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

public class IteratorDemo {
    public static void main(String[] args) {
        // 创建集合对象
        Collection<String> c = new ArrayList<>();

        // 添加元素
        c.add("hello");
        c.add("world");
        c.add("java");
        c.add("javaee");

        // Iterator<E> iterator():返回此集合中元素的迭代器,通过集合的iterator()方法得到
        Iterator<String> it = c.iterator();  // 多态:返回Iterator的实现类对象

        //用 while 循环改进元素的判断和获取
        while (it.hasNext()) {
            String s = it.next();
            System.out.println(s);
        }
    }
}

示例:迭代器的删除方法

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

public class IteratorDemo {
    public static void main(String[] args) {
        ArrayList<String> list = new ArrayList<>();
        list.add("a");
        list.add("b");
        list.add("b");
        list.add("c");
        list.add("d");

        Iterator<String> it = list.iterator();
        while(it.hasNext()){
            String s = it.next();
            if("b".equals(s)){
                // 指向谁,那么此时就删除谁
                it.remove();
            }
        }
        System.out.println(list);
    }
}

forEach()

先看一个 forEach() 方法遍历 List 集合的例子:

List<String> list =Lists.newArrayList("a","b","c","d");

// 遍历方式1(其中anyThing可以用其它字符替换)
list.forEach((anyThing)->System.out.println(anyThing));
// 遍历方式2
list.forEach(any->System.out.println(any));

// 匹配输出"b"
list.forEach(item->{
    if("b".equals(item)){
        System.out.println(item);
    }
);

forEach() 方法是 Iterable<T> 接口中的一个方法。Java 容器中,所有的 Collection 子类(List、Set)都会实现 Iteratable 接口以实现 foreach 功能。

forEach() 方法同样可以遍历存储其它对象的 List 集合:

List<User> list = Lists.newArrayList(new User("aa",10), new User("bb", 11), new User("cc", 12));
// 遍历
// list.forEach(any->System.out.println(any));
// 匹配输出:匹配项可以为 list 集合元素的属性(成员变量)
list.forEach(any->{
    if(new User("bb",11).equals(any)){
        System.out.println(any);
    }
});

增强 for 循环

介绍

  • 它是 JDK5 之后出现的,其原理是一个 Iterator 迭代器。
  • 实现 Iterable 接口的类才可以使用迭代器和增强 for 循环。
  • 作用是简化了数组和 Collection 集合的遍历。

使用方法:

    for(集合/数组中元素的数据类型 变量名: 集合/数组名) {
        // 已经将当前遍历到的元素封装到变量中了,直接使用变量即可
    }

示例:

import java.util.ArrayList;

public class MyCollectonDemo {
    public static void main(String[] args) {
        ArrayList<String> list =  new ArrayList<>();
        list.add("a");
        list.add("b");
        list.add("c");
        list.add("d");
        list.add("e");
        list.add("f");

        // 1. 数据类型一定是集合或者数组中元素的类型
        // 2. str 表示一个变量名,在循环的过程中,依次表示集合或者数组中的每一个元素
        // 3. list 就是要遍历的集合或者数组
        for(String str: list){
            System.out.println(str);
        }
    }
}

List

List 概述

List 集合的特点:

  • 存取有序
  • 元素可以重复
  • 有索引

List 的实现类特点:

  • ArrayList:底层是数组结构实现,查询快、增删慢。

  • LinkedList:底层是链表结构实现,查询慢、增删快。

List 判断两个对象相等的标准:

  • 只要通过 equals() 方法比较返回 true 即可。
  • 使用 remove() 方法时注意重复元素。

List 特有方法

List 集合继承了 Collection 接口,因此 Collection 有的方法 List 都有,即只要学 List 特有的方法。

添加方法 说明
void add(int index, E element) 把元素添加到集合中的指定索引值上
void addAll(int index, Collection<? extends E> c) 把集合元素添加到集合中的指定索引值上
获取方法 说明
E get(int index) 返回指定索引处的元素
E indexOf(Object o) 获取元素所在的第一个索引
E lastIndexOf(Object o) 获取元素所在的最后一个索引
List subList(int fromIndex, int toIndex) 根据开始索引和结束索引获取子集合(包头不包尾)
修改方法 说明
void set(int index, E element) 使用指定的元素替换指定索引值位置的元素
迭代方法 说明
ListIterator listIterator() 返回的是 List 特有的迭代器

listIterator 具备 Iterator 的方法,其特有方法如下:

listIterator 特有方法 说明
add(E e) 把指定元素插入到当前指针指向的位置上
set(E e) 使用指定的元素替换最后一次返回的值
hasPrevious() 判断当前位置是否存在上一个元素
Previous() 当前指针先向上移动一个单位,然后再取出当前指针指向的元素

示例:List 集合的三种遍历方式

public static void main(String[] args){
     List list = new ArrayList();
     list.add("狗娃");
     list.add("狗剩");
     list.add("陈大狗");
     list.add("赵本山");

     // list集合的get方法
     for(int i=0; i<list.size(); i++){
         System.out.println("集合的元素:"+list.get(i));
     }

     // list迭代器正序遍历
     ListIterator it = list.listIterator();
     while(it.hasNext()){
         System.out.println("集合的元素:"+it.next());
         it.add("aa");
     }
     // 最终结果:[1, aa, 2, aa, 3, aa]
     // 但遍历打印结果仍是1、2、3,不会输出 aa,否则的话将是死循环
	
     // list迭代器逆序遍历
     while(it.hasPrevious()){
         System.out.println("集合的元素:"+it.previous());
     }
}

迭代器在遍历元素时的注意事项:在迭代器迭代元素(迭代器创建到使用结束的时间)的过程中,不允许使用集合对象改变集合中的元素个数,如果需要添加或者删除只能使用迭代器的方法进行操作。

ArrayList<String> list = new ArrayList<>();
list.add("a");

// 报错的情况:
ListIterator it = list.listIterator();
list.add("aa");  // 迭代元素过程中使用了集合对象的添加操作
it.next();
// 如果使用了集合对象改变集合的元素个数,就会出现 ConcurrentModificationException 异常

// 不报错的情况:
ListIterator it = list.listIterator();
it.add("aa");  // 迭代元素过程中使用了迭代器对象的添加操作
it.next();

List 实现类

ArrayList

ArrayList 是实现了 List 接口的可扩容数组(动态数组),它的内部是基于数组实现的。它的具体定义如下:

public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable {...}
  • ArrayList 可以实现所有可选择的列表操作,允许存储所有类型的元素(包括 null)。ArrayList 还提供了内部存储 list 的方法,它能够完全替代 Vector,只有一点例外:ArrayList 不是线程安全的容器。
  • ArrayList 有一个容量的概念,这个数组的容量就是 List 用来存储元素的容量。
  • ArrayList 不是线程安全的容器,如果多个线程中至少有两个线程修改了 ArrayList 的结构的话就会导致线程安全问题,作为替代条件可以使用线程安全的 List,应使用Collections.synchronizedList
List list = Collections.synchronizedList(new ArrayList(...))
  • ArrayList 具有 fail-fast 快速失败机制,能够对 ArrayList 作出失败检测。当在迭代集合的过程中该集合在结构上发生改变的时候,就有可能会发生 fail-fast,即抛出 ConcurrentModificationException 异常。

问:使用 ArrayList 无参的构造函数创建一个对象时,默认容量是多少?如果长度不够时又自动增长多少?

答:每次扩容之后容量都会变为原来的 1.5 倍左右

  1. JDK6 以无参数构造方法创建 ArrayList 时,直接创建了长度是 10 的 Object[] 数组。
  2. JDK8 以无参数构造方法创建 ArrayList 时,实际上初始化赋值的是一个空数组。当真正对数组进行添加元素操作时,才真正分配容量。即向数组中添加第一个元素时,数组容量扩为 10 。

特点:

  1. 查询快:因为数组中的元素与元素之间的内存地址是连续的。
  2. 增删慢:因为 ArrayList 在增加元素时首先要检查长度够不够用,若不够,还要把旧数组的内容拷贝到新申请的数组中;而删除时,也要进行移位。因此如果是处理大数据量的场景则不建议用 ArrayList,效率过低。

特有的方法(不常用):

  • ensureCapacity(int minCapacity):指定容量。但一般用构造方法指定容量。
  • trimToSize():删除多余的容量。

ArrayList 应用场景:

  • 如果目前的数据是查询比较多,增删比较少的时候,那么就使用 ArrayList 存储这批数据。
  • 如:高校的图书馆(学生借阅多;书更新少)

代码示例:

import java.util.ArrayList;

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

        // 创建集合
        ArrayList<String> array = new ArrayList<String>();

        // 添加元素
        array.add("hello");
        array.add("world");
        array.add("java");

        System.out.println(array.remove("world"));  // true
        System.out.println(array.remove("javaee"));  // false

        System.out.println(array.remove(1));  // java
        // System.out.println(array.remove(3));  // IndexOutOfBoundsException

        array.add("hello");
        array.add("world");
        array.add("java");

        System.out.println(array.set(1, "javaee"));  // hello
        // System.out.println(array.set(3,"javaee"));  // IndexOutOfBoundsException

        System.out.println(array.get(0));  // hello
        System.out.println(array.get(1));  // javaee
        System.out.println(array.get(2));  // world
        // System.out.println(array.get(4));  // IndexOutOfBoundsException

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

        // 输出集合
        System.out.println("array:" + array);  // array:[hello, javaee, world, java]
    }
}

Vector

Vector 同 ArrayList 一样,都是基于数组实现的,只不过 Vector 是一个线程安全的容器,它会对内部的每个方法都简单粗暴地上锁,避免多线程引起的安全性问题,但是通常这种同步方式需要的开销比较大。因此,访问元素的效率要远远低于 ArrayList。

还有一点在于扩容上,ArrayList 扩容后的数组长度会增加 50%,而 Vector 的扩容长度后数组会增加一倍。


LinkedList

LinkedList 类是 List 接口的实现类——这意味着它是一个 List 集合,可以根据索引来随机访问集合中的元素。

除此之外,LinkedList 还实现了 Deque 接口,本质是一个双向链表,因此既可以被当成栈来使用,也可以当成队列来使用。

LinkedList 的主要特性如下:

  • LinkedList 所有的操作都可以表现为双向性的,索引到链表的操作将遍历从头到屋,视哪个距离近,则为遍历顺序。
  • 允许存储任何元素(包括 null)
  • 注意这个实现也不是线程安全的,如果多个线程并发访问链表,并且至少其中的一个线程修改了链表的结构,那么这个链表必须进行外部加锁。或者使用:
List list = Collections.synchronizedList(new LinkedList(...)) 

特有方法:

方法名 说明
public void addFirst(E e) 在该列表开头插入指定的元素
public void addLast(E e) 将指定的元素追加到此列表的末尾
public E getFirst() 返回此列表中的第一个元素
public E getLast() 返回此列表中的最后一个元素
public E removeFirst() 从此列表中删除并返回第一个元素
public E removeLast() 从此列表中删除并返回最后一个元素

数据结构相关方法:

方法名 说明
public void push() 从集合头部添加元素(模拟堆栈先进后出的存储方式)
public E pop() 从集合头部取出元素(模拟堆栈先进后出的存储方式)
public void offer() 从集合头部添加元素(模拟队列先进先出的存储方式)
public E poll() 从集合尾部取出元素(模拟队列先进先出的存储方式)

迭代器方法:

方法名 说明
public Iterator descendingIterator() 返回逆序的迭代器对象

示例:实现栈操作

public static void main(String[] args){
     LinkedList list = new LinkedList();
     list.push("狗娃");
     list.push("狗剩");
     list.push("美美");

     //此为成功遍历全部的方法
     int size =list.size();
     for(int i = 0; i<size; i++){
         System.out.println(list.pop());
     }

     /* 因为pop会删除元素,因此size()会不断变化,导致输出少一个
     for(int i = 0; i<list.size(); i++){
         System.out.println(list.pop());
     }
     */
}

LinkedList支持多种遍历方式:

  1. 通过迭代器遍历
  2. 通过快速随机访问遍历
  3. 通过 for 循环遍历
  4. 通过 pollFirst() 遍历
  5. 通过 pollLast() 遍历
  6. 通过 removeFirst() 遍历
  7. 通过 removeLast() 遍历

Stack

堆栈是我们常说的后入先出(吃了吐)的容器。它继承了 Vector 类,提供了通常用的 push 和 pop 操作、在栈顶的 peek 方法、测试 stack 是否为空的 empty 方法,和一个寻找与栈顶距离的 search 方法。

第一次创建栈时不包含任何元素。

一个更完善、可靠性更强的 LIFO 栈操作由 Deque 接口和它的实现提供,应该优先使用这个类:

Deque<Integer> stack = new ArrayDeque<Integer>()

Stack 与 Vector 一样,是线程安全的,但是性能较差,因此尽量少用 Stack 类。如果要实现“栈”这种数据结构,可以考虑使用 LinkedList 。


Collections(List 集合工具类)

常见方法:

  • 对 list 进行二分查找(前提该集合是有序)

    • public static int binarySearch(List<T> list, T key):根据键值查找索引值。
    • public static int binarySearch(List<T> list, T key, Comparator):如果集合不具备自然顺序的元素,那么需要借助比较器。
  • 对 list 集合进行排序

    • public static void sort(List<T> list)
    • public static void sort(List<T> list, comparator):如果集合不具备自然顺序的元素,那么需要传入比较器。
  • 对集合取最大值或最小值

    • public static T max(List<T> list)
    • public static T max(List<T> list, comparator):如果集合不具备自然顺序的元素,那么需要传入比较器。
    • public static T min(List<T> list)
    • public static T min(List<T> list, comparator):如果集合不具备自然顺序的元素,那么需要传入比较器。
  • 对 list 进行反转

    • public static void reverse(List<T> list)
  • 将不同步的集合变成同步的集合

    • public static Set<T> synchronizedSet(Set<T> s)
    • public static Map<K, V> synchronizedMap(Map<K, V> m)
    • public static List<T> synchronizedList(List<T> list)
  • 打乱顺序

    • public static void shuffle(List<T> list)
  • 替换所有的元素

    • public static void fill(List<T> list, Object o):将 list 所有的元素替换为 Object 参数
  • 复制:将所有元素从一个列表复制到另一个列表中

    • public static <T> void copy(List<T> dest, List<T> src)
ArrayList<String> strings = new ArrayList<>();
strings.add("1");
strings.add("2");
strings.add("3");
ArrayList<String> result = new ArrayList<>();
result.add("0");
result.add("0");
result.add("0");
// 注意:目标集合大小需先要与源集合一致
Collections.copy(result, strings);
System.out.println(result);  // [1, 2, 3]
  • 返回指定目标的第一次/最后一次的出现位置
    • public static int indexOfSubList(List<T> source, List<T>)
    • public static int lastIndexOfSubList(List<T> source, List<T>)
ArrayList<String> strings = new ArrayList<>();
strings.add("1");
strings.add("2");
strings.add("3");
ArrayList<String> result = new ArrayList<>();
result.add("2");
result.add("3");
int index = Collections.indexOfSubList(strings, result);
System.out.println(index);  // 1

Set

Set 集合的特点:

  • 不可以存储重复元素(add 重复元素时会返回 false)。
  • 无序。
  • 没有索引,不能使用普通 for 循环遍历。

Set 无特有方法


HashSet 实现类

HashSet 特点:

  • 底层数据结构是哈希表,因此具有很好的存取和查找性能。
  • 存取无序。
  • 不可以存储重复元素。
  • 集合元素值可以是 null(只能有一个)。
  • 没有索引,不能使用普通 for 循环遍历。
  • 注意这个实现不是线程安全的。如果多线程并发访问 HashSet,并且至少一个线程修改了 set,必须进行外部加锁。或者使用Collections.synchronizedSet()方法重写。
  • 这个实现支持 fail-fast 机制。
  • HashSet 集合判断两个元素相等的标准是:两个对象通过 equals() 方法比较相等,并且两个对象的 hashCode() 方法返回值也相等。

HashSet 中判断集合元素相等,具体分为如下四个情况:

  1. 如果有两个元素通过 equal() 方法比较返回 false,且它们的 hashCode() 方法返回不相等,HashSet 将会把它们存储在不同的位置。

  2. 如果有两个元素通过 equal() 方法比较返回 true,但它们的 hashCode() 方法返回不相等,HashSet 将会把它们存储在不同的位置。

  3. 如果两个对象通过 equals() 方法比较不相等,hashCode() 方法比较相等,HashSet 将会把它们存储在相同的位置,在这个位置以链表式结构来保存多个对象。(这是因为当向 HashSet 集合中存入一个元素时,HashSet 会调用对象的 hashCode() 方法来得到对象的 hashCode 值,然后根据该 hashCode 值来决定该对象存储在 HashSet 中的存储位置)。

  4. 如果有两个元素通过 equal() 方法比较返回 true,且它们的 hashCode() 方法也相等,HashSet 将不予添加。

哈希值:

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

  • 如何获取哈希值:Object 类中的 public int hashCode():返回对象的哈希值。

  • 哈希值的特点:

    • 同一个对象多次调用 hashCode() 方法返回的哈希值是相同的。
    • 默认情况下,不同对象的哈希值是不同的。而重写 hashCode() 方法可以实现让不同对象的哈希值相同。

哈希表结构:

  • JDK1.8 以前:数组 + 链表

image

  • JDK1.8 以后

    • 节点个数少于等于 8 个:数组 + 链表
    • 节点个数多于 8 个:数组 + 红黑树

image

HashSet 的实质是一个 HashMap。
HashSet 的所有集合元素,构成了 HashMap 的 key,其 value 为一个静态 Object 对象。
因此 HashSet 的所有性质,由 HashMap 的 key 所构成的集合都具备。

综上,HashSet 集合存储自定义类型元素时,要想实现元素的唯一,要求必须重写 hashCode 方法和 equals 方法。

  • 案例需求:

    • 创建一个存储学生对象的集合,存储多个学生对象,使用程序实现在控制台遍历该集合。
    • 要求:学生对象的成员变量值相同,我们就认为是同一个对象。
  • 代码实现:

// 学生类
public class Student {
    private String name;
    private int age;

    public Student() {
    }

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

    public String getName() {
        return name;
    }

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

    public int getAge() {
        return age;
    }

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

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;

        Student student = (Student) o;

        if (age != student.age) return false;
        return name != null ? name.equals(student.name) : student.name == null;
    }

    @Override
    public int hashCode() {
        int result = name != null ? name.hashCode() : 0;
        result = 31 * result + age;
        return result;
    }
}

// 测试类
public class HashSetDemo {
    public static void main(String[] args) {
        // 创建HashSet集合对象
        HashSet<Student> hs = new HashSet<Student>();

        // 创建学生对象
        Student s1 = new Student("林青霞", 30);
        Student s2 = new Student("张曼玉", 35);
        Student s3 = new Student("王祖贤", 33);

        Student s4 = new Student("王祖贤", 33);

        // 把学生添加到集合
        hs.add(s1);
        hs.add(s2);
        hs.add(s3);
        hs.add(s4);

        // 遍历集合(增强 for)
        for (Student s : hs) {
            System.out.println(s.getName() + "," + s.getAge());
        }
    }
}

子类 LinkedHashSet

LinkedHashSet 是 HashSet 的子类,也是根据元素的 hashCode 值来决定元素的存储位置,同时使用链表维护元素的次序,使得元素是以插入的顺序来保存的。

当遍历 LinkedHashSet 集合里的元素时,LinkedHashSet 将会按元素的添加顺序来访问集合里的元素。但是由于要维护元素的插入顺序,在性能上略低于 HashSet,不过在迭代访问 Set 里的全部元素时有更好的性能。

LinkedHashSet 依然不允许元素重复,判断重复标准与 HashSet 一致。


TreeSet 实现类

TreeSet 是一个基于 TreeMap 的 NavigableSet 实现。这些元素由他们的自然排序或者在创建时提供的 Comparator 进行排序,具体取决于使用的构造函数。

  • TreeSet 可以将元素按照规则进行排序:
    • TreeSet():根据其元素的自然排序进行排序。
    • TreeSet(Comparator comparator):根据指定的比较器进行排序。
  • TreeSet 为基本操作 add、remove 和 contains 提供了 log(n) 的时间成本。
  • 注意 TreeSet 不是线程安全的。如果多线程并发访问 TreeSet,并且至少一个线程修改了 set,必须进行外部加锁。或者使用:
SortedSet s = Collections.synchronizedSortedSet(new TreeSet(...)) 
  • TreeSet 持有 fail-fast 机制。

总结:无论使用自然排序还是定制排序,都可以通过自定义比较逻辑实现各种各样的排序方式。

注意:如果向 TreeSet 中添加了一个可变对象后,并且后面程序修改了该可变对象的变量,这将导致它与其他对象的大小顺序发生了改变,但 TreeSet 并不会再次调整它们。
因此,建议不要修改放入 TreeSet 集合中的元素变量。

示例:存储 Integer 类型的整数并遍历

import java.util.TreeSet;

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

        TreeSet<Integer> ts = new TreeSet<>();
        ts.add(1);
        ts.add(2);
        ts.add(3);

        for(Integer i : ts){
            System.out.println(i);
        }
    }
}

代码示例:自然排序 Comparable 的使用

  • 案例需求:

    • 存储学生对象并遍历,创建 TreeSet 集合使用无参构造方法。
    • 要求:按照年龄从小到大排序,年龄相同时,按照姓名的字母顺序排序。
  • 实现步骤:

    1. 使用空参构造创建 TreeSet 集合:用 TreeSet 集合存储自定义对象,无参构造方法使用的是自然排序对元素进行排序的。
    2. 自定义的 Student 类实现 Comparable 接口:自然排序,就是让元素所属的类实现 Comparable 接口,重写 compareTo(T o) 方法。
    3. 重写接口中的 compareTo 方法:重写方法时,一定要注意排序规则必须按照要求的主要条件和次要条件来写。
  • 代码实现:

// 学生类
public class Student implements Comparable<Student> {
    private String name;
    private int age;

    public Student() {
    }

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

    public String getName() {
        return name;
    }

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

    public int getAge() {
        return age;
    }

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

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

    @Override
    public int compareTo(Student o) {
        // 按照对象的年龄进行排序
        // 主要判断条件: 按照年龄从小到大排序
        int result = this.age - o.age;
        // 次要判断条件: 年龄相同时,按照姓名的字母顺序排序
        result = result == 0 ? this.name.compareTo(o.getName()) : result;
        return result;
    }
}
// 测试类
public class MyTreeSet {
    public static void main(String[] args) {
        // 创建集合对象
        TreeSet<Student> ts = new TreeSet<>();
        // 创建学生对象
        Student s1 = new Student("zhangsan", 28);
        Student s2 = new Student("lisi", 27);
        Student s3 = new Student("wangwu", 29);
        Student s4 = new Student("zhaoliu", 28);
        Student s5 = new Student("qianqi", 30);
        // 把学生添加到集合
        ts.add(s1);
        ts.add(s2);
        ts.add(s3);
        ts.add(s4);
        ts.add(s5);
        // 遍历集合
        for (Student student: ts) {
            System.out.println(student);
        }
    }
}

示例:比较器排序 Comparator 的使用

  • 案例需求:

    • 存储老师对象并遍历,创建 TreeSet 集合使用带参构造方法。
    • 要求:按照年龄从小到大排序,年龄相同时,按照姓名的字母顺序排序。
  • 实现步骤:

    • 用 TreeSet 集合存储自定义对象,带参构造方法使用的是比较器排序对元素进行排序的。
    • 比较器排序,就是让集合构造方法接收 Comparator 的实现类对象,重写 compare(T o1, T o2) 方法。
    • 重写方法时,一定要注意排序规则必须按照要求的主要条件和次要条件来写。
  • 代码实现:

// 老师类
public class Teacher {
    private String name;
    private int age;

    public Teacher() {
    }

    public Teacher(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 "Teacher{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}
// 测试类
public class MyTreeSet {
    public static void main(String[] args) {

        // 创建集合对象
        TreeSet<Teacher> ts = new TreeSet<>(new Comparator<Teacher>() {
            @Override
            public int compare(Teacher o1, Teacher o2) {
                //o1 表示现在要存入的那个元素
                //o2 表示已经存入到集合中的元素
              
                // 主要条件
                int result = o1.getAge() - o2.getAge();
                // 次要条件
                result = result == 0 ? o1.getName().compareTo(o2.getName()) : result;
                return result;
            }
        });
		
        // 创建老师对象
        Teacher t1 = new Teacher("zhangsan",23);
        Teacher t2 = new Teacher("lisi",22);
        Teacher t3 = new Teacher("wangwu",24);
        Teacher t4 = new Teacher("zhaoliu",24);
		
        // 把老师添加到集合
        ts.add(t1);
        ts.add(t2);
        ts.add(t3);
        ts.add(t4);
		
        // 遍历集合
        for (Teacher teacher : ts) {
            System.out.println(teacher);
        }
    }
}

两种比较方式总结:

  • 两种比较方式小结:
    • 自然排序:自定义类实现 Comparable 接口,重写 compareTo 方法,根据返回值进行排序。
    • 比较器排序:创建 TreeSet 对象的时候传递 Comparator 的实现类对象,重写compare方法,根据返回值进行排序。
    • 在使用的时候,默认使用自然排序,当自然排序不满足现在的需求时,必须使用比较器排序。
  • 两种方式中关于返回值的规则:
    • 如果返回值为负数,表示当前存入的元素是较小值,存左边。
    • 如果返回值为 0,表示当前存入的元素跟集合中元素重复了,不存。
    • 如果返回值为正数,表示当前存入的元素是较大值,存右边。

HashSet、LinkedHashSet、TreeSet 异同

  1. HashSet、LinkedHashSet 和 TreeSet 都是 Set 接口的实现类,都能保证元素唯一,并且都不是线程安全的。
  2. HashSet、LinkedHashSet 和 TreeSet 的主要区别在于底层数据结构不同:
    • HashSet 的底层数据结构是哈希表(基于 HashMap 实现)
    • LinkedHashSet 的底层数据结构是链表和哈希表,元素的插入和取出顺序满足 FIFO
    • TreeSet 底层数据结构是红黑树,元素是有序的,排序的方式有自然排序和定制排序
  3. 底层数据结构不同又导致这三者的应用场景不同:
    • HashSet 用于不需要保证元素插入和取出顺序的场景
    • LinkedHashSet 用于保证元素的插入和取出顺序满足 FIFO 的场景
    • TreeSet 用于支持对元素自定义排序规则的场景

EnumSet 实现类

EnumSet 是一个专为枚举类设计的集合类,EnumSet 中的所有元素都必须是指定枚举类型的枚举值,该枚举类型在创建 EnumSet 时显示或隐式地指定。

EnumSet 的集合元素也是有序的,EnumSet 以枚举值在 EnumSet 类内的定义顺序来决定集合元素的顺序。

EnumSet 特点:

  1. EnumSet 集合不允许加入 null 元素,EnumSet 中的所有元素都必须是指定枚举类型的枚举值。
  2. EnumSet 类没有暴露任何构造器来创建该类的实例,程序应该通过它提供的类方法来创建 EnumSet 对象。

EnumSet 只增加了一些创建 EnumSet 对象的方法,没有其余增加的方法:

image


HashSet、TreeSet、EnumSet 性能对比

结论:EnumSet > HashSet > LinkedHashSet > TreeSet

原因:

  1. EnumSet 内部以位向量的形式存储,结构紧凑、高效,且只存储枚举类的枚举值,所以最高效。
  2. HashSet 以 hash 算法进行位置存储,特别适合用于添加、查询操作。
  3. LinkedHashSet 由于要维护链表,性能比 HashSet 差点。但由于有了链表,LinkedHashSet 更适合于插入、删除以及遍历操作。
  4. TreeSet 需要额外的红黑树算法来维护集合的次序,性能最次。

但使用时也要考虑具体使用场景:

  • 当需要一个特定排序的集合时,使用 TreeSet 。
  • 当需要保存枚举类的枚举值时,使用 EnumSet 。
  • 当经常使用添加、查询操作时,使用 HashSet 。
  • 当经常插入排序或使用删除、插入及遍历操作时,使用 LinkedHashSet 。

Queue

Queue 用于模拟队列这种数据结构,队列通常是指“先进先出”(FIFO,first-in-first-out)的容器。新元素插入(offer)到队列的尾部,访问元素(poll)操作会返回队列头部的元素。通常,队列不允许随机访问队列中的元素。

接口中定义的方法:

image


Map

Map 集合的特点:

  • 双列集合,一个键对应一个值。
  • 键不可以重复,值可以重复。
  • 无序。
interface Map<K, V>  // K:键的类型;V:值的类型

Map 集合的常用方法:

添加方法 说明
V put(K key, V value) 添加元素
(如果之前没有存在该键,返回 null;如果之前存在该键,则返回原有的 value 值)
putall(Map<? extends K, ? extends V> m) 把指定集合添加到集合中
获取方法 说明
V get(K key) 根据键获取对应的值
int size() 获取 Map 中的键值对的个数
判断方法 说明
boolean containsKey(K key) 判断是否包含指定的键
boolean containsValue(V value) 判断是否包含指定的值
boolean isEmpty() 判断 Map 集合是否为空元素
(null:null 也能作为有效数据)
删除方法 说明
void clear() 清空集合中的所有数据
V remove(Object Key) 根据键删除一条 Map 中的数据,返回的是该键对应的值
迭代方法 说明
Set<K> keySet() 把 Map 集合中的所有键都保存到一个 Set 集合中返回
Collection<V> values() 把 Map 集合中的所有值都保存到一个 Collection 集合中返回
Set<Map.Entry<K, V>> entrySet() 把 Map 集合中的所有键和值都保存到一个 Set 集合中返回

示例:

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

public class Test {
    public static void main(String[] args) {
        // 创建集合对象
        Map<String, String> map = new HashMap<String, String>();

        // V put(K key,V value):添加元素
        map.put("张无忌", "赵敏");
        map.put("郭靖", "黄蓉");
        map.put("杨过", "小龙女");

        // V remove(Object key):根据键删除键值对元素
//        System.out.println(map.remove("郭靖"));
//        System.out.println(map.remove("郭襄"));

        // void clear():移除所有的键值对元素
//        map.clear();

        // boolean containsKey(Object key):判断集合是否包含指定的键
//        System.out.println(map.containsKey("郭靖"));
//        System.out.println(map.containsKey("郭襄"));

        // boolean isEmpty():判断集合是否为空
//        System.out.println(map.isEmpty());

        // int size():集合的长度,也就是集合中键值对的个数
        System.out.println(map.size());

        // 输出集合对象
        System.out.println(map);

        // V get(Object key):根据键获取值
//        System.out.println(map.get("张无忌"));
//        System.out.println(map.get("张三丰"));

        // Set<K> keySet():获取所有键的集合
//        Set<String> keySet = map.keySet();
//        for(String key : keySet) {
//            System.out.println(key);
//        }

        // Collection<V> values():获取所有值的集合
        Collection<String> values = map.values();
        for(String value : values) {
            System.out.println(value);
        }
    }
}

代码示例:Map 集合的遍历方式一

  1. 获取所有键的集合:用 keySet() 方法实现。
  2. 遍历键的集合,获取到每一个键:用增强 for 实现。
  3. 根据键去找值:用 get(Object key) 方法实现。
public class MapDemo01 {
    public static void main(String[] args) {
        // 创建集合对象
        Map<String, String> map = new HashMap<String, String>();

        // 添加元素
        map.put("张无忌", "赵敏");
        map.put("郭靖", "黄蓉");
        map.put("杨过", "小龙女");

        // 获取所有键的集合:用 keySet() 方法实现
        Set<String> keySet = map.keySet();
        // 遍历键的集合,获取到每一个键:用增强 for 实现
        for (String key : keySet) {
            // 根据键去找值:用 get(Object key) 方法实现
            String value = map.get(key);
            System.out.println(key + "," + value);
        }
    }
}

代码示例:Map 集合的遍历方式二

  1. 获取所有键值对对象的集合
    • Set<Map.Entry<K, V>> entrySet():获取所有键值对对象的集合
  2. 遍历键值对对象的集合,得到每一个键值对对象
    • 用增强 for 实现,得到每一个 Map.Entry
  3. 根据键值对对象获取键和值
    • 用 getKey() 得到键
    • 用 getValue() 得到值
public class MapDemo {
    public static void main(String[] args) {
        // 创建集合对象
        Map<String, String> map = new HashMap<String, String>();

        // 添加元素
        map.put("张无忌", "赵敏");
        map.put("郭靖", "黄蓉");
        map.put("杨过", "小龙女");

        // 获取所有键值对对象的集合
        Set<Map.Entry<String, String>> entrySet = map.entrySet();
        // 遍历键值对对象的集合,得到每一个键值对对象
        for (Map.Entry<String, String> me : entrySet) {
            // 根据键值对对象获取键和值
            String key = me.getKey();
            String value = me.getValue();
            System.out.println(key + "," + value);
        }
    }
}

HashMap 实现类

HashMap 特点:

  • HashMap 是一个利用哈希表原理来存储元素的集合,并且允许空的 key-value 键值对。

  • 依赖 hashCode 方法和 equals 方法保证键的唯一。如果键要存储的是自定义对象,需要重写 hashCode 和 equals 方法。

  • HashMap 是非线程安全的,也就是说在多线程的环境下,可能会存在问题(而 Hashtable 是线程安全的容器)。可以使用Collections.synchronizedMap(new HashMap(...))来构造一个线程安全的 HashMap。

  • HashMap 也支持 fail-fast 机制。

  • key 判断相等的标准:
    类似于HashSet,HashMap 与 Hashtable 判断两个 key 相等的标准是:两个 key 通过 equals() 方法比较返回 true,且两个 key 的 hashCode 值也相等,则认为两个 key 是相等的。

  • value 判断相等的标准:
    HashMap 与 Hashtable 判断两个 value 相等的标准是:只要两个对象通过 equals() 方法比较返回 true 即可。

解决哈希冲突:

  • JDK8 以后的 HashMap 在解决哈希冲突时有了较大的变化,即当链表长度大于阈值(默认为 8)时,会将链表转化为红黑树,以减少搜索时间。
  • 将链表转换成红黑树前会判断,如果当前数组的长度小于 64,那么会选择先进行数组扩容,而不是转换为红黑树。

image

HashMap 应用案例:

  • 案例需求:

    • 创建一个 HashMap 集合,键是学生对象(Student),值是居住地 (String)。存储多个元素,并遍历。
    • 要求保证键的唯一性:如果学生对象的成员变量值相同,我们就认为是同一个对象。
  • 代码实现:

// 学生类
public class Student {
    private String name;
    private int age;

    public Student() {
    }

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

    public String getName() {
        return name;
    }

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

    public int getAge() {
        return age;
    }

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

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;

        Student student = (Student) o;

        if (age != student.age) return false;
        return name != null ? name.equals(student.name) : student.name == null;
    }

    @Override
    public int hashCode() {
        int result = name != null ? name.hashCode() : 0;
        result = 31 * result + age;
        return result;
    }
}
// 测试类
public class HashMapDemo {
    public static void main(String[] args) {
        // 创建 HashMap 集合对象
        HashMap<Student, String> hm = new HashMap<Student, String>();

        // 创建学生对象
        Student s1 = new Student("林青霞", 30);
        Student s2 = new Student("张曼玉", 35);
        Student s3 = new Student("王祖贤", 33);
        Student s4 = new Student("王祖贤", 33);

        // 把学生添加到集合
        hm.put(s1, "西安");
        hm.put(s2, "武汉");
        hm.put(s3, "郑州");
        hm.put(s4, "北京");

        // 遍历集合
        Set<Student> keySet = hm.keySet();
        for (Student key : keySet) {
            String value = hm.get(key);
            System.out.println(key.getName() + "," + key.getAge() + "," + value);
        }
    }
}

TreeMap 实现类

TreeMap 特点:

  • TreeMap 类是一个基于 NavigableMap 实现的红黑树。这个 map 根据 key 自然排序存储,或者通过 Comparator 进行定制排序。
  • 如果键存储的是自定义对象,需要实现 Comparable 接口或者在创建 TreeMap 对象时候给出比较器排序规则。
  • TreeMap 为 containsKey、getput 和 remove 方法提供了 log(n) 的时间开销。
  • TreeMap 不是线程安全的。如果多线程并发访问 TreeMap,并且至少一个线程修改了 map,必须进行外部加锁。这通常通过在自然封装集合的某个对象上进行同步来实现,或者使用SortedMap m = Collections.synchronizedSortedMap(new TreeMap(..))
  • TreeMap 持有 fail-fast 机制。

TreeMap 应用案例:

  • 案例需求:

    • 创建一个 TreeMap 集合,键是学生对象(Student),值是籍贯(String),学生属性姓名和年龄,按照年龄进行排序并遍历。
    • 要求按照学生的年龄进行排序,如果年龄相同则按照姓名进行排序。
  • 代码实现:

// 学生类
public class Student implements Comparable<Student>{
    private String name;
    private int age;

    public Student() {
    }

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

    public String getName() {
        return name;
    }

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

    public int getAge() {
        return age;
    }

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

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

    @Override
    public int compareTo(Student o) {
        // 按照年龄进行排序
        int result = o.getAge() - this.getAge();
        // 次要条件,按照姓名排序。
        result = result == 0 ? o.getName().compareTo(this.getName()) : result;
        return result;
    }
}
// 测试类
public class Test {
    public static void main(String[] args) {
        // 创建TreeMap集合对象
        TreeMap<Student,String> tm = new TreeMap<>();
      
        // 创建学生对象
        Student s1 = new Student("xiaohei", 23);
        Student s2 = new Student("dapang", 22);
        Student s3 = new Student("xiaomei", 22);
      
        // 将学生对象添加到TreeMap集合中
        tm.put(s1, "江苏");
        tm.put(s2, "北京");
        tm.put(s3, "天津");
      
        // 遍历TreeMap集合,打印每个学生的信息
        tm.forEach(
                (Student key, String value)->{
                    System.out.println(key + "---" + value);
                }
        );
    }
}

不可变集合

方法介绍:

  • 在 List、Set、Map 接口中,都存在 of 方法,可以用来创建一个不可变的集合。
    • 这个集合不能添加,不能删除,不能修改。
    • 但是可以结合集合的带参构造,实现集合的批量添加。
  • 在 Map 接口中,还有一个 ofEntries 方法可以提高代码的阅读性。
    • 首先会把键值对封装成一个 Entry 对象,再把这个 Entry 对象添加到集合当中。

示例代码:

public class MyVariableParameter4 {
    public static void main(String[] args) {
        // static <E> List<E> of(E…elements):创建一个具有指定元素的 List 集合对象
        // static <E> Set<E> of(E…elements):创建一个具有指定元素的 Set 集合对象
        // static <K, V> Map<K, V> of(E…elements):创建一个具有指定元素的 Map 集合对象

        // method1();
        // method2();
        // method3();
        // method4();

    }

    private static void method4() {
        Map<String, String> map = Map.ofEntries(
                Map.entry("zhangsan", "江苏"),
                Map.entry("lisi", "北京"));
        System.out.println(map);
    }

    private static void method3() {
        Map<String, String> map = Map.of("zhangsan", "江苏", "lisi", "北京", "wangwu", "天津");
        System.out.println(map);
    }

    private static void method2() {
        // 传递的参数当中,不能存在重复的元素。
        Set<String> set = Set.of("a", "b", "c", "d","a");
        System.out.println(set);
    }

    private static void method1() {
        List<String> list = List.of("a", "b", "c", "d");
        System.out.println(list);
        // list.add("Q");
        // list.remove("a");
        // list.set(0,"A");
        // System.out.println(list);

//        ArrayList<String> list2 = new ArrayList<>();
//        list2.add("aaa");
//        list2.add("aaa");
//        list2.add("aaa");
//        list2.add("aaa");

        // 集合的批量添加。
        // 首先是通过调用 List.of 方法来创建一个不可变的集合,of 方法的形参就是一个可变参数。
        // 再创建一个 ArrayList 集合,并把这个不可变的集合中所有的数据,都添加到 ArrayList 中。
        ArrayList<String> list3 = new ArrayList<>(List.of("a", "b", "c", "d"));
        System.out.println(list3);
    }
}

Stream API

Java8 中有两大最为重要的改变:一个是 Lambda 表达式,另外一个则是 tream API(java. util. stream. *) 。

Stream 是 Java8 中处理集合的关键抽象概念,它可以执行非常复杂的查找、过滤和映射数据等操作。

使用 Stream API 对集合数据进行操作,就类似于使用 SQL 执行的数据库查询,还可以使用 Stream API 并发执行操作。

简而言之,StreamAPI 提供了一种高效且易于使用的处理数据的方式。

创建 Stream:

public  void test1(){

        //1.可以通过Collection系列集合提供的stream()或parallelStream()
        List<String> list = new ArrayList<>();
        Stream<String> stream1 = list.stream();

        //2.通过Arrays中的静态方法stream()获取数组流
        Staff[] staffs = new Staff[10];
        Stream<Staff> stream2 = Arrays.stream(staffs);

        //3.通过Steam类中的静态方法of()
        Stream<String> stream3 = Stream.of("aaa","bbb","ccc");

        //迭代
        Stream<Integer> stream4 = Stream.iterate(0, (x)->x+2);
        stream4.forEach(System.out::println);

        //生成
         Stream.generate(()->Math.random());
    }

使用 Stream:

Stream 的作用主要就是对数据的操作,其中重要的有以下几个:

  1. forEach:Stream 提供了新的方法 'forEach' 来迭代流中的每个数据。

  2. map:用于映射每个元素到对应的结果。

  3. filter:用于通过设置的条件过滤出元素。

  4. limit:用于获取指定数量的流。

  5. sorted:用于对流进行排序。

  6. 并行(parallel)程序:流并行处理程序的代替方法。

  7. Collectors:Collectors 类实现了很多归约操作,例如将流转换成集合和聚合元素,Collectors 还可用于返回列表或字符串。

  8. 另外,一些产生统计结果的收集器也非常有用。它们主要用于 int、double、long 等基本类型上,如 getMax()、getMin()、getSum()、getAverage() 等。

示例:

staffs.stream()
    .filter((e)->e.getAge()>=35)  // 获取年龄>=35的员工
    .forEach(System.out::println);  // 迭代输出
posted @ 2021-09-23 00:41  Juno3550  阅读(350)  评论(0编辑  收藏  举报