概述
Generics(泛型),我们经常在Java集合或者框架里面经常看见对泛型的使用场景,那么泛型的作用有哪些呢。
- 可以帮助我们处理多种数据类型执行相同的代码
- 数据安全性,泛型中的类型在使用时指定类型后,不需要强制类型转换,最早起Java是没有泛型的,它是使用Object来代替的,这样程序员在写程序的时候很容易出现类型转换的错误。
泛型的本质是为了参数化类型,就是将类型由原来的具体的类型参数化,就像方法中的变量参数,此时类型也定义成参数形式(可以称之为类型形参),然后在使用/调用时传入具体的类型(类型实参)。也就是说在泛型使用过程中,操作的数据类型被指定为一个参数,这种参数类型可以用在类、接口和方法中,分别被称为泛型类、泛型接口、泛型方法。
泛型类和泛型接口
泛型类/接口的定义,使用一个类型变量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
通配符类型
一般来说泛型的通配符有两种,
- ? extends X
表示类型的上界,类型参数是X的子类或自身 - ? super X
表示类型的下界,类型参数是X的超类
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 泛型原理
Java语言中的泛型一般称为伪泛型,它只在程序源码中存在,在编译后的字节码文件中,就已经替换为原来的原生类型(Raw Type,也称为裸类型)了,并且在相应的地方插入了强制转型代码,因此,对于运行期的Java语言来说,ArrayList<int>与ArrayList<String>就是同一个类,所以泛型技术实际上是Java语言的一颗语法糖,Java语言中的泛型实现方法称为类型擦除,基于这种方法实现的泛型称为伪泛型
由于Java泛型的引入,各种场景(虚拟机解析、反射等)下的方法调用都可能对原有的基础产生影响和新的需求,如在泛型类中如何获取传入的参数化类型等。因此,JCP组织对虚拟机规范做出了相应的修改,引入了诸如Signature、LocalVariableTypeTable等新的属性用于解决伴随泛型而来的参数类型的识别问题,Signature是其中最重要的一项属性,它的作用就是存储一个方法在字节码层面的特征签名[3],这个属性中保存的参数类型并不是原生类型,而是包括了参数化类型的信息。修改后的虚拟机规范要求所有能识别49.0以上版本的Class文件的虚拟机都要能正确地识别Signature参数。
另外,从Signature属性的出现我们还可以得出结论,擦除法所谓的擦除,仅仅是对方法的Code属性中的字节码进行擦除,实际上元数据中还是保留了泛型信息,这也是我们能通过反射手段取得参数化类型的根本依据