欢迎访问悦橙教程(wld5.com),关注java教程。悦橙教程  java问答|  每日更新
页面导航 : > > 文章正文

JAVA 泛型,

来源: javaer 分享于  点击 37625 次 点评:216

JAVA 泛型,


1、泛型是什么

     泛型,即“参数化类型”。一提到参数,最熟悉的就是定义方法时有形参,然后调用此方法时传递实参。那么参数化类型怎么理解呢?顾名思义,就是将类型由原来的具体的类型参数化,类似于方法中的变量参数,此时类型也定义成参数形式(可以称之为类型形参),然后在使用/调用时传入具体的类型(类型实参)。

    上述描述摘自其他关于泛型文章中对泛型的阐释。阐释很到位,但是有点生涩。在实际用途中,泛型多用于List等容器类型,用于对容器的存储对象类型做约束。比如List<Integer> 列表中,只能存放Integer类型的对象,当然存在基础类型和引用类型装箱和拆箱的操作。

    需要注意的是,JAVA中的泛型是伪泛型,它只是在编译期起作用,在运行期泛型是会被擦除,所以无法在运行时得知其类型参数的类型。也就是说,java中的泛型,只是为了让编码更安全,将原来可能要在运行期暴露出来的问题,提前到编译期暴露出来而已。

2、泛型的通配符

     泛型的简单使用,这里不多做介绍。无非是可以用于接口,类,或者具体方法。

     这里想重点解释的是泛型的通配符。

     我们先来看如下这段代码

     

public class A {
    Number[] numberArray;
    Integer[] integerArray;

    void test() {
        numberArray = integerArray;
    }
}

  在编译期,这段代码并未报错。也就是说,java是允许把一个integer数组对象赋值给Number的数组对象的。这个很好理解,因为Integer是Number的子类,而java中的数组是c的。所以编辑期不存在任何问题。

public class A {
    Number[] numberArray;
    Integer[] integerArray = new Integer[5];

    void test() {
        numberArray = integerArray; //numberArray引用指向的是一个存放Integer的列表对象
        numberArray[0]= 1.2d;
    }
}

但是在实际中,谐变是存在安全隐患的,正如上述代码,一个存放Integer的列表对象,在经过谐变后,便可以骗过编译器,存放Double类型。

所以在后来的java版本中,也就是增加泛型的1.5版本,泛型摒弃了原来数组的设计,从原来数组的谐变,演变成泛型的不变。

public class A {
    List<Number> numberList;
    List<Integer> integerList;

    void test() {
        numberList = integerList; //报错
    }
}

上述这段代码直接在编辑期就报错。泛型无法再将存放子类型的容器,赋值给存放父类型的容器。

 

那么如果此时,我们想对numberList,和integerList做相同对处理。

public class A {
    List<Number> numberList;
    List<Integer> integerList;

    void test() {
        //此处我想调用test1方法
        this.test1(numberList); //不报错
        this.test1(integerList); //报错
    }

    void test1(List<Number> param) {

    }
}

上述代码integerList,则无法作为参数对象传进test1,那是不是意味着我们需要分别写两个方法呢?

答案肯定是否定的,这个时候泛型的通配符,就起作用了。

public class A {
    List<Number> numberList;
    List<Integer> integerList;

    void test() {
        //此处我想调用test1方法
        this.test1(numberList); //不报错
        this.test1(integerList); //不报错
        this.test2(numberList); //不报错
        this.test2(integerList); //不报错
    }

    void test1(List<? extends Number> param) {

    }

    void test2(List<? super Integer> param) {

    }
}

3、<? extends Number> 和 <? super Number> 区别

<? extends Number> 顾名思义,该泛型为Number的子类,即param可以是List<Integer> 也可以是List<Number>,所以在执行上述代码时, this.test1(numberList) 或者 this.test1(integerList)都不会报错。

public class A {
    List<Number> numberList;
    List<Integer> integerList;

    void test() {
     
        this.test1(numberList); //不报错
        this.test1(integerList); //不报错
    }

    void test1(List<? extends Number> param) {
        param.forEach(this::process);

        param.add(1);//报错
        param.add(1.2d);//报错
    }

    void process(Number number) {
        System.out.println(number);
    }
}

但是在往param容器中增加对象时,不管是Integer还是Double,都会报错,这又是什么原因?

首先此处param可能是List<Integer>也可能是List<Double>或者其他,所以在往param容器增加对象时,该对象必须同时满足Integer,Double还有其他Number的子类。显然这样的一个对象是不存在的,所以执行param.add(1)或者param(1.2d)都会报错。

 

<? super Integer> 则表示泛型是Integer及其父类,那么该param可能是List<Integer> 或者List<Number>,List<Object> 或者其他。

public class A {
    List<Number> numberList;
    List<Integer> integerList;

    void test() {
        //此处我想调用test1方法
        this.test1(numberList); //不报错
        this.test1(integerList); //不报错
    }

    void test1(List<? super Integer> param) {
        param.forEach(this::process);//报错

        param.add(1);//不报错
        param.add(new Object());//报错
    }

    void process(Number number) {
        System.out.println(number);
    }
}

所以我们在调用 this.test1(numberList) 或 this.test1(integerList)  都不会报错。但是我们在遍历param时,调用process就出错。process参数是Number类型的,但是param中,可能存的是object。那为什么往param中增加Integer类型的参数时不报错,增加Object类型的时候又报错,Object不应该也是Integer的父类吗?原因是Object 是Integer的父类没错,但是该param既要满足是Integer的类型,又要满足Object的类型,最终为了满足所有条件,它只能取最小集,也就是param中只能增加Integer类型的对象。

总结来说<? extends Number> 只适合做读取操作,不适合做写入操作。<? super Number> 只适合做写入操作,不适合做读取操作,同时写入的对象必须是Number的子类,而非像字面理解的是Number的父类。

相关文章

    暂无相关文章
相关栏目:

用户点评