如果不存在,如何执行可选逻辑?

我想用 java8Optional替换以下代码:

public Obj getObjectFromDB() {
Obj obj = dao.find();
if (obj != null) {
obj.setAvailable(true);
} else {
logger.fatal("Object not available");
}


return obj;
}

下面的伪代码不能工作,因为没有 orElseRun方法,但无论如何它说明了我的目的:

public Optional<Obj> getObjectFromDB() {
Optional<Obj> obj = dao.find();
return obj.ifPresent(obj.setAvailable(true)).orElseRun(logger.fatal("Object not available"));
}
185384 次浏览

I don't think you can do it in a single statement. Better do:

if (!obj.isPresent()) {
logger.fatal(...);
} else {
obj.get().setAvailable(true);
}
return obj;

You need Optional.isPresent() and orElse(). Your snippet won;t work because it doesn't return anything if not present.

The point of Optional is to return it from the method.

You will have to split this into multiple statements. Here is one way to do that:

if (!obj.isPresent()) {
logger.fatal("Object not available");
}


obj.ifPresent(o -> o.setAvailable(true));
return obj;

Another way (possibly over-engineered) is to use map:

if (!obj.isPresent()) {
logger.fatal("Object not available");
}


return obj.map(o -> {o.setAvailable(true); return o;});

If obj.setAvailable conveniently returns obj, then you can simply the second example to:

if (!obj.isPresent()) {
logger.fatal("Object not available");
}


return obj.map(o -> o.setAvailable(true));

I suppose you cannot change the dao.find() method to return an instance of Optional<Obj>, so you have to create the appropriate one yourself.

The following code should help you out. I've create the class OptionalAction, which provides the if-else mechanism for you.

public class OptionalTest
{
public static Optional<DbObject> getObjectFromDb()
{
// doa.find()
DbObject v = find();


// create appropriate Optional
Optional<DbObject> object = Optional.ofNullable(v);


// @formatter:off
OptionalAction.
ifPresent(object)
.then(o -> o.setAvailable(true))
.elseDo(o -> System.out.println("Fatal! Object not available!"));
// @formatter:on
return object;
}


public static void main(String[] args)
{
Optional<DbObject> object = getObjectFromDb();
if (object.isPresent())
System.out.println(object.get());
else
System.out.println("There is no object!");
}


// find may return null
public static DbObject find()
{
return (Math.random() > 0.5) ? null : new DbObject();
}


static class DbObject
{
private boolean available = false;


public boolean isAvailable()
{
return available;
}


public void setAvailable(boolean available)
{
this.available = available;
}


@Override
public String toString()
{
return "DbObject [available=" + available + "]";
}
}


static class OptionalAction
{
public static <T> IfAction<T> ifPresent(Optional<T> optional)
{
return new IfAction<>(optional);
}


private static class IfAction<T>
{
private final Optional<T> optional;


public IfAction(Optional<T> optional)
{
this.optional = optional;
}


public ElseAction<T> then(Consumer<? super T> consumer)
{
if (optional.isPresent())
consumer.accept(optional.get());
return new ElseAction<>(optional);
}
}


private static class ElseAction<T>
{
private final Optional<T> optional;


public ElseAction(Optional<T> optional)
{
this.optional = optional;
}


public void elseDo(Consumer<? super T> consumer)
{
if (!optional.isPresent())
consumer.accept(null);
}
}
}
}

I was able to came up with a couple of "one line" solutions, for example:

    obj.map(o -> (Runnable) () -> o.setAvailable(true))
.orElse(() -> logger.fatal("Object not available"))
.run();

or

    obj.map(o -> (Consumer<Object>) c -> o.setAvailable(true))
.orElse(o -> logger.fatal("Object not available"))
.accept(null);

or

    obj.map(o -> (Supplier<Object>) () -> {
o.setAvailable(true);
return null;
}).orElse(() () -> {
logger.fatal("Object not available")
return null;
}).get();

It doesn't look very nice, something like orElseRun would be much better, but I think that option with Runnable is acceptable if you really want one line solution.

There is an .orElseRun method, but it is called .orElseGet.

The main problem with your pseudocode is that .isPresent doesn't return an Optional<>. But .map returns an Optional<> which has the orElseGet method.

If you really want to do this in one statement this is possible:

public Optional<Obj> getObjectFromDB() {
return dao.find()
.map( obj -> {
obj.setAvailable(true);
return Optional.of(obj);
})
.orElseGet( () -> {
logger.fatal("Object not available");
return Optional.empty();
});
}

But this is even clunkier than what you had before.

First of all, your dao.find() should either return an Optional<Obj> or you will have to create one.

e.g.

Optional<Obj> = dao.find();

or you can do it yourself like:

Optional<Obj> = Optional.ofNullable(dao.find());

this one will return Optional<Obj> if present or Optional.empty() if not present.

So now let's get to the solution,

public Obj getObjectFromDB() {
return Optional.ofNullable(dao.find()).flatMap(ob -> {
ob.setAvailable(true);
return Optional.of(ob);
}).orElseGet(() -> {
logger.fatal("Object not available");
return null;
});
}

This is the one liner you're looking for :)

With Java 9 or higher, ifPresentOrElse is most likely what you want:

Optional<> opt = dao.find();


opt.ifPresentOrElse(obj -> obj.setAvailable(true),
() -> logger.error("…"));

Currying using vavr or alike might get even neater code, but I haven't tried yet.

For Java 8 Spring Data offers ifPresentOrElse from "Utility methods to work with Optionals" to achieve what you want. Example would be:

import static org.springframework.data.util.Optionals.ifPresentOrElse;
    

ifPresentOrElse(dao.find(), obj -> obj.setAvailable(true), () -> logger.fatal("Object not available"));

With Java 8 Optional it can be done with:

    Optional<Obj> obj = dao.find();


obj.map(obj.setAvailable(true)).orElseGet(() -> {
logger.fatal("Object not available");
return null;
});

For those of you who want to execute a side-effect only if an optional is absent

i.e. an equivalent of ifAbsent() or ifNotPresent() here is a slight modification to the great answers already provided.

myOptional.ifPresentOrElse(x -> {}, () -> {
// logic goes here
})

ifPresentOrElse can handle cases of nullpointers as well. Easy approach.

   Optional.ofNullable(null)
.ifPresentOrElse(name -> System.out.println("my name is "+ name),
()->System.out.println("no name or was a null pointer"));

Title: "How to execute logic on Optional if not present?"

Answer:

Use orElseGet() as a workaround for the missing ifNotPresent(). And since it expects us to return something just return null.

Optional.empty().orElseGet(() -> {
System.out.println("The object is not present");
return null;
});


//output: The object is not present


or


Optional.ofNullable(null).orElseGet(() -> {
System.out.println("The object is not present");
return null;
});


//output: The object is not present


I also use it to easily implement the singleton pattern with lazy initialization.

public class Settings {
private Settings(){}
private static Settings instance;
public static synchronized Settings getInstance(){
Optional.ofNullable(instance).orElseGet(() -> instance = new Settings());
return instance;
}
}

Of course the getInstance() content can be written in one line by directly returning the first statement, but I wanted to demonstrate the use of orElseGet() as an ifNotPresent().

In order to get the value from one call, or do an extra call if the previous returned an empty value, you can chain the commands.

public Optional<Obj> getObjectFromDB() {
return dao.find().or(() -> dao.findBySomethingElse());
}