Tại sao sử dụng Tùy chọn trong Java 8+ thay vì kiểm tra con trỏ null truyền thống?


110

Gần đây chúng tôi đã chuyển sang Java 8. Bây giờ, tôi thấy các ứng dụng tràn ngập Optionalcác đối tượng.

Trước Java 8 (Kiểu 1)

Employee employee = employeeServive.getEmployee();

if(employee!=null){
    System.out.println(employee.getId());
}

Sau Java 8 (Kiểu 2)

Optional<Employee> employeeOptional = Optional.ofNullable(employeeService.getEmployee());
if(employeeOptional.isPresent()){
    Employee employee = employeeOptional.get();
    System.out.println(employee.getId());
}

Tôi thấy không có giá trị gia tăng Optional<Employee> employeeOptional = employeeService.getEmployee();nào khi dịch vụ tự trả về tùy chọn:

Đến từ nền Java 6, tôi thấy Style 1 rõ ràng hơn và có ít dòng mã hơn. Có bất kỳ lợi thế thực sự tôi đang thiếu ở đây?

Hợp nhất từ ​​sự hiểu biết từ tất cả các câu trả lời và nghiên cứu thêm tại blog



28
Kinh quá! employeeOptional.isPresent()dường như là hoàn toàn thiếu điểm của các loại tùy chọn. Theo nhận xét của @ MikePartridge, điều này không được khuyến nghị hoặc thành ngữ. Hoàn toàn kiểm tra xem một loại tùy chọn là một cái gì đó hay không là không. Bạn được cho là đơn giản maphoặc flatMaphơn họ.
Andres F.

7
Xem một Stack Overflow trả lời trênOptional của Brian Goetz , các kiến trúc sư ngôn ngữ Java của Oracle.
Basil Bourque

6
Đây là những gì xảy ra khi bạn tắt bộ não của mình dưới danh nghĩa "thực hành tốt nhất".
Immibis

9
Đó là cách sử dụng khá kém của tùy chọn nhưng thậm chí có lợi ích. Với null, mọi thứ có thể là null, vì vậy bạn cần liên tục kiểm tra, các tùy chọn gọi ra rằng biến đặc biệt này có khả năng bị thiếu, hãy đảm bảo bạn kiểm tra nó
Richard Tingle

Câu trả lời:


108

Kiểu 2 không phải là Java 8 đủ để thấy toàn bộ lợi ích. Bạn không muốn if ... usechút nào. Xem ví dụ của Oracle . Theo lời khuyên của họ, chúng tôi nhận được:

Phong cách 3

// Changed EmployeeServive to return an optional, no more nulls!
Optional<Employee> employee = employeeServive.getEmployee();
employee.ifPresent(e -> System.out.println(e.getId()));

Hoặc một đoạn dài hơn

Optional<Employee> employee = employeeServive.getEmployee();
// Sometimes an Employee has forgotten to write an up-to-date timesheet
Optional<Timesheet> timesheet = employee.flatMap(Employee::askForCurrentTimesheet); 
// We don't want to do the heavyweight action of creating a new estimate if it will just be discarded
client.bill(timesheet.orElseGet(EstimatedTimesheet::new));

19
thực sự chúng tôi cấm hầu hết việc sử dụng isPftime (bị bắt bởi đánh giá mã)
jk.

10
@jk. thật vậy, nếu có quá tải, void ifPresent(Consumer<T>, Runnable)tôi sẽ tranh luận về việc cấm hoàn toàn isPresentget
Caleth

10
@Caleth: Bạn có thể quan tâm đến Java 9's ifPresentOrElse(Consumer<? super T>, Runnable).
wchargein

9
Tôi tìm thấy một nhược điểm trong phong cách java 8 này, so với java 6: nó đánh lừa các công cụ bao phủ mã. Với câu lệnh if, nó hiển thị khi bạn bỏ lỡ một nhánh, không phải ở đây.
Thierry

14
@Aaron "giảm đáng kể khả năng đọc mã". Phần nào chính xác làm giảm khả năng đọc? Nghe giống như "Tôi luôn luôn làm khác đi và tôi không muốn thay đổi thói quen của mình" hơn là lý luận khách quan.
Voo

47

Nếu bạn đang sử dụng Optionallàm lớp "tương thích" giữa API cũ vẫn có thể quay lại null, có thể hữu ích để tạo Tùy chọn (không trống) ở giai đoạn mới nhất mà bạn chắc chắn rằng mình có thứ gì đó. Ví dụ, nơi bạn đã viết:

