Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Java Generics, Type Inference, Inheritance?

Tags:

java

I'm reading about type inference for generics, and this code was provided as an example that fails to compile.

import java.io.*;
class LastError<T> {
    private T lastError;

    void setError(T t){
        lastError = t;
        System.out.println("LastError: setError");
    }
}

class StrLastError<S extends CharSequence> extends LastError<String>{
    public StrLastError(S s) {
    }
    void setError(S s){
        System.out.println("StrLastError: setError");
    }
}
class Test {
    public static void main(String []args) {
        StrLastError<String> err = new StrLastError<String>("Error");
        err.setError("Last error");
    }
}

And the explanation given in the book was:

"(It looks like the setError() method in StrLastError is overriding setError() in the LastError class. However, it is not the case. At the time of compilation, the knowledge of type S is not available. Therefore, the compiler records the signatures of these two methods as setError(String) in superclass and setError(S_extends_CharSequence) in subclass—treating them as overloaded methods (not overridden). In this case, when the call to setError() is found, the compiler finds both the overloaded methods matching, resulting in the ambiguous method call error."

I really don't understand why type S can't be inferred at compile time. String is passed when invoking the constructor of class StrLastError, and from the API docs, String does implement interface CharSequence, so doesn't that mean that S for <S extends CharSequence> actually is of type String?

I've read the Java online tutorial on the topic of generics several times. I've checked "Type Inference", and Inheritance, I just don't know how the whole thing works. I really need an explanation on this question.

The points I'm stuck at are:

  1. If the subtype can't decide S, how come the super type can decide T, because the superclass does not have an upper bound? Or does it infer that T is String because the subtype calls the supertype's constructor first?
  2. I understand that if the Constructor is invoked as:

    StrLastError<CharSequence> err = newStrLastError<>((CharSequence)"Error");
    

    there will be no ambiguity, since it's plain method overriding then. (Or am I even wrong here?)

However, like I said in the beginning, if String is passed, why can't S be inferred to be String?

like image 344
George Chou Avatar asked Apr 25 '15 14:04

George Chou


2 Answers

You have to remind yourself that the classes are compiled one by one. Java generics are not templates as in other languages. There will only be one compiled class and not one class per type it is used with.

This way you can see that the class StrLastError will need to be compiled in a way such that it can also be used with other classes implementing CharSequence as generic type S.

Thats why the compiler gets you two different methods instead of an overridden one. Now it would be a runtime-job to see that the subclass may have wanted to override the method in the parent just in those cases where the types suggest it. Since this behaviour is hard to understand for the developer and would possibly lead to programming mistakes, it raises an exception.

If you use CharSequence as the generic type parameter of the class StrLastError, you will call the setError method in the parent class, since the type of "Last Error" is String, which is more specific than CharSequence and Java always chooses the most specific method in case it is overloaded. (I hope it is clear the the method was not overridden in this case either)

like image 62
muued Avatar answered Oct 11 '22 18:10

muued


If the subtype can't decide S, how come the super type can decide T, because the superclass does not have an upper bound? Or does it infer that T is String because the subtype calls the supertype's constructor first?

The super type isn't deciding or inferring what T is; you're explicitly telling it what T is by this declaration:

class StrLastError<S extends CharSequence> extends LastError<String>

T is now bound to String for LastError, which makes every reference to T in the parent class a concrete String.

Your child class now has a bound S extends CharSequence attached to it, but this is independent to the bounds applied to the parent class.

What happens now is that Java will compile your child class, and the result of your child class is that two methods with a signature that matches String will be created. (Key note here: A String is-a CharSequence.)

In your child class, setError(Ljava/lang/CharSequence;)V is generated as the signature for setError. Because of the way generics work, LastError#setError will be treated as if it has a signature of setError(Ljava/lang/String;)V. This is also why when you go to actually override the method, it will place a String type as your parameter instead of anything else.

So, what we arrive at are two methods that have override-equivalent signatures.

void setError(CharSequence s)
void setError(String s)

JLS 8.4.8.4. applies here.

It is possible for a class to inherit multiple methods with override-equivalent signatures (§8.4.2).

It is a compile-time error if a class C inherits a concrete method whose signature is a subsignature of another concrete method inherited by C. This can happen if a superclass is generic, and it has two methods that were distinct in the generic declaration, but have the same signature in the particular invocation used.


I understand that if the Constructor is invoked as StrLastError err = new StrLastError<>((CharSequence)"Error"); there will be no ambiguity, since its plain method overriding then.(Or I'm even wrong here)

No, now you're messing with raw types. Interestingly enough it will work, primarily because the signatures for the two methods has become:

void setError(Object s)
void setError(String s)

You want to use generics to avoid a scenario like this; you may want to invoke the super class method at some point, but in this scenario with these bindings, it's very difficult to accomplish.

like image 21
Makoto Avatar answered Oct 11 '22 19:10

Makoto