一、什么是泛型
1.背景:
JAVA推出泛型以前,程序员可以构建一个元素类型为Object的集合,该集合能够存储任意的数据类型对象,而在使用该集合的过程中,需要程序员明确知道存储每个元素的数据类型,否则很容易引发ClassCastException异常。
2.概念:
Java泛型(generics)是JDK5中引入的一个新特性,泛型提供了编译时类型安全监测机制,该机制允许我们在编译时检测到非法的类型数据结构。泛型的本质就是参数化类型,也就是所操作的数据类型被指定为一个参数。
3.好处:
类型安全
消除了强制类型的转换
二、泛型类、接口
泛型类
-
泛型类的定义语法
-
代码演示
-
/** * * @author:西瓜菌 * @date:2022年2月11日下午3:48:04 * @Description:泛型类的定义 * @param <A> 泛型标识----类型形参 * A 创建对象的时候指定的具体的数据类型 */ class Genericity<A>{ private A key; // 构造方法 public Genericity(A key) { super(); this.key = key; } // 读取器方法 public A getKey() { return key; } // 修改器方法 public void setKey(A key) { this.key = key; } }
-
-
常用的泛型标识符:T、E、K、V
- 这些标识符可以随便取,如:SADASLFHAJ……,只是上面的标识符是约定俗成。
-
使用语法(这里是 java1.8 之后的语法:钻石表达式)
-
代码演示
-
public static void main(String[] args) { Genericity<String> gcity = new Genericity<>("jude"); // 泛型类在创建对象的时候,没有指定类型,将按照 Object 类型来操作 Genericity gcity2 = new Genericity("tom"); }
-
从泛型类派生子类
-
子类也是泛型类,子类和父类的泛型类型要一致
-
子类不是泛型类,父类要明确泛型的数据类型
泛型类注意事项
- 泛型类,如果没有指定具体的数据类型,此时,操作类型是 Object
- 泛型的类型参数只能是类类型 ( 引用类型 ),不能是基本数据类型
- 泛型类型在逻辑上(或者说编译时)可以看成是多个不同的类型,但实际上(运行时)都是相同类型( 类型擦除 )
泛型接口
-
泛型接口的定义语法
-
代码演示
/** * @author:西瓜菌 * @date:2022年2月11日下午4:14:59 * @Description:泛型接口的定义语法 */ public interface GenricityInter<T,E,D> { T mothed1(); E mothed2(D d); // 返回值类型为 D }
-
泛型接口的使用
-
实现累不是泛型类,接口要明确数据类型
// 接口要明确数据类型 class CommomClass implements GenricityInter<Integer,String,Double>{ @Override public Integer mothed1() { // TODO Auto-generated method stub return null; } @Override public String mothed2(Double d) { // TODO Auto-generated method stub return null; } }
-
实现类也是泛型类,实现类和接口的泛型类型要一致
// 实现类和接口的泛型类型要一致,GenericityClass<T,E> 少一个类型参数报错 class GenericityClass<T,E,D> implements GenricityInter<T,E,D>{ @Override public T mothed1() { // TODO Auto-generated method stub return null; } @Override public E mothed2(D d) { // TODO Auto-generated method stub return null; } }
-
三、泛型方法
-
泛型类,是在实例化类的时候指明泛型的具体类型
-
泛型方法,是在调用方法的时候指明泛型的具体类型
-
语法
-
代码演示
// 泛型方法 public <T,E> void demoMothed1() { } // <T> 中定义几个类型变量就可以使用几个类型变量,demoMothed2 中返回值,参数只能使用 T,demoMothed3 中可以 使用 E public <T> T demoMothed2(T... t) { return null; } public <T,E> E demoMothed3(E... e) { return null; }
-
public 与返回值中间 非常重要,可以理解为声明此方法为泛型方法
-
只有声明了 的方法才是泛型方法
-
表明该方法将使用泛型类型 T,此时才可以在方法中使用泛型类型 T
泛型方法与可变参数
- 语法
- [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-r8ZfcNPX-1644593051711)(C:\Users\西瓜菌\AppData\Roaming\Typora\typora-user-images\image-20220211171323158.png)]
泛型方法总结
-
泛型方法能使方法独立于类而产生变化
- 建议:在工作中尽量使用泛型方法,比泛型类更加灵活
-
如果 static 方法要使用泛型能力,就必须使其成为泛型方法
-
泛型方法调用
四、类型通配符
什么是类型通配符?
- 类型通配符一般是使用 ? 代替具体的类型实参
- 所以,类型通配符是类型实参,而不是类型形参
类型变量的限定
-
有时,类或方法需要对类型变量加以约束
-
语法
public static <T extends Comparable> void mothod() { }
-
:表示 T 应该是限定类型 Comparable 的子类型
-
T 和 限定类型可以是类,也可以是接口。
-
可以有多个限定
- 一个类型变量或通配符可以有多个限定
- 类型变量的限定和 java 继承语法差不多。一个类型变量或通配符可以有多个限定,但最多有一个限定是类,可以有多个接口超类型,如果有一个类作为限定,它必须是限定列表中的第一个限定
- 限定类型用 & ,逗号用来分隔类型变量
- 代码演示
class Example{ public static <T,E extends Example & Comparable & Serializable> void mothod() { } }
-
例子
-
// 获得数组中最小的值 public static <T> T mothod(T[] arr) { if(arr.length == 0 || arr == null) { return null; } T min = arr[0]; for(int i = 1; i < arr.length; i++) { if(min.compareTo(arr[i]) > 0) { min = arr[i]; } } return min; }
- 注意变量 min 的类型为 T,就是说它可以是任何一个类的对象,那么怎么知道这个对象是否有 compareTo( ) 方法呢?如果这个对象没有 compareTo( ) 方法,那代码 min.compareTo(arr[i]) > 0 将会报错。
- 可以通过给类型变量 T 设置一个限定 T extends Comparable 来实现这一点
-
类型通配符的上限界定符
-
语法
-
要求该泛型的类型,只能是实参类型,或实参类型的子类类型
-
同时上限界定符不能修改值,只能获取值
-
代码演示
public class Case { public static void main(String[] args) { ArrayList<Animal> a = new ArrayList>(); ArrayList<Cat> c = new ArrayList<>(); ArrayList<SmallCat> sc = new ArrayList<>(); // showAnimal(a); Animal 不是 Cat 的子类型,编译报错 showAnimal(c); showAnimal(sc); } /** * 泛型上限通配符,传递的集合类型,只能是 Cat 或 Cat 的子类型 * @param list */ public static void showAnimal(ArrayList<? extends Cat> list) { } } class Animal<T>{ } class Cat<T> extends Animal<T>{ } class SmallCat<T> extends Cat<T>{ }
-
图片代码
类型通配符的下限界定符
-
语法
-
要求该泛型的类型,只能是实参类型,或实参类型的父类型
-
下限界定符可以添加元素。也可以获取值,但是获取的值只能赋给 Object 对象
-
代码演示
public class Case { public static void main(String[] args) { ArrayList<Animal> a = new ArrayList<>(); ArrayList<Cat> c = new ArrayList<>(); ArrayList<SmallCat> sc = new ArrayList<>(); showAnimal(a); showAnimal(c); // showAnimal(sc); 报错 SmallCat 不是 Cat 的父类型 } /** * 泛型上限通配符,传递的集合类型,只能是 Cat 或 Cat 的子类型 * @param list */ public static void showAnimal(ArrayList<? super Cat> list) { list.add(new Cat()); // 可以获取元素,但是只能赋给 Object 类型的对象 Animal am = list.get(0); Object obj = list.get(0); } }
-
图片代码
无界限定符
-
语法
-
getValue 的返回值只能赋给一个 Object
-
setValue 方法不能被调用(**注意:**可以调用 setValue(null))
-
代码演示
/** * */ package genericity; /** * @author:西瓜菌 * @date:2022年2月11日下午8:29:51 * @Description: */ public class Case2 { public static void main(String[] args) { Test<String> test = new Test<>(); method(test); } public static void method(Test<?> t) { // t.setValue(new Object()); 报错无法调用 set()方法 t.setValue(null); // String str = t.getValue(); 报错只有 Object 类型的对象才可以接受 get() 的返回值 Object obj = t.getValue(); System.out.println(str); } } class Test<T>{ T value; public T getValue() { return value; } public void setValue(T value) { this.value = value; } }
-
图片演示
总结
看完了三种通配符的使用,我们来做个总结:
- <? extends T> 子类型限定通配符 无法向其中设置值,但是可以进行正常的取出
- <? super T> 父类型限定通配符 可以设置 T 类型及其子类型的对象,但是取出的时候只能赋值给 Object
- <?> 无限定通配符 无法向其中设置值,取值的时候也只能赋值给 Object
从上面的总结可以看出,<? extends T> 通配符偏向于内容的获取,而 <? super T> 通配符更偏向于内容的存入。
**PECS 原则(Producer Extends Consumer Super)**很好的解释了这两种通配符的使用场景:
- Producer Extends 说的是当你的情景是生产者类型,需要获取资源以供生产时,建议使用 extends 通配符,因为使用了 extends 通配符的类型更适合获取资源。
- Consumer Super 说的是当你的场景是消费者类型,需要存入资源以供消费时,建议使用 super 通配符,因为使用 super 通配符的类型更适合存入资源。
当然,如果你既想设置值又想取出值,那么就不适合使用通配符了。
五、类型擦除
-
泛型是Java 1.5版本才引进的概念,在这之前是没有泛型的,但是泛型代码能够很好地和之前版本的代码兼容。那是因为,泛型信息只存在于代码编译阶段,在进入JVM之前,与泛型相关的信息会被擦除掉,我们称之为–类型擦除。
-
无论何时定义一个泛型类型,都会自动提供一个原始类型。这个原始类型就是类型擦除后的类型,如下图的 Object,Number
-
有限制类型擦除:类型变量会被擦除,并替换为其限定类型(有多个限定类型时,原始类型会用第一个限定来替换类型变量)
-
代码演示,上界限定符只能限定最后一个类型变量,网上查资料也没找到。如果有人知道可以通过文章结尾的联系方式联系告知于我,万分感谢。
-
-
无限制类型擦除:没有给定限定就替换为 Object。
\
-
代码演示
-
-
擦除方法中类型定义的参数
-
代码演示
-
-
桥接方法
-
代码演示:只定义了一个方法,却获取到了两个方法
-
六、泛型与数组
-
可以声明带泛型的数组引用,但是不能直接创建带泛型的数组对象
-
代码演示
ArrayList<String> strList = new ArrayList(); strList.add("as"); // ArrayList<String> list = new ArrayList<String>[]; 报错 不能直接创建带泛型的数组 // 可以 ArrayList<String>[] list = new ArrayList[5]; list[0] = strList;
-
-
可以通过java.lang.reflect.Array的newInstance(Class,int)创建T[]数组
-
代码演示
public class Case6 { public static void main(String[] args) { Fruit<String> fr = new Fruit(String.class,3); } } class Fruit<T> { private T[] array; public Fruit(Class<T> clz, int length){ //通过Array.newInstance创建泛型数组 array = (T[])Array.newInstance(clz, length); System.out.println(array.length); } } // 输出数组长度 3
-
七、泛型和反射
反射常用的泛型类
-
Class
-
Constructor
-
代码演示
/** * */ package genericity; import java.lang.reflect.Constructor; /** * @author:西瓜菌 * @date:2022年2月11日下午11:07:46 * @Description: */ public class Person<T> { private T name; public Person(T name) { super(); this.name = name; } public T getName() { return name; } public void setName(T name) { this.name = name; } } class personDemo { public static void main(String[] args) throws Exception{ // 使用反射,会自动获取对象类型 idea // 获取字节码文件 Class<Person> personClass = Person.class; // 获取构造函数 Constructor<Person> constructor = personClass.getConstructor(); // 通过构造函数创建对象 Person person = constructor.newInstance(); //不使用反射,不会自动获取类型,还需要自己强转 Class personClass2 = Person.class; Constructor<Person> constructor2 = personClass.getConstructor(); Object person2 = constructor.newInstance(); Person per = (Person)person2; } }
八、声明
Java之泛型_Beyondczn的博客-CSDN博客_java之泛型
Java泛型中的通配符,, - 简书 (jianshu.com)
JavaSE强化教程泛型,由点到面的讲解了整个泛型体系。_哔哩哔哩_bilibili
java 核心技术卷一