Làm thế nào để thực thi logic trên Tùy chọn nếu không có?


81

Tôi muốn thay thế mã sau bằng java8 Optional:

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

    return obj;
}

Mã giả sau đây không hoạt động vì không có orElseRunphương pháp nào , nhưng dù sao nó cũng minh họa mục đích của tôi:

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

Bạn muốn trả về từ phương thức nào nếu không có đối tượng?
Duncan Jones

Tôi muốn trả về Optionalluôn luôn như được chỉ ra bởi tham số trả về của phương thức.
viên

Câu trả lời:


122

Với Java 9 trở lên, ifPresentOrElserất có thể là những gì bạn muốn:

Optional<> opt = dao.find();

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

Currying bằng vavr hoặc tương tự có thể nhận được mã gọn gàng hơn, nhưng tôi chưa thử.


61
có vẻ như một thứ gì đó đáng lẽ phải được đưa vào v1 (Java 8) ... oh well ...
ycomp

2
Vâng ... Tôi cũng nghĩ rằng họ thực sự đã bỏ lỡ điều đó trong Java 8. Và hơn thế nữa ... nếu bạn muốn làm điều gì đó khi giá trị hiện diện, họ đã đưa ra "ifPresent ()". Nếu bạn muốn làm điều gì đó khi giá trị hiện diện và điều khác khi không có giá trị, họ đã đưa ra "ifPresentOrElse (f1, f2)". Nhưng nó vẫn thiếu nếu tôi chỉ muốn làm điều gì đó với nó không có mặt (một cái gì đó như "ifNotPresent ()" sẽ phù hợp). Với ifPresentOrElse, tôi buộc phải sử dụng một hàm hiện tại không có tác dụng gì trong trường hợp sau.
hbobenicio

Nếu bạn có thể giới thiệu một khung công tác, hãy xem Vavr (Javaslang cũ) và Tùy chọn của họ, nó có phương pháp onEmpty
Andreas

Thay vì sử dụng Java 9 hoặc sử dụng if, else. vavr không đẹp lắm
senseiwu

điều này là quá phức tạp chỉ đối với một đường thẳng nếu sau đó khác!
JBarros35

36

Tôi không nghĩ rằng bạn có thể làm điều đó trong một tuyên bố. Làm tốt hơn:

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

36
Đây có thể là câu trả lời đúng, nhưng điều này vượt trội hơn nullkiểm tra theo cách nào? Theo quan điểm của tôi, nó là tồi tệ hơn nếu không có orElse....
DaRich

4
@DaRich, bạn có thể quên giá trị rỗng ở giữa mã của bạn, kết quả là NPE. Nhưng bạn không thể bỏ qua một Optionalcách tình cờ, đó luôn là một quyết định rõ ràng (và nguy hiểm).
Dherik

15

Đối với Java 8 Spring cung cấp ifPresentOrElsetừ "Phương pháp tiện ích để làm việc với Tùy chọn" để đạt được những gì bạn muốn. Ví dụ sẽ là:

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

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

11

Bạn sẽ phải chia điều này thành nhiều câu lệnh. Đây là một cách để làm điều đó:

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

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

Một cách khác (có thể được thiết kế quá mức) là sử dụng map:

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

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

Nếu obj.setAvailablethuận tiện trả về obj, thì bạn có thể chỉ cần lấy ví dụ thứ hai để:

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

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

9

Trước hết, bạn dao.find()phải trả về một Optional<Obj>hoặc bạn sẽ phải tạo một.

ví dụ

Optional<Obj> = dao.find();

hoặc bạn có thể tự làm như:

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

cái này sẽ trở lại Optional<Obj>nếu có hoặc Optional.empty()nếu không có.

Vì vậy, bây giờ chúng ta hãy đi đến giải pháp,

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;
        });
    }

Đây là một lớp lót mà bạn đang tìm kiếm :)


9
Trả về giá trị null đánh bại mục đích của Tùy chọn. Trong câu hỏi OP, người ta phải làm gì nếu đối tượng không được tìm thấy là mơ hồ. IMHO tốt hơn hết là trả về một Đối tượng mới được khởi tạo và có lẽ được đặtAvailable thành false. Tất nhiên, ở đây OP đã ghi là chết người, có nghĩa là anh ta có thể định chấm dứt nên nó không thực sự quan trọng.
Somaiah Kumbera

