Java 8 getters có nên trả về loại tùy chọn không?


288

Optional Kiểu được giới thiệu trong Java 8 là một điều mới đối với nhiều nhà phát triển.

Là một phương thức getter trả về Optional<Foo>loại thay cho cổ điển Foolà một thực hành tốt? Giả sử rằng giá trị có thể null.


8
Mặc dù điều này có khả năng thu hút các câu trả lời có ý kiến, nhưng nó là một câu hỏi hay. Tôi đang mong chờ một câu trả lời với sự thật có thật về chủ đề này.
Justin

8
Câu hỏi đặt ra là liệu tính không hợp lệ là không thể tránh khỏi. Một thành phần có thể có một thuộc tính được phép là null, tuy nhiên, lập trình viên sử dụng thành phần đó có thể quyết định giữ nghiêm ngặt thuộc tính đó null. Vì vậy, các lập trình viên không nên phải đối phó với Optionalsau đó. Hay nói cách khác, nullthực sự đại diện cho sự vắng mặt của một giá trị như với kết quả tìm kiếm (khi Optionalthích hợp) hoặc nullchỉ là một thành viên của tập hợp các giá trị có thể.
Holger

1
Xem thêm các cuộc thảo luận về @NotNullcác chú thích: stackoverflow.com/q/4963300/873282
koppor

Câu trả lời:


515

Tất nhiên, mọi người sẽ làm những gì họ muốn. Nhưng chúng tôi đã có một ý định rõ ràng khi thêm tính năng này và nó không phải là mục đích chung Có thể loại, nhiều như nhiều người sẽ thích chúng tôi làm như vậy. Ý định của chúng tôi là cung cấp một cơ chế giới hạn cho các kiểu trả về của phương thức thư viện khi cần có một cách rõ ràng để biểu thị "không có kết quả" và việc sử dụng nullnhư vậy rất có thể gây ra lỗi.

Ví dụ, có lẽ bạn không bao giờ nên sử dụng nó cho một cái gì đó trả về một mảng kết quả hoặc danh sách kết quả; thay vào đó trả về một mảng hoặc danh sách trống. Bạn gần như không bao giờ nên sử dụng nó như một trường của một cái gì đó hoặc một tham số phương thức.

Tôi nghĩ rằng thường xuyên sử dụng nó như một giá trị trả lại cho getters chắc chắn sẽ được sử dụng quá mức.

Không có gì sai với Tùy chọn mà nên tránh, đó không phải là điều mà nhiều người mong muốn, và do đó chúng tôi khá lo ngại về nguy cơ sử dụng quá nhiệt tình.

(Thông báo dịch vụ công cộng: KHÔNG BAO GIỜ gọi Optional.gettrừ khi bạn có thể chứng minh điều đó sẽ không bao giờ được null, thay vào đó sử dụng một trong những phương pháp an toàn như orElsehay ifPresent. Nhìn lại, chúng ta nên đã kêu gọi getmột cái gì đó giống như getOrElseThrowNoSuchElementExceptionhoặc cái gì đó làm cho nó xa rõ ràng rằng đây là một phương pháp rất nguy hiểm điều đó làm suy yếu toàn bộ mục đích của Optionalnơi đầu tiên. Bài học kinh nghiệm (CẬP NHẬT: Java 10 có Optional.orElseThrow(), tương đương về mặt ngữ nghĩa với get(), nhưng tên của nó phù hợp hơn.))


24
(Về phần cuối) ... và khi chúng tôi tin tưởng, rằng giá trị là không bao giờ nullchúng ta có thể sử dụng orElseThrow(AssertionError::new), e hèm hoặc orElseThrow(NullPointerException::new)...
Holger

25
Điều gì sẽ bạn đã làm khác đi nếu ý định của bạn đã được giới thiệu một mục đích chung Có lẽ hoặc một số loại? Có cách nào mà Tùy chọn không phù hợp với hóa đơn cho một người hay chỉ là việc giới thiệu Tùy chọn trên toàn bộ API mới sẽ khiến nó không phải là Java-ish?
David Moles

22
Theo "loại mục đích chung", tôi có nghĩa là xây dựng nó thành hệ thống loại ngôn ngữ, thay vì cung cấp một lớp thư viện gần đúng với nó. (Một số ngôn ngữ có loại cho T? (T hoặc null) và T! (T không thể null)) Tùy chọn chỉ là một lớp; chúng tôi không thể thực hiện chuyển đổi ngầm giữa Foo và <Foo> tùy chọn như chúng tôi có thể có với sự hỗ trợ ngôn ngữ.
Brian Goetz

