1. 什么是泛型
背景
JAVA推出泛型以前,程序员可以构建一个元素类型为Object的集合,改集合能够存储任意的数据类型对象,而在使用该集合的过程中,需要程序员明确知道存储每个元素的元素类型,否则很容易引发Class Cast Exception异常【类型转换异常】
public static void main(String[] args){
ArrayList list = new ArrayList();
list.add("java");
list.add(100);
list.add(true);
for (int i = 0; i < list.size(); i++) {
Object o = list.get(i);
String str = (String)o;
//全都当String类型所以报错
System.out.println(str);
}
}
/*报错
Exception in thread "main" java.lang.ClassCastException: class java.lang.Integer cannot be cast to class java.lang.String (java.lang.Integer and java.lang.String are in module java.base of loader 'bootstrap')at Test.MainClass.main(MainClass.java:17)
*/
泛型的概念
Java泛型(generics)是JDK5中引入的一个新特性,泛型提供了编译时类型安全检测机制,改机制允许我们在编译时检测到非法的类型数据结构。
泛型的本质就是参数化类型,也就是所操作的数据类型被指定为一个参数。
- 编译期间检查类型
- 减少数据类型转换
泛型的好处
- 类型安全
- 消除了强制类型的转换
2. 泛型类
泛型类的定义语法
class 类名称 <泛型标识,泛型标识,...>{
private 泛型标识 变量名;
......
}
/**
* 泛型类的定义
* @param <T> 泛型表示--类型参数
* T创建对象的时候指定具体的数据类型
* */
public class Generic<T>{
//T,是由外部使用类的时候来指定
private T key;
public Generic(T key) {
this.key = key;
}
@Override
public String toString() {
return "Generic{" +
"key=" + key +
'}';
}
public T getKey() {
return key;
}
public void setKey(T key) {
this.key = key;
}
}
常用的泛型标识:T、E、K、V
使用语法
类名<具体的数据类型> 对象名 = new 类名<具体的数据类型>();
Java1.7以后,后面<>中具体的数据类型可以省略不写
类名<具体的数据类型> 对象名 = new 类名<>();
public static void main(String[] args){
//泛型类在创建对象的时候,来指定操作的具体数据类型
Generic<String> generic = new Generic<>("abc");
String key = generic.getKey();
System.out.println(key);
}
注意点
- 泛型类在创建对象的时候,没有指定类型,按照Object类操作
- 不支持基本数据类型(int…)
- 同一泛型类,根据不同数据类型创建的对象,本质上是同一字节码文件
public static void main(String[] args){
Generic<String> strGeneric = new Generic<>("abc");
Generic<Integer> intGeneric = new Generic<>(100);
System.out.println(strGeneric.getClass() == intGeneric.getClass());
//true
}
例子:年会抽奖器
public class ProductGetter<T> {
Random random = new Random();
//奖品
private T product;
//奖品池
private ArrayList<T> list = new ArrayList<>();
//添加奖品
public void addProduct(T t){
list.add(t);
}
//抽奖
public T getProduct(){
product = list.get(random.nextInt(list.size()));
return product;
}
}
从泛型类派生子类
- 子类也是泛型类,子类和父类的泛型类型要一致
class ChildGeneric<T> extends Generic<T>
- 子类不是泛型类,父类要明确泛型的数据类型
class ChildGeneric extends Generic<String>
3. 泛型接口
泛型接口的使用
- 实现类不是泛型类,接口需要明确数据类型
public interface Generator<T>{
T getKey();
}
public class Generic implements Generator<String >{
@Override
public String getKey() {
return "hello";
}
}
- 实现类也是泛型类,实现类和接口的泛型类型要一致
public class Generic<T,E> implements Generator<T>{
private T key;
public E value;
@Override
public T getKey() {
return key;
}
public E getValue(){
return value;
}
}
4. 泛型方法
在调用方法的时候指明泛型的具体类型
修饰符 <T,E,...> 返回值类型 方法名(形参列表){
方法体...
}
- 只有声明了的方法才是泛型方法,泛型类中使用了泛型的成员方法并不是泛型方法
- 此处的T可以随便写为任意表示,常见的如T、E、K、V等形式的参数常用语表示泛型
泛型方法调用,类型是通过方法的时候来指定的
public class ProductGetter<T> {
Random random = new Random();
private T product;
//方法的泛型类型和类的泛型类型是不同的
public <T> T getProduct(ArrayList<T> list){
return list.get(random.nextInt(list.size()));
}
}
public static void main(String[] args){
ProductGetter<Integer> generic = new ProductGetter<>();
ArrayList<String> list = new ArrayList<>();
list.add("Phone");
list.add("PC");
//返回的是String 而不是Integer
String product = generic.getProduct(list);
}
可变参数泛型方法
public <T> void print(T... e){
for (T t : e) {
System.out.println(t);
}
}
5. 类型通配符
- 一般使用 “?” 代替具体的类型实参
- 是类型实参,而不是类型形参
public static void showKey(Generic<?> generic){
Object key = generic.getKey();
System.out.println(key);
}
类型通配符的上限(extends)
语法
类/接口 <? extends 实参类型>
要求该泛型的类型,只能是实参类型,或实参类型的子类类型
public static void main(String[] args){
ArrayList<Animal> animals = new ArrayList<>();
ArrayList<Cat> cats = new ArrayList<>();
ArrayList<MiniCat> miniCats = new ArrayList<>();
//animals不可以
showAnimal(animals);
//cat类型可以
showAnimal(cats);
//cat类型的子类可以
showAnimal(miniCats);
}
/**\
* 上限通配符,传递的集合类型,只能是Cat或Cat的子类
* @param list
*/
public static void showAnimal(ArrayList<? extends Cat> list){
for (Cat cat : list) {
System.out.println(cat);
}
}
注意点:不能填充元素
类型通配符的下限(super)
类/接口<? super 实参类型>
要求该泛型的类型,只能是实参类型,或实参类型的父类类型
public static void main(String[] args){
ArrayList<Animal> animals = new ArrayList<>();
ArrayList<Cat> cats = new ArrayList<>();
ArrayList<MiniCat> miniCats = new ArrayList<>();
//Cat的父类
showAnimal(animals);
//Cat类型可以
showAnimal(cats);
//miniCat类型的子类可以
showAnimal(miniCats);
}
/**\
* 上限通配符,传递的集合类型,只能是Cat或Cat的子类
* @param list
*/
public static void showAnimal(ArrayList<? super Cat> list){
for (Object o : list) {
System.out.println(o);
}
}
注意点:能填充元素
6.泛型擦除
概念
泛型是Java1.5版本才引进的概念,在这之前是没有泛型的,但是,泛型代码能够很好地和之前版本的代码兼容。那是因为,泛型信息值存在与代码编译阶段,在进入JVM之前,与泛型相关的信息会被擦除,称之为——类型擦除。
无限制类型擦除
有限制类型擦除
擦除方法中类型定义的参数
桥接方法
7.泛型与数组(不建议使用,建议使用泛型集合)
- 可以声明带泛型的数组引用,不能直接创建带泛型的数组对象
- 但是可以通过java.lang.reflect.Array的newInstance(Class,int)创建T[]数组
public static void main(String[] args){
//可以声明
ArrayList<String>[] listArr;
//但不能直接生成数组对象
listArr = new ArrayList<>[5];
}
public class Fruit<T> {
private T[] array;
public Fruit(Class<T> clz,int length){
//创建泛型数组
array = (T[])Array.newInstance(clz,length);
}
}
8.泛型与反射
反射常用泛型类
- Class
- Constructor
public static void main(String[] args) throws Exception {
//使用泛型反射创建出相应类型的对象
Class<Person> personClass = Person.class;
Constructor<Person> constructor = personClass.getConstructor();
Person person = constructor.newInstance();
//不使用泛型反射创建出来的类型是object
Class personClass1 = Person.class;
Constructor constructor1 = personClass1.getConstructor();
Object o = constructor1.newInstance();
}