Java 的泛型实例在声明时有点傻,比如像这样的语句 List<String> list = new ArrayList<String>(); 前明的 List<String> 已经提供了足够的信息让编译器知道 ArrayList 的参数类型,当然这是个简单的例子,如果复杂些,来点嵌套的话:
Map<String, List<Map<String, String>>> myMap = new HashMap<String, List<Map<String,String>>>();
那就够呛的,所以 Java 的这一语法要求也让 Scala 这样的语言所诟病,当然人家 Scala 是个趋近于动态性的语言,它认为上面的语句应该这么写:
val x = new HashMap[String, List[String, List[Map[String, String]]]() 或者是
val x: Map[String, List[String, List[Map[String, String]]] = new HashMap()
总之,只要一端的表态就行,其他事情交给编译器来推断。现在 JDK 也反省到了这一点,因为引入一个叫做菱形操作符(Diamond operator) 的东西,即两个尖括号 <>。让使得你像 Scala 一样只要在左边声明参数类型就行啦。这个操作符不免让我想起了在 perl 里有个 <=> 这样的操作符,好像叫做飞机,不是斗地主的飞机,它相当于 Java 里的 compareTo() 的功能。
于是泛型实例的声明就简单化成了 Map<String, List<Map<String, String>>> myMap = new HashMap<>(); 继续往下看:
把问题拉近一点,回归得更简单一些吧,比如对于 JDK5/6 中的泛型要这么写:
1 |
Map<String, List<String>> myMap = new HashMap<String, List<String>>(); |
那转换成 <> 菱形操作符石化后的样子就是:
1 |
Map<String, List<String>> myMap = new HashMap<>(); |
说到底,也就是把右边 new 部分的类型掏空,只留下最外层的 <> 罢了,完全让编译器根据前半部分去进行类型的推断。进一步,要是把后边的 <> 也去掉会是怎么回事呢?
1 |
Map<String, List<String>> myMap = new HashMap(); // unchecked conversion warning |
对了,会提示类型转换的警告,这在我们从 JDK4 过渡到 JDK5 时常见的警告了,也是在原来使用 Hibernate 时不得不黯然接受的提示。
Java(可以说是 Oracle) 的文档说: Java SE7 在创建泛型实例时只支持有限的类型推断,举的例子是下面的代码无法通过编译:
1 2 3 4 5 6 7 |
List<String> list = new ArrayList<>(); list.add("A"); // 这是合法的 // The following statement should fail since addAll expects // Collection<? extends String> list.addAll(new ArrayList<>()); // 这里就编译不通过了 |
我觉得官方在这里反而把问题弄复杂了,好懂的东西变得更糊涂,要单例看 new ArrayList<>() 这创建语句是无法进行类型推断的,因为最后一行的 new ArrayList<>() 与第一行的 List<String> list 声明早就挂不上关系了。肯定是要写成下面的代码才成:
1 2 3 4 |
List<String> newList = new ArrayList<>(); list.addAll(newList); //要么在 new 的时候明确类型,像在 JDK5/6 中一样 list.addAll(new ArrayList<String>()); |
或者是:
1 2 |
List<? extends String> list2 = new ArrayList<>(); list.addAll(list2); |
【 类型推断】 与 【泛型、非泛型类的/ 泛型构造器】,这句连在一点要知道说的是谁与谁,前面有意断一下。看下面的泛型类:
1 2 3 4 5 |
class MyClass<X> { <T> MyClass(T t) { // ... } } |
在 JDK7 之前可以这么使用:
1 |
new MyClass<Integer>("") |
编译器聪明的知道首先把 Integer 类型作为类的形参,进而 new 的时候直接把构造函数的形参断定为字符串类型。
到了JDK 7 了呢,可以像下面那样写:
1 |
MyClass<Integer> myObject = new MyClass<>(""); |
也就是右边部分的 <> 占位符中能够根据前面声明的对应位置进行类型的推断,相当于可以把前部分 Integer 类型填入到菱形操作符中。而构造函数的 T 参数类型完全是靠传入的参数推断出来的。
把构造函数部分的完整调用过程写出来就是:
1 |
MyClass<Integer> myObject = new <String> MyClass<>(""); //Unmi: 我怎么就编译不过呢,写错了! |
本文链接 https://yanbin.blog/jdk-7-enhance-type-inference/, 来自 隔叶黄莺 Yanbin Blog
[版权声明] 本文采用 署名-非商业性使用-相同方式共享 4.0 国际 (CC BY-NC-SA 4.0) 进行许可。