11
Tôi đã tự hỏi một thời gian tại sao sự nhấn mạnh trong thế giới Java là về Tùy chọn và không phải là phân tích tĩnh tốt hơn. Tùy chọn có một số lợi thế, nhưng lợi thế lớn nullcó khả năng tương thích ngược; Map::gettrả về một giá trị nullable V, không phải là an Optional<V>và sẽ không bao giờ thay đổi. Nó có thể dễ dàng được chú thích @Nullable, mặc dù. Bây giờ chúng tôi có hai cách để thể hiện sự thiếu giá trị, cộng với ít động lực hơn để thực sự có được phân tích tĩnh xảy ra, có vẻ như là một vị trí tồi tệ hơn.
yshavit

21
Tôi không biết rằng việc sử dụng nó làm giá trị trả lại cho một tài sản không phải là những gì bạn dự định cho đến khi tôi đọc câu trả lời này ở đây trên StackOverflow. Trên thực tế, tôi đã tin rằng đó chính xác là những gì bạn dự định sau khi đọc bài viết này trên trang web của Oracle: oracle.com/technetwork/articles/java/ nam Cảm ơn bạn đã làm rõ
Jason Thompson

73

Sau khi thực hiện một số nghiên cứu của riêng tôi, tôi đã tìm thấy một số điều có thể gợi ý khi điều này là phù hợp. Có thẩm quyền nhất là trích dẫn sau đây từ một bài viết của Oracle:

"Điều quan trọng cần lưu ý là ý định của lớp Tùy chọn không phảithay thế mọi tham chiếu null đơn lẻ . Thay vào đó, mục đích của nó là giúp thiết kế các API dễ hiểu hơn để chỉ cần đọc chữ ký của phương thức, bạn có thể biết liệu bạn có biết không có thể mong đợi một giá trị tùy chọn. Điều này buộc bạn phải chủ động hủy một Tùy chọn để đối phó với việc không có giá trị. " - Mệt mỏi vì ngoại lệ con trỏ Null? Cân nhắc sử dụng tùy chọn của Java SE 8!

Tôi cũng tìm thấy đoạn trích này từ Java 8 Tùy chọn: Cách sử dụng nó

"Tùy chọn không có nghĩa là được sử dụng trong các bối cảnh này, vì nó sẽ không mua cho chúng tôi bất cứ điều gì:

  • trong lớp mô hình miền (không tuần tự hóa)
  • trong DTOs (cùng lý do)
  • trong các tham số đầu vào của phương thức
  • trong tham số hàm tạo "

Mà dường như cũng tăng một số điểm hợp lệ.

Tôi không thể tìm thấy bất kỳ ý nghĩa tiêu cực hoặc cờ đỏ nào để đề xuất rằng Optionalnên tránh. Tôi nghĩ ý tưởng chung là, nếu nó hữu ích hoặc cải thiện khả năng sử dụng API của bạn, hãy sử dụng nó.


11
stackoverflow.com/questions/25693309 - có vẻ như Jackson đã hỗ trợ nó rồi, vì vậy "không tuần tự hóa" không còn vượt qua như một lý do hợp lệ :)
Vlasec

1
Tôi đề nghị có địa chỉ câu trả lời của bạn tại sao sử dụng Optionaltrong các tham số đầu vào của phương thức (cụ thể hơn là các hàm tạo) "sẽ không mua cho chúng tôi bất cứ thứ gì". dolszewski.com/java/java-8-optional-use-case chứa một lời giải thích hay.
Gili

Tôi đã thấy rằng việc sắp xếp các kết quả tùy chọn cần chuyển đổi thành các API khác yêu cầu một tham số Tùy chọn. Kết quả là một API khá dễ hiểu. Xem stackoverflow.com/a/31923105/105870
Karl the Pagan

1
Liên kết bị hỏng. Bạn có thể vui lòng cập nhật câu trả lời?
softarn

2
Vấn đề là, nó thường không cải thiện API, mặc dù ý định tốt nhất của nhà phát triển. Tôi đã được thu thập các ví dụ về các cách sử dụng xấu của Tùy chọn , tất cả được lấy từ mã sản xuất, mà bạn có thể kiểm tra.
MiguelMunoz

20

Nói chung, tôi nên sử dụng loại tùy chọn cho các giá trị trả về có thể là null. Tuy nhiên, wrt to framework Tôi giả định rằng việc thay thế các getters cổ điển bằng các loại tùy chọn sẽ gây ra nhiều rắc rối khi làm việc với các framework (ví dụ, Hibernate) dựa trên các quy ước mã hóa cho getters và setters.


14
Lời khuyên này chính xác là điều tôi muốn nói bởi "chúng tôi lo ngại về nguy cơ lạm dụng quá nhiệt tình" trong stackoverflow.com/a/26328555/3553087 .
Brian Goetz

13

Lý do Optionalđã được thêm vào Java là vì điều này:

