Java集合Collections
- 一、类集设置的目的
- 二、链表与二叉树思路
- 三、常见数据结构
- 四、Collection接口
- 五、List 接口
- 六、ArrayList
- 七、Vector
- 八、LinkedList
- 九、Iterator与ListIterator
- 十、forEach
- 十一、Set
- 十二、HashSet
- 十三、TreeSet与Comparable
- 十四、Map
- 十五、哈希表概述
- 十六、HashMap源码分析
- 十七、Map集合使用案例
- 十八、存储自定义对象
- 十九、JDK9集合新特性
一、类集设置的目的
对象数组有那些问题?普通的对象数组的最大问题在于数组中的元素个数是固定的,不能动态的扩充大小,所以最 早的时候可以通过链表实现一个动态对象数组。但是这样做毕竟太复杂了,所以在 Java 中为了方便用户操作各个数据结构, 所以引入了类集的概念,有时候就可以把类集称为 java 对数据结构的实现。 在整个类集中的,这个概念是从 JDK 1.2(Java 2)之后才正式引入的,最早也提供了很多的操作类,但是并没有完 整的提出类集的完整概念。 类集中最大的几个操作接口:Collection、Map、Iterator,这三个接口为以后要使用的最重点的接口。 所有的类集操作的接口或类都在 java.util 包中。
二、链表与二叉树思路
2.1 链表
链接: 链表.
2.2 二叉树
链接: 二叉树.
三、常见数据结构
链接: 常见数据结构.
四、Collection接口
4.1 概述
Collection 接口是在整个 Java 类集中保存单值的最大操作父接口,里面每次操作的时候都只能保存一个对象的数据。 此接口定义在
java.util 包中。
此接口定义如下:
public interface Collection<E> extends Iterable<E>
此接口使用了泛型技术,在 JDK 1.5 之后为了使类集操作的更加安全,所以引入了泛型。
本接口中一共定义了 15 个方法,那么此接口的全部子类或子接口就将全部继承以上接口中的方法。
但是,在开发中不会直接使用 Collection 接口。而使用其操作的子接口:List、Set。
Collection:单列集合类的根接口,用于存储一系列符合某种规则的元素,它有两个重要的子接口,分别是 java.util.List 和 java.util.Set
List 的特点是元素有序、元素可重复。
Set 的特点是元素无序,而且不可重复。
List 接口的主要实现类有java.util.ArrayList 和 java.util.LinkedList
Set 接口的主要实现类有java.util.HashSet 和 java.util.TreeSet
4.2 Collection 常用功能
Collection是所有单列集合的父接口,因此在Collection中定义了单列集合(List和Set)通用的一些方法,这些方法可用于操作所有的单列集合。方法如下:
public boolean add(E e) : 把给定的对象添加到当前集合中 。
public void clear() :清空集合中所有的元素。
public boolean remove(E e) : 把给定的对象在当前集合中删除。
public boolean contains(E e) : 判断当前集合中是否包含给定的对象。
public boolean isEmpty() : 判断当前集合是否为空。
public int size() : 返回集合中元素的个数。
public Object[] toArray() : 把集合中的元素,存储到数组中。
五、List 接口
在整个集合中 List 是 Collection 的子接口,里面的所有内容都是允许重复的。
5.1 接口介绍
java.util.List 接口继承自 Collection 接口,是单列集合的一个重要分支,习惯性地会将实现了 List接口的对象称为List集合。在List集合中允许出现重复的元素,所有的元素是以一种线性方式进行存储的,在程序中可以通过索引来访问集合中的指定元素。另外,List集合还有一个特点就是元素有 序,即元素的存入顺序和取出顺序一致。
List 子接口的定义:
public interface List<E> extends Collection<E>
5.2 List接口特点
- 它是一个元素存取有序的集合。例如,存元素的顺序是11、22、33。那么集合中,元素的存储就是按照11、22、33的顺序完成的)。
- 它是一个带有索引的集合,通过索引就可以精确的操作集合中的元素(与数组的索引是一个道理)。
- 集合中可以有重复的元素,通过元素的equals方法,来比较是否为重复的元素。
5.3 List接口中常用方法
public void add(int index, E element) : 将指定的元素,添加到该集合中的指定位置上。
public E get(int index) :返回集合中指定位置的元素。
public E remove(int index) : 移除列表中指定位置的元素, 返回的是被移除的元素。
public E set(int index, E element) :用指定元素替换集合中指定位置的元素,返回值的更新前的元素。
5.4 扩充方法
此接口上依然使用了泛型技术。此接口对于 Collection 接口来讲有如下的扩充方法:
在 List 接口中有以上 10 个方法是对已有的 Collection 接口进行的扩充。所以,证明,List 接口拥有比 Collection 接口更多的操作方法。了解了 List 接口之后,那么该如何使用该接口呢?需要找到此接口的实现类,常用的实现类有如下几个: · ArrayList(95%)、Vector(4%)、LinkedList(1%)
六、ArrayList
6.1 概述
java.util.ArrayList集合数据存储的结构是数组结构。元素增删慢,查找快,由于日常开发中使用最多的功能为查询数据、遍历数据,所以 ArrayList 是最常用的集合。
ArrayList 是 List 接口的子类,此类的定义如下:
public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, Serializable
此类继承了 AbstractList 类。AbstractList 是 List 接口的子类。AbstractList 是个抽象类,适配器设计模式。
6.2 范例:
- 增加及取得元素
- 使用 remove()方法删除若干个元素,并且使用循环的方式输出。 ·
- 根据指定位置取的内容的方法,只有 List 接口才有定义,其他的任何接口都没有任何的定义。
package org.listdemo.arraylistdemo;
import java.util.ArrayList; import java.util.List;
public class ArrayListDemo02 {
public static void main(String[] args) {
List<String> all = new ArrayList<String>(); // 实例化List对象,并指定泛型类型
all.add("hello "); // 增加内容,此方法从Collection接口继承而来
all.add(0, "LAMP ");// 增加内容,此方法是List接口单独定义的
all.add("world"); // 增加内容,此方法从Collection接口继承而来
all.remove(1); // 根据索引删除内容,此方法是List接口单独定义的
all.remove("world");// 删除指定的对象
System.out.print("集合中的内容是:");
for (int x = 0; x < all.size(); x++) { // size()方法从Collection接口继承而来
System.out.print(all.get(x) + "、"); // 此方法是List接口单独定义的
}
}
}
- 以上的操作向集合中增加了三个元素,其中在指定位置增加的操作是 List 接口单独定义的。随后进行输出的时候, 实际上调用的是
toString()方法完成输出的。 - 可以发现,此时的对象数组并没有长度的限制,长度可以任意长,只要是内存够大就行。
输出结果:
七、Vector
与 ArrayList 一样,Vector 本身也属于 List 接口的子类,此类的定义如下:
public class Vector<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, Serializable
此类与 ArrayList 类一样,都是 AbstractList 的子类。所以,此时的操作只要是 List 接口的子类就都按照 List 进行操作。
从Java 2平台v1.2开始,该类被改进以实现List接口,使其成为Java Collections Framework的成员。 与新的集合实现不同, Vector是同步的。 如果不需要线程安全实现,建议使用ArrayList代替Vector 。
7.1范例:
package org.listdemo.vectordemo;
import java.util.List;
import java.util.Vector;
public class VectorDemo01 {
public static void main(String[] args) {
List<String> all = new Vector<String>(); // 实例化List对象,并指定泛型类型
all.add("hello "); // 增加内容,此方法从Collection接口继承而来
all.add(0, "LAMP ");// 增加内容,此方法是List接口单独定义的
all.add("world"); // 增加内容,此方法从Collection接口继承而来
all.remove(1); // 根据索引删除内容,此方法是List接口单独定义的
all.remove("world");// 删除指定的对象
System.out.print("集合中的内容是:");
for (int x = 0; x < all.size(); x++) {
// size()方法从Collection接口继承而来
System.out.print(all.get(x) + "、");
// 此方法是List接口单独定义的
}
}
}
以上的操作结果与使用 ArrayList 本身并没有任何的区别。因为操作的时候是以接口为操作的标准。
但是 Vector 属于 Java元老级的操作类,是最早的提供了动态对象数组的操作类,在 JDK 1.0 的时候就已经推出了此 类的使用,只是后来在 JDK 1.2之后引入了 Java 类集合框架。但是为了照顾很多已经习惯于使用 Vector 的用户,所以在 JDK 1.2 之后将 Vector类进行了升级了,让其多实现了一个 List 接口,这样才将这个类继续保留了下来。
7.2 Vector 类和 ArrayList 类的区别
这两个类虽然都是 List 接口的子类,但是使用起来有如下的区别,为了方便大家笔试,列出此内容:
八、LinkedList
8.1 概述
java.util.LinkedList 集合数据存储的结构是链表结构。方便元素添加、删除的集合。
LinkedList是一个双向链表
此类的使用几率是非常低的,但是此类的定义如下:
public class LinkedList<E> extends AbstractSequentialList<E>
implements List<E>, Deque<E>, Cloneable, Serializable
此类继承了 AbstractList,所以是 List 的子类。但是此类也是 Queue 接口的子类,Queue 接口定义了如下的方法:
8.2 范例
验证 LinkedList 子类
import java.util.LinkedList;
import java.util.Queue;
public class TestDemo {
public static void main(String[] args) {
Queue<String> queue = new LinkedList<String>();
queue.add("A");
queue.add("B");
queue.add("C");
int len=queue.size();//把queue的大小先取出来,否则每循环一次,移除一个元素,就少 一个元素,那么queue.size()在变小,就不能循环queue.size()次了。
for (int x = 0; x <len; x++) {
System.out.println(queue.poll());
}
System.out.println(queue);
}
}
8.3 常用方法
public void addFirst(E e) :将指定元素插入此列表的开头。
public void addLast(E e) :将指定元素添加到此列表的结尾。
public E getFirst() :返回此列表的第一个元素。
public E getLast() :返回此列表的最后一个元素。
public E removeFirst() :移除并返回此列表的第一个元素。
public E removeLast() :移除并返回此列表的最后一个元素。
public E pop() :从此列表所表示的堆栈处弹出一个元素。
public void push(E e) :将元素推入此列表所表示的堆栈。
public boolean isEmpty() :如果列表不包含元素,则返回true。
LinkedList是List的子类,List中的方法LinkedList都是可以使用。在开发时,LinkedList集合也可以作为堆栈,队列的结构使用。
九、Iterator与ListIterator
9.1 概述
在程序开发中,经常需要遍历集合中的所有元素。针对这种需求,JDK专门提供了一个接口java.util.Iterator 。
Iterator接口也是Java集合中的一员,但它与 Collection 、 Map 接口有所不同, Collection 接口与 Map接口主要用于存储元素,而 Iterator 主要用于迭代访(即遍历)Collection 中的元素,因此 Iterator对象也被称为迭代器。
Iterator 属于迭代输出,基本的操作原理:是不断的判断是否有下一个元素,有的话,则直接输出。
此接口定义如下:
public interface Iterator
9.2 常用方法
要想使用此接口,则必须使用 Collection 接口,在 Collection 接口中规定了一个 iterator()方法,可以用于为Iterator 接口进行实例化操作。 此接口规定了以下的三个方法:
public Iterator iterator() : 获取集合对应的迭代器,用来遍历集合中的元素的。
public E next() :返回迭代的下一个元素。
public boolean hasNext() :如果仍有元素可以迭代,则返回 true。
十、forEach
10.1 概述
//forEach
//增强for循环,用于迭代数组或集合(Collection)
10.2 使用
- 格式:
for(元素的数据类型 变量 : Collection集合or数组){
//写操作代码
}
- 实例:
public class Demo4 {
public static void main(String[] args) {
//forEach
//增强for循环,用于迭代数组或集合(Collection)
int[] arr = {1,2,3,5,7,8};
for (int date:arr){ //date代表数组中的每个元素
System.out.println(date);
}
System.out.println("--------------------------");
ArrayList<String> arrayList = new ArrayList<>();
arrayList.add("hello");
arrayList.add("how");
arrayList.add("are");
arrayList.add("you");
for (String s:arrayList){
//接收变量s代表 代表被遍历到的集合元素
System.out.println(s);
}
}
}
tips: 新for循环必须有被遍历的目标。目标只能是Collection或者是数组。新式for仅仅作为遍历操作出现。
十一、Set
11.1概述
Set 接口也是 Collection 的子接口,与 List 接口最大的不同在于,Set 接口里面的内容是不允许重复的。
Set 接口并没有对 Collection 接口进行扩充,基本上还是与Collection 接口保持一致。因为此接口没有 List 接口中定义 的 get(int index)方法,所以无法使用循环进行输出。
那么在此接口中有两个常用的子类:HashSet、TreeSet
public interface Set<E>
extends Collection<E>
不包含重复元素的集合。 更正式地说,集合不包含元素对e1和e2 ,使得e1.equals(e2)和最多一个null元素。 正如其名称所暗示的,此接口模拟数学集抽象。
该Set接口放置额外的约定,超过从继承Collection接口,所有构造函数的合同,而位于该合同add , equals和hashCode方法。 为方便起见,此处还包括其他继承方法的声明。 (这些声明附带的规范是针对Set接口定制的,但它们不包含任何其他规定。)
对构造函数的额外规定,毫不奇怪,所有构造函数必须创建一个不包含重复元素的集合(如上所定义)。
注意:如果将可变对象用作set元素,则必须非常小心。 如果在对象是集合中的元素时以影响equals比较的方式更改对象的值,则不指定集合的行为。 这种禁令的一个特例是,不允许将一个集合作为一个元素包含在内。
某些集合实现对它们可能包含的元素有限制。 例如,某些实现禁止null元素,并且一些实现对其元素的类型有限制。 尝试添加不合格的元素会引发未经检查的异常,通常为NullPointerException或ClassCastException 。 试图查询不合格元素的存在可能会引发异常,或者它可能只是返回false; 一些实现将展示前一种行为,一些将展示后者。 更一般地,尝试对不合格的元素进行操作,其完成不会导致将不合格的元素插入到集合中,可以在实现的选择中抛出异常或者它可以成功。 此类异常在此接口的规范中标记为“可选”。
十二、HashSet
12.1 概述
java.util.HashSet 是 Set 接口的一个实现类,它所存储的元素是不可重复的,并且元素都是无序的(即存取顺序不一致)。
java.util.HashSet 底层的实现其实是一个 java.util.HashMap 支持。
HashSet 是根据对象的哈希值来确定元素在集合中的存储位置,因此具有良好的存取和查找性能。保证元素唯一性的方式依赖于: hashCode与 equals 方法。
有插入和删除的方法,但是没有get方法,所以为了获取元素需要通过迭代器 或 转换成数组 来进行;
TreeSet 的集合因为借用了 Comparable 接口,同时可以去除重复值,而 HashSet 虽然是 Set 接口子类,但是对于没有复写 Object 的 equals 和 hashCode 方法的对象,加入了 HashSet集合中也是不能去掉重复值的;
12.2存储数据结构(哈希表)
- 在JDK1.8之前,哈希表底层采用数组+链表实现,即使用链表处理冲突,同一hash值的链表都存储在一个链表里。但是当位于一个桶中的元素较多,即hash值相等的元素较多时,通过key值依次查找的效率较低。而JDK1.8中,哈希表存储采用数组+链表+红黑树实现,当链表长度超过阈值(8)时,将链表转换为红黑树,这样大大减少了查找时间。
- 简单的来说,哈希表是由数组+链表+红黑树(JDK1.8增加了红黑树部分)实现的,如下图所示。
总而言之,JDK1.8引入红黑树大程度优化了HashMap的性能,那么对于我们来讲保证HashSet集合元素的唯一,其实就是根据对象的hashCode和equals方法来决定的。如果我们往集合中存放自定义的对象,那么保证其唯一,就必须复写hashCode和equals方法建立属于当前对象的比较方式。
十三、TreeSet与Comparable
13.1 定义
与 HashSet 不同的是,TreeSet 本身属于排序的子类,此类的定义如下:
public class TreeSet<E> extends AbstractSet<E>
implements NavigableSet<E>, Cloneable, Serializable
此类的iterator方法返回的迭代器是快速失败的 :如果在创建迭代器之后的任何时间修改集合,除了通过迭代器自己的remove方法之外,迭代器将抛出ConcurrentModificationException 。 因此,在并发修改的情况下,迭代器快速而干净地失败,而不是在未来的未确定时间冒任意,非确定性行为的风险。
补充:
快速失败:遍历的是集合本身,如果遍历过程中,通过其他操作使得集合本身发生改变,就会抛出异常;
安全失败:失败不会出错,在遍历的时候先将集合复制一份,即使原集合发生改变,也不会抛出异常;
13.2 举例
下面通过代码来观察其是如何进行排序的。
package org.listdemo.treesetdemo01;
import java.util.Set;
import java.util.TreeSet;
public class TreeSetDemo01 {
public static void main(String[] args) {
Set<String> all = new TreeSet<String>(); // 实例化Set接口对象\
all.add("D");
all.add("X");
all.add("A"); System.out.println(all);
}
}
虽然在增加元素的时候属于无序的操作,但是增加之后却可以为用户进行排序功能的实现。
定义 Person 类
package org.listdemo.treesetdemo02;
public class Person {
private String name;
private int age;
public Person() {
}
public Person(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;
}
public String toString() {
return "姓名:" + this.name + ",年龄:" + this.age;
}
}
下面定义一个 TreeSet 集合,向里面增加若干个 Person 对象。
package org.listdemo.treesetdemo02;
import java.util.Set;
import java.util.TreeSet;
public class TreeSetPersonDemo01 {
public static void main(String[] args) {
Set<Person> all = new TreeSet<Person>();
all.add(new Person("张三", 10));
all.add(new Person("李四", 10));
all.add(new Person("王五", 11));
all.add(new Person("赵六", 12));
all.add(new Person("孙七", 13));
System.out.println(all);
}
}
执行以上的操作代码之后,发现出现了如下的错误提示:
此时的提示是:Person 类不能向 Comparable 接口转型的问题?
所以,证明,如果现在要是想进行排序的话,则必须在 Person 类中实现 Comparable 接口。
package org.listdemo.treesetdemo03;
public class Person implements Comparable<Person> {
private String name;
private int age;
public int compareTo(Person per) {
if (this.age > per.age) {
return 1;
} else if (this.age < per.age) {
return -1;
} else {
return 0;
}
}
public Person() {
}
public Person(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;
}
public String toString() {
return "姓名:" + this.name + ",年龄:" + this.age;
}
}
那么此时再次使用之前的代码运行程序。程序的执行结果如下:
[姓名:张三,年龄:10, 姓名:王五,年龄:11, 姓名:赵六,年龄:12, 姓名:孙七,年龄:13]
从以上的结果中可以发现,李四没有了。因为李四的年龄和张三的年龄是一样的,所以会被认为是同一个对象。则 此时必须修改 Person 类,如果假设年龄相等的话,按字符串进行排序。
public int compareTo(Person per) {
if (this.age > per.age) {
return 1;
} else if (this.age < per.age) {
return -1;
} else {
return this.name.compareTo(per.name);
}
}
此时,可以发现李四出现了,如果加入了同一个人的信息的话,则会认为是重复元素,所以无法继续加入。
13.3 关于重复元素的说明
之前使用 Comparable 完成的对于重复元素的判断,那么 Set 接口定义的时候本身就是不允许重复元素的,那么证明如果现在真的是有重复元素的话,使用 HashSet 也同样可以进行区分。
public class HashSetPersonDemo01 {
public static void main(String[] args) {
Set<Person> all = new HashSet<Person>();
all.add(new Person("张三", 10));
all.add(new Person("李四", 10));
all.add(new Person("李四", 10));
all.add(new Person("王五", 11));
all.add(new Person("赵六", 12));
all.add(new Person("孙七", 13));
System.out.println(all);
}
}
此时发现,并没有去掉所谓的重复元素,也就是说之前的操作并不是真正的重复元素的判断,而是通过 Comparable 接口间接完成的。
如果要想判断两个对象是否相等,则必须使用 Object 类中的 equals()方法。
从最正规的来讲,如果要想判断两个对象是否相等,则有两种方法可以完成:
·
- 第一种判断两个对象的编码是否一致,这个方法需要通过hashCode()完成,即:每个对象有唯一的编码 ·
- 还需要进一步验证对象中的每个属性是否相等,需要通过 equals()完成。 所以此时需要覆写 Object 类中的
hashCode()方法,此方法表示一个唯一的编码,一般是通过公式计算出来的。
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (!(obj instanceof Person)) {
return false;
}
Person per = (Person) obj; if (
per.name.equals(this.name) && per.age == this.age) {
return true;
} else {
return false;
}
}
public int hashCode() {
return this.name.hashCode() * this.age;
}
发现,此时已经不存在重复元素了,所以如果要想去掉重复元素需要依靠 hashCode()和 equals()方法共同完成。
13.4 小结
-
关于 TreeSet 的排序实现,如果是集合中对象是自定义的或者说其他系统定义的类没有实现 Comparable 接口,则不能实现 TreeSet 的排序,会报类型转换(转向 Comparable 接口)错误。 换句话说要添加到 TreeSet 集合中的对象的类型必须实现了 Comparable 接口。
-
不过 TreeSet 的集合因为借用了 Comparable 接口,同时可以去除重复值,而 HashSet 虽然是 Set 接口子类,但是对于没有复写 Object 的 equals 和 hashCode 方法的对象,加入了 HashSet 集合中也是不能去掉重复值的。
十四、Map
14.1 概述
Map与Collection同一级别,而不是与List和Set同一级别;
Collection 中,每次操作的都是一个对象,如果现在假设要操作一对对象,则就必须使用 Map 了;
取数据操作不同:ArrayList可以通过下标,Set可以通过迭代器,Map需要先取出键形成Set集合,再根据具体的键取出值
Map中的键(key)不可重复
HashSet使用的HashMap,TreeSet使用的TreeMap,LinkedHashSet使用的LinkedHashMap。即Set集合使用Map的键来存储数据
十五、哈希表概述
在JDK1.8之前,哈希表底层采用数组+链表实现,即使用链表处理冲突,同一hash值的链表都存储在一个链表里。但是当位于一个桶中的元素较多,即hash值相等的元素较多时,通过key值依次查找的效率
较低。而JDK1.8中,哈希表存储采用数组+链表+红黑树实现,当链表长度超过阈值(8)时,将链表转换为红黑树,这样大大减少了查找时间。
简单的来说,哈希表是由数组+链表+红黑树(JDK1.8增加了红黑树部分)实现的,如下图所示。
为了方便理解结合一个存储流程图来说明一下:
总而言之,JDK1.8引入红黑树大程度优化了HashMap的性能,那么对于我们来讲保证HashSet集合元素的唯一,其实就是根据对象的hashCode和equals方法来决定的。如果我们往集合中存放自定义的对象,那么保证其唯一,就必须复写hashCode和equals方法建立属于当前对象的比较方式。
十六、HashMap源码分析
16.1 概述
HashMap 是 Map 的子类,此类的定义如下:
public class HashMap<K,V> extends AbstractMap<K,V>
implements Map<K,V>, Cloneable, Serializable
此类继承了 AbstractMap 类,同时可以被克隆,可以被序列化下来。
16.2 构造方法
作为一般规则,默认加载因子(.75)在时间和空间成本之间提供了良好的折衷。 较高的值会减少空间开销,但会增加查找成本(反映在HashMap类的大多数操作中,包括get和put )。 在设置其初始容量时,应考虑映射中的预期条目数及其负载因子,以便最小化重新散列操作的数量。 如果初始容量大于最大条目数除以加载因子,则不会发生重新加载操作。
16.3 源码
HashMap 构造方法
加载因子默认为0.75
初始容量
put方法:
十七、Map集合使用案例
17.1 HashMap
线程不安全(效率高)
17.2 HashTable
线程安全(效率低)
17.3 CurrentHashMap
采用分段机制,线程安全,效率又比较高
17.4 TreeMap
TreeSet借助TreeMap实现。TreeMap保证存储顺序
十八、存储自定义对象
public static void main(String[] args) {
HashMap<Book,String> data = new HashMap<>();
Book b1 = new Book("小黄书","黄色的书");
data.put(b1,"第一本书");
Book b2 = new Book("小红书","红色的书");
data.put(b2,"第二本书");
// b1.setName("小朋友");
System.out.println(data.get(b1));
Book b3 = new Book("小黄书","黄色的书");
System.out.println(data.get(b3));
}
十九、JDK9集合新特性
List,Set,Map三个接口存在of方法,可以存入任意数量的元素,但大小不能改变。