east825
east825

Reputation: 909

How can I use both method and class type parameters in single constraint?

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

Answers (3)

Rohit Jain
Rohit Jain

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

Paul Bellora
Paul Bellora

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 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

Upvotes: 6

Necreaux
Necreaux

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

Related Questions