Optional<Employee> employeeOptional = Optional.ofNullable(employeeService.getEmployee());
if(employeeOptional.isPresent()){
    Employee employeeOptional= employeeOptional.get();
    System.out.println(employee.getId());
}

Tôi muốn chọn:

Optional.of(employeeService)                 // definitely have the service
        .map(EmployeeService::getEmployee)   // getEmployee() might return null
        .map(Employee::getId)                // get ID from employee if there is one
        .ifPresent(System.out::println);     // and if there is an ID, print it

Vấn đề là bạn biết rằng có một dịch vụ nhân viên không có giá trị , vì vậy bạn có thể kết hợp nó Optionalvới một Optional.of(). Sau đó, khi bạn gọi getEmployee()vào đó, bạn có thể hoặc không thể có được một nhân viên. Nhân viên đó có thể (hoặc, có thể, có thể không) có ID. Sau đó, nếu bạn đã kết thúc với một ID, bạn muốn in nó.

Không cần phải kiểm tra rõ ràng bất kỳ null, sự hiện diện, vv, trong mã này.


4
Lợi thế của việc sử dụng (...).ifPresent(aMethod)hơn là if((...).isPresent()) { aMethod(...); }gì? Nó dường như chỉ đơn giản là một cú pháp khác để thực hiện gần như cùng một hoạt động.
Ruslan

2
@Ruslan Tại sao nên sử dụng một cú pháp đặc biệt cho một cuộc gọi cụ thể khi bạn đã có một giải pháp chung hoạt động tốt như nhau cho tất cả các trường hợp? Chúng tôi chỉ đang áp dụng cùng một ý tưởng từ bản đồ và cũng ở đây.
Voo

1
@Ruslan ... một API trả về null thay vì các bộ sưu tập hoặc mảng trống. Ví dụ: bạn có thể làm một cái gì đó như (giả sử một lớp Parent mà getChildren () trả về một tập hợp con hoặc null nếu không có con): Set<Children> children = Optional.of(parent).map(Parent::getChildren).orElseGet(Collections::emptySet); Bây giờ tôi có thể xử lý childrenthống nhất, ví dụ : children.forEach(relative::sendGift);. Những người thậm chí có thể được kết hợp : Optional.of(parent).map(Parent::getChildren).orElseGet(Collections::emptySet).forEach(relative::sendGift);. Tất cả các bản tóm tắt của kiểm tra null, v.v., ...
Joshua Taylor

1
@Ruslan ... được ủy quyền cho mã đã thử và đúng trong thư viện chuẩn. Điều đó làm giảm số lượng mã mà tôi, với tư cách là một lập trình viên, phải viết, kiểm tra, gỡ lỗi, v.v.
Joshua Taylor

1
Vui mừng khi tôi đang sử dụng Swift. if let worker = workerService.employee {print (worker.Id); }
gnasher729

23

Bên cạnh không có giá trị gia tăng nào khi có một Optionalgiá trị duy nhất. Như bạn đã thấy, chỉ cần thay thế một kiểm tra nullbằng một kiểm tra cho sự hiện diện.

Có giá trị gia tăng rất lớn khi có một Collectiontrong những điều như vậy. Tất cả các phương thức phát trực tuyến được giới thiệu để thay thế các vòng lặp cấp thấp kiểu cũ đều hiểu Optionalmọi thứ và thực hiện đúng về chúng (không xử lý chúng hoặc cuối cùng trả lại một unset khác Optional). Nếu bạn xử lý n mục thường xuyên hơn so với các mục riêng lẻ (và 99% tất cả các chương trình thực tế làm), thì đó là một cải tiến lớn để có hỗ trợ tích hợp cho các mục trình bày tùy ý. Trong trường hợp lý tưởng bạn không bao giờ phải kiểm tra null hoặc hiện diện.


24
Ngoài các bộ sưu tập / luồng, một Tùy chọn cũng rất tuyệt vời để làm cho các API tự ghi lại nhiều tài liệu hơn, ví dụ như Employee getEmployee()so với Optional<Employee> getEmployee(). Mặc dù về mặt kỹ thuật cả hai có thể trở lại null, một trong số chúng có nhiều khả năng gây rắc rối.
amon

16
Có rất nhiều giá trị trong một tùy chọn của một giá trị. Có thể .map()mà không cần phải kiểm tra null trên đường đi là tuyệt vời. Ví dụ Optional.of(service).map(Service::getEmployee).map(Employee::getId).ifPresent(this::submitIdForReview);.
Joshua Taylor

