Peter Hull
Peter Hull

Reputation: 7067

Type inference on lambdas

I have been converting some code to use Java 8 features. In the following contrived example

    Arrays.asList("1", "2", "3", "cheese", "5").stream().map(line -> {
        try {
            return Optional.of(Integer.parseInt(line));
        } catch (NumberFormatException xep) {
            return Optional.empty();
        }
    }).forEach( v -> 
        System.out.println(v.orElse(999))
    );

(the intention is to parse some strings as ints and replace any unparseable values with 999)

The compiler reports

error: incompatible types: int cannot be converted to CAP#1
System.out.println(v.orElse(999))
where CAP#1 is a fresh type-variable:
CAP#1 extends Object from capture of ? extends Object"

I've tried casting 999 to an Integer or Object with no success.

It seems that the real problem is that the inferred return type of the first lambda is Optional<Object> and not Optional<Integer>

If I do this

    Arrays.asList("1", "2", "3", "cheese", "5").stream().map(line -> {
        Optional<Integer> ans;
        try {
            ans = Optional.of(Integer.parseInt(line));
        } catch (NumberFormatException xep) {
            ans = Optional.empty();
        }
        return ans;
    }).forEach( v -> 
        System.out.println(v.orElse(999))
    );

it works perfectly, but not quite as elegant. Is there a better way to 'guide' the compiler to the return type that I want?

Upvotes: 4

Views: 1782

Answers (4)

Holger
Holger

Reputation: 298143

As said by assylias, you can fix it by using return Optional.<Integer> empty();.

However, the big question is why are you using Optional here at all?

Stream.of("1", "2", "3", "cheese", "5").mapToInt(line -> {
    try {
        return Integer.parseInt(line);
    } catch (NumberFormatException xep) {
        return 999;
    }
}).forEach(System.out::println);

does the job much simpler when you want to replace the value anyway.

If you want to perform an action for valid values only (the equivalent of Optional.ifPresent(Consumer)) you may consider the rule that you should prefer pre-checks over catching exceptions when you expect invalid values:

Stream.of("1", "2", "3", "cheese", "5")
      .filter(Pattern.compile("^[+-]?[0-9]{1,9}$").asPredicate())
      .mapToInt(Integer::parseInt)
      .forEach(System.out::println);

(I simplified the regex; it does not accept all possible int values but will reject all invalid)

Upvotes: 3

gontard
gontard

Reputation: 29520

The @Assilyas answer is correct. I would like to propose an alternative based on the guava utility Ints::tryParse:

Unlike Integer.parseInt(String), this method returns null instead of throwing an exception if parsing fails

With it and the new method references, you could write:

Arrays.asList("1", "2", "3", "cheese", "5")
        .stream()
        .map(Ints::tryParse)
        .map(Optional::ofNullable)
        .forEach(v -> System.out.println(v.orElse(999)));

Upvotes: 1

Jesper
Jesper

Reputation: 206796

You could do this if you don't want the member variable ans:

try {
    return Optional.<Integer>of(Integer.parseInt(line));
} catch (NumberFormatException xep) {
    return Optional.<Integer>empty();
}

Type inference is complicated and it has limitations, if you really want to know why this happens in this case you'd have to study the Java Language Specification.

Upvotes: 1

assylias
assylias

Reputation: 328598

A simple fix is to use a target type:

return Optional.<Integer> empty();

Also I note that you use Integer.parseInt which returns an int, so you could also use an OptionalInt which will solve your problem and save a boxing operation:

try {
  return OptionalInt.of(Integer.parseInt(line));
} catch (NumberFormatException xep) {
  return OptionalInt.empty();
}

Upvotes: 7

Related Questions