javaexceptionoptional-parametersoptional-chaining

Difference between raw and parameterized Optional when invoking orElseGet(Supplier)


Consider the following code:

public class Main {
    public static void main(String[] args)
             // throws Exception // line 1 
    {
        Optional empty1 = Optional.empty(); // line 2
        Optional<Object> empty2 = Optional.empty(); // line 3

        Optional.empty().orElseThrow(() -> new Exception("Empty optional")); // line 4
        empty1.orElseThrow(() -> new Exception("Empty optional")); // line 5
        empty2.orElseThrow(() -> new Exception("Empty optional")); // line 6
    }

}

Solution

  • The difference is due to the use of raw types in Java. Method signature of orElseThrow is:

    public <X extends Throwable> T orElseThrow(Supplier<? extends X> exceptionSupplier) throws X
    

    So, for case in line 2 and 5 you have:

    Optional empty1 = Optional.empty(); // line 2
    empty1.orElseThrow(() -> new Exception("Empty optional")); // line 5
    

    in above orElseThrow signature case compiler is not provided with T type and even thought it has X, it will generate raw call. It will substitute X with its upperbound which is Throwable, and T will be Object. The result is:

    public Object orElseThrow(Supplier exceptionSupplier) throws Throwable
                                                                 ^^^^^^^^^
    

    thus, compiler reports:

    error: unreported exception Throwable
    

    The case of:

    Optional.empty().orElseThrow(() -> new Exception("Empty optional")); // line 4
    

    is more interesting, here no explicit type is specified, but compiler reports error of Exception being thrown. So its different from the previous case. You again must look at the signature of the used function, in this case Optional.empty():

    public static<T> Optional<T> empty()
                 ^^^ ~~~ type inference
    

    The important part is <T> which indicate that it will infer its type T from the enclosing context. In line 4 compiler is unable to infer type, and uses Object type instead. I believe in JLS its stated here:

    https://docs.oracle.com/javase/specs/jls/se10/html/jls-18.html#jls-18.1.3

    If Pl has no TypeBound, the bound αl <: Object appears in the set.

    and also in oracle tutorial:

    https://docs.oracle.com/javase/tutorial/java/generics/genTypeInference.html

    The compiler requires a value for the type argument T so it starts with the value Object. Consequently, the invocation of Collections.emptyList returns a value of type List<Object>

    The important thing, is that in line 4 you are not dealing with raw types like in lines 2 and 5, but with generic ones (generic type T is Object), so when orElseThrow is called, its signature is

    public Object orElseThrow(Supplier exceptionSupplier) throws Exception
    

    both generic types T nad X are known, so generic function call is generated, which in the end causes in your example this compiler error:

    error: unreported exception Exception;

    Now, line 3 and 6, is explicitly using generic type Object, and this makes the signature of orElseThrow as previously:

    public Object orElseThrow(Supplier exceptionSupplier) throws Exception
    

    which gives the same error:

    error: unreported exception Exception;