简体   繁体   中英

Generic factory of generic containers

I have a generic abstract class Factory<T> with a method createBoxedInstance() which returns instances of T created by implementations of createInstance() wrapped in the generic container Box<T> .

abstract class Factory<T> {
    abstract T createInstance();

    public final Box<T> createBoxedInstance() {
        return new Box<T>(createInstance());
    }

    public final class Box<T> {
        public final T content;

        public Box(T content) {
            this.content = content;
        }
    }
}

At some points I need a container of type Box<S> where S is an ancestor of T . Is it possible to make createBoxedInstance() itself generic so that it will return instances of Box<S> where S is chosen by the caller? Sadly, defining the function as follows does not work as a type parameter cannot be declared using the super keyword, only used .

public final <S super T> Box<S> createBoxedInstance() {
    return new Box<S>(createInstance());
}

The only alternative I see, is to make all places that need an instance of Box<S> accept Box<? extends S> Box<? extends S> which makes the container's content member assignable to S .

Is there some way around this without re-boxing the instances of T into containers of type Box<S> ? (I know I could just cast the Box<T> to a Box<S> but I would feel very, very guilty.)

Try rewriting your other code to not use Box<S> but instead uses Box<? extends S> Box<? extends S> , so it will accept a Box<T> , too. This way, you make it explicit that the Box may also contain subclasses of S .

The following should also work, if you make your Box static :

public static <S, T extends S> Box<S> createBoxedInstance(Factory<T> factory) {
  return new Box<S>(factory.createInstance());
}

However, it may be unable to do type inference for S , at which point you need:

public static <S, T extends S> Box<S> createBoxedInstance(Factory<T> factory, S dummy) {
  return new Box<S>(factory.createInstance());
}

Another variation that is very explict:

public static <S, T extends S> Box<? extends S> createBoxedInstance(Test<T> factory, S dummy) {
  return factory.createBoxedInstance(); // Actually a Box<T>
}

You can achive what you want to some degree:

public final <S extends Box<? super T>> S createBoxedInstance() {
    // sadly we can't capture the wildcard in the bound of S
    // this means we have to cast, but at least it's local..
    // *should* this cast ever break due to reification, we
    // can fix it here and be good to go
    @SuppressWarnings("unchecked")
    S box = (S) new Box<T>(createInstance());
    return box;
}

Factory<Integer> factory = getFactory();
Box<Number> box1 = factory.createBoxedInstance();
Box<Object> box2 = factory.createBoxedInstance();

I do think that your own Box<? extends S> Box<? extends S> -Alternative is the correct approach. However, if this will force client code to be littered with wildcards, the above might be preferable.

The reason why you cannot do it (or you have to use strange conversions) is the following Imagine that the Box content instead of being final also had a setter. Also, let's assume the type S is the super type of both R and T. The following code would be valid without any casting.

Box<T> boxT = new Box<T>(objectT);
Box<S> boxS = boxT;
S objectR = new R();
boxS.set(objectR);

That code would be valid with no warnings, except the setter would fail at runtime with an unexpected exception (unexpected in the sense that it is not obvious that a casting exception can be thrown) because you cannot assign something with a of type R to something of type T.

You can read more at http://www.angelikalanger.com/GenericsFAQ/FAQSections/TechnicalDetails.html#Topic3 (but have aspirin nearby, you will get a headache)

As many people said, probably using box in the other code is the best option.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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