Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Generics - compiler inconsistency [jdk 1.8.0_162]

I came across a problem with generics which keeps me puzzled of how the compiler actually deals with generic types. Consider the following:

// simple interface to make it a MCVE
static interface A<F, S> {
    public F getF();    
    public S getS();
}

static <V, S> Comparator<A<V, S>> wrap(Comparator<S> c) {
    return (L, R) -> c.compare(L.getS(), R.getS());
}

The following will not compile because both generic types are reduced to Object when calling thenComparing:

Comparator<A<String, Integer>> c = wrap((L, R) -> Integer.compare(L, R))
    .thenComparing(wrap((L, R) -> Integer.compare(L, R)));

But if I break them up like the following example, everything compiles (and runs) correctly:

Comparator<A<String, Integer>> c = wrap((L, R) -> Integer.compare(L, R));
c = c.thenComparing(wrap((L, R) -> Integer.compare(L, R)));

So the question is: what happens here? I suspect this is due to some weird behavior of the compiler rather than intended language specification? Or am I missing something obvious here?

like image 784
n247s Avatar asked Jan 12 '19 09:01

n247s


People also ask

What is meant by reified generics in Java?

Reified Generics is a generics system that makes generics type information available at runtime. C# is one language that supports Reified Generics natively, as opposed to Java's Type-Erased Generics.

What are the restrictions on generics in Java programming?

To use Java generics effectively, you must consider the following restrictions: Cannot Instantiate Generic Types with Primitive Types. Cannot Create Instances of Type Parameters. Cannot Declare Static Fields Whose Types are Type Parameters.

Is Java generics compile time or runtime?

Generics are checked at compile-time for type-correctness. The generic type information is then removed in a process called type erasure. For example, List<Integer> will be converted to the non-generic type List , which ordinarily contains arbitrary objects.

How do you add two generic values in Java?

You have to add the numbers as the same type, so you could do x. intValue() + y. intValue(); .


1 Answers

The second attempt compiles correctly, because you specified the type of a variable yourself, telling the compiler what it is, because compiler does not have enough information to figure it out.

Look at this simplified example, it's from vavr (great by the way). There is a Try<T> class that represents a result of some operation. Generic parameter T is the type of that result. There is a static factory to create a failure immediately, meaning we have no result here, but the generic parameter is still there:

static <T> Try<T> failure(Throwable exception) {
    return new Try.Failure(exception);
}

Where does the T come from here? The usage looks like this:

public Try<WeakHashMap> method() {
  return Try.failure(new IllegalArgumentException("Some message"));
}

The Try<WeakHashMap> here is my choice, not the compilers, you can actually put anything you want in there because you are choosing the type.

The same thing in your example, the Comparator has generic parameter String only, because you specified it and compiler agreed to it (like with Try<WeakHashMap>). When you added a chained call you forced the compiler to infer the type itself, and it was Object, because what another type it could have been?

What else you can do (notice the Testing.<String, Integer>wrap):

public class Testing {
  static interface A<F, S> {
    public F getF();
    public S getS();
  }

  static <V, S> Comparator<A<V, S>> wrap(Comparator<S> c) {
    return (L, R) -> c.compare(L.getS(), R.getS());
  }

  public static void main(String[] args) {
    Comparator<A<String, Integer>> comp = Testing.<String, Integer>wrap((L, R) -> Integer.compare(L, R))
      .thenComparing(wrap((L, R) -> Integer.compare(L, R)));
  }
}
like image 127
Shadov Avatar answered Oct 17 '22 21:10

Shadov