JAVA泛型的使用和深入理解

JAVA泛型的使用和深入理解_第1张图片

泛型

目录

泛型

为什么我们需要泛型?

泛型的使用

泛型类

泛型接口

泛型方法

限定类型变量

泛型的限制

泛型类型的继承规则

通配符

泛型的实现原理(类型擦除)

获取一个对象上的泛型类型

Gson 反序列化需要借助TypeToken


为什么我们需要泛型?

1,类型安全。

泛型的主要目标是提高 Java 程序的类型安全。编译时的强类型检查;通过知道使用泛型定义的变量的类型限制,编译器可以在一个高得多的程度上验证类型假设。没有泛型,这些假设就只存在于程序员的头脑中(或者如果幸运的话,还存在于代码注释中)。

2,消除强制类型转换

泛型的一个附带好处是,消除源代码中的许多强制类型转换。这使得代码更加可读,并且减少了出错机会。

3,潜在的性能收益

泛型为较大的优化带来可能。在泛型的初始实现中,编译器将强制类型转换(没有泛型的话,程序员会指定这些强制类型转换)插入生成的字节码中。但是更多类型信息可用于编译器这一事实,为未来版本的 JVM 的优化带来可能。由于泛型的实现方式,支持泛型(几乎)不需要 JVM 或类文件更改。所有工作都在编译器中完成,编译器生成类似于没有泛型(和强制类型转换)时所写的代码,只是更能确保类型安全而已。

Java语言引入泛型的好处是安全简单。泛型的好处是在编译的时候检查类型安全,并且所有的强制转换都是自动和隐式的,提高代码的重用率。

4、更好的代码复用性,比如实现泛型加法

举个例子,想要实现加法运算方法,不借助泛型需要实现float,int,double的不同参数类型的方法法,使用泛型就可以屏蔽掉float,int,double的区别,一个方法即可概括所有的参数类型。

泛型的使用

泛型类

public class NormalGeneric {
    private K data;
​
    public NormalGeneric() {
    }
​
    public NormalGeneric(K data) {
        this.data = data;
    }
​
    public K getData() {
        return data;
    }
​
    public void setData(K data) {
        this.data = data;
    }
    public static void main(String[] args) {
        NormalGeneric normalGeneric = new NormalGeneric<>();
        normalGeneric.setData("OK");
        //normalGeneric.setData(1);
        System.out.println(normalGeneric.getData());
        NormalGeneric normalGeneric1 = new NormalGeneric();
        normalGeneric1.setData(1);
        normalGeneric1.setData("dsf");
    }
}

可根据传入的类型,动态改变属性的类型

泛型接口

public interface Genertor {
    public T next();
}

泛型类实现泛型接口

public class ImplGenertor implements Genertor {
    @Override
    public T next() {
        return null;
    }
}

普通类实现泛型接口

public class ImplGenertor implements Genertor {
    @Override
    public String next() {
        return null;
    }
}

泛型方法

泛型方法必须带有<>,如果只是在泛型类里定义的方法恰好返回了泛型类的类型的话,那么这个方法不是泛型方法

public  T genericMethod(T...a){
     return a[a.length/2];
}

泛型方法的解析

