Reputation: 909
I'll try to illustrate my problem in the following simplified example:
public class DataHolder<T> {
private final T myValue;
public DataHolder(T value) {
myValue = value;
}
public T get() {
return myValue;
}
// Won't compile
public <R> DataHolder<R super T> firstNotNull(DataHolder<? extends R> other) {
return new DataHolder<R>(myValue != null ? myValue : other.myValue); }
public static <R> DataHolder<R> selectFirstNotNull(DataHolder<? extends R> first,
DataHolder<? extends R> second) {
return new DataHolder<R>(first.myValue != null ? first.myValue : second.myValue);
}
}
Here I want to write generic method firstNotNull
that returns DataHolder
parametrized by common supertype of type parameter T
of the this
and other
argument, so later I could write e.g.
DataHolder<Number> r = new DataHolder<>(3).firstNotNull(new DataHolder<>(2.0));
or
DataHolder<Object> r = new DataHolder<>("foo").firstNotNull(new DataHolder<>(42));
The problem is that this definition of firstNotNull
is rejected by compiler with message that super T
part of type constraint is illegal (syntactically).
However without this constraint definition is also wrong (obviously), because in this case T
and R
are unrelated to each other.
Interestingly, definition of similar static method selectFirstNotNull
is correct and the latter works as expected. Is it possible to achieve the same flexibility with non-static methods in Java type system at all?
Upvotes: 11
Views: 182
Reputation: 213351
I don't think there is any easy and type-safe way to do this. I've tried a couple of approaches, but the only working approach that I found is to start with a super
type generic instance, and make the method pretty simple like this:
public DataHolder<T> firstNotNull(DataHolder<? extends T> other) {
return new DataHolder<T>(myValue != null ? myValue : other.myValue);
}
Now you have to change your invocation to:
DataHolder<Number> r = new DataHolder<Number>(3).firstNotNull(new DataHolder<>(2.0));
You might argue that this doesn't really answer your question, but this is the simplest thing you're going to get, or better resort to a static
method approach. You can surely come up with some highly convoluted (and type-unsafe) methods to do so, but readability should be of major concern here.
Upvotes: 4
Reputation: 55233
It isn't possible to do this. The authors of Guava ran into the same issue with Optional.or
. From that method's documentation:
Note about generics: The signature
public T or(T defaultValue)
is overly restrictive. However, the ideal signature,public <S super T> S or(S)
, is not legal Java. As a result, some sensible operations involving subtypes are compile errors:Optional<Integer> optionalInt = getSomeOptionalInt(); Number value = optionalInt.or(0.5); // error FluentIterable<? extends Number> numbers = getSomeNumbers(); Optional<? extends Number> first = numbers.first(); Number value = first.or(0.5); // error
As a workaround, it is always safe to cast an
Optional<? extends T> to Optional<T>
. Casting either of the above exampleOptional
instances toOptional<Number>
(whereNumber
is the desired output type) solves the problem:Optional<Number> optionalInt = (Optional) getSomeOptionalInt(); Number value = optionalInt.or(0.5); // fine FluentIterable<? extends Number> numbers = getSomeNumbers(); Optional<Number> first = (Optional) numbers.first(); Number value = first.or(0.5); // fine
Since DataHolder
is immutable like Optional
, the above workaround will work for you too.
See also: Rotsor's answer to Bounding generics with 'super' keyword
Upvotes: 6
Reputation: 9786
Try changing your method as follows:
public <R> DataHolder<R> firstNotNull(DataHolder<? super T> other) {
return new DataHolder<R>((R)(this.myValue != null ? myValue : other.myValue));
}
WARNING: This compiles and gives the appearance of being properly checked for the most part but is not perfect. It will restrict the input parameters, but not the output. This cannot be done perfectly. In some ways you might be better off doing this unchecked rather than giving the illusion of being checked. Here are some examples:
DataHolder<BigDecimal> a = new DataHolder<>(new BigDecimal(34.0));
DataHolder<Number> b = new DataHolder<>(new Integer(34));
DataHolder<String> c = new DataHolder<>("");
DataHolder<Number> p = a.firstNotNull(b); // WORKS (good)
DataHolder<BigDecimal> q = b.firstNotNull(a); // FAILS (good)
DataHolder<BigDecimal> r = b.firstNotNull(c); // FAILS (good)
DataHolder<String> s = a.firstNotNull(b); // WORKS (not good!!!)
Upvotes: 0