java - 有没有更好的方法在 Java 中使用通配符?
问题描述
考虑以下 java 构造函数头...
public FirstPair(Map<Enum, Set<Enum>> f, List<Set<Enum>> fr)
现在,我想通过为泛型添加一些空间来使这个“未来安全”。例如,如果我想传入 a Map<Enum, HashSet<Enum>>
for f 怎么办?
...所以,我将构造函数更改为...
public FirstPair(Map<? extends Enum, ? extends Set<? extends Enum>> f,
List<? extends Set<? extends Enum>> fr)
这看起来正确吗?是否有更容易解决不同输入类型的问题?或者这是最好的方法?
**** 更多细节 ****
考虑以下函数。
static void foo(List<Set<Integer>> listOfSetsOfInts)
以下代码将导致错误...
List<HashSet<Integer>> diffSetType = new ArrayList<HashSet<Integer>>();
foo(diffSetType);
原因是 HashSets 列表不符合 Sets 列表。我想通过更改函数头来处理这个错误,而不是把负担留给函数调用者......
我能想到解决错误的唯一方法是将函数头更改为此...
static void foo(List<? extends Set<Integer>> listOfSetsOfInts)
// of even more secure...
static void foo(List<? extends Set<? extends Integer>> listOfSetsOfInts)
我的问题是我在这里使用 java 通配符是否正确?我是否过度使用它们?我缺少一个明确的解决方法吗?
解决方案
问题是构造函数的使用,它(自然)具有非常特定的类型,然后与您尝试存储它的变量的类型不兼容。为了让这个解释更深入地挖掘 Java 类型通配符的乐趣和兴奋,我做了一个类型参数而不是你的Enum
. (所有这些都是使用 Java 的var
,以保持代码更简洁。如果您使用的是 Java11,则此代码将编译得很好,除了我在下面讨论的类型错误和其他警告。此外,您的原始代码存在问题Liskov 替换原则。我将暂时搁置它并在下面修复它(4)。
public interface Foo {
class A { }
class B extends A {}
class C extends B {}
class FooMap<T> {
Map<? extends T, ? extends Set<? extends T>> f; // (1)
List<? extends Set<? extends T>> fr;
public FooMap(
Map<? extends T, ? extends Set<? extends T>> f,
List<? extends Set<? extends T>> fr) {
this.f = f;
this.fr = fr;
}
static void exercise1() {
var hm = new HashMap<B, HashSet<B>>();
var list = new ArrayList<HashSet<B>>(); // (2)
var fm = new FooMap<A>(hm, list);
}
static void foo(
List<? extends Set<? extends Integer>> listOfSetsOfInts) { }
static void exercise2() {
var diffSetType = new ArrayList<HashSet<Integer>>(); // (3)
foo(diffSetType);
}
}
}
注释:
(1) 也可以去掉这些通配符。这是一些可以编译的替代代码,但您会收到有关未经检查的类型转换的警告。假设您将地图和列表视为只读,那么您可以替换下面的代码。(注意:里氏替换原则对此有话要说;参见下面的(4)。)一旦允许突变,事情就会变得更加复杂。
Map<T, Set<T>> f;
List<Set<T>> fr;
public FooMap(
Map<? extends T, ? extends Set<? extends T>> f,
List<? extends Set<? extends T>> fr) {
this.f = (Map<T, Set<T>>) f;
this.fr = (List<Set<T>>) fr;
}
list
(2) is的具体类型ArrayList<HashSet<B>>
,但我们希望像使用它一样使用它,这在构造函数的通配符List<Set<A>>
中是允许的。FooMap
请注意,我们向构造函数传递了一个显式类型参数来表示 this 是一个FooMap<A>
.
(3) 的具体类型diffSetType
是ArrayList<HashSet<Integer>>
,与 的参数类型兼容foo()
。
(4) 您需要更仔细地考虑 的第一个类型参数HashMap
,因为我们要满足 Liskov 替换原则,这意味着满足“生产者扩展,消费者超级”(PECS)规则。的第一个参数Map
是“消费者”类型,所以我们真的应该这样写代码:
class FooMap<T> {
Map<T, Set<T>> f;
List<Set<T>> fr;
public FooMap(
Map<? super T, ? extends Set<? extends T>> f,
List<? extends Set<? extends T>> fr) {
this.f = (Map<T, Set<T>>) f;
this.fr = (List<Set<T>>) fr;
}
static void exercise1() {
var hmA = new HashMap<A, HashSet<B>>();
var hmB = new HashMap<B, HashSet<B>>();
var hmC = new HashMap<C, HashSet<B>>();
var list = new ArrayList<HashSet<B>>();
var fm1 = new FooMap<A>(hmA, list); // okay
var fm2 = new FooMap<B>(hmA, list); // okay
var fm3 = new FooMap<C>(hmA, list); // type error (5)
var fm4 = new FooMap<B>(hmB, list); // okay
var fm5 = new FooMap<B>(hmC, list); // type error (6)
var fm6 = new FooMap<C>(hmC, list); // type error (7)
}
(5) HashSet<B>
fromlist
不匹配? extends Set<? extends C>
(6) HashMap<C, HashSet<B>>
fromhmC
不匹配Map<? super B, ? extends Set<? extends B>>
,因为C
不匹配? super B
。
(7)hmC
是HashMap<C, HashSet<B>>
;HashSet<B>
不匹配? extends Set<? extends C>
推荐阅读
- docker - Docker Swarm:节点恢复活动后重新应用服务的放置偏好
- apache - 从同一目录提供的 API 和静态 html
- c++ - 为什么在这种情况下编译器无法优化 vtable(静态关键字/单例模式)
- r - 如何将多个 2D R 图组合成一个 3D 图
- python - 如何使用 pyspark 随着时间的推移有效地实现 groupby 和后续滚动应用
- python-3.x - 如何在 python 中使用特定字符串删除 Power Point 幻灯片?
- html - 为什么这部分会在 Outlook Web 中出现空白?
- html - 如何在动态网页中使用@media 打印查询?我现在得到的只是一个空白页
- java - 从 JAR 加载类时出现 ClassNotFoundException
- selenium - Selenium:无法单击自定义弹出按钮