Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Initialize variable by evaluating lambda directly

Tags:

java

lambda

I am wondering if there is a way to call .apply() or .get() on a lambda function directly in the same expression it is defined. The question came to mind when I wanted to initialize a variable which could be private, but I cannot declare it final because the value is the return value of a function that can throw an exception. As an example, consider Files.size(path):

 final s = Files.size(path);
 // code that uses s

Now, if I want to use a default value for s if there is an exception, I have to add a try/catch, but this is a statement and not an expression:

s = 0;
try {
    s = Files.size();
}
catch (IOException e) {}

 // code that uses s

The obvious solution is to just declare a small helper function that wraps the try/catch, e.g. getFileSizeOrZero. I could also write a generic function that wraps this as a general case. This is nice and descriptive, but a more local function approach whould be better.

In C++, I used lambdas:

const s = []() {
    try {
        return Files.size(path);
    } catch (...) {
        return 0;
    }
}();

so I wondering if this can be done in Java. The best I could come up with was

final int s = new IntSupplier() {
    @Override
    public int getAsInt() {
        try {
            return Files.size(path);
        }
        catch (IOException e) {
            return 0;
        }
    }
 }.getAsInt();

This seems to be quite expressive, so I am hoping that there is some syntac that I don't know enabling

final int s = () -> {
    try {
        return Files.size(path);
    }
    catch (IOException e) {
        return 0;
    }
}.apply();

It seems that the problem is there is no function call operator as in C++, so there has to be a named member function which can be called.

like image 994
Jens Avatar asked Dec 22 '17 14:12

Jens


4 Answers

You can do this but you need to explicitly cast the anonymous lambda to the correct type. That's because you need to coerce the type of the lambda to the specific functional interface, Java can't guess the final type, eg:

final int x = ((IntSupplier)() -> 5).getAsInt();

If you don't like it you need to store it in a variable:

final IntSupplier xx = () -> 5;
final int x = xx.getAsInt();
like image 140
Jack Avatar answered Oct 03 '22 13:10

Jack


One of your problems is that Files.size(path) actually returns a long, so you need to assign it to one.

That allows you to do:

    long size = ((LongSupplier) () -> {
        try
        {
            return Files.size(path);
        }
        catch (IOException e)
        {
            return 0;
        }
    }).getAsLong();

So you only have to cast the lambda and have a single logical line expression.

I would also say a general function should be in order

public long defaultOnException(LongSupplier s, long defaultValue) {
    try { 
        return s.getAsLong(); 
    } catch (Exception e) {
        return defaultValue;
    }
}

which would make your client code

long size = defaultOnException(() -> Files.size(path), 0);
like image 20
daniu Avatar answered Oct 03 '22 14:10

daniu


I cannot declare it final because the value is the return value of a function that can throw an exception

Why does this stop you making it final? Just declare the variable outside a try/catch.

final long s;
{
  long ss;
  try {
    ss = Files.size(path);
  } catch (IOException e) {
    ss = 0;
  }
  s = ss;
}

Note that you can't simply use s in place of ss int the try/catch, because of the rules of definite assignment, specifically:

V is definitely unassigned before a catch block iff all of the following are true:

  • V is definitely unassigned after the try block.
  • ...

That first condition is not true, so the rest are irrelevant.

like image 42
Andy Turner Avatar answered Oct 03 '22 15:10

Andy Turner


There is the alternative:

    final int s = (IntSupplier) () -> {
        try {
            return Files.size(path);
        } catch (IOException e) {
            return 0;
        }
    }.getAsInt();
like image 33
Maurice Perry Avatar answered Oct 03 '22 14:10

Maurice Perry