0

When I try to compile the following code, compilation fails with the following error. I'm not sure why it should as I am only returning a class that implements the contract

public interface Contract {
  static <T extends Contract> T get() {
    return new ConcreteContract();
  }
}

class ConcreteContract implements Contract {
}

Contract.java:3: error: incompatible types: ConcreteContract cannot be converted to T
    return new ConcreteContract();
           ^
  where T is a type-variable:
    T extends Contract declared in method <T>get()
1 error

Does anyone have a clue why java behaves this way (or) am I missing something obvious

PS: I have read more than 10 top searches in SO before posting this query

Ashok Koyi
  • 5,327
  • 8
  • 41
  • 50
  • 1
    you forget the "following error" message – jhamon Aug 02 '18 at 12:20
  • Your design is flawed. `Contract` as an interface shouldn't know anything about `ConcreteContract`, an implementation. – Kayaman Aug 02 '18 at 12:41
  • First & foremost, your response has nothing to with the question. Second, Your response is not nuanced. I'm using that as Factory. Every factory should know what it is instantiating. The only valid questions are. 1. Is your factory complex enough to merit a separate class. 2. Do u want users of your API to remember multiple classes or only few classes, in the second case, I win – Ashok Koyi Aug 02 '18 at 12:57
  • 1
    @AshokKoyi Your factory knows that it is instantiating ConcreteClass, because the new it does is new ConcreteClass(). That changes nothing about what it should declare it is returning – kumesana Aug 02 '18 at 13:00
  • @kumesana Responded to that query in the other thread :) – Ashok Koyi Aug 02 '18 at 13:04
  • @kumesana The `Contract` is declaring its return type correctly. It says, it returns an object of type `Contract`. It perfectly valid from the point of view of the `Contract` – Ashok Koyi Aug 02 '18 at 13:09
  • 1
    @AshokKoyi I have no idea what you're saying. But when you say you return T, you're not saying you return Contract. Writing different things has the effect of them meaning different things. – kumesana Aug 02 '18 at 13:11
  • @kumesana Explained in detail in the other thread – Ashok Koyi Aug 02 '18 at 13:19
  • 1
    very related, possible duplicate: https://stackoverflow.com/questions/450807/how-do-i-make-the-method-return-type-generic and https://stackoverflow.com/q/338887/2513200 – Hulk Aug 02 '18 at 13:52
  • @Hulk Thanks. I just went over the questions. They address the core of this issue, but not the actual compilation error itself. It would easy for others to search based on the cryptic compilation error of javac which what I used for searching before posting this query – Ashok Koyi Aug 02 '18 at 14:01
  • 1
    @AshokKoyi here is another related topic - I think this is the at core of your misunderstanding: https://stackoverflow.com/q/4231305/2513200 - the fundamental design decisions that were made when defining Java's Generics, specifically choosing call-site variance – Hulk Aug 02 '18 at 14:07

2 Answers2

7

Since your method returns a generic type T, and T can be any class that implements Contract, it can be called (for example) with:

OtherConcreteContract variable = Contract.get();

and you can't assign a ConcreteContract to a OtherConcreteContract variable (assuming ConcreteContract is not a sub-class of OtherConcreteContract).

To avoid that error, you should either return the interface type:

static Contract get() {
    return new ConcreteContract();
}

or the concrete type:

static ConcreteContract get() {
    return new ConcreteContract();
}

The former (returning the interface type) is usually the better choice.

Generics don't help you here.