9
Tôi không đồng ý với nhận xét ban đầu của bạn về việc không có giá trị gia tăng. Tùy chọn cung cấp ngữ cảnh cho mã của bạn. Nhìn lướt qua có thể cho bạn biết chính xác về tính chính xác của chức năng và tất cả các trường hợp được bảo hiểm. Trong khi với kiểm tra null, bạn có nên kiểm tra null mỗi loại Tham chiếu duy nhất trên mỗi phương thức không? Một trường hợp tương tự có thể được áp dụng cho Lớp và công cụ sửa đổi công khai / riêng tư; họ thêm giá trị 0 vào mã của bạn, phải không?
ArTs

3
@JoshuaTaylor Thành thật mà nói, so với biểu hiện đó , tôi thích kiểm tra kiểu cũ hơn null.
Kilian Foth

2
Một ưu điểm lớn khác của Tùy chọn ngay cả với các giá trị đơn là bạn không thể quên kiểm tra null khi nào bạn nên làm. Nếu không, rất dễ quên việc kiểm tra và kết thúc với NullPulumException ...
Sean Burton

17

Miễn là bạn sử dụng Optionalgiống như một API ưa thích isNotNull(), thì có, bạn sẽ không tìm thấy bất kỳ sự khác biệt nào nếu chỉ kiểm tra null.

Những thứ bạn KHÔNG nên sử dụng Optionalcho

OptionalChỉ sử dụng để kiểm tra sự hiện diện của một giá trị là Bad Code ™:

// BAD CODE ™ --  just check getEmployee() != null  
Optional<Employee> employeeOptional =  Optional.ofNullable(employeeService.getEmployee());  
if(employeeOptional.isPresent()) {  
    Employee employee = employeeOptional.get();  
    System.out.println(employee.getId());
}  

Những thứ bạn nên sử dụng Optionalcho

Tránh một giá trị không có mặt, bằng cách tạo một giá trị khi cần thiết khi sử dụng các phương thức API trả về null:

Employee employee = Optional.ofNullable(employeeService.getEmployee())
                            .orElseGet(Employee::new);
System.out.println(employee.getId());

Hoặc, nếu tạo Nhân viên mới quá tốn kém:

Optional<Employee> employee = Optional.ofNullable(employeeService.getEmployee());
System.out.println(employee.map(Employee::getId).orElse("No employee found"));

Ngoài ra, làm cho mọi người biết rằng các phương thức của bạn có thể không trả về giá trị (nếu vì lý do nào đó, bạn không thể trả về giá trị mặc định như trên):

// Your code without Optional
public Employee getEmployee() {
    return someCondition ? null : someEmployee;
}
// Someone else's code
Employee employee = getEmployee(); // compiler doesn't complain
// employee.get...() -> NPE awaiting to happen, devs criticizing your code

// Your code with Optional
public Optional<Employee> getEmployee() {
    return someCondition ? Optional.empty() : Optional.of(someEmployee);
}
// Someone else's code
Employee employee = getEmployee(); // compiler complains about incompatible types
// Now devs either declare Optional<Employee>, or just do employee = getEmployee().get(), but do so consciously -- not your fault.

Và cuối cùng, có tất cả các phương thức giống như luồng đã được giải thích trong các câu trả lời khác, mặc dù những phương pháp đó không thực sự khiến bạn quyết định sử dụng Optionalmà sử dụng phương thức Optionalđược cung cấp bởi người khác.


1
@Kapep Thật vậy. Chúng tôi đã có cuộc tranh luận này tại / r / java và tôi nói : «Tôi thấy đó Optionallà một cơ hội bị bỏ lỡ. "Ở đây, có cái mới Optionalnày tôi đã làm để bạn buộc phải kiểm tra giá trị null. Ồ, và nhân tiện ... tôi đã thêm một get()phương thức cho nó để bạn thực sự không phải kiểm tra bất cứ thứ gì;);)" . (...) OptionalĐẹp như một dấu hiệu neon "NÀY MIGHT BE NULL", nhưng sự tồn tại trần trụi của get()phương pháp làm tê liệt nó » . Nhưng OP đã yêu cầu lý do để sử dụng Optional, không phải lý do chống lại nó hoặc lỗi thiết kế.
walen

1
Employee employee = Optional.ofNullable(employeeService.getEmployee());Trong đoạn thứ ba của bạn, không nên là loại Optional<Employee>?
Charlie Harding

2
@immibis Theo cách nào làm tê liệt? Lý do chính để sử dụng getlà nếu bạn phải tương tác với một số mã kế thừa. Tôi không thể nghĩ ra nhiều lý do hợp lệ trong mã mới để sử dụng get. Điều đó nói rằng khi tương tác với mã kế thừa thường không có sự thay thế nào, nhưng walen vẫn có một điểm là sự tồn tại của getnguyên nhân khiến người mới bắt đầu viết mã xấu.
Voo

