美文网首页Java
Java 泛型机制

Java 泛型机制

作者: 未见哥哥 | 来源:发表于2019-04-01 19:06 被阅读46次
    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> 表示, 中间的可以带多种类型,使用 , 分割,例如<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日愚人节~

    相关文章

      网友评论

        本文标题:Java 泛型机制

        本文链接:https://www.haomeiwen.com/subject/bydbbqtx.html