Java 泛型机制

Java 泛型

为什么需要泛型?

先来看一段代码,这段代码是用来计算两个数之和,可以看到每次新增一种数据类型,那么就要新增一个方法,这显然是不好的解决方法。

public class NoGeneric {
    
    public int addInt(int a, int b) {
        return a + b;
    }

    public double addDouble(double a, double b) {
        return a + b;
    }

    public float addFloat(float a, float b) {
        return a + b;
    }

}

这里就引出为什么要使用泛型的第一原因:

其实计算两个数之和的逻辑是一样的,只是参数类型不一样而已,那么泛型就是可以用于多种数据类型执行相同的代码。

下面再看另一段代码:

public class NoGeneric {
    public static void main(String[] args) {
        //定义一个集合    
        ArrayList datas = new ArrayList();

        //往集合中添加数据
        datas.add("Android");
        datas.add(1);
        datas.add("Flutter");
        
        //使用集合数据
        String rank = datas.get(1)//这段代码会报类型转化异常。
    }
}

在 JDK1.5 之前,集合是没有泛型的,添加的数据都是 Object 类型,因此可以往里面添加任意类型的数据,而在取出数据时,是需要判断类型,然后进行强制转化的,不然就会出现 ClassCastException。这里就是引入泛型的第二个原因:泛型中的类型在使用时指定,不需要强制类型转换

泛型引入的好处:

  • 泛型就是可以用于多种数据类型执行相同的代码。

  • 泛型中的类型在使用时指定,不需要强制类型转换

泛型在类,接口以及方法的使用

泛型使用 表示, 中间的可以带多种类型,使用 , 分割,例如<T,E>等。

  • 泛型类:
public class GenericType<T> {}
  • 泛型接口

泛型接口与泛型类的定义基本相同。

//泛型接口
public interface Callback<T> {

    void next(T data);
}

//未传入泛型实参时
public class ResponseCallback<T> implements Callback<T> {
    T data;
    public void next(T data){
        this.data = data;
    }
}

//传入泛型实参
public class SuccessCallback implements Callback<String> {
    String data ;
    public void next(String data){
        this.data=data;
    }
}   
  • 泛型方法

泛型方法,是在调用方法的时候指明泛型的具体类型 ,泛型方法可以在任何地方和任何场景中使用,包括普通类和泛型类。

注意泛型类中定义的普通方法和泛型方法的区别。

public class GenericMethod<T> {

    private T data;

    /**
     * 这个不是泛型方法,虽然该方法使用了泛型,但是这个泛型是在 GenericMethod 中定义的,
     */
    public T getData() {
        return data;
    }

    /**
     * 泛型方法:
     * 
     * 在方法的修饰符后面,返回值前面使用<T,...>,内部定义的泛型可以出现在方法的任意位置。
     */
    public <T> T getData(GenericType<T> data) {
        return data.getData();
    }
}

限定类型变量

有时候,我们需要对类型变量加以约束,比如计算两个变量的最小,最大值。

public <T> T min(T a, T b) {
    return a.compareTo(b)>0?a:b;
}

那么直接这样写代码,肯定是会报错的,那么如何确保传入的参数类型 T 的两个变量具备 compareTo 方法。

解决方法就是将 T 限制为实现 Comparable 接口。

改造代码如下:

public <T extends Comparable> T min(T a, T b) {
    return a.compareTo(b)>0?a:b;
}

T extends Comparable

T 表示绑定类型的子类型,Comparable 表示绑定类型,子类型和绑定类型可以是类也可以是接口。

**注意:**extends左右都允许有多个,如 <T,V extends Comparable & Serializable>
注意限定类型中,只允许有一个类(这是单继承的原因),而且如果有类,这个类必须是限定列表的第一个。
这种类的限定既可以用在泛型方法上也可以用在泛型类上。

public class GenericMethodLimit {