Giải pháp này trả về một Object, trong khi câu hỏi ban đầu dành cho một phương thức trả về Optional<Object>. Câu trả lời (cũ hơn) của tôi rất giống nhau nhưng khác ở chỗ: stackoverflow.com/a/36681079/3854962
UTF_or_Death

Tại sao việc sử dụng flatMap?
Lino

vì anh ấy trả về một tùy chọn thay vì một giá trị. FlatMap cải tùy chọn <Tùy chọn <X >> để tùy chọn <X>
Aman Garg

9

một .orElseRunphương pháp, nhưng nó được gọi .orElseGet.

Vấn đề chính với mã giả của bạn là .isPresentnó không trả về Optional<>. Nhưng .maptrả về một Optional<>orElseRunphương thức.

Nếu bạn thực sự muốn làm điều này trong một tuyên bố, bạn có thể thực hiện điều này:

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();
    });
}

Nhưng điều này thậm chí còn phức tạp hơn những gì bạn có trước đây.


3

Tôi đã có thể đưa ra một vài giải pháp "một dòng", ví dụ:

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

hoặc là

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

hoặc là

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

Nó trông không đẹp lắm, một cái gì đó giống như orElseRunsẽ tốt hơn nhiều, nhưng tôi nghĩ rằng tùy chọn đó với Runnable có thể chấp nhận được nếu bạn thực sự muốn giải pháp một dòng.


1

Đối với những người bạn muốn thực hiện một hiệu ứng phụ chỉ khi không có tùy chọn

tức là tương đương ifAbsent()hoặc ifNotPresent()ở đây là một sửa đổi nhỏ đối với các câu trả lời tuyệt vời đã được cung cấp.

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

1
ifPresentOrElse yêu cầu Java 9.
JL_SO

0

Bạn cần Optional.isPresent ()orElse () . Đoạn mã của bạn đã thắng; không hoạt động vì nó không trả về bất kỳ thứ gì nếu không có.

Điểm của Tùy chọn là trả về nó từ phương thức.


0

Với Java 8, Optionalnó có thể được thực hiện với:

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

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

0

ifPresentOrElse cũng có thể xử lý các trường hợp nullpointers. Cách tiếp cận dễ dàng.

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

-2

Tôi cho rằng bạn không thể thay đổi dao.find()phương thức để trả về một phiên bản Optional<Obj>, vì vậy bạn phải tự tạo một phiên bản thích hợp.

Đoạn mã sau đây sẽ giúp bạn. Tôi đã tạo lớp OptionalAction, lớp này cung cấp cơ chế if-else cho bạn.

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);
      }
    }
  }
}

1
Vui lòng để lại bình luận, nếu bạn bỏ phiếu. Điều này giúp tôi cải thiện câu trả lời.
mike

Tôi đồng ý downvoter nên bình luận ở đây. Tôi cho rằng điều này là do tôi đang tìm cách cấu trúc lại mã java7 thành java8, trong khi mã cũ bao gồm 8 dòng. Và nếu tôi sẽ thay thế nó với đề nghị của bạn mà có thể không giúp đỡ bất cứ ai, nhưng chỉ làm cho mọi việc tồi tệ hơn.
viên

Tôi không hiểu quan điểm của bạn. Tôi đã cấu trúc lại java 7 thành 8, phải không? Và nó sẽ làm cho mọi thứ tồi tệ hơn đến mức nào? Tôi không thấy bất kỳ sai sót nào trong giải pháp này. Người ta có thể tranh luận xem toàn bộ quan điểm của việc có một phương pháp khác (hoặc một giải pháp thay thế) Optionalcó hợp lý hay không. Nhưng tôi đã trả lời chính xác câu hỏi của bạn và cung cấp một ví dụ hoạt động.
mike

Giải pháp của bạn có vẻ hoàn toàn hợp lệ với tôi, mike. Dù sao thì việc giới thiệu các lớp rõ ràng giống OptionalActionnhư một giải pháp để có thể chuyển mã sang java8 có vẻ hơi bị khai thác quá mức, nếu trong java7 thì đây chỉ là một vài lớp lót.
viên

2
Đối tượng <DbObject> tùy chọn = (v == null)? Optional.empty (): Tùy chọn.of (v); có thể được viết lại thành: Đối tượng <DbObject> tùy chọn = Optional.ofNullable (v);
Geir
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.