泛型深入
一、泛型的概述和优势
1、概述
- 泛型:是JDK5中引入的特性,可以在编译阶约束操作的数据类型,并进行检查。
2、格式
<数据类型>
-
注意:泛型仅支持引用数据类型。
-
集合体系的全部接口和实现类都是支持泛型的使用。
3、好处、优势
- 统一数据类型。
- 把运行期间的问题提到了编译期间,避免了强制类型转换可能出现的异常,因为编译阶段类型就能确定下来了。
package com.app.d7_genericity;
import java.util.ArrayList;
import java.util.List;
/**
目标:理解泛型,深入体会泛型的优势
*/
public class GenericityDemo1 {
public static void main(String[] args) {
// 不用泛型,当你在运行期间,会出现强制类型转换的bug
List list1 = new ArrayList<>();
list1.add("Java");
list1.add("赵敏");
list1.add(34.6);
list1.add(false);
list1.add(343);
System.out.println("list1 = " + list1);
/*for (Object o : list1) {
String ele = (String) o;
System.out.println(ele);
}*/
// 使用泛型,在编译期间已经确定了数据类型,后面需要对数据做处理的话,就不会出现强制类型转换这样的操作了
// List<int> list2 = new ArrayList<>(); // 报错!因为泛型仅支持引用数据类型
List<Integer> list2 = new ArrayList<>();
list2.add(343);
list2.add(33);
System.out.println("list2 = " + list2);
// 报错!因为此集合需要整数类型的数据,给的却是字符串类型
// list2.add("Java");
// list2.add("赵敏");
// 报错!因为此集合需要整数类型的数据,给的却是浮点类型
// list2.add(34.6);
// 报错!因为此集合需要整数类型的数据,给的却是布尔类型
// list2.add(false);
System.out.println("-----------------------------------");
// 想使用泛型,但是又想存储任意类型的数据
List<Object> list3 = new ArrayList<>();
list3.add("Java");
list3.add("赵敏");
list3.add(34.6);
list3.add(false);
list3.add(343);
System.out.println("list3 = " + list3);
}
}
list1 = [Java, 赵敏, 34.6, false, 343]
list2 = [343, 33]
-----------------------------------
list3 = [Java, 赵敏, 34.6, false, 343]
Process finished with exit code 0
4、泛型的用武之地
- 类后面——>泛型类。
- 方法申明上——>泛型方法。
- 接口后面——>泛型接口。
二、自定义泛型类
1、概述
- 定义类的同时定义了泛型的类,就是泛型类。
2、格式
修饰符 class 类名<泛型变量>{}
- 范例:
public class MyArrayList<T> {
}
- 此处泛型变量
T
可以随便写为任意标识,常见的如E、T、K、V
等。
3、作用
- 编译阶段可以指定数据类型,类似于泛型在集合中的作用。
4、案例导学
-
模拟ArrayList集合自定义一个集合MyArrayList集合,完成添加和删除功能的泛型设计即可。
package com.app.d8_genericity_class; import java.util.ArrayList; /** 定义泛型类 */ public class MyArrayList<E> { /** * 拓展:装饰模式的思想 * 通俗点讲: * 我买了一片国外的芯片,装饰上我的自己的芯片外壳 */ private ArrayList<Object> list = new ArrayList<>(); /** * 模拟添加功能 * @param e 要添加的元素 */ public void add(E e){ // 内部用的是国外芯片的添加方法 list.add(e); } /** * 模拟删除功能 * @param e 要删除的元素 */ public void remove(E e){ // 内部用的是国外芯片的删除方法 list.remove(e); } /** * 重写toString,格式化输出,不然会输出一个内存地址信息 * @return */ @Override public String toString() { // 里面返回的是国外芯片的toString return list.toString(); } }
package com.app.d8_genericity_class; /** 目标:通过案例导学,学会定义泛型类、理解装饰模式的思想 需求:模拟ArrayList集合自定义一个集合MyArrayList集合,完成添加和删除功能的泛型设计即可。 */ public class Test { public static void main(String[] args) { // 创建我们自己定义的泛型类:MyArrayList集合 MyArrayList<String> list1 = new MyArrayList<>(); // 添加元素 list1.add("JavaSE"); list1.add("JavaSE"); list1.add("我的集合"); list1.add("我的集合"); list1.add("你是唯一"); // list1.add(34); // 报错 System.out.println("删除前:" + list1); // 删除元素 list1.remove("JavaSE"); System.out.println("删除后:" + list1); System.out.println("--------------------------------"); MyArrayList<Double> list2 = new MyArrayList<>(); list2.add(45.3); list2.add(99.0); list2.add(1034.54); System.out.println("删除前:" + list2); list2.remove(45.3); System.out.println("删除后:" + list2); } }
删除前:[JavaSE, JavaSE, 我的集合, 我的集合, 你是唯一] 删除后:[JavaSE, 我的集合, 我的集合, 你是唯一] -------------------------------- 删除前:[45.3, 99.0, 1034.54] 删除后:[99.0, 1034.54] Process finished with exit code 0
5、原理
-
把出现泛型变量的地方全部替换成传输的真实数据类型。
总结
1、泛型类的核心思想是什么?
- 把出现泛型变量的地方全部替换成传输的真实数据类型。
2、泛型类的作用是什么?
- 编译阶段约定操作的数据的类型,类似于泛型在集合中的作用。
三、自定义泛型方法
1、概述
- 定义方法的同时定义了泛型的方法,就是泛型方法。
2、格式
修饰符 <泛型变量> 方法返回值 方法名(形参列表){
}
- 范例:
public <T> void show(T t){
}
3、作用
- 方法中可以使用泛型接收一切实际类型的参数,方法更具备通用性。
4、案例导学
-
给你任何一个类型的数组,都能返回它的内容。也就是实现Arrays.toString(数组)的功能!
package com.app.d9_genericity_method; import java.util.Arrays; /** 目标:通过案例导学,学会定义泛型方法来实现想要的功能 需求:给你任何一个类型的数组,都能返回它的内容。也就是实现Arrays.toString(数组)的功能 */ public class Test { public static void main(String[] args) { // 1、定义两个不同类型的数组,随便传入两组数据 String[] names = {"赵敏", "小昭", "周芷若"}; Double[] scores = {99.0, 59.9, 69.5, 100.0}; printArrays(names); printArrays(scores); Integer[] arrTest1 = {}; Integer[] arrTest2 = null; printArrays(arrTest1); printArrays(arrTest2); } /** * 2、定义泛型方法:printArrays,打印数组内容 * @param arr 接收要打印的数组 * @param <T> 任意类型的数组 */ public static <T> void printArrays(T[] arr){ if (arr != null) { // 不为null StringBuilder sb = new StringBuilder("["); for (int i = 0; i < arr.length; i++) { sb.append(arr[i]).append(i == arr.length-1 ? "" : ", "); } sb.append("]"); System.out.println(sb); }else { System.out.println(arr); // 为null,输出空数组变量 } } }
[赵敏, 小昭, 周芷若] [99.0, 59.9, 69.5, 100.0] [] null Process finished with exit code 0
5、原理
- 把出现泛型变量的地方全部替换成传输的真实数据类型。
总结
1、泛型方法的核心思想是什么?
- 把出现泛型变量的地方全部替换成传输的真实数据类型。
2、泛型方法的作用是什么?
- 方法中可以使用泛型接收一切实际类型的参数,方法更具备
四、自定义泛型接口
1、概述
- 使用了泛型定义的接口,就是泛型接口。
2、格式
修饰符 interface 接口名<泛型变量>{
}
- 范例:
public interface Data<E> {
}
3、作用
- 泛型接口可以让实现类选择当前功能需要操作的数据类型。
4、案例导学
- 教务系统,提供一个接口可约束一定要完成数据(学生,老师)的增删改查操作。
5、原理
-
实现类可以在实现接口的时候传入自己操作的数据类型,这样重写的方法都将是针对于该类型的操作。
总结
1、泛型接口的作用是什么?
- 泛型接口可以约束实现类,实现类可以在实现接口的时候传入自己操作的数据类型,这样重写的方法都将是针对于该类型的操作。
五、泛型通配符、上下限
1、通配符:?
?
:可以在 “使用泛型” 的时候代表一切类型。
2、通配符:E、T、K、V
E、T、K、V
:是在定义泛型的时候使用的。
3、案例导学
- 模拟开发一个极品飞车游戏,所有的汽车都能一起参与比赛。
- 注意:虽然BMW和BENZ都继承了Car,但是ArrayList< BMW>和ArrayList< BENZ>与ArrayList< Car>之间半毛钱关系都没有!!
-
泛型的上下限:
-
? extends Car
:? 必须是Car或者其子类(泛型上限)。 -
? super Car
:? 必须是Car或者其父类(泛型下限)。
-