1
@immibis Tôi đã không đề cập Mapmột lần và tôi không biết có ai thực hiện thay đổi tương thích ngược không mạnh mẽ như vậy, vì vậy chắc chắn bạn có thể cố tình thiết kế các API xấu với Optional- không chắc điều đó chứng tỏ điều gì. An Optionalsẽ có ý nghĩa cho một số tryGetphương pháp mặc dù.
Voo

2
@Voo Maplà một ví dụ mà mọi người đều quen thuộc. Tất nhiên, họ không thể Map.gettrả lại Tùy chọn vì khả năng tương thích ngược, nhưng bạn có thể dễ dàng tưởng tượng ra một lớp giống như Bản đồ khác sẽ không có các ràng buộc tương thích như vậy. Nói class UserRepository {Optional<User> getUserDetails(int userID) {...}}. Bây giờ bạn muốn lấy thông tin đăng nhập của người dùng và bạn biết họ tồn tại vì họ đã đăng nhập và hệ thống của bạn không bao giờ xóa người dùng. Vì vậy, bạn làm theUserRepository.getUserDetails(loggedInUserID).get().
Immibis

12

Điểm mấu chốt đối với tôi khi sử dụng Tùy chọn là giữ rõ ràng khi nhà phát triển cần kiểm tra xem hàm có trả về giá trị hay không.

//service 1
Optional<Employee> employeeOptional = employeeService.getEmployee();
if(employeeOptional.isPresent()){
    Employee employeeOptional= employeeOptional.get();
    System.out.println(employee.getId());
}

//service 2
Employee employee = employeeService.getEmployeeXTPO();
System.out.println(employee.getId());

8
Nó gây trở ngại cho tôi rằng tất cả các công cụ chức năng tiện lợi với các tùy chọn, mặc dù thực sự tốt đẹp để có, được coi là quan trọng hơn điểm này ngay tại đây. Trước java 8, bạn phải tìm hiểu qua Javadoc để biết liệu bạn có phải kiểm tra null phản hồi từ một cuộc gọi hay không. Đăng java 8, bạn biết từ loại trả về nếu bạn có thể lấy lại "không có gì" hay không.
Buhb

3
Chà, có một vấn đề "mã cũ" nơi mọi thứ vẫn có thể trả về null ... nhưng vâng, có thể nói từ chữ ký là tuyệt vời.
Haakon Løtveit

5
Vâng, nó chắc chắn hoạt động tốt hơn trong các ngôn ngữ trong đó tùy chọn <T> là cách duy nhất để thể hiện rằng một giá trị có thể bị thiếu, tức là các ngôn ngữ không có null ptr.
dureuill

8

Sai lầm chính của bạn là bạn vẫn đang suy nghĩ theo các thuật ngữ thủ tục hơn. Điều này không có nghĩa là một lời chỉ trích bạn như một người, nó chỉ là một quan sát. Suy nghĩ theo các thuật ngữ chức năng hơn đi kèm với thời gian và thực hành, và do đó các phương pháp là Hiện diện và có được vẻ như là những điều chính xác rõ ràng nhất để gọi cho bạn. Lỗi nhỏ thứ cấp của bạn là tạo Tùy chọn bên trong phương thức của bạn. Tùy chọn có nghĩa là để giúp tài liệu rằng một cái gì đó có thể hoặc không thể trả về một giá trị. Bạn có thể không nhận được gì.

Điều này đã dẫn đến việc bạn viết mã hoàn toàn dễ đọc có vẻ hoàn toàn hợp lý, nhưng bạn đã bị quyến rũ bởi những cám dỗ song sinh tệ hại có được và có mặt.

Tất nhiên câu hỏi nhanh chóng trở thành "tại sao isPftime và thậm chí đến đó?"

Một điều mà nhiều người ở đây bỏ lỡ đó là isPftime () là nó không phải là thứ được tạo ra cho mã mới được viết bởi những người hoàn toàn trên tàu với cách thức lambdas hữu ích, và những người thích chức năng này.

Tuy nhiên, nó mang lại cho chúng ta một vài (hai) lợi ích tốt, tuyệt vời, quyến rũ (?):

  • Nó giúp giảm bớt sự chuyển đổi của mã kế thừa để sử dụng các tính năng mới.
  • Nó làm giảm các đường cong học tập của Tùy chọn.

Cái đầu tiên khá đơn giản.

Hãy tưởng tượng bạn có một API trông như thế này:

public interface SnickersCounter {
  /** 
   * Provides a proper count of how many snickers have been consumed in total.
   */
  public SnickersCount howManySnickersHaveBeenEaten();

