Java泛型-2(通配符)

泛型学习目录:

Java泛型-1(泛型的定义)
Java泛型-2(通配符)
Java泛型-3(实践篇-protostuff序列化与反序列化)
Java泛型-4(类型擦除后如何获取泛型参数)

1.1 泛型的通配符

在泛型代码中,称为通配符的问号(?)表示未知类型。
通配符可用于各种情况:作为参数,字段或局部变量的类型,有时候作为返回类型。

类型 说明 用途
上限有界通配符,泛型参数一般是A类型或者A子类型 一般用于输入参数
无界限通配符
下限有界通配符,泛型类型一般是A类型或者A的父类型 一般用于输出参数

1.2 上限有界通配符(Upper Bounded)

You can use an upper bounded wildcard to relax the restrictions on a variable.

你可以使用上限通配符来放宽对变量的限制。

举一个例子:
例如:假如要编写适用于ListListList的方法,你会怎么做?

  • 你可能回想到,参数类型边界,即我们泛型上篇讲到的extends关键字。指定参数边界。
  • 所以可以使用通配符(?),后面跟着extends关键字,后面跟着上限

(小胖友情提醒:此时extends关键字代表的就是extends或者implements)。

答案:

List

1.3 无界通配符

The unbounded wildcard type is specified using the wildcard character (?), for example, List. This is called a list of unknown type.

使用通配符(?)指定无界限通配符类型,例如List。这被称为未知类型的列表。

无界匹配符的适用范围:

  • 无论该对象的泛型是何种类型,均允许传入,例如:允许ListList传入。

  • 代码使用泛型类方法不依赖于类型参数(T)时。例如,Class经常被使用因为Class里面的大部分方法都不依赖与T

还是举个例子说明吧:

   public static void printList(List list) {
        for (Object elem : list)
            System.out.println(elem + " ");
        System.out.println();
    }
 
 

请问是否能打印ListList等类型?