Eran
  • 387,369
  • 54
  • 702
  • 768
  • Not a convincing argument. The compiler should know when it sees `OtherConcreteContract variable = Contract.get();` that the return type must be whats specified on the left side & move on with it. javac must be dumb. It fixed the same issue with generic types when it introduced diamond `new ArrayList<>` operator. It knows what the developer means when we write that specific statement – Ashok Koyi Aug 02 '18 at 12:28
  • 3
    @AshokKoyi when you let your method have a generic return type, you allow the caller of the method to decide what that type would be. Therefore your method must return an instance that matches any choice the caller may make. Returning a `ConcreteContract` instance doesn't work if the caller chooses to assign the result of this method to a variable of some other type (that also implements the interface). – Eran Aug 02 '18 at 12:31
  • I believe typescript (dumb javascript's smart cousin) is smart enough to infer this. Java is not – Ashok Koyi Aug 02 '18 at 12:31
  • 2
    `new ArrayList<>` has no effect on the runtime, it just spares you from repeating what you already declared with the variable type, by implicitly considering that you mean the same generic type if you use `<>` instead of being specific. In your case, you're calling a method. This method cannot guess what its caller wants you to do if you don't provide it with parameters that tells it at runtime. – kumesana Aug 02 '18 at 12:32
  • 2
    @AshokKoyi there are more to generics and inference than just being more or less smart. Java's generics are a fully compile-time type safety verification tool, while most languages define their generics as a runtime tool which come with its advantages and inconveniences. But by all means, if you prefer typescript over Java, it's a perfectly fine choice to just use it instead. – kumesana Aug 02 '18 at 12:43
  • @Eran I believe there is a fundamental issue here. When the statement is parsed by compiler it knows that the type `T` must be `implementing` Contract. The compiler can say the return type is of type `Contract`, not whatever the caller has specified. In this case, the compiler can show a warning saying Contract.get() is guaranteed to return any type (Not the specific `ConcreteContract`) & ask the user to coerce the return value explicitly to remove the warning. Instead compiler gives some cryptic error. Return types needs to be handled separately than what javac does now. Thanks – Ashok Koyi Aug 02 '18 at 12:43
  • 1
    @AshokKoyi except if you want the compiler to take it as the return type being of type Contract, then you just make the return type "Contract" and not a generic "T". There is no logic doing anything else than what you intend. – kumesana Aug 02 '18 at 12:45
  • @Eran @kumesana Yes I see your point. Since the only guarantee the caller will have is `Contract`, its better if compiler just restricts the user to that for return types when assigning & ask the user to coerce on the caller side. But the compiler instead says, `ConcreteContract` cannot be converted to `T` which is outright misleading & wrong statement. Also, there is no reason why the compilation should fail tho – Ashok Koyi Aug 02 '18 at 12:52
  • 1
    @AshokKoyi T could be type OtherConcreteContract, and ConcreteContract cannot be converted to OtherConcreteContract. How is it in any way misleading to inform you that such a ConcreteContract cannot be converted to T in these circumstances? – kumesana Aug 02 '18 at 12:55
  • @kumesana I see your point. But there is no reason for the compiler to fail the compilation. It knows that the method is valid at runtime. Only valid questions are 1. Is compiler message helpful to developer 2. Why should compilation fail when the compiler can determine that users can use `Contract` as a valid return type 3. When to show warning. Compiler already knows when (at assignment), which is why I suggested the compiler should show a warning on the caller side if user tries to assign to anything other than Contract (only a warning, not an error, as the developer knows what he is doing) – Ashok Koyi Aug 02 '18 at 13:04
  • 2
    Generics have no other purpose than for the compiler to reject such a program. They exist purely to assist in ensuring type safety, and you're insisting on not caring about type safety, hence not respecting it. Therefore, generics exist only so that what you're doing is rejected. The compiler can absolutely not know that it is valid at runtime, since it may very well not be valid at runtime if the caller is expecting an OtherConcreteClass, which will obviouly fail. The message is helpful: as a matter of fact ConcreteClass cannot be converted to T, and it is saying so. – kumesana Aug 02 '18 at 13:09
  • @kumesana Even if I grant you every thing, you have to grant me this. The code wrote is perfectly valid & it does what it says. It says its returning an object that extends `Contract` interface. It does not say what concrete type it is returning. So the compiler enforcement should happen on the other end of this equation. i.e at the time of assignment. 1/2 – Ashok Koyi Aug 02 '18 at 13:17
  • The compiler should allow the user to assign the return type to `Contract` (or) show a warning like unchecked assignment warning it shows in other places if the user assigns to any concrete descendent of `Contract`. There is no sane reason for compiler to say `ConcreteType` is not assignable to `T` because `ConcreteType` extends `Contract` which what `T`'s definition is 2/2 – Ashok Koyi Aug 02 '18 at 13:17
  • 1
    @AshokKoyi The program does say that it returns an object that is a subtype of Contract. And it does not say what concrete type it is returning. But **it does also say that it is returning an object that is a subtype of the type T**, type T defined by the method call. And it is failing to respect that constraint. – kumesana Aug 02 '18 at 13:20
  • 1
    @AshokKoyi The reason why the compiler must absolutely not accept this program, is because it defines a method that can be called like this: `OtherConcreteContract contract = Contract.get();` and that will obviously fail since the object returned is ConcreteContract and not OtherConcreteContract. – kumesana Aug 02 '18 at 13:22
  • @kumesana In which case, it should show a warning/error in this assignment statement like it does in every other place. Not at the place where the contract is perfectly valid (method definition). When you are assigning `List concrete = new List();` the compiler does not complain. Here also it does not know what objects are present in the other list. This is where the tricky part is. This is where smart compilers are distinguished from dumb ones. That's why I said, return type generics should be handled separately by compiler – Ashok Koyi Aug 02 '18 at 13:26
  • 1
    @AshokKoyi At the end of the day, the whole point of generics is that ConcreteClass cannot be converted to T. If you had organized the code so that ConcreteClass could be converted to T, then there would be no problem anywhere else. Therefore, the problem is the one pointed out by the compiler, and stated exactly like it is: the problem with your program is that you're giving a ConcreteClass where a T is expected, and ConcreteClass cannot be converted to T, which is the only problem there is anywhere in your program. – kumesana Aug 02 '18 at 13:29
  • Lets agree to disagree. Thanks for your time :) – Ashok Koyi Aug 02 '18 at 13:31
  • * A stupid mistake on my part. Correction `List concrete = new ArrayList();`. Slipped my mind, but you got the point anyways – Ashok Koyi Aug 02 '18 at 13:34
  • @AshokKoyi Dude I have been doing nothing but citing Java specifications and their direct consequences. There is nothing to disagree with what I told about, unless by disagreeing that Java is what it is. You can feel free to think that Java is not being helpful by being what it is, but with your reasons you demonstrated a lot of not understanding what is even the reason why generics exist, and how they're supposed to be helpful. Instead insisting that the reason why they're there is not the one you want. – kumesana Aug 02 '18 at 13:36
  • Ok. Then we disagree to disagreeing then. Fine by me – Ashok Koyi Aug 02 '18 at 13:37
  • You gave your side from java spec. I think return types needs to be handled separately & the current javac implementation w.r.t this specific issue is not helping. Since java needs to compete with other languages, the more intuitive the compiler messages feel, the more people would gravitate towards it. Java compiler is quite behind the market as of today, but new languages are forcing java in a better direction. Hope we reach there sooner – Ashok Koyi Aug 02 '18 at 13:42
0

You need to typecast the final return type into the type T.

With you are specifying the if T must be either Contract or subclass of it. Means you are bounding the type which will be used as generic.

At the time of return, you should be strict to the generic type which is defined at the time of using that's why the compiler is complaining you to return the type T instead of What you are trying to return.

Shivang Agarwal
  • 1,825
  • 1
  • 14
  • 19