public class GenericMethod {
    //这个类是个泛型类,在上面已经介绍过
    public class Generic{
        private T key;
​
        public Generic(T key) {
            this.key = key;
        }
​
        //虽然在方法中使用了泛型,但是这并不是一个泛型方法。
        //这只是类中一个普通的成员方法,只不过他的返回值是在声明泛型类已经声明过的泛型。
        //所以在这个方法中才可以继续使用 T 这个泛型。
        public T getKey(){
            return key;
        }
​
        /**
         * 这个方法显然是有问题的,在编译器会给我们提示这样的错误信息"cannot reslove symbol E"
         * 因为在类的声明中并未声明泛型E,所以在使用E做形参和返回值类型时,编译器会无法识别。
         */
        //        public E setKey(E key){
        //            this.key = key;
        //        }
    }
​
    /**
     * 这才是一个真正的泛型方法。
     * 首先在public与返回值之间的必不可少,这表明这是一个泛型方法,并且声明了一个泛型T
     * 这个T可以出现在这个泛型方法的任意位置.
     * 泛型的数量也可以为任意多个
     *    如:public  K showKeyName(Generic container){
     *        ...
     *        }
     */
​
​
    //这也不是一个泛型方法,这就是一个普通的方法,
    // 只是使用了Generic这个泛型类做形参而已。
    public void show(Generic obj){
​
    }
​
    /**
     * 这个方法是有问题的,编译器会为我们提示错误信息:"UnKnown class 'E' "
     * 虽然我们声明了,也表明了这是一个可以处理泛型的类型的泛型方法。
     * 但是只声明了泛型类型T,并未声明泛型类型E,因此编译器并不知道该如何处理E这个类型。
     */
    //    public  T show(E ab){
    //        //
    //    }
​
    /**
     * 这个方法也是有问题的,编译器会为我们提示错误信息:"UnKnown class 'T' "
     * 对于编译器来说T这个类型并未项目中声明过,因此编译也不知道该如何编译这个类。
     * 所以这也不是一个正确的泛型方法声明。
     }
     */
    //    public void show(T obj){
    //
    //    }
​
    public static void main(String[] args) {
​
​
    }
}
 
  

限定类型变量

如果在使用泛型的时候调用了某种方法,而参数的类不一定实现此方法,所以这时候要对传入的参数进行限制,下面的例子是返回较小值,此时就必须传入实现了compareTo()方法的对象,即使用进行限制

public class ArrayAlg {
//    public static  T min(T a,T b){
//        if(a.comapareTo(b)>0) return a; else return b;
//    }
    public static  T min(T a, T b){
        if(a.compareTo(b)>0) return a; else return b;
    }
​
    static class Test{}
​
    public static void main(String[] args) {
        //ArrayAlg.min(new Test(),new Test());
    }
}

泛型可以继承一个类和多个接口,语法如下,继承的类必须写在第一个,&后可以跟多个接口

泛型的限制

  • 不能实例化

//报错 
T t = new T();
  • 静态方法的参数和静态域不能使用泛型,但是静态方法可以定义成泛型方法

    静态方法在 JVMClassLoder 阶段被写到内存的方法区,有且只有一份,在多线程之间对象共享,包括静态方法的类型参数在方法 区中只有一份,也是对象共享。如果使用了泛型,当多个对象调用静态方法先后注入String、Integer类型参数,那么该静态方法的类型参数最后保存的是Integer类型,此时,先前调用静态方法的对象将会因为类型不匹配,而抛出运行时异常。当然,为了杜绝这种错误,一开始就会提示不能在静态方法中使用泛型

  • 基本类型不能当做泛型使用必须用包装器类型

        泛型必须是对象,而基本类型不是对象

  • 泛型不能使用instanceof关键字

    泛型并不会改变一个泛型类的原生类型

//报错 if(restrict instanceof  Restrict)
//报错 if(restrict instanceof  Restrict)
​
 Restrict restrict = new Restrict<>();
 Restrict restrictString= new Restrict<>();
 System.out.println(restrict.getClass()==restrictString.getClass());/true
  • 数组不能初始化

    可以定义泛型数组但是不能初始化

Restrict[] restrictArray;//正确 可以定义
Restrict[] restricts = new Restrict[10];//报错 不能初始化
  • 泛型不能被try-catch捕获

    可以抛,但不能捕获

    /*不能捕获泛型类对象*/
    //    public  void doWork(T x){
    //        try{
    //
    //        }catch(T x){
    //            //do sth;
    //        }
    //    }
    ​
    ​
    public  void doWorkSuccess(T x) throws T{
        try{
    ​
        }catch(Throwable e){
            throw x;
        }
    }

泛型类型的继承规则

泛型类之间不会因为泛型之间的改变而改变继承关系,但是泛型类和泛型类之间可以继承

