集合框架
概述
集合是JAVA中提供的一种容器,用来存储多个数据。
前面学习过数组的知识,数组也是一种数据的容器,但是数组使用起来非常不方便,区别:
- 数组特点:类型固定,长度固定
- 集合特点:类型不固定,长度也不固定,随意存放任何数据
JAVA中提供了很多不同的集合,在不同的场景下选择合适的集合,这些众多的集合称为集合框架。
集合框架中大概分为两大类,分别是单列集合java.util.Collection和双列集合java.util.Map,集合框架都位于java.util包中
使用比较多的有接口有List、Set、Map接口,List,Set都是继承自Collection接口,Map则不是。
- List接口:元素有放入顺序,元素可重复 。和数组类似,List可以动态增长,查找元素效率高,插入删除元素效率低,因为会引起其他元素位置改变。
- Set接口:元素无放入顺序,元素不可重复,重复元素会覆盖掉。检索元素效率低下,删除和插入效率高,插入和删除不会引起元素位置改变。
- Map接口:Map用于保存具有映射关系的数据,Map里保存着两组数据:key和value,它们都可以使任何引用类型的数据,但key不能重复。所以通过指定的key就可以取出对应的value。Map 没有继承 Collection 接口。
ArrayList类
概述
我们先学习单列集合中的List接口跟实现类ArrayList,这个是开发过程使用得最多最频繁的一个集合。ArrayList的底层结构是一个数组实现,并提供了非常丰富使用的API供使用。
ArrayList实现了长度可变的数组,所有的元素是以一种线性方式进行存储的,在内存中分配连续的空间,遍历元素和随机访问元素的效率比较高。
常用API
- public boolean add(E e): 把给定的对象添加到当前集合中 。
- public E get(int index): 返回此列表中指定位置上的元素。
- public void clear() : 清空集合中所有的元素。
- public boolean remove(E e): 把给定的对象在当前集合中删除。
- public boolean contains(E e): 判断当前集合中是否包含给定的对象。
- public boolean isEmpty(): 判断当前集合是否为空。
- public int size(): 返回集合中元素的个数。
- public Object[] toArray(): 把集合中的元素,存储到数组中。
代码演示
public static void main(String[] args) {
// 创建集合容器
List<String> list = new ArrayList<>();
list.add("关羽");// 添加元素关羽,索引为0
list.add("张飞");// 添加元素张飞,索引为1
list.add("刘备");// 添加元素刘备,索引为2
list.add("赵云");// 添加元素赵云,索引为3
list.add("诸葛亮");// 添加元素诸葛亮,索引为4
System.out.println(list);// 打印集合信息
System.out.println(list.get(2));// 根据索引查找对应的元素
System.out.println(list.size());// 查看集合中一共有多少个元素
System.out.println(list.contains("赵云")); // 判断赵云是否存在集合中
list.remove("诸葛亮");// 删除诸葛亮
//Object[] toArray()转换成一个Object数组
Object[] objects = list.toArray();
// 遍历数组
for (int i = 0; i < objects.length; i++) {
System.out.println(objects[i]);
}
list.clear();// 清空集合
System.out.println(list.isEmpty());// boolean isEmpty() 判断是否为空
}
tips: java.util.Vector集合用法同java.util.ArrayList,区别在于
java.util.Vector是线程安全的,而java.util.ArrayList是线程非安全的。
遍历
我们经常需要遍历集合,那么如果遍历ArrayList中的所有元素呢?
方式1
public static void main(String[] args) {
// 创建集合容器
List<String> list = new ArrayList<>();
list.add("关羽");// 添加元素关羽,索引为0
list.add("张飞");// 添加元素张飞,索引为1
list.add("刘备");// 添加元素刘备,索引为2
list.add("赵云");// 添加元素赵云,索引为3
list.add("诸葛亮");// 添加元素诸葛亮,索引为4
// 遍历方式1
for (int i=0;i<list.size();i++){
System.out.println(list.get(i));
}
}
方式2
public static void main(String[] args) {
// 创建集合容器
List<String> list = new ArrayList<>();
list.add("关羽");// 添加元素关羽,索引为0
list.add("张飞");// 添加元素张飞,索引为1
list.add("刘备");// 添加元素刘备,索引为2
list.add("赵云");// 添加元素赵云,索引为3
list.add("诸葛亮");// 添加元素诸葛亮,索引为4
// 遍历方式2
for (String name : list){
System.out.println(name);
}
}
方式3
此外,我们还可以通过迭代器的方式来遍历,后面专门章节来讲解迭代器的用法。
LinkedList类
概述
LinkedList也是List接口的实现类,所以List接口中定义的方法在LinkedList中也可以使用。
LinkedList底层的存储结构是一个链表,在元素的前后分别有一个前置结点和后置结点,用于连接集合中的上一个元素和下一个元素,依次“手拉手”,构成一条链式数据的集合。
LinkedList集合的元素在内存中并不连续,从图中我们可以发现LinkedList对于添加、删除元素效率比较高,对于查找跟修改效率比较低。
常用API
- 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():从此列表所表示的堆栈处弹出一个元素。此方法等效于 removeFirst()。
- public void push(E e):将元素推入此列表所表示的堆栈。此方法等效于 addFirst(E)。
- public boolean isEmpty():如果列表不包含元素,则返回true。
代码演示
public static void main(String[] args) {
LinkedList<String> link = new LinkedList<String>();
//添加元素
link.addFirst("关羽");
link.addFirst("张飞");
link.addFirst("刘备");
System.out.println(link);
System.out.println(link.getFirst()); // 获取第1个元素
System.out.println(link.getLast()); // 获取最后1个元素
System.out.println(link.removeFirst());// 删除第1个元素
System.out.println(link.removeLast());// 删除最后1个元素
while (!link.isEmpty()) { //判断集合是否为空
System.out.println(link.pop()); //弹出集合中的栈顶元素
}
System.out.println(link);
}
遍历
方式1
public static void main(String[] args) {
LinkedList<String> link = new LinkedList<String>();
//添加元素
link.addFirst("关羽");
link.addFirst("张飞");
link.addFirst("刘备");
for (int i=0; i<link.size(); i++) {
System.out.println(link.get(i));
}
}
方式2
public static void main(String[] args) {
LinkedList<String> link = new LinkedList<String>();
//添加元素
link.addFirst("关羽");
link.addFirst("张飞");
link.addFirst("刘备");
for (String s : link) {
System.out.println(s);
}
}
方式3
此外,我们还可以通过迭代器的方式来遍历,后面专门章节来讲解迭代器的用法。
HashSet类
概述
java.util.Set接口和java.util.List接口一样,同样继承自Collection接口,它与Collection接口中的方法基本一致,并没有对Collection接口进行功能上的扩充,只是比Collection接口更加严格了。与List接口不同的是,Set接口中元素无序,并且都会以某种规则保证存入的元素不出现重复。
Set集合有多个子类,这里我们介绍其中的java.util.HashSet、java.util.TreeSet这两个集合。
java.util.HashSet是Set接口的一个实现类,它所存储的元素是不可重复的,并且元素都是无序的(即存取顺序不一致)。java.util.HashSet底层的实现其实是一个java.util.HashMap。
常用API
- public boolean add(E e): 如果此 set 中尚未包含指定元素,则添加指定元素 。
- public boolean contains(Object o): 如果此 set 包含指定元素,则返回 true。
- public void clear(): 从此 set 中移除所有元素。此调用返回后,该 set 将为空。
- public boolean remove(Object o): 如果指定元素存在于此 set 中,则将其移除。
- public boolean isEmpty(): 如果此 set 不包含任何元素,则返回 true。
- public int size(): 返回此 set 中的元素的数量(set 的容量)。
- public Iterator iterator(): 返回对此 set 中元素进行迭代的迭代器。返回元素的顺序并不是特定的。
代码演示
public static void main(String[] args) {
// 创建集合容器
Set<String> set = new HashSet<>();
set.add("关羽");
set.add("张飞");
set.add("刘备");
set.add("赵云");
set.add("诸葛亮");
System.out.println(set);// 打印集合信息
System.out.println(set.size());// 查看集合中一共有多少个元素
System.out.println(set.contains("赵云")); // 判断赵云是否存在集合中
set.remove("诸葛亮");// 删除诸葛亮
set.clear();// 清空集合
System.out.println(set.isEmpty());// boolean isEmpty() 判断是否为空
}
遍历
方式1
public static void main(String[] args) {
LinkedList<String> link = new LinkedList<String>();
//添加元素
link.addFirst("关羽");
link.addFirst("张飞");
link.addFirst("刘备");
for (String s : link) {
System.out.println(s);
}
}
方式2
此外,我们还可以通过迭代器的方式来遍历,后面专门章节来讲解迭代器的用法。
TreeSet类
TreeSet同样也是Set接口的一个实现类TreeSet是一个有序集合,底层结构为红黑树。会根据自然排序排列或比较器进行排序,且没有重复元素,也没有下标。通过TreeMap实现。
TreeSet由于底层结构是红黑树,红黑树满足2叉查找树的特性,故在TreeSet中的元素需要满足具有可比性的特性,可比性需要内部比较器或者外部比较器来实现,否则会抛出java.lang.ClassCastException。
代码演示
创建TreeSet容器,里面元素使用自定义Person类,拥有name、age属性,按照age的大小进行比较。
import java.util.TreeSet;
public class Demo05 {
public static void main(String[] args) {
TreeSet<Person> treeSet = new TreeSet<>();
treeSet.add(new Person("刘备",33));
treeSet.add(new Person("关羽",23));
treeSet.add(new Person("张飞",27));
}
}
class Person implements Comparable<Person>{
private String name ;
private int age ;
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;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
@Override
public int compareTo(Person person) {
return this.age - person.age;
}
}
迭代器
Iterator接口
在开发过程中,经常需要遍历集合中的所有元素。JDK专门提供了一个接口java.util.Iterator。它与Collection、Map接口有所不同,Collection接口与Map接口主要用于存储元素,而Iterator主要用于迭代访问(即遍历)Collection中的元素,因此Iterator对象也被称为迭代器。
想要遍历Collection集合,那么就要获取该集合迭代器完成迭代操作,下面介绍一下获取迭代器的方法:
- public Iterator iterator(): 获取集合对应的迭代器,用来遍历集合中的元素的。
- 迭代:简而言之,就是使用循环从头开始遍历所有元素。迭代的方法为在取元素之前先要判断集合中有没有元素,如果有,就把这个元素取出来,继续在判断,如果还有就再取出出来。一直把集合中的所有元素全部取出。这种取出方式专业术语称为迭代。
Iterator接口的常用方法如下:
- public E next():返回迭代的下一个元素。
- public boolean hasNext():如果仍有元素可以迭代,则返回 true。
代码演示
public static void main(String[] args) {
// 准备集合跟元素
List<String> list = new ArrayList<>();
list.add("关羽");
list.add("张飞");
list.add("刘备");
list.add("赵云");
list.add("诸葛亮");
// 获取集合对应的迭代器
Iterator<String> iterator = list.iterator();
// 每次判断是否有下一个元素,如果有返回true,否则返回false
while (iterator.hasNext()){
String hero = iterator.next();
System.out.println(hero);
}
}
实现原理
我们在之前案例已经完成了Iterator遍历集合的整个过程。当遍历集合时,首先通过调用t集合的iterator()方法获得迭代器对象,然后使用hashNext()方法判断集合中是否存在下一个元素,如果存在,则调用next()方法将元素取出,否则说明已到达了集合末尾,停止遍历元素。
Iterator是使用索引指针完成遍历过程的。在调用Iterator的next方法之前,迭代器的索引位于第一个元素之前,不指向任何元素,当第一次调用迭代器的next方法后,迭代器的索引会向后移动一位,指向第一个元素并将该元素返回,当再次调用next方法时,迭代器的索引会指向第二个元素并将该元素返回,依此类推,直到hasNext方法返回false,表示到达了集合的末尾,终止对元素的遍历。
Iterator是快速失败的,意思就是在获取迭代器后,遍历结束之前,不能修改集合的数据结构。否则会出现ConcurrentModificationException异常。如果在迭代过程中需要删除元素的话,只能通过Iterator的void remove()方法实现,而不能通过集合的remove方法。
代码演示
public static void main(String[] args) {
// 准备集合跟元素
List<String> list = new ArrayList<>();
list.add("关羽");
list.add("张飞");
list.add("刘备");
list.add("赵云");
list.add("诸葛亮");
// 获取集合对应的迭代器
Iterator<String> iterator = list.iterator();
// 获取迭代器之后修改了集合结构,会异常
// list.add("黄忠");
// 每次判断是否有下一个元素,如果有返回true,否则返回false
while (iterator.hasNext()){
String hero = iterator.next();
System.out.println(hero);
// 遍历过程中修改了集合的结构,会异常
// list.remove(hero);
}
}
tips:增强for循环的底层就是通过迭代器实现的,所以使用这种方式的时候,也同样是快速失败的。
总结
集合
问题: 数组长度固定,在很多场景不方便使用。因此有了集合。
特点: 数组:数据类型固定,长度固定
集合:数据类型不固定,长度不固定
集合不是某一个类,而是一大的体系,通常称为集合框架。
Collection(单列集合)
List:有序可重复的集合
ArrayList:线程不安全的,底层也是基于数组,特点是:查询、修改快,添加、删除相对LinkedList来说要慢。
扩容机制:原长度的1.5倍,如果待添加的数据比扩容长度要大,就使用待添加数据长度,如果超过int类型最大长度-8,则使用int类型最大长度
LinkedList:线程不安全,底层是双向链表,特点是:添加、删除快,查询和修改相对ArrayList来说要慢。
Vector:线程安全的,底层也是基于数组,相对于ArrayList和LinkedList都要慢。
Set:无序不可重复集合
HashSet:底层使用是HashMap。(无序不可重复)
TreeSet:底层使用是TreeMap。(有序不可重复)
判定对象是否重复是依据对象的hashcode和equals方法
Map(双列集合)
HashMap
ArrayList和LinkedList的区别
- ArrayList基于数组,LinkedList基于链表
- ArrayList查询快,修改快(不改变结构)
- LinkedList添加快,删除快
hashcode
规则1:同一对象在程序同一执行时间内无论调用多少次,都返回相同的hashcode值
规则2:两对象equals方法相等,hashcode必须也相同。
规则3:两对象equals方法不相等,hashcode也可能相等。
哈希冲突:两不同对象的hashcode值相同即为哈希冲突,一般在使用hashmap时会出现hash冲突问题。
1、重写equals方法,需要重写hashcode方法吗?
需要
2、重写hashcode方法,需要重写equals方法吗?
不需要
Map接口
概述
前面学习的List跟Set主要用来存储单个实体的元素,但在生活中,很多时候我们会遇到这种集合:老公跟老婆、个人与身份证号等成对的数据组合,显然用List跟Set就显得力不从心了。对于这种数据就可以使用Map来完成。
Map中的集合,元素是成对存在的。每个元素由键与值两部分组成,通过键可以找对所对应的值。成为双列集合。其中键(Key)不能重复,值(Value)可以重复,每个键对应一个值。简称键值(K-V)对。
Map常用子类有HashMap、TreeMap。
常见API
Map接口中定义了很多方法,常用的如下:
- public V put(K key, V value): 把指定的键与指定的值添加到Map集合中。
- public V remove(Object key): 把指定的键 所对应的键值对元素 在Map集合中删除,返回被删除元素的值。
- public V get(Object key) 根据指定的键,在Map集合中获取对应的值。
- boolean containsKey(Object key)判断集合中是否包含指定的键。
- public Set keySet(): 获取Map集合中所有的键,存储到Set集合中。
- public Set> entrySet(): 获取到Map集合中所有的键值对对象的集合(Set集合)。
HashMap类
API的使用
HashMap是使用最频繁的一个Map接口的子类。底层结构在JDK1.8主要采用数组+链表+红黑树实现。
我们看下HashMap的基本用法:
//三国时期,刘备派关羽守樊城,张飞守新野,赵子龙守徐州,他自己坐镇荆州。
public static void main(String[] args) {
//创建 map对象
HashMap<String, String> map = new HashMap<String, String>();
//添加元素到集合
map.put("关羽", "樊城");
map.put("张飞", "新野");
map.put("赵子龙", "徐州");
map.put("刘备", "荆州");
System.out.println(map);
// 删除赵子龙
System.out.println(map.remove("赵子龙"));
System.out.println(map);
// 想要查看刘备守哪座城?
System.out.println(map.get("刘备"));
}
那如果存储的键改为自定义类呢?
代码演示
三国时期,刘备派关羽守樊城,张飞守新野,赵子龙守徐州,他自己坐镇荆州。使用HashMap来实现,并打印输出,张飞守的是那座城?
public class Demo08HashMap {
public static void main(String[] args) {
//创建 map对象
HashMap<Hero, String> map = new HashMap<Hero, String>();
//添加元素到集合
map.put(new Hero("关羽"), "樊城");
map.put(new Hero("张飞"), "新野");
map.put(new Hero("赵子龙"), "徐州");
map.put(new Hero("刘备"), "荆州");
map.put(new Hero("刘备"), "荆州");
System.out.println(map);
// {Hero{name='张飞'}=新野, Hero{name='刘备'}=荆州, Hero{name='刘备'}=荆州, Hero{name='关羽'}=樊城, Hero{name='赵子龙'}=徐州}
}
}
class Hero{
private String name ;
public Hero(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "Hero{" +
"name='" + name + '\'' +
'}';
}
}
这里通过Map的输出可以看到刘备的Hero重复显示了,但是前面我们说了Map有个特性,Key不能重复。那这里为什么重复了呢?主要原因是对于自定义类型是如何判断这个Key是否是重复的呢?对于HashMap而言,主要是通过对象的hashcode跟equals方法来决定的。
- equals()相等的两个对象他们的hashCode()肯定相等。
- hashCode()相等的两个对象他们的equals()不一定相等。
修改后的代码演示
class Hero{
private String name ;
public Hero(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof Hero)) return false;
Hero hero = (Hero) o;
return Objects.equals(name, hero.name);
}
@Override
public int hashCode() {
return Objects.hash(name);
}
@Override
public String toString() {
return "Hero{" +
"name='" + name + '\'' +
'}';
}
}
public class Demo08HashMap {
public static void main(String[] args) {
//创建 map对象
HashMap<Hero, String> map = new HashMap<Hero, String>();
//添加元素到集合
map.put(new Hero("关羽"), "樊城");
map.put(new Hero("张飞"), "新野");
map.put(new Hero("赵子龙"), "徐州");
map.put(new Hero("刘备"), "荆州");
map.put(new Hero("刘备"), "荆州");
System.out.println(map);
// {Hero{name='张飞'}=新野, Hero{name='关羽'}=樊城, Hero{name='赵子龙'}=徐州, Hero{name='刘备'}=荆州}
}
}
这里我们可以通过结果看到重复的刘备Hero没有重复显示了。
遍历
方式1
通过map的keySet方法可以获取装所有的key的set,然后遍历再获取value。
代码演示
//创建 map对象
HashMap<String, String> map = new HashMap<String, String>();
//添加元素到集合
map.put("关羽", "樊城");
map.put("张飞", "新野");
map.put("赵子龙", "徐州");
map.put("刘备", "荆州");
Set<String> keys = map.keySet();
for (String name : keys) {
System.out.println(name + " " + map.get(name));
}
tips: 如果只想获取所有的值的话,可以通过map的values方法。
Collection<String> values = map.values();
for (String value : map.values()) {
System.out.println("Value = " + value);
}
方式1使用比较简单,但是效率比较低。
方式2
HashMap将所有的Key跟所有的Value重新包装成Map.Entry类了,并且通过entrySet方法返回所有数据对应的Map.Entry集合。
代码演示
public static void main(String[] args) {
//创建 map对象
HashMap<String, String> map = new HashMap<String, String>();
//添加元素到集合
map.put("关羽", "樊城");
map.put("张飞", "新野");
map.put("赵子龙", "徐州");
map.put("刘备", "荆州");
// 方式2
Iterator<Map.Entry<String, String>> entries = map.entrySet().iterator();
while (entries.hasNext()) {
Map.Entry<String, String> entry = entries.next();
System.out.println("Key = " + entry.getKey() + ", Value = " + entry.getValue());
}
}
方式3
public static void main(String[] args) {
//创建 map对象
HashMap<String, String> map = new HashMap<String, String>();
//添加元素到集合
map.put("关羽", "樊城");
map.put("张飞", "新野");
map.put("赵子龙", "徐州");
map.put("刘备", "荆州");
//方式3 推荐
for(Map.Entry<String, String> entry : map.entrySet()) {
System.out.println("key = " + entry.getKey() + ", value = " + entry.getValue());
}
}
tips: 这种方式简单好用又高效,推荐使用。
Collections类
JDK提供了一个工具类java.utils.Collections,可以用来对集合进行操作。
常用API
- public static boolean addAll(Collection c, T... elements):往集合中添加一些元素。
@Test
public void testAddAll() {
ArrayList<String> list = new ArrayList<String>();
// 往集合中添加元素
Collections.addAll(list,"关羽","张飞","赵子龙","刘备");
System.out.println(list);
}
- public static void shuffle(List list) 打乱顺序:打乱集合顺序。
@Test
public void testShuffle() {
ArrayList<String> list = new ArrayList<String>();
// 往集合中添加元素
Collections.addAll(list,"关羽","张飞","赵子龙","刘备");
System.out.println(list);
// 随机打乱这个集合中的元素
Collections.shuffle(list);
System.out.println(list);
}
- public static void sort(List list):将集合中元素按照默认规则排序。
- public static void sort(List list,Comparator ):将集合中元素按照指定规则排序。
@Test
public void testSort() {
ArrayList<Integer> list = new ArrayList<Integer>();
// 往集合中添加元素
Collections.addAll(list,3,9,6,4,1,5);
System.out.println(list);
// 用默认的比较器对集合中的元素排序
Collections.sort(list);
System.out.println(list);
Collections.sort(list, new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
return o2-o1;
}
});
System.out.println(list);
}
- public static void reverse(List list) : 反转指定列表中元素的顺序。
@Test
public void testReverse() {
ArrayList<String> list = new ArrayList<String>();
Collections.addAll(list,"关羽","张飞","赵子龙","刘备");
// 往集合中所有元素反转
Collections.reverse(list);
System.out.println(list);
}
- public static > T min(Collection coll):根据其元素的自然顺序返回给定集合的最小元素。
- public static > T max(Collection coll):根据其元素的自然顺序返回给定集合的最大元素。
@Test
public void testMinAndMax() {
ArrayList<Integer> list = new ArrayList<Integer>();
Collections.addAll(list,3,9,6,4,1,5);
System.out.println(Collections.min(list));
System.out.println(Collections.max(list));
}
public static void swap(List list, int i, int j) : 交换指定列表中指定位置的元素。
@Test
public void testSwap() {
ArrayList<String> list = new ArrayList<String>();
Collections.addAll(list,"关羽","张飞","赵子龙","刘备");
// 往集合中的元素进行互换
Collections.swap(list,0,2);
System.out.println(list);
}
- public static int binarySearch(List> list,T key):使用二叉搜索算法搜索指定对象的指定列表。必须具有有序性。
@Test
public void testBinarySearc() {
ArrayList<Integer> list = new ArrayList<Integer>();
Collections.addAll(list,1,2,3,4,5,6,7,8,9);
System.out.println(Collections.binarySearch(list, 4));
}
- public static boolean replaceAll(List list,T oldVal,T newVal): 将列表中一个指定值的所有出现替换为另一个。
@Test
public void testReplaceAll() {
ArrayList<String> list = new ArrayList<String>();
Collections.addAll(list,"关羽","张飞","赵子龙","刘备");
Collections.replaceAll(list,"刘备","马超");
System.out.println(list);
}
泛型
概述
在集合存放数据时,我们可以存入任意类型,但通常为了严谨,我们更希望集合中的所有数据具有相同类型,比如书柜里面全是书,酒柜里面全是酒,衣柜里面全是衣服……
此时我们可以通过泛型来规范集合的所有元素的类型,能够使API更简洁,也能得到编译期间的语法检查,将运行期的类型不一致的问题调整到编译期的编译错误。
泛型,用来灵活地将数据类型应用到不同的类、方法、接口当中。将数据类型作为参数进行传递。
代码演示
public static void main(String[] args) {
// 指定了泛型,决定了这个集合中的元素只能放入String类型
List<String> list = new ArrayList<>();
list.add("唐僧");
list.add("孙悟空");
list.add("猪八戒");
list.add("沙僧");
// list.add(1);// 编译错误,因为不是String类型
}
泛型是数据类型的一部分,我们将类名与泛型合并一起看做数据类型。
泛型类
- 定义
修饰符 class 类名<泛型> {
...
}
查看ArrayList的API
class ArrayList<E>{
public boolean add(E e){ }
public E get(int index){ }
....
}
在创建对象的时候ArrayList list = new ArrayList();就自动将泛型确定下来,用定义的String替换定义中的E。
class ArrayList<String>{
public boolean add(String e){ }
public String get(int index){ }
...
}
- 自定义泛型类
// 仿照ArrayList自定义泛型类
class MyGeneric<E> {
private E e ;
public void set(E e){
this.e = e ;
}
public E get(){
return e ;
}
}
public class Demo12 {
public static void main(String[] args) {
// 泛型类确定为String
MyGeneric<String> g1 = new MyGeneric<String>();
g1.set("blb");
System.out.println(g1.get());
// 泛型类确定为Integer
MyGeneric<Integer> g2 = new MyGeneric<Integer>();
g2.set(9);
System.out.println(g2.get());
}
}
泛型方法
- 定义
修饰符 <泛型> 返回值类型 方法名(参数){ }
代码演示
class GenericMethod {
public <E> void show(E e){
System.out.println(e);
}
}
public class Demo13 {
public static void main(String[] args) {
GenericMethod gm = new GenericMethod();
// 泛型方法的类型确定为字符串
gm.show("blb");
// 泛型方法的类型确定为Integer
gm.show(99);
}
}
tips:泛型方法中的泛型是在方法被调用的时候被确定下来。
泛型接口
- 定义
修饰符 interface接口名<泛型> { }
interface GenericInterface<E>{
public abstract void set(E e);
public abstract E get();
}
实现泛型接口并使用
//此时泛型接口中的泛型确定为String
class GenericImpl implements GenericInterface<String>{
private String data ;
@Override
public void set(String s) {
this.data = s ;
}
@Override
public String get() {
return data;
}
}
public class Demo14 {
public static void main(String[] args) {
GenericInterface gi = new GenericImpl() ;
gi.set("blb");
System.out.println(gi.get());
}
}
tips: 泛型接口中的泛型是在创建实现类的时候确定的。
通配符泛型
当使用泛型类跟泛型接口传递参数的时候,泛型类型不确定的时候可以使用通配符来表示任意泛型类。
一旦使用泛型的通配符后,带泛型参数的API就不能使用了。
代码演示
public static void main(String[] args) {
// 定义泛型为String
List<String> list1 = new ArrayList<String>();
show(list1);
// 定义泛型为Integer
List<Integer> list2 = new ArrayList<Integer>();
show(list2);
}
public static void show(List<?> list){
// list.add(); //不能调用add方法
for(int i=0;i<list.size();i++){
System.out.println(list.get(i));
}
}
- 受限泛型
刚刚可以使用来传递任意泛型,我们还可以指定一个泛型的上限和下限。
泛型的上限:
- 格式: 类型名称<? extends 类> 对象名称
- 意义: 只能接收该类型及其子类
泛型的下限:
- 格式: 类型名称<? super 类> 对象名称
- 意义: 只能接收该类型及其父类型
代码演示
现已知Object类,String 类,Number类,Integer类,其中Number是Integer的父类。
public class Demo16Generic {
public static void main(String[] args) {
List<Integer> list1 = new ArrayList<Integer>();
List<String> list2 = new ArrayList<String>();
List<Number> list3 = new ArrayList<Number>();
List<Object> list4 = new ArrayList<Object>();
show1(list1);
// show1(list2);//报错
show1(list3);
// show1(list4);//报错
// show2(list1);//报错
// show2(list2);//报错
show2(list3);
show2(list4);
}
public static void show1(List<? extends Number> list){
}
public static void show2(List<? super Number> list){
}