11

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?

east825
  • 909
  • 1
  • 8
  • 20
  • DataHolder. This is syntactically not possible. Besides, this is not a question about static vs non static constraints. The static and non static method are not at all similar with respect to the type parameters. – Chetan Kinger Mar 09 '15 at 16:55
  • It looks like the super wildcard cannot be used with type, and can be only used like super Object>, I am not too sure about that though. – Artemkller545 Mar 09 '15 at 17:11

3 Answers3

6

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 example Optional instances to Optional<Number> (where Number 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

Community
  • 1
  • 1
Paul Bellora
  • 54,340
  • 18
  • 130
  • 181
  • Thank you! Indeed I should have thought about Guava's `Optional` in the first place. Well, it seems that I will use more restrictive version suggested by @rohit-jain in the end, if even folks in Google didn't know how to deal with this limitation better. – east825 Mar 10 '15 at 09:18
4

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.

J Richard Snape
  • 20,116
  • 5
  • 51
  • 79
Rohit Jain
  • 209,639
  • 45
  • 409
  • 525
0

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!!!)
Necreaux
  • 9,451
  • 7
  • 26
  • 43
  • 1
    Did you try compiling the method invocation with this method? Doesn't work. – Rohit Jain Mar 09 '15 at 18:17
  • 1
    I did and it does compile for me. It gives an unchecked warning, which can easily be suppressed, but it does compile. What Java version are you using? – Necreaux Mar 09 '15 at 18:21
  • 1
    You're right. You can actually leave T out in this example, but that's unsafe too. I think changing the parameter for the input variable is probably the best way to go. Code edited. The internals of that method are now unchecked, but at least the signature and all that is as expected. – Necreaux Mar 09 '15 at 18:52
  • This is unsafe, since `R` could be anything. – Paul Bellora Mar 09 '15 at 19:40
  • Added some more information showing why this will compile, but is not a good solution. – Necreaux Mar 09 '15 at 20:18
  • And the last example is exactly the reason why it's an unchecked cast... Avoid them.. Unchecked warnings are not just to be easily `@Suppressed` – Rohit Jain Mar 09 '15 at 20:21
  • `DataHolder b = new DataHolder<>(new Integer(34));` does not compile in my Eclipse – fps Mar 09 '15 at 20:42