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变量使用时,不要使用通配符

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

  • 相关阅读:
    Codechef EDGEST 树套树 树状数组 线段树 LCA 卡常
    BZOJ4319 cerc2008 Suffix reconstruction 字符串 SA
    Codechef STMINCUT S-T Mincut (CodeChef May Challenge 2018) kruskal
    Codeforces 316G3 Good Substrings 字符串 SAM
    Codechef CHSIGN Change the Signs(May Challenge 2018) 动态规划
    BZOJ1396 识别子串 字符串 SAM 线段树
    CodeForces 516C Drazil and Park 线段树
    CodeForces 516B Drazil and Tiles 其他
    CodeForces 516A Drazil and Factorial 动态规划
    SPOJ LCS2
  • 原文地址:https://www.cnblogs.com/justforcon/p/6073825.html
Copyright © 2011-2022 走看看