0

I'm having a strange behaviour with Generics I'm using Java8.

Here is a small sample code to demonstrate the problem Following code works fine without any issues with type inference. where, SpecificError is a subtype of GenericError.

public GenericType<AbstractError> method{
   Optional<SpecificError> error = Optional.of(new SpecificError());
   if (error.isPresent()) {
    return GenericType.error(error.get());
  } else {
     // return something else
  }
}

I have lot of this places in the code, where I had to do this if/else checks with optional, I decided to make a new function which receives the Optional, checks for presence and returns the Generic type object New function code:

public static <R extends AbstractError> GenericType<R> shortcut(Optional<R> error) {
    if (error.isPresent()) {
      return GenericType.error(error.get());
    } else {
    // something else
    }
}

This is new code calling the above function:

public GenericType<AbstractError> method{

       Optional<SpecificError> error = Optional.of(new SpecificError());
       return GenericType.shortcut(error);
    }

And strangely this does not work and breaks the following compilation error:

[ERROR]     inferred: AbstractError

[ERROR]     equality constraints(s): AbstractError, SpecificError

I just do not understand, why this won't work. The only thing, I have done is to make a small function which the job of doing isPresent check on Optional, everything else is the same. Why can't Java see that SpecificError is subtype of AbstractError

Aditya Rewari
  • 2,343
  • 2
  • 23
  • 36
Nitesh
  • 193
  • 1
  • 2
  • 17
  • at which line is the error coming? – Aditya Rewari May 12 '20 at 17:50
  • error is at the line: return GenericType.shortcut(error); I have the feeling that shortcut method is returning Explicity subtype (SpecificError), but the expected return type of the function is AbstractError, but for me, this should work, as this is what inheritance is all about? – Nitesh May 12 '20 at 17:53
  • As a note, avoid `ifPresent` and `get` if at all possible; they're serious code smells. Instead, `GenericType.error(error.orElse(...))`. – chrylis -cautiouslyoptimistic- May 12 '20 at 17:55
  • It's due to the fact that by using ```SpecificError``` in your method you can't guarantee anymore to return "anything that extends ```AbstractError```". So if you implement a ```AnotherSpecificError extends AbstractError```, theorically you could have ```GenericType aes = method();``` to pass the compilation and explode on runtime due to the ```SpecificError``` inferrence – Olivier Depriester May 12 '20 at 18:02
  • @OlivierDepriester as far as I understand, the following code is valid in Java BaseType b = new ChildType(c); So, specificError can be referred with this parent type (AbstractError) here. I do not see, Type safety issues here. Caller of 'method' needs an instance only with the interface in AbstractError. And also how do you explain that First piece of code is working, it is also working with SpecificError and uses the same underlying function GenericType.error()? I'm not able to follow. – Nitesh May 12 '20 at 18:44
  • @Nitesh Could you provide the implementation of `error()`? – MC Emperor May 12 '20 at 21:39

3 Answers3

1

From the method method(), you are calling GenericType.shortcut(error), where error is of the type Optional<SpecificError>. shortcut demands a type argument R, which you are trying to fulfill with R = SpecificError. So you are trying to return a GenericType<SpecificError>, but your method signature declares that it returns a GenericType<AbstractError>.

A GenericType<SpecificError> is not a GenericType<AbstractError>, because generics are invariant.

You could fix this by replacing

Optional<SpecificError> error = Optional.of(new SpecificError());

with

Optional<AbstractError> error = Optional.of(new SpecificError());
MC Emperor
  • 22,334
  • 15
  • 80
  • 130
  • thanks for your explanation. But I'm still curious, why the first piece of code works? I'm doing exactly the same there, which is calling the method GenericMethod.error which has following syntax `public static GenericType error(R value) {}` I'm pasting the working code again here ` `Optional error = Optional.of(new SpecificError()); if (error.isPresent()) { ` ` return GenericType.error(error.get()); }` – Nitesh May 12 '20 at 20:00
  • The first code works because there is no requirement for the return type and the argument to match. – Andy Turner May 12 '20 at 21:51
  • @MC emperor, I had a method which was retuning SpecificError, so following did not work `Optional error = getMethodWithSpecificError();` I do not know why, but then I changed the syntax of getMethodWithSpecificError to return Optional, it works, even though the `getMethodWithSpecificError` still creates an instance of SpecificError. So, i assume, there are some strange rules with Optional subtyping too, they work in some cases and do not work in some – Nitesh May 13 '20 at 10:27
  • @Nitesh One thing is sure, the rules are very well defined in the JLS. But in order to reproduce this behavior, you should post the exact code along with your question. – MC Emperor May 13 '20 at 10:31
  • it is unfortunately, not possible to share the full code because of privacy concerns. But, i have learnt my lesson that GenericType and GenericType are not related, i was under the wrong impression – Nitesh May 14 '20 at 19:01
  • @Nitesh It's indeed a little counter-intuitive: a `Dog` is an `Animal`, but a `List` is *not* a `List`. Jon Skeet explains it very well in [one of his answers](https://stackoverflow.com/a/2745301/507738) to a related post. – MC Emperor May 15 '20 at 06:19
0

As you declare the GenericType as <R extends AbstractError> at shortcut()

It is now forced to return a subtype. That is why the compiler is creating issues.

The concept witnessed here is Type Equality Constraint , covered under Java Generics

Thing should get going if you change the method signature to

public static <R> GenericType<R> shortcut(Optional<R> error) { // remove extends AbstractError
Aditya Rewari
  • 2,343
  • 2
  • 23
  • 36
  • I can not remove R extends AbstractError, class GenericType works only with types extending AbstractError. And i also do not understand why it works when I do the Optional expansion' in the actual method. GenericType.Error also works with the R extending Abstract error. Here is the signature of error methd public static Either error(R value) { – Nitesh May 12 '20 at 18:37
0
public GenericType<AbstractError> method{
   Optional<SpecificError> error = Optional.of(new SpecificError());
   return GenericType.shortcut(error);
}

public static <R extends AbstractError> GenericType<R> shortcut(Optional<R> error) {

shortcut needs an Optional parameter whose type parameter matches the return type.

But yours doesn't: SpecificError isn't the same as AbstractError.

All you need is for error to be R or a subclass, so add an upper bound to the parameter type:

Optional<? extends R> error
Andy Turner
  • 137,514
  • 11
  • 162
  • 243