Java Generics Puzzler,扩展 class 并使用通配符
Java Generics Puzzler, extending a class and using wildcards
一段时间以来,我一直在为这个问题苦苦思索,我想也许一些新鲜的眼睛会看到这个问题;谢谢你的时间。
import java.util.*;
class Tbin<T> extends ArrayList<T> {}
class TbinList<T> extends ArrayList<Tbin<T>> {}
class Base {}
class Derived extends Base {}
public class Test {
public static void main(String[] args) {
ArrayList<Tbin<? extends Base>> test = new ArrayList<>();
test.add(new Tbin<Derived>());
TbinList<? extends Base> test2 = new TbinList<>();
test2.add(new Tbin<Derived>());
}
}
使用Java 8. 在我看来直接在test
中创建容器相当于在test2
中创建容器,但是编译器说:
Test.java:15: error: no suitable method found for add(Tbin<Derived>)
test2.add(new Tbin<Derived>());
^
如何编写 Tbin
和 TbinList
以便最后一行可以接受?
请注意,我实际上将添加类型化的 Tbin
s,这就是我在最后一行指定 Tbin<Derived>
的原因。
您正在使用有界通配符 (TbinList<? extends Base>> ...
)。此通配符将阻止您向列表中添加任何元素。如果您想了解更多信息,请参阅文档中有关 Wildcards 的部分。
您不能向 TbinList<? extends Base>
添加任何对象,无法保证您将哪些对象插入到列表中。当您使用通配符 extends
时,它应该从 test2
读取数据
如果你声明为 TbinList<? extends Base>
,这意味着你是 class Base 的任何子 class 或 class Base 本身,当你初始化它时你使用diamond 而不是具体的 class 名称,它使您的 test2
不明显,这使得更难判断可以插入哪些对象。我的建议是避免这样的声明,它很危险,它可能没有编译错误,但它是可怕的代码,你可能会添加一些东西,但你也可能会添加错误的东西,这会破坏你的代码。
您可以按如下方式定义泛型类型:
class Tbin<T> extends ArrayList<T> {}
class TbinList<K, T extends Tbin<K>> extends ArrayList<T> {}
然后您将创建如下实例:
TbinList<? extends Base, Tbin<? extends Base>> test2 = new TbinList<>();
test2.add(new Tbin<Derived>());
将 TbinList
的定义替换为
class TbinList<T> extends ArrayList<Tbin<? extends T>> {}
并用
定义test2
TbinList<Base> test2 = new TbinList<>();
相反会解决问题。
根据您的定义,您将得到一个 ArrayList<Tbin<T>>
,其中 T 是任何固定的 class 扩展 Base
。
好的,这是答案:
import java.util.*;
class Tbin<T> extends ArrayList<T> {}
class TbinList<T> extends ArrayList<Tbin<? extends T>> {}
class Base {}
class Derived extends Base {}
public class Test {
public static void main(String[] args) {
TbinList<Base> test3 = new TbinList<>();
test3.add(new Tbin<Derived>());
}
}
正如我所料,我一看到它就很明显了。但是为了到达这里要经过很多的折腾。 Java 如果只看工作代码,泛型看起来很简单。
谢谢大家的共鸣。
发生这种情况是因为 capture conversion 的工作方式:
There exists a capture conversion from a parameterized type G<T<sub>1</sub>,...,T<sub>n</sub>>
to a parameterized type G<S<sub>1</sub>,...,S<sub>n</sub>>
, where, for 1 ≤ i ≤ n :
- If
T<sub>i</sub>
is a wildcard type argument of the form ? extends B<sub>i</sub>
, then S<sub>i</sub>
is a fresh type variable [...].
Capture conversion is not applied recursively.
注意结束位。所以,这意味着,给定这样的类型:
Map<?, List<?>>
// │ │ └ no capture (not applied recursively)
// │ └ T<sub>2</sub> is not a wildcard
// └ T<sub>1</sub> is a wildcard
仅捕获“外部”通配符。捕获了 Map
键通配符,但没有捕获 List
元素通配符。这就是为什么,例如,我们可以添加到 List<List<?>>
,而不是 List<?>
。通配符的位置很重要。
把这个带到TbinList
,如果我们有一个ArrayList<Tbin<?>>
,通配符在一个没有被捕获的地方,但是如果我们有一个TbinList<?>
,那么通配符在它被捕获的地方。
正如我在评论中提到的,一个非常有趣的测试是:
ArrayList<Tbin<? extends Base>> test3 = new TbinList<>();
我们收到此错误:
error: incompatible types: cannot infer type arguments for TbinList<>
ArrayList<Tbin<? extends Base>> test3 = new TbinList<>();
^
reason: <strong>no instance(s) of type variable(s) T exist so that
TbinList<T> conforms to ArrayList<Tbin<? extends Base>></strong>
所以没有办法让它按原样工作。 class 声明之一需要更改。
另外,这样想。
假设我们有:
class Derived1 extends Base {}
class Derived2 extends Base {}
由于通配符允许子类型化,我们可以这样做:
TbinList<? extends Base> test4 = new TbinList<Derived1>();
我们是否可以在 test4
中添加一个 Tbin<Derived2>
?不,这将是堆污染。我们最终可能会在 TbinList<Derived1>
.
中漂浮 Derived2
s
一段时间以来,我一直在为这个问题苦苦思索,我想也许一些新鲜的眼睛会看到这个问题;谢谢你的时间。
import java.util.*;
class Tbin<T> extends ArrayList<T> {}
class TbinList<T> extends ArrayList<Tbin<T>> {}
class Base {}
class Derived extends Base {}
public class Test {
public static void main(String[] args) {
ArrayList<Tbin<? extends Base>> test = new ArrayList<>();
test.add(new Tbin<Derived>());
TbinList<? extends Base> test2 = new TbinList<>();
test2.add(new Tbin<Derived>());
}
}
使用Java 8. 在我看来直接在test
中创建容器相当于在test2
中创建容器,但是编译器说:
Test.java:15: error: no suitable method found for add(Tbin<Derived>)
test2.add(new Tbin<Derived>());
^
如何编写 Tbin
和 TbinList
以便最后一行可以接受?
请注意,我实际上将添加类型化的 Tbin
s,这就是我在最后一行指定 Tbin<Derived>
的原因。
您正在使用有界通配符 (TbinList<? extends Base>> ...
)。此通配符将阻止您向列表中添加任何元素。如果您想了解更多信息,请参阅文档中有关 Wildcards 的部分。
您不能向 TbinList<? extends Base>
添加任何对象,无法保证您将哪些对象插入到列表中。当您使用通配符 extends
test2
读取数据
如果你声明为 TbinList<? extends Base>
,这意味着你是 class Base 的任何子 class 或 class Base 本身,当你初始化它时你使用diamond 而不是具体的 class 名称,它使您的 test2
不明显,这使得更难判断可以插入哪些对象。我的建议是避免这样的声明,它很危险,它可能没有编译错误,但它是可怕的代码,你可能会添加一些东西,但你也可能会添加错误的东西,这会破坏你的代码。
您可以按如下方式定义泛型类型:
class Tbin<T> extends ArrayList<T> {}
class TbinList<K, T extends Tbin<K>> extends ArrayList<T> {}
然后您将创建如下实例:
TbinList<? extends Base, Tbin<? extends Base>> test2 = new TbinList<>();
test2.add(new Tbin<Derived>());
将 TbinList
的定义替换为
class TbinList<T> extends ArrayList<Tbin<? extends T>> {}
并用
定义test2
TbinList<Base> test2 = new TbinList<>();
相反会解决问题。
根据您的定义,您将得到一个 ArrayList<Tbin<T>>
,其中 T 是任何固定的 class 扩展 Base
。
好的,这是答案:
import java.util.*;
class Tbin<T> extends ArrayList<T> {}
class TbinList<T> extends ArrayList<Tbin<? extends T>> {}
class Base {}
class Derived extends Base {}
public class Test {
public static void main(String[] args) {
TbinList<Base> test3 = new TbinList<>();
test3.add(new Tbin<Derived>());
}
}
正如我所料,我一看到它就很明显了。但是为了到达这里要经过很多的折腾。 Java 如果只看工作代码,泛型看起来很简单。
谢谢大家的共鸣。
发生这种情况是因为 capture conversion 的工作方式:
There exists a capture conversion from a parameterized type
G<T<sub>1</sub>,...,T<sub>n</sub>>
to a parameterized typeG<S<sub>1</sub>,...,S<sub>n</sub>>
, where, for 1 ≤ i ≤ n :
- If
T<sub>i</sub>
is a wildcard type argument of the form? extends B<sub>i</sub>
, thenS<sub>i</sub>
is a fresh type variable [...].Capture conversion is not applied recursively.
注意结束位。所以,这意味着,给定这样的类型:
Map<?, List<?>>
// │ │ └ no capture (not applied recursively)
// │ └ T<sub>2</sub> is not a wildcard
// └ T<sub>1</sub> is a wildcard
仅捕获“外部”通配符。捕获了 Map
键通配符,但没有捕获 List
元素通配符。这就是为什么,例如,我们可以添加到 List<List<?>>
,而不是 List<?>
。通配符的位置很重要。
把这个带到TbinList
,如果我们有一个ArrayList<Tbin<?>>
,通配符在一个没有被捕获的地方,但是如果我们有一个TbinList<?>
,那么通配符在它被捕获的地方。
正如我在评论中提到的,一个非常有趣的测试是:
ArrayList<Tbin<? extends Base>> test3 = new TbinList<>();
我们收到此错误:
error: incompatible types: cannot infer type arguments for TbinList<>
ArrayList<Tbin<? extends Base>> test3 = new TbinList<>();
^
reason: <strong>no instance(s) of type variable(s) T exist so that
TbinList<T> conforms to ArrayList<Tbin<? extends Base>></strong>
所以没有办法让它按原样工作。 class 声明之一需要更改。
另外,这样想。
假设我们有:
class Derived1 extends Base {}
class Derived2 extends Base {}
由于通配符允许子类型化,我们可以这样做:
TbinList<? extends Base> test4 = new TbinList<Derived1>();
我们是否可以在 test4
中添加一个 Tbin<Derived2>
?不,这将是堆污染。我们最终可能会在 TbinList<Derived1>
.
Derived2
s