我目前正在阅读Horstmann的“不耐烦的核心Java”(我推荐它,喜欢简洁的风格),我很难理解与集合API相关的练习之一。练习如下:
我鼓励您使用接口而不是具体的数据结构,例如,a
Map<String, Set<Integer>>
代替Map<String, Set<Integer>> toc = new HashMap<>(); toc.put("element1", IntStream.of(1, 2, 3).boxed().collect(Collectors.toSet())); toc.put("element2", IntStream.of (3, 4, 7).boxed().collect(Collectors.toSet())); toc.forEach( (k, v) -> { System.out.print(k + " "); v.forEach(val -> System.out.print(val + " ")); System.out.println(); } ); }
。不幸的是,这个建议只是到目前为止。为什么不能 您使用CONTAINS
来表示目录? (提示:你会如何初始化它?)你可以用什么类型?
以下代码编译并正常工作,即使接口用于变量声明。我错过了什么?
SCAN
答案 0 :(得分:1)
像Map
这样的接口是继承它的所有接口的超类型以及实现它的所有类。因此,TreeMap
继承自Map
,因为您始终可以为变量分配任何属于子类型的引用,因此为TreeMap
分配Map
引用是完全可以接受的。变量。这称为扩展参考转换
https://docs.oracle.com/javase/specs/jls/se8/html/jls-5.html#jls-5.1.5
“扩展引用转换在运行时从不需要特殊操作,因此决不会在运行时抛出异常。它们只是将引用视为具有某种其他类型,在编译时可以证明是正确的。”
所以,是的,您当然可以使用Map<String, Set<Integer>>
来表示域模型中的某些内容,但是您无法直接实例化接口;您必须实例化实现它的具体类型(类)。这正是您声明时所做的
Map<String, Set<Integer>> toc = new HashMap<>();
作为这个原则的延伸,你可以轻松写出来
AbstractMap<String, Set<Integer>> toc = new HashMap<>();
因为AbstractMap
也是HashMap
的超类型。
通常,您希望为变量声明最宽的类型,该变量可以包含在逻辑中有效的最大可能的子类型引用集。如果你需要一个有序的地图,那么'地图'太宽了;它不会强制排序。您必须将变量声明为TreeMap
,或更好,SortedMap
。
通常界面是最广泛的适用类型,但如果不是,则必须考虑它。
编辑:根据评论提到SortedMap
。
答案 1 :(得分:0)
我与本书的作者有联系,他同意这个问题不清楚,并且引导读者使用通配符类型。有问题的练习改为:
假设您有一个类型为
Map<String, Set<Integer>>
的方法参数,并且有人使用HashMap<String, HashSet<Integer>>
调用您的方法。怎么了?您可以使用哪种参数类型?
答案是在这种情况下应该使用通配符类型:Map<String, ? extends Set<Integer>>
。