Java8之泛型

Java8之泛型,第1张

Java8之泛型

写在开头:本文为学习后的总结,可能有不到位的地方,错误的地方,欢迎各位指正。

目录

一、泛型入门

        1、泛型基本介绍

        2、泛型的作用

      (1)提供安全保证

      (2)简化代码

二、泛型的上下限

     1、下限

     2、上限

     3、使用建议 

三、泛型擦除

        1、证明泛型擦除

        2、泛型擦除的规则

                (1)擦除类定义中的类型参数 - 无限制类型擦除

                (2)擦除类定义中的类型参数 - 有限制类型擦除

                (3)擦除方法定义中的类型参数

四、泛型在异常中的使用

        1、不能抛出也不能捕获泛型类的对象

        2、不能再catch子句中使用泛型变量


一、泛型入门         1、泛型基本介绍

        泛型是JDK1.5版本引入的,以泛型类为例,申明泛型类时需带上参数类型T,成员变量

就可以被限制为T的类型。

class Example{
    
    private T  variable;

    public T getVariable() {
        return variable;
    }

    public void setVariable(T variable) {
        this.variable = variable;
    }     
    
} 

        不过需要注意的是,申明为泛型类,不代表新建对象时必须增加泛型限制。

        Example ex = new Example();
        Example ex2 = new Example();
        2、泛型的作用

        主要目的有2个

      (1)提供安全保证

        这里我们以集合为例,我们初始化一个ArrayList,并限制器内部元素为Integer,当我们add其他类型进入容器中时,编译器就会告诉我们这样做是不允许的。通过这种方式,我们可以确保容器中元素都属于同一个类,避免了get时异常的发生。

        List list = new ArrayList();
        list.add(1);
        list.add("abc"); // 编译错误
      (2)简化代码

        假设我们需要写一个两数相加的功能,如果没有泛型的话,代码需要进行多次重载

    public int add(int x, int y) {
        return x + y;
    }

    public float add(float x, float y) {
        return x + y;
    }

    public double add(double x, double y) {
        return x + y;
    }

        而有了泛型之后,我们可以简化为下面这样(T extends Number利用到了泛型的上下限,这里的作用是限制T的类必须是Number的子类),避免了代码的冗余。

    public  double add(T x, T y) {
        return x.doublevalue() + y.doublevalue();
    }

二、泛型的上下限

        首先来看以下样例,参考自《Java 基础 - 泛型机制详解》

class A{}
class B extends A {}

// 如下两个方法不会报错
public static void funA(A a) {
    // ...          
}
public static void funB(B b) {
    funA(b);
    // ...             
}

// 如下funD方法会报错
public static void funC(List listA) {
    // ...          
}
public static void funD(List listB) {
    funC(listB); // Unresolved compilation problem: The method doPrint(List) in the type test is not applicable for the arguments (List)
    // ...             
}

        我们可以看到,虽然B继承于A,A的引用是可以指向B的,但泛型在类型转换时,并没有智能到可以替我们自动识别出子类。因此就需要我们手动将B加入到泛型的限制中去,于是我们使用List 来修改泛型限制。

public static void funC(List listA) {
    // ...          
}
public static void funD(List listB) {
    funC(listB); // OK
    // ...             
}

        由上面这个案例我们可以看出,普通的泛型限制如在遇到继承关系时过于僵硬,于是就有了泛型上下限的概念,协助我们在泛型中处理继承的关系。

     1、下限

        extends 关键字声明了类型的上界,表示参数化的类型可能是所指定的类型,或者是此类型的子类。

class Example{
    
    private T  variable;

    public T getVariable() {
        return variable;
    }

    public void setVariable(T variable) {
        this.variable = variable;
    }  
   
    
} 
public static void main(String[] args)  {     
        Example ex = new Example();
        ex.setVariable(1);
    }
     2、上限

super 关键字声明了类型的下界,表示参数化的类型可能是指定的类型,或者是此类型的父类

Example ex = new Example();
     3、使用建议 

        《Effictive Java》中建议为了获得最大限度的灵活性,要在表示 生产者或者消费者 的输入参数上使用通配符,使用的规则就是:生产者有上限、消费者有下限
        1. 如果参数化类型表示一个 T 的生产者,使用 < ? extends T>;
        2. 如果它表示一个 T 的消费者,就使用 < ? super T>;
        3. 如果既是生产又是消费,那使用通配符就没什么意义了,因为你需要的是精确的参数类型。

三、泛型擦除

        java中的泛型是伪泛型,只在编译前起作用,编译时会被替换掉,即擦除。

        1、证明泛型擦除

        第一节中我们说过,泛型的作用之一就是限制参数类型,这里我们同样限定一个参数类型为Integer的ArrayList,当我们尝试将字符串加入其中时,编译器会告诉我们这样是不允许的。但这并不代表我们就没有办法把字符串加入进去。

        上文中提到了,泛型限制在编译后就被擦除了,那么自然就想到了利用反射来将add方法延后到运行时执行,代码如下。

    public static void main(String[] args) throws IllegalAccessException, IllegalArgumentException,
            InvocationTargetException, NoSuchMethodException, SecurityException {

        ArrayList list = new ArrayList();

        list.add(1); 

        // list.add("abc");   编译错误

        list.getClass().getMethod("add", Object.class).invoke(list, "abc");

        for (int i = 0; i < list.size(); i++) {
            System.out.println(list.get(i));
        }
    }

        结果如下,至此,我们证明了泛型限制在编译后即不复存在。

1
abc

        2、泛型擦除的规则

                内容参考自《Java泛型的类型擦除》

                (1)擦除类定义中的类型参数 - 无限制类型擦除

                  当类定义中的类型参数没有任何限制时,在类型擦除中直接被替换为Object,即形如的类型参数都被替换为Object。


                (2)擦除类定义中的类型参数 - 有限制类型擦除

                   当类定义中的类型参数存在限制(上下界)时,在类型擦除中替换为类型参数的上界或者下界,比如形如的类型参数被替换为Number,被替换为Object。


                (3)擦除方法定义中的类型参数

                    擦除方法定义中的类型参数原则和擦除类定义中的类型参数是一样的,这里仅以擦除方法定义中的有限制类型参数为例。

        

四、泛型在异常中的使用

          try-catch中catch块中如果有多个异常,必须由上到下满足继承关系。

        1、不能抛出也不能捕获泛型类的对象

        假设有如下自定义异常,注意,如下代码是无法通过编译的,这里只是假设成立来论证会产生的问题

public class MyException extends Exception {

}
try{

} catch(MyException e) {

} catch(MyException e) {

} 

        A、B可以是任意类型,满足try-catch要求即A是B的子类即可。由第三节泛型擦除可以得出,在编译后,try-cathc块会变成如下代码

try{

} catch(MyException e1) {

} catch(MyException e2) {

}  

         类型信息被擦除后,那么两个地方的catch都变为原始类型Object,这样便违反了try-cathc的基本规则。


        2、不能再catch子句中使用泛型变量

           假设我们可以在cathc中使用泛型变量,在泛型擦除后,T会被替换为Throwable,和第一点一样,catch中的继承关系被打破,因此必然不被允许。

        public  void myException(Class t) {
            try {

            }
            catch (T e) { // 编译错误

            }
            catch (IndexOutOfBounds e) {

            }
        }

欢迎分享,转载请注明来源:内存溢出

原文地址: https://outofmemory.cn/zaji/5694600.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-12-17
下一篇 2022-12-17

发表评论

登录后才能评论

评论列表(0条)