通过之前对 JAVA基础 —— 集合 的学习,我们对于集合的概念以及ArrayList都有了初步的了解。
Collection单列集合 | JAVA基础(集合进阶) —— Collection单列集合 |
List集合 | JAVA基础(集合进阶) —— List集合 |
泛型 | JAVA基础(集合进阶) —— 泛型 |
Set集合 | JAVA基础(集合进价) —— Set集合 |
接下来我们将整体的了解集合的体系结构。
目录
泛型定义:
- 泛型类:在类名后面定义泛型,创建该类对象的时候,确定类型
- 泛型方法:在修饰符后面定义方法,调用该方法的时候,确定类型
- 泛型接口:在接口名后面定义泛型,实现类确定类型,实现类延续泛型
使用场景:
- 定义类、方法、接口的时候,如果类型不确定,就可以定义泛型
- 如果类型不确定,但是能知道是哪个继承体系中的,可以使用泛型的通配符
一、 泛型引入
1. 泛型概述
- 泛型:所谓泛型,就是允许在定义类、接口时通过一个标识表示类中某个属性的类型或者是某个方法的返回值及参数类型。是JDK5引入的特性,可以在编译阶段约束操作的数据类型,并进行检查。
- 泛型的格式:< 数据类型 >
- 注意:泛型只能支持引用数据类型。
思考:没有泛型的时候,集合如何存储数据?
public class GenericTest {
public static void main(String[] args) {
// 没有泛型的时候,集合如何存储数据
// 1.创建集合对象
ArrayList list = new ArrayList();
// 2.添加对象
// 集合里面可以添加任意数据类型
list.add(123);
list.add("aaa");
list.add(new Student("zhangsan", 23));
// 3.遍历集合 获取集合内每一个元素
Iterator it = list.iterator();
;
while (it.hasNext()) {
Object obj = it.next();
// 多态:
// 弊端是不能访问子类特有功能
// 报错:obj.length()
System.out.println(obj);
}
}
}
结论:
- 如果我们没有给集合指定类型,默认认为所有的数据类型都是Object类型。
- 此时可以在集合中添加任意的数据类型。
- 带来一个坏处:我们在获取数据的时候,无法使用该数据的特有行为。
因此,我们引入的泛型的概念:可以在添加数据的时候,就把数据类型进行统一。而且在获取数据的时候,也不需要再进行强转,操作变得方便。
public class GenericTest {
public static void main(String[] args) {
// 1.创建集合对象
ArrayList<String> list = new ArrayList<>();
// 2.添加对象
// list.add(123);
list.add("aaa");
// list.add(new Student("zhangsan", 23));
// 3.遍历集合 获取集合内每一个元素
// 迭代器泛型与集合泛型保持一致
Iterator<String> it = list.iterator();
;
while (it.hasNext()) {
String str = it.next();
System.out.println(str);
}
}
}
2. 泛型的好处
- 统一数据类型
- 把运行时期的问题提前到了编译期间,避免了强制类型转换可能出现的异常,因为在编译阶段类型就能确认下来。
3. 泛型的细节
- 泛型中不能写基本数据类型(即使我们在集合中写入基本数据类型,在存入集合时仍会转换Object类型。)
- 指定泛型的具体类型后,传递数据时,可以传入该类类型或者子类类型
- 如果不写泛型,类型默认Object类型。
4. 泛型的扩展知识
- Java中的泛型是伪泛型。
在Java文件中真正存在泛型 当编译成Class字节码文件时,泛型就会消失
二、 泛型类
使用场景:当一个类中,某个变量的数据类型不确定时,就可以定义带有泛型的类。
此处E可以理解为变量,但是不是用来记录数据的,而是记录类型的,可以写成:T、E、K、V等
// MyArrayList.java
//在我们编写一个类的时候,如果不确定类型
//那么这个类就可以定义为泛型类
public class MyArrayList<E> {
Object[] obj = new Object[10];
int size;
// MyArrayList<E>中<E>删除 下方报错
// E: 表示不确定的类型 ,该类在类名后面已经定义
// e:表示形参名,变量名
public boolean add(E e) {
obj[size] = e;
size++;
return true;
}
public E get(int index) {
// Object类型强转成E类型
return (E) obj[index];
}
@Override
public String toString() {
return Arrays.toString(obj);
}
}
public class GenericTest {
public static void main(String[] args) {
//存入String类型
// 将String 传递给 E
MyArrayList<String> list1 = new MyArrayList<>();
list1.add("aaa");
list1.add("bbb");
list1.add("ccc");
System.out.println(list1);
//[aaa, bbb, ccc, null, null, null, null, null, null, null]
//存入Integer类型
// 将Integer 传递给 E
MyArrayList<Integer> list2 = new MyArrayList<>();
list2.add(123);
list2.add(456);
list2.add(789);
Integer i = list2.get(0);
System.out.println(i); //123
System.out.println(list2);
//[123, 456, 789, null, null, null, null, null, null, null]
}
}
三、 泛型方法
此处T可以理解为变量,但是不是用来记录数据的,而是记录类型的,可以写成:T、E、K、V等 当方法中的形参类型不确定时:
方案①:使用类名后面的定义类型
所有方法都能够使用
方案②:在方法申明上定义自己的泛型
只有本方法能够使用
练习:泛型方法的联系
定义一个工具类:ListUtil
类中定义一个静态方法addAll,用来添加多个集合的元素。
//只有一个方法用到不确定类型
// 所以不需要在类后面添加
public class ListUtil {
private ListUtil() { }
// 参数一:集合 参数二:最后要添加的元素
// 直接定义在方法上面
// 修饰符后面:public static(静态方法)
public static <E> void addAll(ArrayList<E> list, E e1, E e2, E e3, E e4) {
list.add(e1);
list.add(e2);
list.add(e3);
list.add(e4);
}
public static <E> void addAll2(ArrayList<E> list,E ...e) {
for(E element: e) {
list.add(element);
}
}
public void show() {
System.out.println();
}
}
public class GenericTest {
public static void main(String[] args) {
ArrayList<String> list1 = new ArrayList<>();
ListUtil.addAll(list1, "aaa", "bbb", "ccc", "ddd");
System.out.println(list1); // [aaa, bbb, ccc, ddd]
ArrayList<Integer> list2 = new ArrayList<>();
ListUtil.addAll(list2, 12, 34, 56, 78);
System.out.println(list2); // [12, 34, 56, 78]
// 添加任意个数元素
ArrayList<Integer> list3 = new ArrayList<>();
ListUtil.addAll2(list3, 1, 2, 3, 4, 5, 6, 7, 8, 9);
System.out.println(list3); // [1, 2, 3, 4, 5, 6, 7, 8, 9]
}
}
四、 泛型接口
- 当一个接口中,类型不确定时,就可以使用泛型接口。
重点:如何使用一个带泛型的接口?
- 方案①:实现类给出具体类型
- 方案②:实现类延续泛型,创建对象时再确定
1:实现类给出具体类型
public class GenericTest {
public static void main(String[] args) {
MyArrayList list = new MyArrayList();
list.add("aaa");
//报错:list.add(123);
}
}
2: 实现类不明确具体类型
public class GenericTest {
public static void main(String[] args) {
MyArrayList<String> list = new MyArrayList<>();
list.add("aaa");
//报错:list.add(123);
}
}
五、 泛型的继承和通配符
- 泛型不具备继承性,数据具备继承性
方法形参是集合,且集合泛型是Animal类型,那么调用方法时传递的只能是Animal,不能传递Animal子类。 集合泛型是Ye,集合添加对象时可以添加Ye,也就可以添加Ye子类对象。
- 泛型通配符: ?
? entend E E或者E的子类 ? super E E或者E的父类