zoukankan      html  css  js  c++  java
  • java基础-泛型2

    浏览以下内容前,请点击并阅读 声明

    6 类型推测

      java编译器能够检查所有的方法调用和对应的声明来决定类型的实参,即类型推测,类型的推测算法推测满足所有参数的最具体类型,如下例所示:

    //泛型方法的声明
    static <T> T pick(T a1, T a2) { return a2; }
    //调用该方法,根据赋值对象的类型,推测泛型方法的类型参数为Serializable
    //String和ArrayList<T>都实现接口Serializable,后者是最具体的类型
    Serializable s = pick("d", new ArrayList<String>());

    6.1 泛型方法的类型推测

      类型的推测可以使泛型方法的使用语法和普通的方法一样,不必指定尖括号内的类型,如上述例子。

    6.2 泛型类的类型推测

      对于泛型类的使用,java编译器也可以进行类型的推测,因此调用泛型类时,可以不用指定尖括号内的类型参数,不过尖括号不可省略,之前的总结已经提到,空的尖括号又叫钻石(中文怪怪的),如下例所示:

    //以下用法没有指定类型参数,尖括号为空
    Map<String, List<String>> myMap = new HashMap<>();
    //注意,空的简括号不能省略,如下代码编译器会发出警告
    Map<String, List<String>> myMap = new HashMap();

       上述代码中的第二个赋值语句中new HashMap() 实际是用的原始类型。

    6.3 非泛型类的泛型构造器的类型参数推测

      无论是泛型还是非泛型的类都可以使用泛型的构造器,如方法一样。

    //类定义
    class MyClass<X> {
      <T> MyClass(T t) {
        // ...
      }
    }
    //以下是实例化以上类的表达式
    new MyClass<Integer>("")

      以上代码中的实例化表达式虽然没有指定构造器的类型参数,但是可以根据传入的参数推测其类型参数为String。

      java7以前的版本能够推测出构造其的参数类型,而java7以后,使用钻石的语法也推测泛型类的参数类型。

      需要注意的是,类型参数的推测算法只会使用传入的参数,目的类型或者和明显的返回类型来推测类型。

    6.4 目的类型

      java编译器充分利用了目的类型来推测泛型方法或者类的类型参数,如下例:

    //Collections中的一个方法的声明如下
    static <T> List<T> emptyList();
    //现在调用该方法
    List<String> listOne = Collections.emptyList();

      以上中的第二个语句中,listOne变量类型为List<string>,就是目的类型,所以需要方法emptyList的返回类型也必须是List<Stirng>,这样可以推测泛型方法声明中的T为String,java7和8都可以实现这样的推测,当然你可以在调用泛型方法时指明方括号中的类型参数。

      值得注意的是java7中方法的参数还不属于目的类型,而java8则把方法参数加入目的类型,如下例所示:

    //如下方法接受的参数为List<String> 
    void processStringList(List<String> stringList) {
        // process stringList
    }
    //Collections中的emptyList方法的签名如下
    static <T> List<T> emptyList();
    //java7中,下列调用语句的编译会报错,而java8则不存在这样的问题
    processStringList(Collections.emptyList());

     7 通配符

      在泛型的代码中问号(?)代表通配符,代表未知的类型,通配符可以用在许多场合,可用作参数,字段,返回值的类型,但是通配符不能用作方法调用,泛型实例的创建和父类型的实参。

    7.1 上限通配符

      利用上限通配符可以放松对变量的限制。

      上限通配符的声明方法如下例所示:

    public static void process(List<? extends Foo> list) { /* ... */ }

      上述声明的方法,的泛型参数使用了上限通配符,通配符"?"加extends关键词后跟其上限,此处的extends类似于通常意义上的extends和implements,意思是该方法是针对于Number类型的子类型,包括Integer,Float等的列表。

      通配符<? extends Foo>匹配所有的Foo的子类型和Foo类型自身。

    7.2 无限制通配符

      无限制通配符就是简单的"?",如List<?>就代表未知类型的列表,以下两种情况适合使用无限制通配符:

    • 声明一个要用到继承的Object类中的方法时
    • 当代码中需要用到不依赖于类型参数的泛型类的方法时, 如List.size或者List.clear,Class<?> 经常被用到,因为Class<T>中的许多方法是不依赖于类型参数T的。

    以下例子很好的说明使用Object类中的方法时使用无限制通配符的好处:

    //普通的方法声明
    public static void printList(List<Object> list) {
        for (Object elem : list)
            System.out.println(elem + " ");
        System.out.println();
    }
    //使用通配符的泛型作为方法参数,该方法的参数能够传入任何类型的列表(List)
    public static void printList(List<?> list) {
        for (Object elem: list)
            System.out.print(elem + " ");
        System.out.println();
    }

       注意:既然定义了列表List<?>的类型的广泛性,就要承担广泛性的造成的后果,在方法声明中,只能对List<?>类型的变量插入null,因为你无法预知传入方法的类型变量,而List<Object>作为参数则可以插入任何类型的对象。

    7.3 下限通配符

       与上限通配符类似,下限通配符指定了类型参数的下限,未知的类型必须是指定类型的父类型,下限通配符的写法:<? super A>,此处关键词为super

      注意:不能同时指定上限和下限。

    7.4 通配符和子类型

      之前提到过,泛型之间的关系不仅仅是由他们的类型实参决定的,如不能说List<Number>就是List<Integer>的父类,不过使用通配符可以构成如下关系:

      箭头表示“是其子类型”的关系,如List<Integer>是List<? extends Integer>的子类型,可以这样理解:List<Integer>是一种List<? extends Integer>。

    7.5 通配符的捕获与辅助方法

       有时候编译器会推测通配符的类型,如果一个字段的类型被定义为List<?>,当运算一个表达式的时候,编译器会从代码中推测该字段为一个特定的类型,这就叫通配符的捕获。

    import java.util.List;
    public class WildcardError {
        void foo(List<?> i) {
            i.set(0, i.get(0));
        }
    }

      上述代码会编译出错,foo方法调用List.set(int,E),编译器首先将set方法内作为参数的i视为Object类型,无法判断将要插入的对象类型是否和目标列表类型是否一致,所以编译不能通过。

      此时可以加入一个辅助方法,使其能能够顺利通过编译:

    public class WildcardFixed {
    
        void foo(List<?> i) {
            fooHelper(i);
        }
        // 创建辅助方法,调用该方法可以通过类型推测来实现通配符的捕获
        private <T> void fooHelper(List<T> l) {
            l.set(0, l.get(0));
        }
    }

      再来看一下一个例子:

    import java.util.List;
    
    public class WildcardErrorBad {
    
        void swapFirst(List<? extends Number> l1, List<? extends Number> l2) {
          Number temp = l1.get(0);
          l1.set(0, l2.get(0)); 
          l2.set(0, temp);       
        }
    }

      上述代码中的方法功能是将两个列表的首个元素交换,然而无法判断两个传入的实参的类型参数是否兼容,所以,无法编译通过,此处代码本质上就是错误的,没有相应的辅助方法。

     7.6 通配符使用原则

      泛型的使用有一点让人疑惑的就是不知道什么时候该用上限通配符,什么时候使用下限通配符,一下是几点原则:

      为了说明问题,先列出两种变量1)In变量:作为代码中的数据来源,比如复制的方法copy(src,dest)中的src参数就是in变量,;2)out变量,在代码中用来存储数据作为他用,如copy(src,dest)中的dest参数就是out变量。变量列出之后,说原则:

    • in变量使用上限通配符,使用extends关键词
    • out变量使用下限通配符,使用super关键词
    • 当需要使用的in变量可以通过Object类中的方法访问时,使用无限制通配符
    • 当代码中既需要访问的变量既要当做in变量使用,又要当做out变量使用时,不要使用通配符

      上述原则不试用与方法的返回类型,不建议在返回类型中使用通配符,否则将必须处理通配符的问题。

  • 相关阅读:
    遇到的面试题目
    获取本机IP_考虑多网卡的情况
    C#发送电子邮件
    C#获取局域网中的所有正在使用的IP地址
    C#获取本机IP且过滤非真实网卡(如虚拟机网卡)
    C#获取本机的MAC地址
    C#获取本机磁盘信息
    C#获得系统打开的端口和状态
    C#通过编程方式实现Ping
    千万不要使用xfce和KDE版Manjaro Linux--之荒谬言论
  • 原文地址:https://www.cnblogs.com/justforcon/p/6073825.html
Copyright © 2011-2022 走看看