集合框架学习
目录
【引言】
在使用集合框架之前,我们较多使用数组来完成数据的存储,但目前数组存在问题有:
1. 复用度差
目前对于数据操作有且只能支持一个数据类型,一旦需要操作其他类型,全部重构,从头来过
2. 空间固定
数组的空间一旦在创建过程中确定,空间无法修改
3. 方法较少
Java中对于数组操作没有提供太多的有效方法,需要自己编写完成
以上问题集合都可以解决
1. 复用性,没有问题
集合创建采用了泛型模式,可以用户指定任意类型操作,既满足普适性,又满足数据类型一致化要求
2. 空间在合理范围以内自行扩展,不需要考虑容量问题
3. 方法很多,操作性很好
【集合框架组成】
【Collection接口各实现类简介】
-|Collection为java集合中所有集合的总接口
--|List<E>:有序,有下标,可重复
---|ArrayList<E>:可变长数组
---|LinkedList<E>:双向链表
—--|Vector<E>:线程安全可变长数组
--|Set<E>:无序,无下标,不可重复
---|HashSet:底层储存数据的结构是一个哈希表,储存效率,查询效率极高
---|TreeSet:底层储存数据的是平衡二叉树,要求数据必须有比较方式
[Tips]
ArrayList,Vector的有序性在于底层是数组实现
LinkedList的有序性在于底层链表实现
List:有序,有下标,可重复
Set:无序,无下标,不可重复
Collection:抽象两种方式,因此Collection也是无序无下标的
Collection重点方法
Collection方法共有
增:2:
boolean add(E e)
添加受当前集合约束的元素类型到当前集合中,如果改变数组则返回true,否则返回false
boolean addAll(Collection<? extends E> c)
添加指定元素集合到当前集合中,要求添加的元素集合必须是当前集合的本身类型元素或者当前集合子类对象类型
删:4
boolean remove(Object obj)
在当前集合中删除指定元素,删除成功返回true,否则返回false;若存在多个元素则删除第一个
boolean removeAll(Collection<?> c)
在当前集合中删除两集合交集
boolean retainAll(Collection<?> c)
在当前集合中保留两集合交集
void clear()
清空当前集合
查:5
int size()
查询当前集合有效元素个数
boolean isEmpty()
判断当前集合是否为空
boolean contains()
判断当前集合是否包含指定元素
boolean containsAll()
判断当前是否包含指定集合
Object toArray()
返回当前集合元素的Object类型的数组
import java.util.ArrayList;
import java.util.Collection;
class Animal {}
class Dog extends Animal {}
class Cat extends Animal {}
class Flower {}
/*
* 泛型上限演示
*/
public class Demo3 {
public static void main(String[] args) {
Collection<Animal> c1 = new ArrayList<Animal>();
Collection<Dog> c2 = new ArrayList<Dog>();
Collection<Cat> c3 = new ArrayList<Cat>();
Collection<Flower> c4 = new ArrayList<Flower>();
Collection<Object> c5 = new ArrayList<Object>();
/*
* 当前方法所需的参数类型是Collection<? extends Animal>
*
* 要求参数是一个Collection集合
* 要求Collection集合中保存的元素是Animal本身或者其子类对象
*/
c1.addAll(c1);
c1.addAll(c2);
c1.addAll(c3);
/*
* c4 对应的数据类型是Collection<Flower>
* 满足当前方法所需数据类型必须是Collection集合,但是不满足
* 当前集合中保存元素是Animal本身或者其子类对象的需求。参数错误。
*/
// c1.addAll(c4);
/*
* c5 对应的数据类型是Collection<Object>
* 满足参数要求为Collection集合,但是存储元素是Object类型
* 不是Animal的子类
*/
// c1.addAll(c5);
c1.add(new Animal());
c1.add(new Dog());
c1.add(new Cat());
System.out.println(c1);
}
}
Collections工具类
+ 's'表示工具类
除存取以外的其他方法
Collections.reverse(list);//反转序列
Collections.shuffle(list);//随机排序
Collections.sort(list);//对集合升序排序(元素必须实现Comparable接口)
Colletions.indexOfSubList(List<?> source, List<?> target);//在当前集合中查找指定集合第一次出现的位置
public static <T extends Comparable<? super T>> void sort (List<T> list ){}
【List接口】
List接口概述
List接口特征:
1.数据可重复
2.有序,添加顺序与保存数据一致
3.List的有序性一方面体现在有下标,一方面保存顺序是按照添加顺序保存
List<String> list = new ArrayList<String>();
List重点方法
Collection是List与Set的共同父接口,Collection和Set都具有无序性,List具有有序性,因此List相对与Collection的方法要多出关于有序性的方法
增:2
boolean add(int index, E e)
在指定位置增加指定元素
boolean addAll(int index, Collection<? extends E> c)
在指定位置增加指定集合,指定集合为该集合的本身类型或者子类型
删:1
E remove(int index)
删除并返回指定下标元素
改:1
E set(int index, E e)
用指定元素替换指定位置元素,并返回替换之前的元素
查:4
E get(int index)
返回指定下标元素
List<E> subList(int fromIndex, int toIndex)
按指定方式截取子序列
int indexOf(Object obj)
返回指定元素在序列中的位置
int lastIndexOf(Object obj)
返回指定元素在序列中最后一次出现的位置
package code1;
import java.util.ArrayList;
import java.util.List;
public class TestArrayList {
public static void main(String[] args) {
List<Integer> list = new ArrayList<>();
list.add(100);
list.add(200);
list.add(300);
System.out.println(list);
List<Double> list2 = new ArrayList<Double>();
list2.add(10.0);
// list2为Double类型,以下传入的参数为int类型,
// 编译器会先将10进行自动装箱,转为Integer类型
// Integer类型和Double类型为同级类,报错
// list2.add(10);
// 可先将20赋值给x,再将x传入
double x = 20;
list2.add(x);
System.out.println(x);
}
}
【ArrayList】
ArrayList概述
ArrayList是在Java中集合非常重要的一个组成,基于数组完成的数据结构。
ArrayList重点方法
ArrayList使用的方法都是List接口中的方法,有两个需要了解的成员方法:
void ensureCapacity();
数据量很大时,调用ensure Capacity()方法,可以一定程度上减少程序运行时间
void trimToSize();
节省空间,将底层数组的容量缩容至有效元素个数
细节问题
1. DEFAULT_CAPACITY
默认容量 private static final int DEFAULT_CAPACITY = 10;
在调用ArrayList无参数构造方法时,会使用DEFAULT_CAPACITY,作为底层Object数组的初始化容量
如果用户指定调用的是带有初始化底层Object数组容量的构造方法,会根据用户指定的容量创建一个ArrayList集合
2.扩容时的扩容倍数是1.5倍
3.MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
int[] arr = new int[10];
arr.length 是什么??? 数组容量
这里是一个方法还是属性??? 属性
属性是不是成员变量??? 是
成员变量是否需要占用内存??? 需要
new数组占用的空间什么地方??? 堆区
arr.length 属性是不是也在堆区??? 是
为什么 - 8???
因为在数组中存在很多属性,length只是众多属性中的一个,在创建数组使用的过
程中,需要留有内存空间用于保存数组中属性。
效率问题
ArrayList特征:
增删慢
增加慢
1. 数组当前容量无法满足添加操作,需要进行grow扩容方法执行,在扩容方法中,存在数组创建,数组数据拷贝。非常浪费时间,而且浪费内存。
2. 数组在添加数据的过程中,存在在指定位置添加元素,从指定位置开始之后的元素整体向后移动。
删除慢
1. 删除数据之后,从删除位置开始,之后的元素整体向前移动,移动过程非常浪费时间
2. 删除操作会导致数据空间的浪费,内存的浪费
查询快
ArrayList底层是一个数组结构,在查询操作的过程中,是按照数组+下标的方式来操作对应的元素,数组+下标方式可以直接获取对应的空间首地址,CPU访问效率极高。
【LinkedList】
LinkedList简述
LinkedList的父类AbstractList
LinkedList底层是双向链表
LinkedList重点方法
LinkedList使用的方法都是从List接口实现而来的方法,需要了解的是LinkedList特有方法:
boolean addFirst(E e);
在当前链表开始位置加元素
boolean addLast(E e);
在当前链表末尾添加元素
E getFirst();
获取第一个Node节点元素数据
E getLast();
获取末尾Node节点元素数据
E removeFirst();
删除头节点
E removeLast();
删除末尾节点
LinkedList效率问题
【Vector】
查询快,增删慢,线程安全,效率较低(类似等价于线程安全的ArrayList)
【Set接口】
Set接口:无序,不可重复
无序:体现在添加顺序和储存顺序不一致
不可重复:Set储存的元素不允许出现重复的情况
--|HashSet:底层储存结构是哈希表
--|TreeSet:底层储存数据的结构是【平衡二叉树】
【HashSet】
HashSet简介
HashSet没有重复元素:
1.基于HashCode实现元素不重复
2.当存入元素的哈希码相同时,会调用euqlas进行确认,如果比较结果为true,则拒绝后者存入
HashSet构造方法
HashSet构造方法:
Hashset(),底层HashMap实力的默认初始容量是16,加载因子是0.75(扩容)
可以传入参数按默认容量进行扩容
HashSet如何确认两个对象是否相同?
1.比较对象时先比较两个对象的HashCode码是不是一样,如果HashCode一样,再调用equals方法
2.调用equals方法
3.因为可能存在两个对象地址不同,内容相同的情况,这种情况我们一般认为是相同对象。但程序算出来的两个对象不是同一个对象。因此我们需要重写hashCode和equals方法进行调整。
1..在HashSet中重写equals方法(触发equals方法调用的前提条件:两个对象的hashCode一样,才执行equals):
--|this == obj
--|obj == null
--|类型比较
--|强转
--|自定义比较内容
2..重写hashCode方法
--|将尽可能多的对象属性加入hashCode方法中
增强for循环
没Set有下标,不能做遍历for循环
可以用foreach访问:
for(数据内容 中间变量 :集合) {}
【TreeSet】
TreeSet简介
树集合基于排列顺序实现元素不重复:比较依据存在相等情况就叫重复元素,比较结果为0就代表重复
树要求的集合必须可排序,实现java.lang.Comparable接口,指定排序规则,通过compareTo方法确定是否为重复元素;
或者通过java.util.Comparator接口中的compare方法使该集合具有可比性。
Comparable接口compareTo()
public int compareTo(Teacher o) {
if (this.salary > o.salary) {
return 1;
} else(this.salary < .salary) {
return -1;
}
return 0;
}
compareTo方法
字符串的compareTo方法比的是字符串的ASCII码值
public class TestComparable {
public static void main(String[] args) {
System.out.println("bbc".compareTo("bbd"));//-1
}
}
Comparator/Comparable对比
1.Comparable是一个排序接口,如果某个列实现了该接口,则该类支持排序
2.Comparator是一个比较器,如果需要实现某个类的排序,则可以利用Comparator接口实现该类一个比较器,在需要比较的接口除传入该比较器
Comparable是外部比较器,Comparator是内部比较器
package codehandup;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
/**
* Comparable接口实现和Comparator接口实现
*
* @author GXC
*
*/
public class ListWorker {
public static void main(String[] args) {
// 工人类别,采用Comparable接口实现
ArrayList<Worker> wo = new ArrayList<Worker>();
wo.add(new Worker("zhang3", 18, 3200));
wo.add(new Worker("li4", 22, 3200));
wo.add(new Worker("wang5-1", 26, 1500));
wo.add(new Worker("wang5-1600", 26, 1600));
wo.add(new Worker("wang5-2", 26, 1500));
wo.add(new Worker("wang5-1800", 26, 1800));
wo.add(new Worker("wang5-1200", 26, 1200));
wo.add(new Worker("wang5-1000", 26, 1000));
show(wo);
Collections.sort(wo);
show(wo);
System.out.println("########################");
//Singer类别,采用Comparator实现
ArrayList<Singer> s = new ArrayList<Singer>();
s.add(new Singer("lily1", 87));
s.add(new Singer("lily2", 83));
s.add(new Singer("lily3", 89));
s.add(new Singer("lily4", 83));
s.add(new Singer("lily5", 97));
s.add(new Singer("lily6", 99));
s.add(0,new Singer("lily7", 83));
show(s);
Comparator<Singer> ss = new SingerComparator();
Collections.sort(s, ss);
show(s);
}
public static <T> void show(ArrayList<T> arr) {
for (T w : arr) {
System.out.println(w);
}
System.out.println("---------------");
}
}
class Worker implements Comparable<Worker> {
private String name;
private int age;
private double salary;
public Worker(String name, int age, double salary) {
this.name = name;
this.age = age;
this.salary = salary;
}
@Override
public String toString() {
return "Worker [name=" + name + ", age=" + age + ", salary=" + salary + "]";
}
public int compareTo(Worker o) {
if (this.salary < o.salary) {
return -1;
} else if (this.salary > o.salary) {
return 1;
} else {
return 0;
}
}
}
class Singer {
private String name;
private double score;
public double getScore() {
return score;
}
@Override
public String toString() {
return "Student [name=" + name + ", score=" + score + "]";
}
public Singer(String name, double score) {
this.name = name;
this.score = score;
}
}
class SingerComparator implements Comparator<Singer> {
@Override
public int compare(Singer o1, Singer o2) {
if (o1.getScore() > o2.getScore()) {
return 1;
} else if (o1.getScore() < o2.getScore()) {
return -1;
} else {
return 0;
}
}
}
LinkedHashSet
链表实现的HashSet。按照链表进行储存,即可以保留元素的插入顺序
有序:参考插入位置
排序:可排序
【泛型上下限在集合中的应用】
泛型上限:
<? extends A>:表示A的子类
泛型下限:
<? super A>:表示A的父类
泛型集合:
(Collection<?> c)
?是泛型通配符
【迭代器在集合中的应用】
迭代器概述和操作模式
迭代器是操作集合中元素的第二种方式,并且存在一个升级版内容:【增强for循环】
迭代器和集合本身有着密切关系,首先迭代器的获取,就是通过集合对象得到对应当前集合的迭代器。
获取迭代器方法:
Iterator<E> iterator();
获取迭代器对象,泛型对应的具体数据类型和集合中约束的泛型具体数据类型一致
迭代器操作使用到的方法:
boolean hasNext();
判断当前集合中是否可以继续得到元素,继续遍历。
E next();
1. 获取迭代器当前指向的元素
2. 将迭代器指向下一个元素
void remove();
删除通过next方法获取到元素
【注意事项】
1. remove方法只能删除next方法获取到元素
2. remove方法只能在next方法之后执行,且不能跨过一个next执行
3. 没有next不能使用remove
迭代器案例代码
package interator;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
/**
* 迭代器的获取方式
* Iterator<E> iterator();
*
* 迭代器的使用 迭代器的三个方法
* boolean hasNext()
* 判断当前集合中是否可以继续得到元素,继续遍历
* E next()
* 1.获取迭代器当前指向的对象
* 2.将迭代器指向下一个对象
* void remove() 删除当前迭代器指向的元素 【注意事项】
* 1.remove()只能移除next方法获取到的元素
* 2.remove()方法只能在next()方法以后执行,不能两个remove相邻执行
* 3.没有next()不能使用remove()
*
* @author GGGXXC
*
*/
public class Demo1 {
public static void main(String[] args) {
// 现创建集合对象
Collection<String> c = new ArrayList<String>();
// 加入集合元素
c.add("雪花纯生");
c.add("修道院啤酒");
c.add("1664");
c.add("泰山精酿");
c.add("时光精酿");
System.out.println(c);
/*
* 获取迭代器对象 集合中元素是什么类型,迭代器就是什么类型
*
* 迭代器获取以后,默认指向第一个元素
*
* c.iterator();后直接“ Ctrl + 1”
*/
Iterator<String> iterator = c.iterator();
System.out.println("hasNext(): " + iterator.hasNext());
System.out.println("next(): " + iterator.next());
iterator.remove();
System.out.println(c);
}
}
package interator;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
public class Demo2 {
public static void main(String[] args) {
// 现创建集合对象
Collection<String> c = new ArrayList<String>();
// 加入集合元素
c.add("雪花纯生");
c.add("修道院啤酒");
System.out.println(c);
/*
* 获取迭代器对象 集合中元素是什么类型,迭代器就是什么类型
*/
Iterator<String> iterator = c.iterator();
while(iterator.hasNext()) {
String string = iterator.next();
System.out.println(string);
iterator.remove();
}
System.out.println(c);
}
}
迭代器和集合引用数据类型变量冲突问题 【难点】
package interator;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
public class Demo3 {
public static void main(String[] args) {
// 现创建集合对象
Collection<String> c = new ArrayList<String>();
// 加入集合元素
c.add("雪花纯生");
c.add("修道院啤酒");
c.add("1664");
c.add("泰山精酿");
c.add("时光精酿");
System.out.println(c);
/*
* 获取迭代器对象 集合中元素是什么类型,迭代器就是什么类型
* 迭代器根据集合对象生成,迭代器会依据当前集合对所有数组做一个整体规划
*/
Iterator<String> iterator = c.iterator();
while (iterator.hasNext()) {
String string = iterator.next();
System.out.println(string);
/*
* 此处是集合对象删除了元素
* 通过对象获取迭代器时,迭代器会知晓集合的整个元素分布
* 集合对象删除元素后,迭代器还是按原来方案进行迭代,但实际上集合删除了一个元素,这个元素在迭代器这里还是存在的,因此会出错
*
* 对于集合在内存中占用的空间而言:
* 1.集合对象可通过引用数据类型变量操作对应空间
* 2.迭代器可以直接操作对应空间
*
* 【共享资源冲突】
* 【多线程】
*/
c.remove("1664");
}
System.out.println(c);
}
}
//Exception in thread "main" java.util.ConcurrentModificationException
【Map】
Map概述
Map 地图,映射关系。
ORM 对象关系映射 类对象 <==> 数据库数据 Object Relational Mapping
双边队列
数据存储形式都是键(Key)值(Value)对形式
参照:
姓名 骚磊
年龄 16
性别 男
Map双边队列中对于数据存储类型:有限制,也没有限制
有限制:存储数据类型在创建Map双边队列时进行约束,保证数据类型一致化
没限制:Map可以满足任意类型
Map使用了两个泛型!!!
Map<K, V>
Map整体结构和常用API
interface Map<K, V>
--| class HashMap<K, V>
底层存储数据结构采用的方式是哈希表方式。存储数据时根据当前存储Key作为计算存储位置,和查询元素的唯一表示。
--| class TreeMap<K, V>
底层存储数据结构是一个二叉树结构,要求存储的键值对,Key必须有对应排序方式。这里就需要Comparator<T> 或者 Comparable<T>
常用
API Application Programing Interface
SDK Software Development Kits
增
put(K key, V value);
添加符合Map要求的键值对存入到双边队列中
putAll(Map<? extends K, ? extends V> map)
添加另一个Map到当前Map中,要求K是当前Map本身对应的K,或者其子类
V是当前Map本身对应的V,或者其子类
删
remove(Object key);
删除对应Key键值对
改
put(K key, V value);
使用value修改已存在的key对应的值
查
int size();
Map双边队列个数
boolean isEmpty();
判断当前Map双边队列中是否为空
boolean containsKey(Object key);
判断指定Key是否存在
boolean containsValue(Object value);
判断指定Value是否存在
Set<K> keySet();
返回Map双边队列中所有Key对应的Set集合
Collection<V> values();
返回Map双边队列中所有value对应Collection集合
【补充】
setId
setName
以上方法是Setter方法,设置类对象属性的方法
keySet
返回值类型是一个Set集合,具有数据存储唯一性
values
以s结尾的方法,表示复数,一般返回值类型都是Collection,List或者数组
总结:
set开头 设置方法
get开头 获取方法
Set结尾,返回值是Set集合,数据唯一
s结尾,返回值类是Collection,List或者数组
HashMap方法演示
package com.qfedu.a_map;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
/*
* Map方法演示
*/
public class Demo1 {
public static void main(String[] args) {
/*
* Map是一个接口,没有自己的类对象,这里是Map接口的实现类HashMap做方法演示
*/
Map<String, Integer> map1 = new HashMap<String, Integer>();
map1.put("Dior 999", 280);
map1.put("YSL", 220);
map1.put("Mac", 180);
map1.put("阿玛尼 405", 230);
System.out.println(map1);
Map<String, Integer> map2 = new HashMap<String, Integer>();
map2.put("TF", 179);
map2.put("雅诗兰黛", 229);
map1.putAll(map2);
System.out.println(map1);
map1.remove("Mac");
System.out.println(map1);
map1.put("阿玛尼 405", 280);
System.out.println(map1);
System.out.println(map1.size());
System.out.println(map1.isEmpty());
//map1.clear();
//System.out.println(map1.isEmpty());
System.out.println(map1.containsKey("YSL"));
System.out.println(map1.containsKey("杨树林"));
System.out.println(map1.containsValue(179));
System.out.println(map1.containsValue(100));
Set<String> keySet = map1.keySet();
System.out.println(keySet);
Collection<Integer> values = map1.values();
System.out.println(values);
}
}
TreeMap演示
/*
Map中的Key需要对应的排序方式
*/
package com.qfedu.a_map;
import java.util.TreeMap;
public class Demo2 {
public static void main(String[] args) {
TreeMap<Person, Integer> treeMap = new TreeMap<Person, Integer>(new MyComparator());
Person person = new Person(1, "骚磊", 16);
treeMap.put(person, 100);
treeMap.put(new Person(2, "骚杰", 26), 100);
treeMap.put(new Person(3, "宝哥", 16), 200);
treeMap.put(new Person(4, "茂林", 56), 100);
treeMap.put(new Person(5, "康爷", 76), 100);
treeMap.put(new Person(6, "大熊", 96), 100);
System.out.println(treeMap);
System.out.println(treeMap.size());
treeMap.put(new Person(6, "大熊", 96), 200);
System.out.println(treeMap);
System.out.println(treeMap.size());
person.setAge(96);
System.out.println(treeMap);
}
}
package com.qfedu.a_map;
import java.util.Comparator;
public class MyComparator implements Comparator<Person> {
@Override
public int compare(Person o1, Person o2) {
System.out.println("Comparator接口操作");
return o1.getAge() - o2.getAge();
}
}
public class Person {
private int id;
private String name;
private int age;
// 按照需求完成Constructor,Setter and Getter
}
关于Map键值对整体思想
Map双边队列中把Key和Value进行一个封装操作,完全按照一个数据类型来处理
例如:
class Entry<K, V> {
K k;
V v;
......
}
Map双边队列中提供了操作Entry的方法
Set<Map.Entry<K, V>> entrySet();
返回值类型是Entry键值对形式数据的Set集合
Set<Map.Entry<K, V>>
Map.Entry<K, V> Map接口的内部接口Entry,使用的泛型 K,V对应Map
创建过程中约束的K,V
因为返回值是Set集合,集合带有泛型 Set<Map接口中的内部接口Entry>
Entry对应的API
K getKey();
V getValue();
V setValue(V value);
package com.qfedu.a_map;
import java.util.HashMap;
import java.util.Map.Entry;
import java.util.Set;
public class Demo3 {
public static void main(String[] args) {
HashMap<String, String> map = new HashMap<String, String>();
map.put("迈巴赫", "好");
map.put("兰博基尼", null);
map.put("科尼塞克", "太贵了。。。");
map.put("布加迪", "威龙");
map.put("五菱宏光", "神车");
Set<Entry<String,String>> entrySet = map.entrySet();
for (Entry<String, String> entry : entrySet) {
System.out.println("Key : " + entry.getKey() + " Value : " + entry.getValue());
System.out.println(entry.setValue("都比较贵"));
}
}
}
小总结
1. Map很重要,尤其是HashMap,键值对操作在后期开发中非常常见。
数据库,Session Cookie 数据传递 Json XML...
【TIPS】
数组:只能存放相同或者【相兼容】的数据
集合框架在java.util包下
ArrayList接口的父类是AbstractList
Java集合只能存放引用类型数据,不能直接存放8种基本数据类型。
JDK1.5提供了自动装箱机制,集合可以直接将基本数据 类型放入容器中,容器会将基本数据类型装箱为对应的包装类
List<Integer> list = new ArrayList<>();//List<这里只能放引用类型>
System.out.println(list);这里的toString调用的是父类AbstractCollection的方法
集合框架在JDK1.5以后要求使用泛型,并且泛型为引用类型,如果没有指定泛型,则该集合类型为Object类型
另:如果在集合中未指定泛型,编译器会进行提示
ArrayLIst,LinkedList可以添加多个null
Set最多只能放一个null