1

In java, it is possible to store a function in a variable, then apply it later like this:

import java.util.function.Function;

class Main {
    Function<Integer, Integer> f;

    public Main(){
        f = this::add1;

        f.apply(1); //returns 2

        f.apply(20); //returns 21
    }

    public Integer add1(Integer value){
        return value+1;
    }
}

However, when I try to make the function throw an exception then catch that exception with a try/catch, I get some errors:

import java.util.function.Function;

class NumberTooSmall extends Exception{
    public NumberTooSmall(String message){ super(message); }
}

class Main {
    Function<Integer, Integer> f;

    public Main(){
        f = this::add1OrThrowError; // Unhandled exception

        try {
            //should throw error
            f.apply(1);
        } catch (NumberTooSmall e){ //Exception is never thrown in the corresponding try block
            // should be called here
            caseWhenErrorIsThrown();
        }

        try {
            // should return 21
            f.apply(20);
        } catch (NumberTooSmall e){ //Exception is never thrown in the corresponding try block
            // shouldn't be called here
            caseWhenErrorIsThrown();
        }
    }

    public Integer add1OrThrowError(Integer value) throws NumberTooSmall {
        if(value < 10) {
            throw new NumberTooSmall("Value is too low");
        }
        return value+1;
    }

    public void caseWhenErrorIsThrown(){ 
        // not important
    }
}

How can I resolve these errors? I expect I need to change something about the class of my function variable f, but I can't find what it should be. Also, just calling the function normally isn't an option, since there could be more methods I want f to become.