return Arrays.asList(enclosingInfo.getEnclosingClass().getDeclaredMethods())
    .stream()
    .filter(m -> Objects.equals(m.getName(), enclosingInfo.getName())
    .filter(m ->  Arrays.equals(m.getParameterTypes(), parameterClasses))
    .filter(m -> Objects.equals(m.getReturnType(), returnType))
    .findFirst()
    .getOrThrow(() -> new InternalError(...));

sạch hơn thế này:

Method matching =
    Arrays.asList(enclosingInfo.getEnclosingClass().getDeclaredMethods())
    .stream()
    .filter(m -> Objects.equals(m.getName(), enclosingInfo.getName())
    .filter(m ->  Arrays.equals(m.getParameterTypes(), parameterClasses))
    .filter(m -> Objects.equals(m.getReturnType(), returnType))
    .getFirst();
if (matching == null)
  throw new InternalError("Enclosing method not found");
return matching;

Quan điểm của tôi là Tùy chọn được viết để hỗ trợ lập trình chức năng , được thêm vào Java cùng một lúc. (Ví dụ được cung cấp bởi một blog của Brian Goetz . Một ví dụ tốt hơn có thể sử dụng orElse()phương thức này, vì mã này sẽ đưa ra một ngoại lệ, nhưng bạn sẽ có được hình ảnh.)

Nhưng bây giờ, mọi người đang sử dụng Tùy chọn vì một lý do rất khác. Họ đang sử dụng nó để giải quyết một lỗ hổng trong thiết kế ngôn ngữ. Lỗ hổng là thế này: Không có cách nào để chỉ định tham số và giá trị trả về nào của API được phép là null. Nó có thể được đề cập trong javadocs, nhưng hầu hết các nhà phát triển thậm chí không viết javadocs cho mã của họ và không nhiều người sẽ kiểm tra javadocs khi họ viết. Vì vậy, điều này dẫn đến rất nhiều mã luôn kiểm tra các giá trị null trước khi sử dụng chúng, mặc dù chúng thường không thể là null vì chúng đã được xác thực liên tục chín hoặc mười lần trong ngăn xếp cuộc gọi.

Tôi nghĩ rằng đã có một khao khát thực sự để giải quyết lỗ hổng này, bởi vì rất nhiều người nhìn thấy lớp Tùy chọn mới cho rằng mục đích của nó là để thêm sự rõ ràng cho các API. Đó là lý do tại sao mọi người đặt câu hỏi như "getters có nên trả về Tùy chọn không?" Không, có lẽ họ không nên, trừ khi bạn mong đợi getter được sử dụng trong lập trình chức năng, điều này rất khó xảy ra. Trong thực tế, nếu bạn nhìn vào nơi Tùy chọn được sử dụng trong API Java, thì chủ yếu là trong các lớp Stream, vốn là cốt lõi của lập trình chức năng. (Tôi đã không kiểm tra rất kỹ, nhưng các lớp Stream có thể là nơi duy nhất chúng được sử dụng.)

Nếu bạn có kế hoạch sử dụng một getter trong một chút mã chức năng, thì có thể có một getter tiêu chuẩn và một thứ hai trả về Tùy chọn.

Ồ, và nếu bạn cần lớp của mình được tuần tự hóa, bạn tuyệt đối không nên sử dụng Tùy chọn.

Tùy chọn là một giải pháp rất tệ cho lỗ hổng API vì a) chúng rất dài dòng và b) Chúng không bao giờ có ý định giải quyết vấn đề đó ngay từ đầu.

Một giải pháp tốt hơn cho lỗ hổng API là Trình kiểm tra Nullness . Đây là bộ xử lý chú thích cho phép bạn chỉ định tham số nào và giá trị trả về được phép là null bằng cách chú thích chúng với @Nullable. Bằng cách này, trình biên dịch có thể quét mã và tìm hiểu xem một giá trị thực sự có thể là null đang được chuyển đến một giá trị không cho phép null. Theo mặc định, nó cho rằng không có gì được phép là null trừ khi nó được chú thích như vậy. Bằng cách này, bạn không phải lo lắng về các giá trị null. Truyền giá trị null cho tham số sẽ dẫn đến lỗi trình biên dịch. Việc kiểm tra một đối tượng cho null không thể tạo ra cảnh báo trình biên dịch. Tác dụng của việc này là thay đổi NullPulumException từ lỗi thời gian chạy sang lỗi thời gian biên dịch.

Điều này thay đổi mọi thứ.

