Generics(泛型),我们经常在Java集合或者框架里面经常看见对泛型的使用场景,那么泛型的作用有哪些呢。
泛型的本质是为了参数化类型,就是将类型由原来的具体的类型参数化,就像方法中的变量参数,此时类型也定义成参数形式(可以称之为类型形参),然后在使用/调用时传入具体的类型(类型实参)。也就是说在泛型使用过程中,操作的数据类型被指定为一个参数,这种参数类型可以用在类、接口和方法中,分别被称为泛型类、泛型接口、泛型方法。
泛型类/接口的定义,使用一个类型变量T(其他大写字母都可以,不过常用的就是T,E,K,V等等),相当于一个占位符,并且用<>括起来,并放在类/接口名的后面。泛型类是允许有多个类型变量的
public class GenericsClass<T> { private void print(){ System.out.println("泛型类型"); } public static void main(String [] args){ GenericsClass<String> genericsClass = new GenericsClass<>(); genericsClass.print(); } } 复制代码
public class GenericsClass<T,V> { private void print(){ System.out.println("泛型类型"); } public static void main(String [] args){ GenericsClass<String,Integer> genericsClass = new GenericsClass<>(); genericsClass.print(); } } 复制代码
public interface ImpGenerics<V> { } 复制代码
而实现泛型接口的类,有两种实现方法
public class Generics<T> implements ImpGenerics<T> { } 复制代码
这中实现的时候,没有指定具体的类型,这种实现方式在创建对象的时候,需要传入具体的类型
public class Generics implements ImpGenerics<String> { } 复制代码
这种是在实现的时候传入具体的实参,创建对象的时候就想普通类一样使用就OK
泛型方法,是在调用方法的时候指明泛型的具体类型 ,泛型方法可以在任何地方和任何场景中使用,包括普通类和泛型类,
public class GenericsClass<K,V> { private K date; private V value; //普通方法 private V getValue(K date){ return value; } //泛型方法 private <T> T genericsMethod(T date){ return date; } public GenericsClass(K date, V value) { this.date = date; this.value = value; } private void print(){ System.out.println("泛型类型"); } public static void main(String [] args){ GenericsClass<String,Integer> genericsClass = new GenericsClass<>("k",123); genericsClass.print(); int a = genericsClass.getValue("k"); System.out.println("v="+a); } } 复制代码
泛型方法必须通过“<类型占位符>”来声明返回的类型,譬如<V>等
通常在使用时候,我们需要让所有的类型具体同一个方法,我们需要对类型变量加以约束,比如计算两个变量的最小,最大值,为了确保传入的两个变量一定有compareTo方法?我们就需要将T限制为实现了接口Comparable的类
private <T extends Comparable> T min(T a,T b){ return a.compareTo(b)>0 ? b:a; } 复制代码
T extends Comparable中 T表示应该绑定类型的子类型,Comparable表示绑定类型,子类型和绑定类型可以是类也可以是接口,同时extends左右都允许有多个,如 T,V extends Comparable & Serializable 注意限定类型中,只允许有一个类,而且如果有类,这个类必须是限定列表的第一个。 这种类的限定既可以用在泛型方法上也可以用在泛型类上
private <T extends Comparable & Serializable> T min(T a, T b){ return a.compareTo(b)>0 ? b:a; } 复制代码
public class GenericsClass<K,V> { private K date; private V value; //普通方法 private V getValue(K date){ return value; } //泛型方法 private <T> T genericsMethod(T date){ return date; } private <T extends Comparable & Serializable> T min(T a, T b){ return a.compareTo(b)>0 ? b:a; } public GenericsClass(K date, V value) { this.date = date; this.value = value; } private void print(){ System.out.println("泛型类型"); } public static void main(String [] args){ //不能使用基本类型 GenericsClass<String,int> genericsClass = new GenericsClass<>("k",123); } } 复制代码
public class GenericsClass<K> { private K date; private void print(){ System.out.println("泛型类型"); } public static void main(String [] args){ GenericsClass<String> genericsClass = new GenericsClass<>(); GenericsClass<String> genericsClass_ = new GenericsClass<>(); System.out.println("泛型类型genericsClass="+genericsClass_.getClass().getName().toString()); System.out.println("泛型类型genericsClass_="+genericsClass_.getClass().getName().toString()); } } 复制代码
输出
泛型类型genericsClass=com.mtx.javalib.GenericsClass 泛型类型genericsClass_=com.mtx.javalib.GenericsClass 复制代码
不能在静态域或方法中引用类型变量。因为泛型是要在对象创建的时候才知道是什么类型的,而对象创建的代码执行先后顺序是static的部分,然后才是构造函数等等。所以在对象初始化之前static的部分已经执行了,如果你在静态部分引用的泛型,那么毫无疑问虚拟机根本不知道是什么东西,因为这个时候类还没有初始化。 4. 不能创建参数化类型的数组
public class GenericsClass<K> { private K date; private void print(){ System.out.println("泛型类型"); } public static void main(String [] args){ //编译会报错 GenericsClass<String>[] genericsClass = new GenericsClass<String>()[3]; } } 复制代码
为什么是这样,主要是Java的泛型实现方式有关,后续会说到
public <T extends Throwable> T testMethod(T t){ try { System.out.println("泛型类型异常无法捕获"); }catch (T e){ } return t; } 复制代码
但是这种方式是可以的
public <T extends Throwable> T testMethod(T t){ try { System.out.println("泛型类型异常无法捕获"); }catch (Throwable e){ } return t; } 复制代码
泛型类可以继承或者扩展其他泛型类,比如List和ArrayList
一般来说泛型的通配符有两种,
public class Car { private String name; public String getName() { return name; } public void setName(String name) { this.name = name; } } public class Bmw extends Car { @Override public String getName() { return super.getName(); } @Override public void setName(String name) { super.setName(name); } } 复制代码
public class Bmw extends Car { @Override public String getName() { return super.getName(); } @Override public void setName(String name) { super.setName(name); } public static void testMethod(GenericsClass<? extends Car> car){ System.out.println("类型参数只能是Car的子类"); } public static void main(String[] args){ GenericsClass<Bmw> genericsClass = new GenericsClass(); testMethod(genericsClass); } } 复制代码
Java语言中的泛型一般称为伪泛型,它只在程序源码中存在,在编译后的字节码文件中,就已经替换为原来的原生类型(Raw Type,也称为裸类型)了,并且在相应的地方插入了强制转型代码,因此,对于运行期的Java语言来说,ArrayList<int>与ArrayList<String>就是同一个类,所以泛型技术实际上是Java语言的一颗语法糖,Java语言中的泛型实现方法称为类型擦除,基于这种方法实现的泛型称为伪泛型
由于Java泛型的引入,各种场景(虚拟机解析、反射等)下的方法调用都可能对原有的基础产生影响和新的需求,如在泛型类中如何获取传入的参数化类型等。因此,JCP组织对虚拟机规范做出了相应的修改,引入了诸如Signature、LocalVariableTypeTable等新的属性用于解决伴随泛型而来的参数类型的识别问题,Signature是其中最重要的一项属性,它的作用就是存储一个方法在字节码层面的特征签名[3],这个属性中保存的参数类型并不是原生类型,而是包括了参数化类型的信息。修改后的虚拟机规范要求所有能识别49.0以上版本的Class文件的虚拟机都要能正确地识别Signature参数。 另外,从Signature属性的出现我们还可以得出结论,擦除法所谓的擦除,仅仅是对方法的Code属性中的字节码进行擦除,实际上元数据中还是保留了泛型信息,这也是我们能通过反射手段取得参数化类型的根本依据