简体   繁体   English

Java泛型怪异行为

[英]Java generics weird behaviour

It's hard to explain in words, but Java Generics are given me an unexpected result. 用语言很难解释,但是Java泛型给了我意外的结果。 I expected that if I say a list is of type ? extends Object 我期望如果我说列表是类型的? extends Object ? extends Object , I could store anything in there. ? extends Object ,我可以在其中存储任何内容。 Therefore, if the list of of type Wrapper<? extends Object> 因此,如果类型为Wrapper<? extends Object>的列表 Wrapper<? extends Object> , I could store any kind of Wrapper in there. Wrapper<? extends Object> ,我可以在其中存储任何类型的包装器。 And so on. 等等。 That makes sense to me. 这对我来说很有意义。 But let's assume we have: 但让我们假设我们有:

private static class Wrapper<T> {
    public Wrapper(T t) { /**/ }
}

And I want something like: 我想要这样的东西:

private static final List<Wrapper<Wrapper<? extends Object>>> ls1 = new ArrayList<>();

Note that this gives me an error: 请注意,这给了我一个错误:

public static <T> doit(T t) {
    Wrapper<Wrapper<T>> l1 = new Wrapper<>(new Wrapper<>(t));
    ls1.add(l1); // nok
    // add (Wrapper<Wrapper<? extends java.lang.Object>>) in List
    // cannot be applied to (Wrapper<Wrapper<T>>
}

But if I wrap the Wrapper in a secondary Wrapper (rs), then: 但是,如果我将包装器包装在辅助包装器(rs)中,则:

private static class C<T> extends Wrapper<Wrapper<T>> {
    public C(T t) {
        super(new Wrapper<>(t));
    }
}

private static final List<C<? extends Object>> ls2 = new ArrayList<>();

public static <T> doit(T t) {
    ls2.add(new C<>(t)); // ok
}

Note that it is the same thing; 注意这是同一回事。 this make no sense to me. 这对我来说毫无意义。

PS. PS。 In my case, I'm not making a wrapper wrapper, but a ThreadLocal of a generic class. 就我而言,我不是在做包装器,而是在泛型类的ThreadLocal。

I think 我认为

? extends Object ---> equals ?

You can do like this 你可以这样

List<Wrapper<Wrapper<?>>> ls1 = new ArrayList<>();

And

Wrapper<Wrapper<?>> l1 = new Wrapper<>(new Wrapper<>(t));
ls1.add(l1); // OK

To be honest, I would have to look into it in more detail why it does not work. 老实说,我将不得不更详细地研究为什么它不起作用。 Its most likely related to the generic type erasure. 它最有可能与通用类型擦除有关。 What I have is a workaround, I know this solution looses the typ for the l1 object. 我有一个解决方法,我知道此解决方案会失去l1对象的类型。 If this is ok for you take it. 如果可以,请接受。

Wrapper<Wrapper<? extends Object>> l1 = new Wrapper<>(new Wrapper<>(t));
ls1.add(l1);

Basically you need to decide what's more important, being generic over what you want to put into the wrapper, over or what you take out of the wrapper (you cannot safely both). 基本上,您需要确定更重要的是什么,要对要放入包装器中的内容,包装内容或包装器中取出的内容进行泛型(您不能安全地兼顾两者)。 After deciding, you will then need to choose between extends and super . 确定后,您将需要在extendssuper之间进行选择。

If you just want to store the pointer then List<Wrapper<Wrapper>> will suffice, but then you will need to do some casting later. 如果只想存储指针,则List<Wrapper<Wrapper>>就足够了,但是稍后您需要进行一些转换。


When you are using extends then ? extends Foo 当您使用extends ? extends Foo ? extends Foo means that any data type returned by the structure will a subtype of type Foo (or Foo itself). ? extends Foo意味着结构返回的任何数据类型都将是Foo类型的子类型(或Foo本身)。

For example if you have void doIt1( List< ? extends Mammal> l ){} you could pass in List<Human> , List<Primate> , List<Simian> or a List<Mammal> . 例如,如果您有void doIt1( List< ? extends Mammal> l ){} ,则可以传入List<Human>List<Primate>List<Simian>List<Mammal>

However when using ? extends 但是,当使用? extends ? extends you don't know what is safe to put in. ? extends您不知道什么是安全的。

Consider this: 考虑一下:

void addElephant( List< ? extends Mammal> l  ){
    l.add( new Elephant() ); //wrong!
}

This is clearly unsafe ! 这显然是不安全的 I could have passed in a List<Human> as l and now my list of Human s has a Elephant in it! 我本可以通过List<Human>作为l传递的,现在我的Human列表中有一个Elephant

On the other hand you have super where ? super Foo 另一方面,你在哪里super ? super Foo ? super Foo means that the container can take a Foo . ? super Foo表示容器可以装Foo

So for void doIt2( List< ? extends Human> l ){} as you could pass in List<Human> , List<Primate> , List<Simian> , List<Mammal> , or a List<Object> . 因此,对于void doIt2( List< ? extends Human> l ){} ,您可以传入List<Human>List<Primate>List<Simian>List<Mammal>List<Object>

void ( List< ? super Human> l  ){
    l.add( new Human() );
}

And this is fine, but you cannot guarantee anything about what you will get out of the container. 很好,但是您不能保证会从容器中得到什么。

void ( List< ? super Human> l  ){
    Human h = l.get(0); //wrong!
}

That is because if l was really a List<Primate> it could have a mixture of Human s and Gorilla s and there is no guarantee that the first element will be a Human . 这是因为,如果l确实是List<Primate>则它可能包含HumanGorilla的混合,并且不能保证第一个元素将是Human

For this you will get the acronym PECS = "[a] P roducer ** E xtends, [but a] C onsumer S upers". 对于这一点,你会得到的缩写PECS = “[a] roducer **éxtends,[]Çonsumer 小号 upers”。

If you're container is returning object to a method (eg get ), then you are a "producing" (so use extends ), otherwise if you are accepting elements (eg add ) to a container, the container is "consuming" the object so use "super". 如果您是容器正在将对象返回到某个方法(例如get ),那么您就是一个“生产者”(因此请使用extends ),否则,如果您正在接受容器中的元素(例如add ),则该容器正在“消费”对象,因此请使用“超级”。 ( Nb: tricky parts to remember about PECS is that it is from the point-of-view of the container and not the calling code!). (注关于PECS,需要记住的棘手部分是它是从容器的角度而不是调用代码的角度!)。

If you want to both produce and consume you'll need to have a concrete type list List<Primate> where you can add Human s and retrieve Primate s. 如果您想同时生产和消费,则需要有一个具体的类型列表List<Primate> ,您可以在其中添加Human并检索Primate


See also: 也可以看看:

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM