zoukankan      html  css  js  c++  java
  • 类型通配符

    一、类型通配符

    当声明一个方法时,某个形参的类型是一个泛型类或泛型接口类型,但是在声明方法时,又不确定该泛型实际类型,可以考虑使用类型通配符。

    先来看下面一个案例

    import java.util.ArrayList;
    import java.util.List;
    
    /*
    泛型中通配符
    
    常用的 T,E,K,V,?
    
    ?无界通配符,表示不确定的 java 类型,代表任意类型,有位置有求
      需要传入具体类型的位置,不能单独使用
      声明,泛型类,泛型接口,泛型方法时不能单独使用
    
    T (type) 表示具体的一个java类型
    K V (key value) 分别代表java键值中的Key Value
    E (element) 代表Element
    
    上界通配符 < ? extends E>  传入的类型,可以是当前类型以及当前类型的孩子
    下界通配符 < ? super E>    传入的类型,可以使当前类型,也可以是当前类型的父类
    
    ?和 T 的区别
    * */
    public class Demo1 {
        public static void main(String[] args) {
            List<String> list1 = new ArrayList<>();
            list1.add("项羽");
            list1.add("刘邦");
            list1.add("张饵");
            test1(list1);
    
        }
    
        public static void test1(List c){
            for (int i = 0; i <c.size() ; i++) {
                System.out.println(c.get(i));
            }
        }
    }

    上面的方法执行是没有问题的,但是此处使用 List 接口时没有传入实际类型参数,这将引起泛型警告。如何消除这个警告呢?

      方式一:

    public class Demo1 {
        public static void main(String[] args) {
            List<Object> list1 = new ArrayList<>();
            list1.add("项羽");
            list1.add("刘邦");
            list1.add("张饵");
            test1(list1);
    
        }
    
        public static void test1(List<Object> c){
            for (int i = 0; i <c.size() ; i++) {
                System.out.println(c.get(i));
            }
        }
    }

    这样的形参太局限了,只能传入 List<Object> 类型的实参。

      方式二:声明一个泛型方法

    public class Demo1 {
        public static void main(String[] args) {
            List<String> list1 = new ArrayList<>();
            list1.add("项羽");
            list1.add("刘邦");
            list1.add("张饵");
            test1(list1);
    
        }
    
        public static<T> void test1(List<T> c){
            for (int i = 0; i <c.size() ; i++) {
                System.out.println(c.get(i));
            }
        }
    }

    该方法需要声明泛型形参T。

      方式三:使用类型通配符

    public class Demo1 {
        public static void main(String[] args) {
            List<String> list1 = new ArrayList<>();
            list1.add("项羽");
            list1.add("刘邦");
            list1.add("张饵");
            test1(list1);
    
        }
    
        public static void test1(List<?> c){
            for (int i = 0; i <c.size() ; i++) {
                System.out.println(c.get(i));
            }
        }
    }

     那么方式二的泛型方法 test1() 和方式三的 test1() 使用类型通配符有什么区别?

       方式三方法带通配符的List仅表示它可以接受指定了任意泛型实参的List,并不能把元素加入其中,例如如下代码将会引起编译错误:

    
    
    public class Demo1 {
    public static void main(String[] args) {
    List<String> list1 = new ArrayList<>();
    list1.add("项羽");
    list1.add("刘邦");
    list1.add("张饵");
    test1(list1,"陈余");

    }

    public static void test1(List<?> c,String string){
    c.add(string);
    }
    }
     

    因为我们不知道上面程序中c集合里元素的类型,所以不能向其中添加对象,除了null对象,因为它是所有引用数据类型的实例。

        方式二方法带泛型的List,表示该集合的元素类型是T,因此允许T系列的对象加入其中,例如如下代码是可行的:

    public class Demo1 {
        public static void main(String[] args) {
            List<String> list1 = new ArrayList<>();
            list1.add("项羽");
            list1.add("刘邦");
            list1.add("张饵");
            test1(list1,"陈余");
    
        }
    
        public static<T> void test1(List<T> c, T string){
            c.add(string);
            for (int i = 0; i <c.size() ; i++) {
                System.out.println(c.get(i));
            }
        }
    }

    即如果不涉及添加元素到带泛型的集合中,那么两种方式都可以,如果涉及到添加元素到带泛型的集合中,使用类型通配符<?>的不支持

    二、设定类型通配符的上限

      当直接使用 List<?> 这种形式时,即表明这个 List 集合接收泛型实参指定为任意类型的 List。但有时候不想这样,只希望接收某些类型的 List。

      Demo:一个图形的抽象父类 Graphic,两个子类 Circle和Rectangle,想定义一个方法,可以打印不同图形的面积。

    import java.util.ArrayList;
    import java.util.List;
    
    public class Demo2 {
        public static void main(String[] args) {
            Graphic g1 = new Graphic("yuan",2,1);
            Graphic g2 = new Graphic("fang",3,2);
            List<Graphic> list1 = new ArrayList<>();
            list1.add(g1);
            list1.add(g2);
            printArea(list1);
    
        }
    
        public static void printArea(List<Graphic> graphics){
            for(Graphic g:graphics){
                g.getArea();
            }
        }
    }
    
    
    class Graphic{
        String name;
        int lengths;
        int wides;
    
        public Graphic() {
        }
    
        public Graphic(String name, int lengths, int wides) {
            this.name = name;
            this.lengths = lengths;
            this.wides = wides;
        }
    
        public void getArea(){
            if (name.equals("fang")){
                int area=lengths*wides;
                System.out.println("父类面积是:"+area);
            }else if (name.equals("yuan")){
                Double area=Double.valueOf(lengths)*3.14;
                System.out.println("父类面积是:"+area);
            }
        }
    }

    但是,List<Graphic> 的形参只能接收 List<Graphic>的实参,如果想要接收 List<Circle>,List<Graphic>的集合,可以使用List<?>

    package day19;
    
    import java.util.ArrayList;
    import java.util.List;
    
    public class Demo2 {
        public static void main(String[] args) {
            Graphic g1 = new Graphic("yuan",2,1);
            Graphic g2 = new Graphic("fang",3,2);
            Circle c1 = new Circle("yuan",3.0);
            List<Graphic> list1 = new ArrayList<>();
            list1.add(g1);
            list1.add(g2);
            list1.add(c1);
            printArea(list1);
    
        }
    
        public static void printArea(List<?> graphics){
            for(Object obj:graphics){
                Graphic g=(Graphic)obj;
                g.getArea();
            }
        }
    }
    
    
    class Graphic{
        String name;
        int lengths;
        int wides;
    
        public Graphic() {
        }
    
        public Graphic(String name, int lengths, int wides) {
            this.name = name;
            this.lengths = lengths;
            this.wides = wides;
        }
    
        public void getArea(){
            if (name.equals("fang")){
                int area=lengths*wides;
                System.out.println("父类面积是:"+area);
            }else if (name.equals("yuan")){
                Double area=Double.valueOf(lengths)*3.14;
                System.out.println("父类面积是:"+area);
            }
        }
    }
    
    class Circle extends Graphic{
        String name;
        Double lengths;
    
        public Circle() {
        }
    
        public Circle(String name, Double lengths) {
            this.name = name;
            this.lengths = lengths;
        }
    
        @Override
        public void getArea() {
            Double area = lengths*3.14;
            System.out.println(area);
        }
    
    }

    但是这样有两个问题,一个是 List<?> 可以接收任意类型,不仅仅图形,第二个是需要强制类型转换。

      为了解决这个问题,Java 允许设定通配符的上限:

    <? extends Type>,这个通配符表示它必须是Type本身,或是Type的子类。

     如:

    import java.util.ArrayList;
    import java.util.List;
    
    public class Demo2 {
        public static void main(String[] args) {
            Graphic g1 = new Graphic("yuan",2,1);
            Graphic g2 = new Graphic("fang",3,2);
            Circle c1 = new Circle("yuan1",3.0);
            List<Graphic> list1 = new ArrayList<>();
            list1.add(g1);
            list1.add(g2);
            list1.add(c1);
            printArea(list1);
    
        }
    
        public static void printArea(List<? extends Graphic> graphics){
            for(Graphic g:graphics){
                g.getArea();
            }
        }
    }
    
    
    class Graphic{
        String name;
        int lengths;
        int wides;
    
        public Graphic() {
        }
    
        public Graphic(String name, int lengths, int wides) {
            this.name = name;
            this.lengths = lengths;
            this.wides = wides;
        }
    
        public void getArea(){
            if (name.equals("fang")){
                int area=lengths*wides;
                System.out.println("父类面积是:"+area);
            }else if (name.equals("yuan")){
                Double area=Double.valueOf(lengths)*3.14;
                System.out.println("父类面积是:"+area);
            }
        }
    }
    
    class Circle extends Graphic{
        String name;
        Double lengths;
    
        public Circle() {
        }
    
        public Circle(String name, Double lengths) {
            this.name = name;
            this.lengths = lengths;
        }
    
        @Override
        public void getArea() {
            Double area = lengths*3.14;
            System.out.println(area);
        }
    
    }

    与前面的完全相同,因为不知道这个受限制的通配符的具体类型,所以不能把 Graphic 对象或其子类对象加入到这个泛型集合中。

        public static void printArea(List<? extends Graphic> graphics){
            for(Graphic g:graphics){
                graphics.add(new Circle("yuan1",3.0));//编译错误,因为不知道?的具体类型
                g.getArea();
            }
        }
    }

    如果需要将 Graphic 对象或其子类对象加入这个泛型集合,那么就只能用泛型方法。

    三、设定通配符的下限

      假设自己实现一个工具方法:实现将 src 集合里元素复制到 dest 集合中的功能,因为 dest 集合需要接受 src 的所有元素,所以 dest 集合元素的类型应该是 src 集合元素的父类。为了表示两个参数之间的类型依赖,考虑同时使用通配符、泛型形参类实现该方法。

      Demo:

    package day19;
    
    import java.util.ArrayList;
    import java.util.Collection;
    
    public class Demo3 {
    
        public static void main(String[] args) {
            ArrayList<String> src = new ArrayList<>();
            src.add("贾宝玉");
            src.add("秦钟");
            src.add("薛宝钗");
    
            ArrayList<Object> dest = new ArrayList<>();
            Object last=copy(dest,src);
            System.out.println(last);
        }
    
        public static <T> T copy(Collection<T> dest,Collection<? extends T> src){
            T last=null;
            for (T t:src){
                dest.add(t);
                last=t;
            }
            return last;
        }
    }

    表面上看这个实现没有问题,实际上有一个问题,当遍历src元素的类型是不确定(但可以肯定是T的子类),程序只能用T来笼统的表示,所以返回值类型是T

    发现返回的T是Object,也就是说,程序在复制集合元素的过程中,丢失了src集合元素的类型String。

    对于上面的copy方法,可以这样理解两个集合参数之间的依赖关系:不管src集合元素的类型是什么,只要dest集合元素类型与src的元素类相同或是它的父类即可。

    为了表示这种约束关系,Java允许设定通配符的下限:

    <? super Type>,这个通配符表示它必须是Type本身或是Type的父类。

    代码

    import java.util.ArrayList;
    import java.util.Collection;
    
    public class Demo3 {
    
        public static void main(String[] args) {
            ArrayList<String> src = new ArrayList<>();
            src.add("贾宝玉");
            src.add("秦钟");
            src.add("薛宝钗");
    
            ArrayList<Object> dest = new ArrayList<>();
            String last=copy(dest,src);
            System.out.println(last);
        }
    
        public static <T> T copy(Collection<? super T> dest,Collection<T> src){
            T last=null;
            for (T t:src){
                dest.add(t);
                last=t;
            }
            return last;
        }
    }

    注意:只有类型通配符才可以设定下限,泛型形参是不能设定下限的

    四、泛型方法与方法重载

    因为泛型既允许设定通配符的上限,也允许设定通配符的下限,如果在一个类里包含这样两个方法定义,会报错:

    public static <T> T copy(Collection<? super T> dest,Collection<T> src){
            //....省略代码
        }
    
        public static <T> T copy(Collection<T> dest,Collection<? extends T> src){
            //....省略代码
        }
  • 相关阅读:
    Linux主要shell命令详解(下)
    mget命令, ftp命令详解
    VI 基本可视模式
    vim使用技巧
    cd及目录快速切换
    du命令解决linux磁盘空间满的问题(很不错的哦)
    Mysql删除数据后磁盘空间未释放的解决办法【转】
    MYSQL-innodb性能优化几个点
    Apache服务器出现Forbidden 403错误提示的解决方法总结
    MySQL 分区表原理及数据备份转移实战
  • 原文地址:https://www.cnblogs.com/hbxZJ/p/15791991.html
Copyright © 2011-2022 走看看