    /**
     * 比较两个参数
     */
    public static <T extends Comparable> T min(T a, T b) {
        return a.compareTo(b) > 0 ? a : b;
    }


    public static void main(String[] args) {
        //使用 min 方法
        min(1, 2);
        min("a", "b");
        //min(new Test(),new Test());//Test 类没有实现 Comparable 接口。所以这里编译报错
    }
    class Test{

    }
}

泛型中的约束和局限性

不能实例化类型变量

public class RestrictGeneric<T> {
    public RestrictGeneric() {
        //不能实例化类型变量
        //new T();
    }
}

不能用基本类型实例化类型参数

//RestrictGeneric<double> restrictGeneric = new RestrictGeneric<>();//报错

不能创建参数化类型的数组

RestrictGeneric<String>[] restrictGenericArr = null;//可以这样定义
//RestrictGeneric<String>[] restrictGenericArr1 = new RestrictGeneric<String>[10];//不允许

泛型类的泛型不能用于静态变量的定义和静态方法上
??????????????????????????????

public class RestrictGeneric<T> {
    //泛型类的静态上下文中类型变量失效
    //static T instance;
    
    
    //泛型类定义的泛型也是不能用在静态方法上
    //public static T getInstance(){
    //    return null;
    //}
    
    //但是可以这样用-单独定义泛型方法
    public static <T> T getInstance(){
        return null;
    }
}

泛型类不能继承 Exception,Throwable

private class Problem<T> extends Exception{//有问题的
}

不能 catch 泛型类对象

//private class Problem<T extends Exception> {
//    public void exception(){
//        try{
//            //不能 catch 泛型类对象
//        }catch (T e){
//        }
//    }
//}

/**
 * 抛出 T 类型的
 * 这种操作的是可以的
 */
private <T extends Exception> void throwException(T t) throws T {
    try {
    } catch (Exception e) {
        //do sth
        throw t;
    }
}

运行时类型查询只适用于原始类型

RestrictGeneric<String> stringRestrictGeneric = new RestrictGeneric<>();
RestrictGeneric<Integer> integerRestrictGeneric = new RestrictGeneric<>();
System.out.println(stringRestrictGeneric.getClass() == integerRestrictGeneric.getClass());//true
System.out.println(stringRestrictGeneric.getClass().getName());//com.example.generic.RestrictGeneric
System.out.println(integerRestrictGeneric.getClass().getName());//com.example.generic.RestrictGeneric

if(stringRestrictGeneric instanceof RestrictGeneric<String>){ }//不允许

泛型类型的继承规则

public class Animal {
    @Override
    public String toString() {
        return Animal.class.getSimpleName();
    }
}

public class Cat extends Animal {

    @Override
    public String toString() {
        return Cat.class.getSimpleName();
    }
}

public class InheritGeneric<T> {
    public static void main(String[] args) {
        InheritGeneric<Animal> animalInheritGeneric = new InheritGeneric<Animal>();
        InheritGeneric<Cat> catInheritGeneric = new InheritGeneric<Cat>();
        
        //animalInheritGeneric = catInheritGeneric;//泛型不一致
        
        //InheritGeneric<Animal> animalInheritGeneric = new InheritGeneric<Cat>();//这种是会报错的,因为两者的泛型不一样
        
        //为了实现这种方式,需要引入通配符
    }
}

观察上面的代码,
来看看 animalInheritGeneric 和 catInheritGeneric 有什么关系?

即使 Animal和 Cat 是继承关系,但是 animalInheritGeneric 和 catInheritGeneric 它们之间没有关系,因为泛型不一致。

通配符类型

? extends X
表示传递给方法的参数,必须是X的子类(包括X本身)

public class GenericType<T> {

    private T data;

    public GenericType() {
    }

    public GenericType(T data) {
        this.data = data;
    }

    public T getData() {
        return data;
    }