  /**
    * returns the last snickers eaten.<br>
    * If no snickers have been eaten null is returned for contrived reasons.
    */
  public Snickers lastConsumedSnickers();
}

Và bạn đã có một lớp kế thừa bằng cách sử dụng này (điền vào chỗ trống):

Snickers lastSnickers = snickersCounter.lastConsumedSnickers();
if(null == lastSnickers) {
  throw new NoSuchSnickersException();
}
else {
  consumer.giveDiabetes(lastSnickers);
}

Một ví dụ giả định để chắc chắn. Nhưng hãy chịu đựng tôi ở đây.

Java 8 hiện đã được khởi chạy và chúng tôi đang tranh giành để có được. Vì vậy, một trong những điều chúng tôi làm là chúng tôi muốn thay thế giao diện cũ của mình bằng một cái gì đó trả về Tùy chọn. Tại sao? Bởi vì như một người khác đã đề cập một cách ân cần: Điều này đưa ra phỏng đoán về việc liệu thứ gì đó có thể là null hay không. Điều này đã được những người khác chỉ ra. Nhưng bây giờ chúng tôi có một vấn đề. Hãy tưởng tượng chúng ta có (xin lỗi trong khi tôi nhấn alt + F7 bằng một phương thức vô tội), 46 vị trí mà phương thức này được gọi là mã kế thừa được kiểm tra tốt, thực hiện một công việc tuyệt vời khác. Bây giờ bạn phải cập nhật tất cả những điều này.

ĐÂY là nơi tỏa sáng.

Bởi vì bây giờ: Snickers lastSnickers = snickersC gặp.lastConsumedSnickers (); if (null == lastSnickers) {ném NoSuchSnickersException () mới; } other {Consumer.giveDzheim (LastSnickers); }

trở thành:

Optional<Snickers> lastSnickers = snickersCounter.lastConsumedSnickers();
if(!lastSnickers.isPresent()) {
  throw new NoSuchSnickersException();
}
else {
  consumer.giveDiabetes(lastSnickers.get());
}

Và đây là một thay đổi đơn giản mà bạn có thể cung cấp cho đàn em mới: Anh ấy có thể làm điều gì đó hữu ích, và anh ấy sẽ được khám phá cơ sở mã hóa cùng một lúc. đôi bên cùng có lợi Rốt cuộc, một cái gì đó giống với mô hình này là khá phổ biến. Và bây giờ bạn không phải viết lại mã để sử dụng lambdas hay bất cứ thứ gì. (Trong trường hợp cụ thể này sẽ là chuyện nhỏ, nhưng tôi để lại những ví dụ về việc nó sẽ khó khăn như một bài tập cho người đọc.)

Lưu ý rằng điều này có nghĩa là cách bạn đã làm về cơ bản là một cách để xử lý mã kế thừa mà không phải viết lại tốn kém. Vậy còn mã mới thì sao?

Chà, trong trường hợp của bạn, nơi bạn chỉ muốn in ra một cái gì đó, bạn chỉ cần làm:

snickersC gặp.lastConsumedSnickers (). ifPftime (System.out :: println);

Đó là khá đơn giản, và hoàn toàn rõ ràng. Điểm mà từ từ nổi lên trên bề mặt sau đó, là có các trường hợp sử dụng cho get () và isPftime (). Họ ở đó để cho phép bạn sửa đổi mã hiện có một cách máy móc để sử dụng các loại mới hơn mà không phải suy nghĩ quá nhiều về nó. Do đó, những gì bạn đang làm là sai lầm theo những cách sau:

  • Bạn đang gọi một phương thức có thể trả về null. Ý tưởng đúng sẽ là phương thức trả về null.
  • Bạn đang sử dụng các phương thức băng bó kế thừa để đối phó với tùy chọn này, thay vì sử dụng các phương thức mới ngon miệng có chứa lambda fanciness.

Nếu bạn muốn sử dụng Tùy chọn dưới dạng kiểm tra an toàn null đơn giản, điều bạn nên làm chỉ đơn giản là:

new Optional.ofNullable(employeeServive.getEmployee())
    .map(Employee::getId)
    .ifPresent(System.out::println);

Tất nhiên, phiên bản ưa nhìn của nó trông giống như:

employeeService.getEmployee()
    .map(Employee::getId)
    .ifPresent(System.out::println);

Nhân tiện, mặc dù không có nghĩa là bắt buộc, tôi khuyên bạn nên sử dụng một dòng mới cho mỗi thao tác để dễ đọc hơn. Dễ đọc và hiểu nhịp đập bất kỳ ngày nào trong tuần.

