Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

Tags:

java

generics

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?

like image 887
east825 Avatar asked Mar 09 '15 16:03

east825


People also ask

How do you restrict the types used as type arguments in generic classes and methods?

Whenever you want to restrict the type parameter to subtypes of a particular class you can use the bounded type parameter. If you just specify a type (class) as bounded parameter, only sub types of that particular class are accepted by the current generic class. These are known as bounded-types in generics in Java.

Is it possible to declared a multiple bounded type parameter?

Multiple BoundsBounded type parameters can be used with methods as well as classes and interfaces. Java Generics supports multiple bounds also, i.e., In this case, A can be an interface or class. If A is class, then B and C should be interfaces. We can't have more than one class in multiple bounds.

What is the purpose of the class constraint on a type parameter?

Object, you'll apply constraints to the type parameter. For example, the base class constraint tells the compiler that only objects of this type or derived from this type will be used as type arguments. Once the compiler has this guarantee, it can allow methods of that type to be called in the generic class.

How many type parameters can be used in a generic class?

Multiple parameters You can also use more than one type parameter in generics in Java, you just need to pass specify another type parameter in the angle brackets separated by comma.


2 Answers

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

like image 185
Paul Bellora Avatar answered Sep 30 '22 05:09

Paul Bellora


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.

like image 33
Rohit Jain Avatar answered Sep 30 '22 05:09

Rohit Jain