4

I noticed something seemly reasonable by Eclipse JDT but doesn't seem to be defined anywhere:

<!-- language: lang-java -->

public static <T, TException extends Exception> void iterateEx(
        Iterable<T> iterable, PredicateEx<T, TException> step) throws TException
{
    for (T item : iterable)
    {
        if (step.testEx(item))
        {
            ThreadExt.yield(); // sleep 0.001s
        }
    }
}

When I call the method with a lambda as the PredicateEx step, the unspecified TException is assumed to be RuntimeException if the lambda throws nothing. I found the piece of code in Eclipse JDT doing that but is it an well-defined behavior from lambda type inference, or just some decision made in the compiler implementation? Because it might also be possible for the default exception to be Exception instead (= upper bound of TException), and I'm quite concerned because I'm about to rewrite all function-taking methods that way to handle checked exceptions correctly.

Callers' examples are like:

<!-- language: lang-java -->

iterateEx(listOfResultSet, rs -> true); // throws RuntimeException, no try-catch required
iterateEx(listOfResultSet, rs -> rs.getBoolean("SOME_COLUMN")); // throws SQLException

The PredicateEx is an variant of Predicate that allows throwing of exception:

<!-- language: lang-java -->

@FunctionalInterface
public interface PredicateEx<T, TException extends Exception>
{
    boolean testEx(T t) throws TException;
}
2
  • This is not really a lambda issue - any behaviour will be specified in the generics section of the specification. It is normal for Java to let overriding methods/implementations of methods to narrow exception declarations. Commented Oct 22, 2014 at 7:34
  • It has to do with type inference from lambda - what should the inferred TException be if nothing is thrown or more than one types of exceptions are thrown? Commented Oct 22, 2014 at 12:42

1 Answer 1

1

Type inference is described in chapter 18 of Java Language Specification. Particular case of checked exceptions and throws clause is described in 18.2.5. This section specifies what type bounds inference variables in throws clause should have. Part that is most interesting for the case you described is

Otherwise, let E1, ..., En be the types in the function type's throws clause that are not proper types. If the lambda expression is implicitly typed, let its parameter types be the function type's parameter types. If the lambda body is a poly expression or a block containing a poly result expression, let the targeted return type be the function type's return type. Let X1, ..., Xm be the checked exception types that the lambda body can throw (§11.2). Then there are two cases:

  • ...

  • If n > 0, the constraint reduces to a set of subtyping constraints: for all i (1 ≤ i ≤ m), if Xi is not a subtype of any proper type in the throws clause, then the constraints include, for all j (1 ≤ j ≤ n), ‹Xi <: Ej›. In addition, for all j (1 ≤ j ≤ n), the constraint reduces to the bound throws Ej.

What does this mean? It means that every inference variable in the throws clause (i.e. exception types to be inferred) must be a supertype of every checked exception thrown by lambda body. Sounds strange, right? But there is a note in JLS about this:

Note that the handling of the case in which more than one inference variable appears in a function type's throws clause is not completeness-preserving. Either variable may, on its own, satisfy the constraint that each checked exception be declared, but we cannot be sure which one is intended. So, for predictability, we constrain them both.

So that was the part about general aspects of exception type inference. Now let's see what JLS says about "default" exception type.

Sections 18.4 specifies how to get the "final" types from the type bounds. It has this item (I will omit the text around to avoid copying of all paragraph):

  • If αi has one or more proper lower bounds, L1, ..., Lk, then Ti = lub(L1, ..., Lk) (§4.10.4).
  • Otherwise, if the bound set contains throws αi, and the proper upper bounds of αi are, at most, Exception, Throwable, and Object, then Ti = RuntimeException.
  • Otherwise, where αi has proper upper bounds U1, ..., Uk, Ti = glb(U1, ..., Uk) (§5.1.10).

This is it! Just as you suggested, when the exception type in the throws clause has no other type to bound to RuntimeException becomes it's "final" type.

Sign up to request clarification or add additional context in comments.

2 Comments

are you involved with the java lambda project? I would like to report some bugs.
@momo Please, report bugs at bugreport.java.com. On the first form choose Type:"Bug", Category:"Java Platform Standard edition", Subcategory:"javac compiler" (for lambda-related issues), release you use and your OS. Then fill in the bug details form. You can also check open bugs here to see if the problem you encountered is known.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.