Tất nhiên đây là một ví dụ rất đơn giản, nơi dễ hiểu mọi thứ chúng tôi đang cố gắng thực hiện. Nó không phải lúc nào cũng đơn giản trong cuộc sống thực. Nhưng hãy chú ý làm thế nào trong ví dụ này, những gì chúng tôi thể hiện là ý định của chúng tôi. Chúng tôi muốn NHẬN nhân viên, NHẬN ID của anh ấy và nếu có thể, hãy in nó. Đây là chiến thắng lớn thứ hai với Tùy chọn. Nó cho phép chúng ta tạo mã rõ ràng hơn. Tôi cũng nghĩ rằng làm những việc như tạo ra một phương pháp thực hiện một loạt các công cụ để bạn có thể đưa nó vào bản đồ nói chung là một ý tưởng tốt.


1
Vấn đề ở đây là bạn cố gắng làm cho nó có vẻ như các phiên bản chức năng này có thể đọc được như các phiên bản cũ hơn, trong một trường hợp thậm chí nói một ví dụ là "hoàn toàn rõ ràng". Đó không phải là tình huống. Ví dụ đó là rõ ràng, nhưng không hoàn hảo như vậy, vì nó đòi hỏi nhiều suy nghĩ hơn. map(x).ifPresent(f)chỉ đơn giản là không làm cho cùng một loại cảm giác trực quan mà if(o != null) f(x)làm. Các ifphiên bản là hoàn toàn rõ ràng. Đây là một trường hợp khác của những người nhảy vào một đoàn xe phổ biến, * không * là một trường hợp tiến bộ thực sự.
Aaron

1
Lưu ý rằng tôi không nói rằng không có lợi ích trong việc sử dụng lập trình chức năng hoặc của Optional. Tôi chỉ đề cập đến việc sử dụng tùy chọn trong trường hợp cụ thể này và hơn thế nữa là khả năng đọc, vì nhiều người đang hành động như định dạng này là bằng hoặc dễ đọc hơn if.
Aaron

1
Nó phụ thuộc vào quan niệm của chúng tôi về sự rõ ràng. Trong khi bạn thực hiện những quan điểm rất tốt, tôi cũng muốn chỉ ra rằng với nhiều người, lambdas mới và lạ ở một số điểm. Tôi dám đặt một cookie internet mà khá nhiều người nói là Có mặt và nhận được và cảm thấy nhẹ nhõm thú vị: Bây giờ họ có thể tiếp tục với việc cập nhật mã kế thừa và sau đó học cú pháp lambda sau đó. Mặt khác, những người có Lisp, Haskell hoặc trải nghiệm ngôn ngữ tương tự có lẽ rất vui mừng khi giờ họ có thể áp dụng các mô hình quen thuộc cho các vấn đề của họ trong java ...
Haakon Løtveit

@Aaron - sự rõ ràng không phải là điểm của các loại Tùy chọn. Cá nhân tôi thấy rằng họ không làm giảm sự rõ ràng, và có một số trường hợp (mặc dù không phải trường hợp này) khi họ làm tăng nó, nhưng lợi ích chính là (miễn là bạn tránh sử dụng isPresentgetcàng nhiều càng tốt) nâng cao an toàn . Thật khó để viết mã không chính xác mà không kiểm tra sự vắng mặt của một giá trị nếu loại đó là Optional<Whatever>thay vì sử dụng nullable Whatever.
Jules

Ngay cả khi bạn lấy ngay các giá trị ra, miễn là bạn không sử dụngget , bạn buộc phải chọn làm gì khi giá trị bị thiếu xảy ra: bạn sẽ sử dụng orElse()(hoặc orElseGet()) để cung cấp mặc định hoặc orElseThrow()ném một lựa chọn ngoại lệ ghi lại bản chất của vấn đề , tốt hơn NPE chung là vô nghĩa cho đến khi bạn đào sâu vào dấu vết ngăn xếp.
Jules

0

Tôi không thấy lợi ích trong Phong cách 2. Bạn vẫn cần nhận ra rằng bạn cần kiểm tra null và bây giờ nó còn lớn hơn, do đó ít đọc hơn.

Phong cách tốt hơn sẽ là, nếu workerServive.getEmployee () sẽ trả về Tùy chọn và sau đó mã sẽ trở thành:

Optional<Employee> employeeOptional = employeeServive.getEmployee();
if(employeeOptional.isPresent()){
    Employee employee= employeeOptional.get();
    System.out.println(employee.getId());
}

