15

Using Optional, I want to return a certain implementation (First or Second) of an interface according to the mapping result. This is the interface that First and Second implement:

public interface MyInterface {
    Number number();
}

The following Optional usage is erroneous:

final String string = ...                          // might be null
final Number number = Optional.ofNullable(string)
        .map(string -> new First())
        .orElse(new Second())                      // erroneous line
        .number();

orElse (com.mycompany.First) in Optional cannot be applied to (com.mycompany.Second)

Why is the line erroneous since both of the classes First and Second implement the interface MyInterface and the method MyInterface::number returns Number? How to implement this correctly?

Nikolas Charalambidis
  • 40,893
  • 16
  • 117
  • 183

5 Answers5

12

I have discovered out that the method Optional::map returns U which doesn't allow apply returned First to another type such Second is. An explicit casting to its interface or requiring it within the map method is a way to go:

final Number number = Optional.ofNullable("")
        .<MyInterface>map(string -> new First())
        .orElse(new Second())
        .number(); 

__

Edit: I have found this out after posting the question. However, I am keeping both since I haven't found a similar solution anywhere else yet.

Naman
  • 27,789
  • 26
  • 218
  • 353
Nikolas Charalambidis
  • 40,893
  • 16
  • 117
  • 183
  • 7
    I would say specifying the type parameter explicitly, is better than casting. Something like this: `.map(string -> new First())` – marstran Jan 17 '19 at 14:56
  • 5
    @Nikolas In what way do you feel it is "shamefully"? Self-answers are perfectly acceptable here. – glglgl Jan 17 '19 at 15:01
6

The problem is that Java infers the mapped type to be First, and Second is not an instance of First. You need to explicitly give Java a bit of a nudge to know the right type:

private static void main(String... args)
{
    final String string = "";
    final Number number = Optional.ofNullable(string)
        .<MyInterface>map(str -> new First())  // Explicit type specified 
        .orElse(new Second())
        .number();
}

This is a generic limitation of type inference along method chains. It's not limited to Optional.

There has been some suggestion to have type inference work along method chains. See this question: Generic type inference not working with method chaining?

Maybe in a future version of Java the compiler will be clever enough to figure this out. Who knows.

Michael
  • 41,989
  • 11
  • 82
  • 128
  • `Java *infers* the mapped type to be First` - that's a right formulation. – Nikolas Charalambidis Jan 17 '19 at 15:04
  • @Nikolas Yep. It's not a cast as you stated but rather simply explicitly *specifying* the type. – Michael Jan 17 '19 at 15:11
  • @Michael I don't think that would be possible, AFAIK, inference works on per method basis - first infer one, then the next and so on; what you are implying must work for all chained methods at once - I doubt that would be ever possible. – Eugene Jan 17 '19 at 20:11
  • @Eugene Read the link in my answer. It ends with "but perhaps with additional enhancements it could be added in the future". – Michael Jan 18 '19 at 09:15
  • @Michael I was not objecting against a "future possibility", I was just saying that it would be a massive change... – Eugene Jan 18 '19 at 09:20
  • @Eugene Huh? That's exactly what you said! "I doubt that would be ever possible" – Michael Jan 18 '19 at 09:22
  • @Michael in my understanding of how java platform has evolved, this massive change, would not be possible IMHO, don't know if that makes sense to you – Eugene Jan 18 '19 at 09:23
  • 3
    @Eugene compare Java 8’s type inference for nested method invocations with what we had before. Extending it to chained method invocations would be a change of a similar magnitude. A big change, but not impossible. – Holger Jan 18 '19 at 10:29
  • @Holger you know this *a lot* better than I ever will, will take your word on it – Eugene Jan 18 '19 at 10:33
  • This solution worked for me. I am just getting the warning: "Unchecked assignment Set to Set". Is there any way to avoid it? – fascynacja Jun 15 '20 at 13:44
  • @Michael you are right. My case has additional generics, (Instead of MyInterface I need in fact Set>). Please ignore my comment, because it has nothing to do with your example. Sorry for troubles. – fascynacja Jun 15 '20 at 14:34
3

I would write that without an explicit cast:

Optional.ofNullable(string)
        .map(s -> {
             MyInterface m = new First();
             return m;  
        })
        .orElse(new Second())
        .number();
Eugene
  • 117,005
  • 15
  • 201
  • 306
  • 1
    *I would write that without a cast* - Writing without a cast doesn't mean it won't happen, in this case you'll have an implicit upcast. An explicit cast makes the lambda shorter (but it's still better to specify the type in the `map` call) – BackSlash Jan 17 '19 at 15:00
  • 3
    @BackSlash an explicit type cast like with `map(s -> (MyInterface)new First())`, is error prone. The problem is that a type cast can be either, narrowing or widening. While widening is intended, narrowing is still allowed, so if `First` mistakenly doesn’t implement `MyInterface` (and is not final), the compiler will not object and insert the unintended runtime type cast which will fail with a `ClassCastException`. In contrast, `.map(s -> new First())` and the code of this answer both use implicit conversions, which are only allowed if `First` actually implements `MyInterface`. – Holger Jan 18 '19 at 10:59
1

You can also write:

Optional.ofNullable(string)
    .map(s -> new First())
    .filter(MyInterface.class::isInstance)
    .map(MyInterface.class::map)
    .orElse(new Second())
    .number()

Or, add a utility function to your codebase:

// the Class Object is unused and only present so the Compiler knows which Type you actually want
public static <T, R> Function<? super T, R> mapAs(Function<? super T, ? extends R> mappingFunction, Class<R> clazz) {
    return mappingFunction::apply;
}
Optional.ofNullable(string)
    .map(mapAs(s -> new First(), MyInterface.class))
    .orElse(new Second())
    .number()
Felix
  • 2,256
  • 2
  • 15
  • 35
0

Well as the explanation stands true in other answers as well, its the type inferred while using the map that errors out the orElse in use, a cleaner way to represent the suggested solution would be :

Optional.ofNullable(string)
        .map(s -> (MyInterface) new First()) // casting rather than binding here
        .orElse(new Second())
        .number();
Naman
  • 27,789
  • 26
  • 218
  • 353
  • 1
    *Opinion*: Its just more verbal in terms of casting that would be required for the contracts of `map` and `orElse` to be both followed at the same time. – Naman Jan 17 '19 at 17:25