    public void setData(T data) {
        this.data = data;
    }
}
GenericType<? extends Cat> genericType = null;
GenericType<Cat> catGenericType = new GenericType<>();
GenericType<Animal> animalGenericType = new GenericType<Animal>();

//<? extends Cat>接受 Cat 及其 Cat 子类
genericType = catGenericType;//编译通过
//Cat 是 Animal 的子类
genericType = animalGenericType;//编译不通过

//--------setData
//setData() 他知道传入的类型是 Cat 类型,但是具体是什么子类型,还是不清楚的。因此这种方式是不安全的。
genericType.setData(new BlackCat());//编译不通过
genericType.setData(new Cat());//编译不通过


//-------getData
genericType = new GenericType<Cat>(new BlackCat());
Cat data = genericType.getData();//编译通过

这里需要关注两个问题:

  • genericType.setData(new Cat()) 不能编译通过的原因

setData() 他知道传入的类型是 Cat 类型,但是具体是什么子类型,还是不清楚的。因此这种方式是不安全的。

  • 为什么 Cat data = genericType.getData() 返回的是 Cat 类型的。

genericType.getData 接受的数据,一定会是 Cat 或者 Cat 的子类,但是不管是哪个,都是可以用 Cat 这个父类去接受,这个可以用多态去解释。
因此 genericType.getData() 返回的是 Cat 类型。

? super X
表示传递给方法的参数,必须是X的超类(包括X本身)

GenericType<? super Cat> genericType2 = new GenericType<Animal>();//right
//GenericType<? super Cat> genericType3 = new GenericType<BlackCat>();//error


//设置数据
//GenericType其中提供了get和set类型参数变量的方法的话,set方法可以被调用的,且能传入的参数只能是泛型X或者泛型X的子类
//用到多态的思想来理解:既然传给 GenericType 的参数必须是 Cat 的超类,那么在调用 setData 时肯定是能接受 Cat 或者  Cat 的子类。
//Cat 或者 Cat 的之类。
genericType2.setData(new Cat());
genericType2.setData(new BlackCat());

//genericType.setData(new Animal());//不允许

//获取数据
// ? super  X  表示类型的下界,类型参数是X的超类(包括X本身),
// 那么可以肯定的说,get方法返回的一定是个X的超类,那么到底是哪个超类?
// 不知道,但是可以肯定的说,Object一定是它的超类,所以get方法返回Object。
// 编译器是可以确定知道的。对于set方法来说,编译器不知道它需要的确切类型,
// 但是X和X的子类可以安全的转型为X。
Object data = genericType.getData();

Java 虚拟机是如何实现泛型的?

Java语言中的泛型实现方法称为类型擦除,基于这种方法实现的泛型称为伪泛型。

/**
 * 下面这两种声明方法方式是不行的,因为在编译时会进行泛型擦除
 * 因此得到的参数都是 ArrayList ,因此这两个方法的签名是一样的
 */
//public static  void method(ArrayList<Animal> data){}
//public static  void method(ArrayList<Cat> data){}

上面这段代码是不能被编译的,因为参数List<Animal>和List<Cat>编译之后都被擦除了,变成了一样的原生类型List<E>,擦除动作导致这两种方法的特征签名变得一模一样。

下面通过泛型擦除的机制,来往List<Integer>集合中添加一个 String 字符串。

/**
 * 反射机制动态给集合添加其他类型的数据---绕过泛型的限制,泛型擦除
 */
private static void generic3() {
    try {
        List<Integer> collection = new ArrayList<Integer>();
        collection.add(1);
        Class<? extends List> collectionClass = collection.getClass();
        Method addMethod = collectionClass.getDeclaredMethod("add", Object.class);
        addMethod.setAccessible(true);
        addMethod.invoke(collection, "我是 String 类型的数据");
        for (Object obj : collection) {
            System.out.println("元素:" + obj);
        }
    } catch (Exception e) {
        e.printStackTrace();
    }
}

记录于2019年4月1日愚人节~

  • 7
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值