Bằng cách này, bạn không thể gọiemployeeServive.getEmployee (). GetId () và bạn nhận thấy rằng bạn nên xử lý valoue tùy chọn bằng cách nào đó. Và nếu trong toàn bộ các phương thức cơ sở mã của bạn không bao giờ trả về null (hoặc gần như không bao giờ được biết đến với các ngoại lệ trong nhóm của bạn đối với quy tắc này), thì nó sẽ tăng tính an toàn chống lại NPE.


12
Tùy chọn trở thành một sự phức tạp vô nghĩa ngay lúc bạn sử dụng isPresent(). Chúng tôi sử dụng tùy chọn để chúng tôi không phải kiểm tra.
candied_orange

2
Giống như những người khác đã nói, không sử dụng isPresent(). Đây là cách tiếp cận sai với các tùy chọn. Sử dụng maphoặc flatMapthay thế.
Andres F.

1
@CandiedOrange Và câu trả lời được bình chọn hàng đầu (nói là sử dụng ifPresent) chỉ là một cách khác để kiểm tra.
Immibis

1
@CandiedOrange ifPresent(x)cũng giống như một bài kiểm tra if(present) {x}. Giá trị trả lại là không liên quan.
Immibis

1
@CandiedOrange Tuy nhiên, ban đầu bạn nói "vì vậy chúng tôi không phải kiểm tra." Bạn không thể nói "vì vậy chúng tôi không phải kiểm tra" khi thực tế bạn vẫn đang kiểm tra ... chính là bạn.
Aaron

0

Tôi nghĩ lợi ích lớn nhất là nếu chữ ký phương thức của bạn trả về một Employeebạn không kiểm tra null. Bạn biết bằng chữ ký đó rằng nó được đảm bảo để trả lại một nhân viên. Bạn không cần phải xử lý một trường hợp thất bại. Với một tùy chọn bạn biết bạn làm.

Trong mã tôi đã thấy có các kiểm tra null sẽ không bao giờ thất bại vì mọi người không muốn theo dõi mã để xác định xem có thể có null hay không, vì vậy họ ném séc null ở mọi nơi một cách phòng thủ. Điều này làm cho mã chậm hơn một chút, nhưng quan trọng hơn là nó làm cho mã nhiễu hơn nhiều.

Tuy nhiên, để làm việc này, bạn cần áp dụng mô hình này một cách nhất quán.


1
Cách đơn giản hơn để đạt được điều này là sử dụng @Nullable@ReturnValuesAreNonnullByDefaultcùng với phân tích tĩnh.
maaartinus

@maaartinus Thêm công cụ bổ sung dưới dạng phân tích tĩnh và tài liệu trong chú thích trang trí, có đơn giản hơn sau đó biên dịch hỗ trợ API thời gian không?
Sled

Thêm công cụ bổ sung thực sự là loại đơn giản hơn, vì nó không yêu cầu thay đổi mã. Hơn nữa, bạn cũng muốn có dù sao nó cũng bắt được các lỗi khác. +++ Tôi đã viết một kịch bản tầm thường thêm package-info.javavới @ReturnValuesAreNonnullByDefaulttới tất cả các gói của tôi, vì vậy tất cả tôi phải làm là thêm @Nullablenơi bạn phải chuyển sang Optional. Điều này có nghĩa là ít ký tự hơn, không thêm rác và không có thời gian chạy. Tôi không phải tìm tài liệu như nó hiển thị khi di chuột qua phương thức. Công cụ phân tích tĩnh bắt lỗi và thay đổi vô hiệu hóa sau này.
maaartinus

Mối quan tâm lớn nhất của tôi Optionallà nó bổ sung một cách khác để đối phó với sự vô hiệu. Nếu nó ở Java ngay từ đầu, nó có thể ổn, nhưng bây giờ nó chỉ là một nỗi đau. Đi theo con đường của Kotlin sẽ tốt hơn nhiều. +++ Lưu ý rằng đó List<@Nullable String>vẫn là một danh sách các chuỗi, trong khi List<Optional<String>>thì không.
maaartinus

0

Câu trả lời của tôi là: Chỉ cần không. Ít nhất hãy suy nghĩ kỹ về việc nó có thực sự là một sự cải tiến hay không.

Một biểu thức (lấy từ một câu trả lời khác) như

Optional.of(employeeService)                 // definitely have the service
        .map(EmployeeService::getEmployee)   // getEmployee() might return null
        .map(Employee::getId)                // get ID from employee if there is one
        .ifPresent(System.out::println);     // and if there is an ID, print it

dường như là cách thức chức năng phù hợp để xử lý các phương thức trả về ban đầu vô giá trị. Nó trông đẹp, nó không bao giờ ném và nó không bao giờ khiến bạn phải suy nghĩ về việc xử lý trường hợp "vắng mặt".

