文章目录
Java泛型
什么是泛型?
-
泛型是jdk1.5的新特性,泛型提供了编译时数据类型安全监测机制,泛型在编译时检测出非法的类型数据结构
-
泛型本质时参数化类型,指在操作数据类型时被指定为一个参数
-
优点:
-
数据安全
泛型提供了编译时数据类型安全监测机制,一旦创建对象指定了类型,该对象只能是使用该类型的数据
-
不用强制类型转化
在没有使用泛型之前,使用的是Object,可以存放多种数据类型,但是使用时必须强制转化类型,否则会出现ClassCastException异常
-
泛型类与接口
泛型类
-
语法:
public class 类名<泛型标识,泛型标识...>{ private 泛型标识 变量名; ... }
-
常用的泛型标识:T E K V
-
创建泛型
-
jdk1.7之前泛型
ArrayList<具体的数据类型> 变量名 = new ArrayList<具体的变量名>();
-
JDK1.7之后
ArrayList<具体的数据类型> 变量名 = new ArrayList<>();
-
-
泛型类注意事项:
-
1,泛型的类型只能是类类型,不能是基本数据类型
-
2,在使用泛型时,如果没有指定数据类型,默认使用Object
List list = new ArrayList(); list.add("小明"); Object o = list.get(0);
-
3,泛型类型在逻辑上可以看出是多个不同的类型,但实际上是相同类型
Animal<Cat> cat = new Animal<>(new Cat("小明")); Animal<Dog> dog = new Animal<>(new Dog("夏冬")); System.out.println("cat:"+cat.getClass()); System.out.println("dog:"+dog.getClass()); System.out.println(cat.getClass() == dog.getClass()); 运行结果: cat:class com.example.test.Animal dog:class com.example.test.Animal true
-
泛型派生子类
-
子类也是泛型,父类是泛型,子类与父类的泛型一致
-
例如:子类与父类泛型都是T
public class Cat<T> extends Animal<T>{ public Cat(T t) { super(t); } }
-
子类进行泛型拓展,但子类泛型中必须含有父类泛型
public class Cat<T,K,V> extends Animal<T>{ public Cat(T t) { super(t); } Map<K,V> map = new HashMap<>(); }
-
-
子类不是泛型,父类要指定具体的数据类型,如果不指定,默认为Object
public class Cat extends Animal<String>{ public Cat(String s) { super(s);} }
泛型接口
-
语法:
public interface 接口名称<泛型标识>{ 泛型标识 方法名(); }
-
与泛型派生子类相似。实现类不是泛型,泛型接口必须指定具体的数据类型;实现类是泛型,泛型接口与实现类的泛型标识一致
泛型方法
-
泛型方法中,泛型类型是在泛型方法被调用时明确的
-
泛型类中,泛型类型是在创建泛型类对象时指定具体的数据类型。
public class Animal<T> { private T t; //泛型类的成员方法 public T getT() { return t;} //泛型类的泛型方法 public <T> T getT(T t){ return t;} public void setT(T t) { this.t = t;} public static void main(String[] args) { //测试泛型类的成员方法 String s = "张三"; Animal<String> stringAnimal = new Animal<>(); stringAnimal.setT(s); System.out.println(stringAnimal.getT()); //测试泛型类的泛型方法 Integer t = stringAnimal.getT(11); System.out.println(t); } } 运行结果: 张三 11 再次证明了,泛型方法中,泛型类型在方法调用时指定
-
语法:
修饰符 <泛型标识,泛型标识,...> 返回值类型 方法名称(泛型标识,泛型标识,...){ 方法体 }
- 修饰符与返回值类型之间的,表示此方法为泛型方法。
- 表示方法中使用了泛型类型T,这时方法中才可以使用泛型类型T
- 只有使用了的方法才算泛型方法,泛型类中使用的成员方法不是泛型方法
- 泛型标识可以是T,E,K,V
可变泛型方法
修饰符 <T> 返回值类型 方法名称(T... t){
方法体
}
类型通配符
一般采用?
作为类型通配符,类型通配符只能是实参类型
,不能是形参类型
类型通配符上限
-
语法:
类/接口<? extends 实参类型>
-
类型通配符:只能是实参类型或者是实参类型的子类
-
示例:
public class Animal { class Cat extends Animal{ } class Dog extends Cat{} public static void main(String[] args) { ArrayList<Cat> cats = new ArrayList<>(); ArrayList<Dog> dogs = new ArrayList<>(); ArrayList<Animal> animals = new ArrayList<>(); show(cats);√ show(dogs);√ show(animals);× } public static void show(ArrayList<? extends Cat> list){ list.add(new Cat());× } }
由于animel是cat的父类,超过了类型通配符的上限,所有报错
类型统配符的实参类型不能直接使用list.add添加,因为通配符并不知道添加的数据的上限
类型通配符下限
-
语法:
类/接口<? super 实参类型>
- 类型通配符:只能是实参类型或者实参类型的父类
-
示例:
public class Animal { class Cat extends Animal{ } class Dog extends Cat{} public static void main(String[] args) { ArrayList<Cat> cats = new ArrayList<>(); ArrayList<Dog> dogs = new ArrayList<>(); ArrayList<Animal> animals = new ArrayList<>(); show(cats);//√ show(dogs);//√ show(animals);//× } public static void show(ArrayList<? extends Cat> list){ list.add(new Cat());//√编译通过 list.add(new Dog());//√编译通过 } }
**注意:虽然类型通配符下限可以添加元素,但是并不保证数据类型的约束要求**
类型擦除
泛型是JDK1.5之后的特性,但是泛型代码能很好的与之前的代码兼容,主要是因为泛型信息只存在于编译时期,在进入JVM之前,与泛型相关的信息会被擦除。称之为类型擦除
深入理解泛型擦除
直接上代码
/**
* <h2>简单理解泛型</h2>
*/
private static void easyUse() throws Exception{
List<String> left = new ArrayList<>();
List<Integer> right = new ArrayList<>();
System.out.println(left.getClass());
System.out.println(left.getClass()==right.getClass());
}
运行结果
class java.util.ArrayList
true
为什么List left 与List right 类型相等?在获取left.getClass()并没有反应到它是一个String类型?
这是因为泛型的一个重要概念:类型擦除
大家都是Java的泛型其实是伪泛型,Java在编译期间,所有的泛型信息都会被擦掉,正确理解泛型的概念吗,首先要理解类型擦除。Java的泛型基本上都是在编译器这个层次上实现的,在生成的字节码中不包含泛型的类型信息的。使用泛型时,加上类型参数,在编译器编译时,会被去掉
。这个过程被称为类型擦除
。
所以说,虽然我们指定了left是String类型,right是Integer类型,但是实际上在编译器编译时,它们的类型被擦除掉了。也就是在编译后,left与right的类型都会变成List,JVM看到的也仅仅是List。而泛型附加的类型信息JVM时看不到的,所以Java的编译器在编译时尽可能去发现可能出现的错误,但是无法在运行时对类型转换异常这个情况进行捕获,同时类型擦除也是Java泛型与C++模板机制实现方式重要的区别
,java的泛型是伪泛型,而C++模板机制是真泛型。
无限制类型擦除
-
-
代码示例
public class Animal<T> { private T t; public T getT() { return t;} public void setT(T t) { this.t = t; } public static void main(String[] args) { Animal<String> stringAnimal = new Animal<>(); //获取字节码文件 Class<? extends Animal> aClass = stringAnimal.getClass(); // 获取所有Animal<String>的属性 Field[] declaredFields = aClass.getDeclaredFields(); for (Field declaredField : declaredFields) { System.out.println(declaredField.getName()+":"+declaredField.getType().getSimpleName()); } } } 运行结果: t:Object
有限制类型擦除
-
-
代码示例:
public class Animal<T extends Number> { private T t; public T getT() { return t;} public void setT(T t) { this.t = t; } public static void main(String[] args) { Animal<Integer> stringAnimal = new Animal<>(); //获取字节码文件 Class<? extends Animal> aClass = stringAnimal.getClass(); // 获取所有Animal<String>的属性 Field[] declaredFields = aClass.getDeclaredFields(); for (Field declaredField : declaredFields) { System.out.println(declaredField.getName()+":"+declaredField.getType().getSimpleName()); } } } 运行结果: t:Number
方法中类型定义的参数擦除
-
-
代码示例:
public class Animal<T> { private T t; public T getT() { return t;} public void setT(T t) { this.t = t; } public <T extends Number> T getValue(T t){ return t; } public static void main(String[] args) { Animal<Integer> stringAnimal = new Animal<>(); stringAnimal.getValue(100); //获取字节码文件 Class<? extends Animal> aClass = stringAnimal.getClass(); Method[] declaredMethods = aClass.getDeclaredMethods(); for (Method method : declaredMethods) { System.out.println(method.getName()+":"+method.getReturnType().getSimpleName()); } } } 运行结果: main:void getValue:Number//方法中类型定义的参数擦除 setT:void getT:Object
桥接擦除
-
-
代码示例
public interface Animal<T> { public T getT(T t); class Cat implements Animal<Integer> { @Override public Integer getT(Integer integer) { return integer; } } public static void main(String[] args) { Animal<Integer> stringAnimal = new Cat(); //获取字节码文件 Class<? extends Animal> aClass = stringAnimal.getClass(); Method[] declaredMethods = aClass.getDeclaredMethods(); for (Method method : declaredMethods) { System.out.println(method.getName()+":"+method.getReturnType().getSimpleName()); } } } 运行结果: getT:Integer getT:Object
泛型数组
方式一:可以申明带泛型数组的引用,不能直接创建带泛型数组的对象
-
解决方法:
创建原生的泛型数组,引用给泛型数组
-
代码示例:
ArrayList<String>[] arrayLists = new ArrayList[5]; ArrayList<String> list = new ArrayList<>(); list.add("小明"); arrayLists[0] = list; System.out.println(arrayLists[0].get(0)); 运行结果: 小明
方式二:采用java.lang.reflect.Array.newInstance(Class<?> componentType, int length)
- 代码示例
public class Cat<T> {
private T[] t;
public Cat(Class<T> clz,int length) {
t = (T[]) Array.newInstance(clz, length);
}
/**
* 获取指定下标的元素
* @param index
* @return
*/
public T getT(int index){
return t[index];
}
/**
* 向指定下标的存放元素
* @param t
* @param index
*/
public void put(T t,int index){
this.t[index] = t;
}
/**
* 获取泛型数组
* @return
*/
public T[] getArrays(){
return t;
}
public static void main(String[] args) {
Cat<String> stringCat = new Cat<>(String.class,3);
stringCat.put("香蕉", 0);
stringCat.put("苹果",1 );
stringCat.put("柠檬", 2);
System.out.println("下标为2的元素:"+stringCat.getT(2));
System.out.println("------------------------");
System.out.println(Arrays.toString(stringCat.getArrays()));
}
}
运行结果:
下标为2的元素:柠檬
------------------------
[香蕉, 苹果, 柠檬]
泛型与反射
-
代码示例:
Class<? extends Dog> aClass = new Dog<String>().getClass(); Constructor<?>[] constructors = aClass.getConstructors(); Class<TestAnimal> testAnimalClass = TestAnimal.class; Constructor<TestAnimal> constructor = testAnimalClass.getConstructor(testAnimalClass);