EDIT: Exception name changed from Error to NumberTooSmall since I learned that Error was already the name of a built-in java class (java.lang.Error).

  • 3
    Avoid creating a class called `Error`, because there's already a `java.lang.Error` (which happens to be a `Throwable`, but not an `Exception`!) While that's not technically a problem, it can become very confusing. – Joachim Sauer Aug 17 '21 at 13:10
  • to start with you can move lined with comment unhandled exception to try block – sanjeevRm Aug 17 '21 at 13:12
  • Does this answer your question? [Java 8 Lambda function that throws exception?](https://stackoverflow.com/questions/18198176/java-8-lambda-function-that-throws-exception) – Joe Jan 08 '22 at 14:21

3 Answers3

3

You have two options:

  • throw a RuntimeException (or any of its subclasses): those need not be declared so you can "silently" throw them

  • declare your own alternative to Function which contains an throws clause a little like this:

    public interface ThrowingFunction<I, O, E extends Throwable> {
        public O apply(I input) throws E;
    }
    

    Note that some utility libraries such as Apache Commons Lang3 already have such an interface. For example FailableFunction.

There's a third nasty approach, that I wouldn't recommend, but for completeness sake I should mention it: you can sneakily throw an exception that your method is not declared to actually throw. ExceptionUtils.rethrow implements this.

Joachim Sauer
  • 302,674
  • 57
  • 556
  • 614
1

"Error" is a very bad name for your Exception subclass. There is already a java.lang.Error class in the JDK, which "indicates serious problems that a reasonable application should not try to catch", so naming your Exception subclass Error will be very confusing to readers of your code. I'll call your Exception subclass NumberTooSmallException for the rest of the answer.

NumberTooSmallException inherits from Exception but not RuntimeException, which means that it is a checked exception.

Your add1OrThrowError method is declared to throw this checked exception, so the compiler will check that every time you use add1OrThrowError, you have either handled the exception, or you have declared that the enclosing function throws NumberTooSmallException too. The idea is to make sure that every exception thrown is handled.

Notice that Function.apply is declared to not throw any checked exceptions, so this is an error:

f = this::add1OrThrowError;

f is declared to not throw anything, but this::add1OrThrowError is declared to throw NumberTooSmallException.

Therefore, you can either create your own version of the Function interface that have an apply that throws NumberTooSmallException:

interface ErrorThrowingFunction<T, R> {
    R apply(T t) throws NumberTooSmallException;
}

(or the more generic version in Joachim Sauer's answer)

or you can make NumberTooSmallException an unchecked exception by inheriting from RuntimeException, so that the compiler doesn't check if it's always handled. This allows you to assign this::add1OrThrowError to a Function, but also permits you from calling f.apply without try...catch.

class NumberTooSmallException extends RuntimeException{
    public NumberTooSmallException(String message){ super(message); }
}
Sweeper
  • 213,210
  • 22
  • 193
  • 313
  • This answer generally is written as assuming that `Error` will remain. If you're going to do that, this answer needs to start with a caveat that `Error` is an _extremely_ unfortunate name, given that `java.lang.Error` already exists. I suggest starting with 'Error is a bad name' and then using another name for the rest of the answer to avoid confusion. – rzwitserloot Aug 17 '21 at 13:53
  • @rzwitserloot Thanks. I was thinking of saying something about that, but didn't know where to put it while I was writing the answer. – Sweeper Aug 17 '21 at 14:09
0

Some background on the 'why' of it all:

Closures in java are not transparent.

In java, closures (a -> foo(a) or someExpr::someMethodRef) are not transparent for the following 3 concepts:

  • Checked exceptions: The list of checked exceptions a closure throws needs to be 'handled' (caught within the closure, or the method in the @FunctionalInterface that defines the type of the closure needs to be declared to throws it) then and there, you cannot rely on a catch block, or a throws clause, on the context where you made the closure. That is the problem you're running into here.
  • non-effectively-final local vars: You cannot access (read or write) local variables from outside your lambda at all, unless they are either final, or they could be made final without compiler errors, in which case javac does you a favour and acts as if you put final on it.
  • control flow: You cannot write a break inside a closure that applies to a for/while/switch/etc from outside of it. e.g. this does not work:
boolean banned = false;
for (String x : userNames) {
 dbAccess.exec(db -> {
   if (db.selectBool("SELECT banned FROM users WHERE un = ?", x)) {
      banned = true; // won't compile - mutable local var
      break; // won't compile - control flow
   }
 });
}

Why not?

Because that's actually what you want, if it is a 'travelling' closure. It's just annoying and weird for use-it-and-lose-it closures though. Thus, to make sense of this, consider the notion of a travelling closure, and then it makes sense.

Travelling vs. use-it-and-lose-it

A function is, by design, something you can 'ship around'. It can 'escape your context'. You are free to store a function in a field, or pass it to another method which stores it in a field. Then that field can be read by another thread 5 days from now (let's assume some very long running VM, e.g. a JVM serving web pages) and the code is then run.

In other words, given:

try {
    // some code that _DEFINES_ a function, such as:
    foo(a -> System.out.println(a));
} catch (Something e) {
}

There is no guarantee that this 'makes sense'. Perhaps foo will take the closure, store it in a field, and run it 5 days from now - or, at any rate, runs it after the above code has long since completed. By completing that catch block and all context it needed to run is just gone. There is no way to run it! It sure feels like that catch block contains the error handler for when the code contained inside the try block throws it, but that is not how it works, given that the catch block no longer 'exists', in this scenario.

If your write code that makes no sense, it's a good idea if the compiler or runtime stops you from doing this. Better to get your error fast and with a good explanation, than to have to observe bizarre behaviour and go on a wild goose chase trying to figure out what you're missing, after all!

So javac does just that and won't compile this code.

Even if that code makes perfect sense and would work exactly as you'd expected it to if that closure doesn't travel.

Thus, you can separate out closures into two camps:

  • Use-it-or-lose-it closures: They are run (0 to many times) and then forgotten about within the lexical context where they are defined. Many usages of functions in java work like this, such as list.stream().map(closureHere).filter(anotherClosureHere).collect(), or list.sort(closureHere).
  • travelling closures: They are stored and run later, and/or sent to other threads (even if those threads then run on the spot and the code can't continue until they're done, such as with fork/join: Those exceptions still aren't going to end up in your catch block. For example, new TreeSet<>(comparatorHere), new Thread(runnableHere). (note how it doesn't depend on type; with list.sort, we passed a Comparatorwhich is use-it-and-lose-it, but withnew TreeSet, we _also_ passed a Comparator` but that's a travelling closure.

It would have been fantastic if a method that accepts a function can declare whether it runs that function in 'use it and lose it' mode or in 'travelling' mode so that javac can add the appropriate sugar to give you transparency for checked exceptions, mutable locals, and control flow (and javac can conveniently also tell you if you then take that function ref and store it in a field or pass it to a method that isnt explicitly declared to treat it as use-it-and-lose-it). Maybe one day, because I'm pretty sure java can add this to the language in a backwards compatible fashion.

As a consequence, you should consider using closures for inline operations a minor evil. If you can write it just as well without, then do that (for loops and such are superior to streams if they're equally simple). For the same reason we write getters instead of direct field access, and for the same reason style guides tend to make braces mandatory even if javac does not: You may not need any of the 3 transparencies today, but perhaps tomorrow you do, and it's annoying to have to rewrite at that point.

rzwitserloot
  • 85,357
  • 5
  • 51
  • 72