Đối với getters của bạn, không sử dụng tùy chọn. Và cố gắng thiết kế các lớp học của bạn để không thành viên nào có thể là null. Và có thể thử thêm Trình kiểm tra Nullness vào dự án của bạn và khai báo getters và setter tham số @Nullable nếu họ cần. Tôi chỉ thực hiện điều này với các dự án mới. Nó có thể tạo ra rất nhiều cảnh báo trong các dự án hiện có được viết với rất nhiều bài kiểm tra không cần thiết cho null, vì vậy có thể rất khó để trang bị thêm. Nhưng nó cũng sẽ bắt được rất nhiều lỗi. Tôi thích nó. Mã của tôi sạch hơn và đáng tin cậy hơn vì nó.

(Ngoài ra còn có một ngôn ngữ mới giải quyết vấn đề này. Kotlin, biên dịch thành mã byte Java, cho phép bạn chỉ định nếu một đối tượng có thể là null khi bạn khai báo nó. Đó là một cách tiếp cận sạch hơn.)

Phụ lục cho bài gốc (phiên bản 2)

Sau khi suy nghĩ rất nhiều, tôi miễn cưỡng đưa ra kết luận rằng có thể chấp nhận trả lại Tùy chọn với một điều kiện: Giá trị được truy xuất có thể thực sự là null. Tôi đã thấy rất nhiều mã nơi mọi người thường xuyên trả về Tùy chọn từ các getters mà không thể trả về null. Tôi thấy đây là một thực hành mã hóa rất tệ, chỉ làm tăng thêm độ phức tạp cho mã, điều này làm cho các lỗi có nhiều khả năng hơn. Nhưng khi giá trị trả về thực sự có thể là null, hãy tiếp tục và bọc nó trong Tùy chọn.

Hãy nhớ rằng các phương thức được thiết kế để lập trình chức năng và yêu cầu tham chiếu chức năng, sẽ (và nên) được viết dưới hai dạng, một trong số đó sử dụng Tùy chọn. Ví dụ, Optional.map()Optional.flatMap()cả hai đều tham khảo chức năng. Cái đầu tiên lấy tham chiếu đến một getter thông thường, và cái thứ hai lấy một cái trả về Tùy chọn. Vì vậy, bạn sẽ không làm bất cứ ai ủng hộ bằng cách trả lại Tùy chọn trong đó giá trị không thể là null.

Đã nói tất cả, tôi vẫn thấy cách tiếp cận được sử dụng bởi Trình kiểm tra Nullness là cách tốt nhất để xử lý null, vì chúng biến NullPulumExceptions từ lỗi thời gian chạy sang biên dịch lỗi thời gian.


2
Câu trả lời này có vẻ phù hợp nhất với tôi. Tùy chọn chỉ được thêm vào trong java 8 khi luồng được thêm vào. Và chỉ các hàm stream trả về tùy chọn theo như tôi đã thấy.
Archit

1
Tôi nghĩ rằng đây là một trong những câu trả lời tốt nhất. Mọi người biết những gì là tùy chọn và làm thế nào nó hoạt động. Nhưng phần khó hiểu nhất là / là nơi sử dụng nó và cách sử dụng nó. bằng cách đọc nó sẽ xóa rất nhiều nghi ngờ.
ParagFlume

3

Nếu bạn đang sử dụng các trình tuần tự hóa hiện đại và các khung công tác khác hiểu Optionalthì tôi đã thấy các hướng dẫn này hoạt động tốt khi viết Entitycác lớp đậu và miền:

  1. Nếu lớp tuần tự hóa (thường là DB) cho phép một nullgiá trị cho một ô trong cột BARtrong bảng FOO, thì getter Foo.getBar()có thể trả về Optionalcho nhà phát triển rằng giá trị này có thể được dự kiến ​​là null và họ nên xử lý điều này. Nếu DB đảm bảo giá trị sẽ không có giá trị thì getter không nên bọc nó trong một Optional.
  2. Foo.barnên privatekhông được Optional. Thực sự không có lý do cho nó là Optionalnếu nó là private.
  3. Các setter Foo.setBar(String bar)nên có loại barkhông Optional . Nếu bạn có thể sử dụng một nullđối số thì hãy nêu điều này trong bình luận JavaDoc. Nếu không ổn khi sử dụng nullmột IllegalArgumentExceptionhoặc một số logic kinh doanh phù hợp thì IMHO, phù hợp hơn.
  4. Các nhà xây dựng không cần Optionalđối số (vì lý do tương tự như điểm 3). Nói chung tôi chỉ bao gồm các đối số trong hàm tạo phải không rỗng trong cơ sở dữ liệu tuần tự hóa.

Để làm cho việc trên hiệu quả hơn, bạn có thể muốn chỉnh sửa các mẫu IDE của mình để tạo getters và các mẫu tương ứng toString(), equals(Obj o)v.v. hoặc sử dụng các trường trực tiếp cho các mẫu đó (hầu hết các trình tạo IDE đã xử lý null).

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.