Nó trông tốt hơn so với cách cũ

Employee employee = employeeService.getEmployee();
if (employee != null) {
    ID id = employee.getId();
    if (id != null) {
        System.out.println(id);
    }
}

Điều này làm cho rõ ràng rằng có thể có elsecác mệnh đề.

Các phong cách chức năng

  • trông hoàn toàn khác biệt (và không thể đọc được đối với những người không sử dụng nó)
  • là một nỗi đau để gỡ lỗi
  • không thể dễ dàng gia hạn để xử lý trường hợp "vắng mặt" ( orElsekhông phải lúc nào cũng đủ)
  • đánh lạc hướng để bỏ qua trường hợp "vắng mặt"

Trong mọi trường hợp, nó nên được sử dụng như thuốc chữa bách bệnh. Nếu nó được áp dụng phổ biến, thì ngữ nghĩa của .toán tử sẽ được thay thế bằng ngữ nghĩa của ?.toán tử , NPE bị lãng quên và tất cả các vấn đề bị bỏ qua.


Trong khi là một người hâm mộ Java lớn, tôi phải nói rằng phong cách chức năng chỉ là một thay thế tuyên bố của người đàn ông Java nghèo

employeeService
.getEmployee()
?.getId()
?.apply(id => System.out.println(id));

nổi tiếng từ các ngôn ngữ khác. Chúng tôi có đồng ý rằng tuyên bố này

  • không (thực sự) có chức năng?
  • Rất giống với mã kiểu cũ, chỉ đơn giản hơn nhiều?
  • dễ đọc hơn nhiều so với phong cách chức năng?
  • có thân thiện với trình gỡ lỗi không?

Bạn dường như đang mô tả việc triển khai khái niệm này của C # với một tính năng ngôn ngữ hoàn toàn khác với triển khai của Java với một lớp thư viện cốt lõi. Họ trông giống hệt tôi
Caleth

@Caleth Không đời nào. Chúng ta có thể nói về "khái niệm đơn giản hóa một đánh giá không an toàn", nhưng tôi sẽ không gọi đó là "khái niệm". Chúng ta có thể nói rằng Java thực hiện điều tương tự bằng cách sử dụng một lớp thư viện lõi, nhưng nó chỉ là một mớ hỗn độn. Chỉ cần bắt đầu employeeService.getEmployee().getId().apply(id => System.out.println(id));và làm cho nó an toàn một lần bằng cách sử dụng toán tử điều hướng An toàn và một lần sử dụng Optional. Chắc chắn, chúng ta có thể gọi nó là "chi tiết thực hiện", nhưng vấn đề thực hiện. Có những trường hợp khi lamdas làm cho mã đơn giản và đẹp hơn, nhưng ở đây nó chỉ là một sự lạm dụng xấu xí.
maaartinus

Lớp thư viện: Optional.of(employeeService).map(EmployeeService::getEmployee).map(Employee::getId).ifPresent(System.out::println);Tính năng ngôn ngữ : employeeService.getEmployee()?.getId()?.apply(id => System.out.println(id));. Hai cú pháp, nhưng cuối cùng chúng trông rất giống tôi. Và "khái niệm" là Optionalaka NullableakaMaybe
Caleth

Tôi không hiểu tại sao bạn nghĩ một trong những điều đó là "lạm dụng xấu xí", và điều tốt đẹp khác. Tôi có thể hiểu bạn nghĩ cả "lạm dụng xấu xí" hoặc cả hai tốt.
Caleth

@Caleth Một điểm khác biệt là thay vì thêm hai dấu hỏi, bạn phải viết lại tất cả. Vấn đề không phải là tính năng Ngôn ngữ và mã lớp Thư viện khác nhau nhiều. Vậy là được rồi. Vấn đề là mã gốc và mã lớp Thư viện khác nhau rất nhiều. Cùng một ý tưởng, mã khác nhau. Điều đó thật tệ. +++Những gì bị lạm dụng là lamdas để xử lý vô hiệu. Lamdas vẫn ổn, nhưng Optionalkhông phải. Nullability đơn giản chỉ cần một sự hỗ trợ ngôn ngữ thích hợp như trong Kotlin. Một vấn đề khác: List<Optional<String>>không liên quan đến List<String>, nơi chúng ta cần chúng có liên quan.
maaartinus

0

Tùy chọn là một khái niệm (một loại bậc cao hơn) đến từ lập trình chức năng. Sử dụng nó sẽ giúp kiểm tra null lồng nhau và cứu bạn khỏi kim tự tháp diệt vong.

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.