//Employee extends Worker但是下面两行代码毫无关系
Pair employeePair = new Pair<>();
Pair workerPair = new Pair<>();
​
//泛型类之间的继承
private static class ExtendPair extends Pair{
​
}

通配符

向下面这种情况就会报错,但是我们又想泛型之间的继承关系能够正确的使用,所以引入了通配符?

//class Fruit
//class Orange extend Fruit
//class Apple extend Fruit
public static void print(GenericType p){
    System.out.println(p.getData().getColor());
}
​
public class GenericType {
    private T data;
​
    public T getData() {
        return data;
    }
​
    public void setData(T data) {
        this.data = data;
    }
}
​
GenericType a = new GenericType<>();
print(a);
GenericType b = new GenericType<>();
//print(b);报错
GenericType c = new GenericType<>();
//print(c);报错

如果想要这个继承能够实现的话即必须使用? 通配符不能使用在泛型类上

//这样定义的话上面的代码就不会报错
public static void print(GenericType p){
    System.out.println(p.getData().getColor());
}

通配符的上界和下界问题

//class Food
//class Fruit extend Food
//class Orange extend Fruit
//class Apple extend Fruit
//class Apple extend Hongfushi
​
//上界问题,在放过程中并不能确定具体哪个子类,但是取过程中上界已经规定好所以说已经确定,使得放操作报错,取操作不报错
List list=new ArrayList<>();
Orange a=new Orange();
Apple b=new Apple();
Fruit c=nwe Fruit();
//list.add(a); 报错
//list.add(b); 报错
//list.add(c); 报错
Fruit c=list.get(0);//不报错
​
//下界问题,在放过程中能确定具体哪个子类,但是取过程中下界已经规定好所以说已经确定,使得取操作报错,放操作不报错
List list=new ArrayList<>();
Hongfushi a=new Hongfushi();
Apple b=new Apple();
Fruit c=new Fruit();
list.add(a); 
list.add(b); 
//list.add(c); 报错 即使规定超类为Apple,再真实传递时也只能传递Apple的子类和Apple本身,传父类就会报错
Object o=list.get(0);//不报错 因为不能确定哪个具体的类,但是最顶层肯定是Object所以会返回一个Object

总结:如果规定上界是一定不能放数据的,取数据的时候上界一定规定所以传递哪个泛型就返回哪个泛型

规定下界是能放数据的(有限制),取数据的时候上界没有规定所以返回Object类型

泛型的实现原理(类型擦除)

在定义泛型类时,编译器会自动改变他的上界,如果你不用extends进行限制那么就等于上界就是Object,如果规定了上界那么属性就会变为我们规定的上界而不是Object

//我们定义的泛型类
class Pair {  
    private T value;  
    public T getValue() {  
        return value;  
    }  
    public void setValue(T  value) {  
        this.value = value;  
    }  
}  
//不规定上界编译器生成的类
class Pair {  
    private Object value;  
    public Object getValue() {  
        return value;  
    }  
    public void setValue(Object  value) {  
        this.value = value;  
    }  
}
​
//规定上界为Number   class Pair
class Pair {  
    private Number value;  
    public Number getValue() {  
        return value;  
    }  
    public void setValue(Number  value) {  
        this.value = value;  
    }  
}

当重载方法时,这两个方法在编译器不能实现,而在jdk中是可以实现的,编译器只会判断参数类型来决定是否重载,而jdk不仅仅以参数为依据,还会以返回值为依据进行判断

 public static String method(List stringList){
        System.out.println("List");
        return "OK";
    }
​
//    public static Integer method(List stringList){
//        System.out.println("List");
//        return 1;
//    }

如果我们的泛型继承了多个例如,当调用Comparable中的方法时,会给我们的变量进行强制类型转换

T data;
data.compareTo();//编译之后变为(Comparable)data.compareTo();

并不是说java在类型擦除的时候会完全擦除,他也会对类型进行一个Signature (弱记忆),对泛型类型进行一个记录。

获取一个对象上的泛型类型