重要的事情说三遍:

  1. List不是List的子类型;
  2. List不是List的子类型;
  3. List不是List的子类型;

    对于具体的类型AListList的子类型。此方法中,可以输入ListList

    public static void printList(List  list){
        for(Object elem:list)
            System.out.print(elem +“”);
        的System.out.println();
    }
    

    无界通配符的注意事项:插入除null以外的数据,出现编译错误。

    Class类型的特点

    ListList是不一样的,可以将Object或者Object的任何子类插入到List。但是只能在List中插入null

    1.4 下限有界通配符(Lower Bounded)

    a lower bounded wildcard restricts the unknown type to be a specific type or a super type of that type.

    下限有界通配符将未知类型(?)限制为该类型的特定类型或者超类类型。

    使用通配符(?)表示下限有界通配符,后跟super关键字,后跟下限

    注意:可以指定通配符的上限,也可以指定下限,但不能同时指定两者。

    假设要编写方法,他的参数是Integer类型,但是为了最大限度地提高灵活性,可以在方法里面输入ListListList任何保存Integer值的方法。

    可以使用List类型,因为List值匹配Integer类型的List,但是List匹配Integer类型以及超类类型。

    1.5 通配符和子类型

    我们在上一节知道,尽管IntegerNumber的子类,但List不是List的子类型,实际上,两个类型不相关,ListList的共同父类是List

    公共父类是List

    为了在这些类之间创建关系以便代码可以通过List的元素访问List的方法,可以使用上限有界通配符

    List intList=new ArrayList<>();
    List numList=intList; 
    

    因为IntegerNumber的子类型,而numListNumber对象的列表。所以intList(Integer对象列表)numList之间存在关系。

    通配符之间的继承关系

    1.6 泛型的多重边界

    在泛型中,可以使用&符号来设定多重边界,指定泛型类型t必须是A和B的共有子类型,而此时t就具有所有限定的方法和属性。可以更加方便的进行判断。

    比如,社会中,每个人都有多重身份,比如是员工,是乘客,是儿子,是父亲。若是在程序中对一类人进行管理:在公交车费优惠系统中,对部分人员(如工资低于2500元的上班族并且是站立的乘客)车费打八折。

    public class Me implements Staff, Passenger {
        @Override
        public int getSalary() {
            return 2000;
        }
    
        @Override
        public boolean isStanding() {
            return true;
        }
    
        //泛型的多重限定
        public static  void discount(T t) {
            if (t.getSalary() < 2500 && t.isStanding()) {
                System.out.println("恭喜你,车票打8折");
            }
        }
    
        //工资低于2500元的员工并且站立的乘客车票打8折
        public static void main(String[] args) {
    
            discount(new Me());
    
        }
    }
    
    interface Staff {
        //工资
        int getSalary();
    }
    
    //乘客
    interface Passenger {
        boolean isStanding();
    }
    

    注意:使用&符号设定多重边界,指定泛型类型T必须是Staff和Passenger的共有类型。

    泛型通配符和泛型的多重边界的区别

    为什么要说明多重边界?是因为编辑者太少使用它。使用通配符虽然可以缩小泛型边界,但是可能还需要进行泛型类型判断。故可以使用泛型的多重限定来优雅的解决泛型问题。

    1.7 通配符使用指南(重点)

    我们在学习了泛型编程之后,是不是不确定什么时候使用上限有界通配符什么时候使用下限有界通配符

    参考代码:

    //带有两个参数的复制方法
    void copy(src,dest){
    }
    

    1. 一个In变量:
    "in"变量向代码中提供数据,正如上面代码中:src参数提供要复制的数据,因此它是in参数。

    2. 一个out变量
    "out"输出变量以供其他地方使用,正如示例中的desc

    3. in&out变量
    当然,一些变量即用于"进入"又用于"输出"。


    在决定是否使用通配符以及适合使用哪种类型的通配符时,可以使用inout原则。下面是通配符指南:

    • in变量使用extends关键字定义带有上界有限通配符。
    • out变量使用super关键字定义带有下界有限通配符。
    • in变量是Object类型时,使用无界通配符。
    • in&out变量时,不使用通配符。

    这些指南不适用于方法的返回类型应该避免使用通配符作为返回类型,因为他会强制程序员使用代码来处理通配符。

    注意的是:
    一个list被定义为List可以被认为是只读的,但并不是一个严格的结论。
    假设有两个类:

    public class NaturalNumber {
        private int i;
        public NaturalNumber(int i) {
            this.i = i;
        }
    }
    
    class EvenNumber extends NaturalNumber {
        public EvenNumber(int i) {
            super(i);
        }
    }
    
    
    测试类编译错误

    我们可以看到List在严格意义上不是只读的,因为可能无法存储或改变列表中现有的元素。

    1.8 通配符的顺序

    建议采用的顺序是List、List、List

    List、List、List这三者都可以容纳所有的对象,但使用的顺序应该是首选List,次之List,最后选择List

    1. List是确定的某一个类型

    List表示的是List集合中的元素都为T类型,具体类型在运行期决定;List表示的是任何类型;List则表示List集合中的所有元素为Object类型,因为Object是所有类型的父类,所以Object也可以容纳所有的类类型。从这一字面意义上分析,List更符合习惯:编译者知道它是某一个类型,只是在运行期才确定。

    2. List可以进行读写操作

    • List可以进行诸如add、remove等操作,因为他的类型是固定的T类型,在编译期不需要任何的转型操作。

    • List是只读操作的,不能进行增加、修改操作。因为编译器不知道List容纳的是什么类型的元素,也就无法校验类型是否安全。而且List读取出来的元素都是Object类型,需要主动转型,所以常用于泛型方法的返回值。但是List虽然无法增加,修改元素,但是可以删除元素,如执行remove、clear等方法。

    • List也可以进行读写操作,但是它执行写入操作时需要向上转型,在读取操作后需要向下转型,而此时已经失去了泛型存在的意义了。

      你可能感兴趣的:(Java泛型-2(通配符))