Type genericType=field.getGenericType();
ParameterizedType pt = (ParameterizedType) genericType;
​
// 得到泛型里的class类型对象
Class actualTypeArgument = (Class)pt.getActualTypeArguments()[0];
// 得到<>前的类型
Class actualTypeArgument = (Class)pt.getRawType();
// 得到o.e o的类型
Class actualTypeArgument = (Class)pt.getOwnerType();

Gson 反序列化需要借助TypeToken

假设有以下bean

public class Response {
    T data;
    int code;
    String message;
​
    @Override
    public String toString() {
        return "Response{" +
                "data=" + data +
                ", code=" + code +
                ", message='" + message + '\'' +
                '}';
    }
​
    public Response(T data, int code, String message) {
​
        this.data = data;
        this.code = code;
        this.message = message;
    }
}
​
public class Data {
    String result;
​
    public Data(String result) {
        this.result = result;
    }
​
    @Override
    public String toString() {
        return "Data{" +
                "result=" + result +
                '}';
    }
}

Gson序列化

Response dataResponse = new Response(new Data("数据"), 1, "成功");
Gson gson = new Gson();
String json = gson.toJson(dataResponse);
//{"data":{"result":"数据"},"code":1,"message":"成功"}

Gson反序列化

Response response = gson.fromJson(json, type);
System.out.println(response.data.getClass());
//Response{data=Data{result=数据}, code=1, message='成功'}

报错,越界错误,其内部会默认为LinkedTreeMap,但是数据data的真实类型为Data,两者转换错误

java.lang.ClassCastException: class com.google.gson.internal.LinkedTreeMap cannot be cast to 。。。。

Gson在解析时需要借助TypeToken

正确使用:

Type type = new TypeToken>(){
    
}.getType();
Response response = gson.fromJson(json, type);
System.out.println(response.toString());

为何使用TypeToken可让Gson知道泛型的具体类型?

泛型的信息是可以通过类保存下来的,上文TypeToken中笔者声明的为内部类,泛型信息则可被保存,后续解析则可对泛型的真实类型进行解析

下面实现一个功能类似TypeToken的类

public class TypeRefrence {
    Type type;
    T t;
    //protect 只允许子类和同包使用,所以用户不使用匿名内部类,直接报错
    protect TypeRefrence() {
        //获得泛型类型,为何能获取,因为类可以保存泛型信息
        Type genericSuperclass = getClass().getGenericSuperclass();
        ParameterizedType parameterizedType = (ParameterizedType) genericSuperclass;
        //因为类泛型可以定义多个  A 所以是个数组
        Type[] actualTypeArguments = parameterizedType.getActualTypeArguments();
        type = actualTypeArguments[0];
​
    }
    public Type getType() {
        return type;
    }
}

使用上文定义的类

Type type = new TypeRefrence>(){
    
}.getType();
Response response = gson.fromJson(json, type);
System.out.println(response.toString());
//Response{data=Data{result=数据}, code=1, message='成功'}

若去掉上文的内部类的{},使用对象可否呢?

报错

java.lang.ClassCastException: class com.google.gson.internal.LinkedTreeMap cannot be cast to 。。。。

原理分析

分析Type type = new TypeRefrence>(){}.getType();的字节码

Type type = new TypeRefrence>(){}.getType();等价于写一个类继承TypeRefrence

编写类如下:

public class MyTypeToken extends TypeRefrence> {
    public MyTypeToken() {
    }
}

其字节码为

// signature Lcom/enjoy/reflect/TypeRefrence;>;
// declaration: com/enjoy/reflect/MyTypeToken extends com.enjoy.reflect.TypeRefrence>
public class com/enjoy/reflect/MyTypeToken extends com/enjoy/reflect/TypeRefrence {
    ...
}

可以看到上方存在泛型签名信息,当虚拟机扫描此字节码时,即可获取到其上的泛型信息。


原创不易,还希望各位大佬支持一下
点赞,你的认可是我创作的动力!
⭐️ 收藏,你的青睐是我努力的方向!
✏️ 评论,你的意见是我进步的财富!

你可能感兴趣的:(java基础,java)