Tại sao không nên sử dụng tùy chọn của Java 8 trong các đối số


392

Tôi đã đọc trên nhiều trang web Tùy chọn chỉ nên được sử dụng làm loại trả về và không được sử dụng trong các đối số phương thức. Tôi đang đấu tranh để tìm một lý do hợp lý tại sao. Ví dụ tôi có một đoạn logic có 2 tham số tùy chọn. Do đó, tôi nghĩ sẽ rất hợp lý khi viết chữ ký phương thức của mình như thế này (giải pháp 1):

public int calculateSomething(Optional<String> p1, Optional<BigDecimal> p2 {
    // my logic
}

Nhiều trang web chỉ định Tùy chọn không nên được sử dụng làm đối số phương thức. Với ý nghĩ này, tôi có thể sử dụng chữ ký phương thức sau và thêm một nhận xét Javadoc rõ ràng để xác định rằng các đối số có thể là null, hy vọng các nhà bảo trì trong tương lai sẽ đọc Javadoc và do đó luôn thực hiện kiểm tra null trước khi sử dụng các đối số (giải pháp 2) :

public int calculateSomething(String p1, BigDecimal p2) {
    // my logic
}

Ngoài ra, tôi có thể thay thế phương thức của mình bằng bốn phương thức công khai để cung cấp giao diện đẹp hơn và làm cho p1 và p2 rõ ràng hơn là tùy chọn (giải pháp 3):

public int calculateSomething() {
    calculateSomething(null, null);
}

public int calculateSomething(String p1) {
    calculateSomething(p1, null);
}

public int calculateSomething(BigDecimal p2) {
    calculateSomething(null, p2);
}

public int calculateSomething(String p1, BigDecimal p2) {
    // my logic
}

Bây giờ tôi thử viết mã của lớp để gọi đoạn logic này cho mỗi cách tiếp cận. Trước tiên tôi lấy hai tham số đầu vào từ một đối tượng khác trả về Optionals và sau đó, tôi gọi calculateSomething. Do đó, nếu giải pháp 1 được sử dụng, mã gọi sẽ như thế này:

Optional<String> p1 = otherObject.getP1();
Optional<BigInteger> p2 = otherObject.getP2();
int result = myObject.calculateSomething(p1, p2);

nếu giải pháp 2 được sử dụng, mã cuộc gọi sẽ như thế này:

Optional<String> p1 = otherObject.getP1();
Optional<BigInteger> p2 = otherObject.getP2();
int result = myObject.calculateSomething(p1.orElse(null), p2.orElse(null));

nếu giải pháp 3 được áp dụng, tôi có thể sử dụng mã ở trên hoặc tôi có thể sử dụng mã sau (nhưng đó là mã nhiều hơn đáng kể):

Optional<String> p1 = otherObject.getP1();
Optional<BigInteger> p2 = otherObject.getP2();
int result;
if (p1.isPresent()) {
    if (p2.isPresent()) {
        result = myObject.calculateSomething(p1, p2);
    } else {
        result = myObject.calculateSomething(p1);
    }
} else {
    if (p2.isPresent()) {
        result = myObject.calculateSomething(p2);
    } else {
        result = myObject.calculateSomething();
    }
}

Vì vậy, câu hỏi của tôi là: Tại sao nó được coi là thực hành xấu khi sử dụng Optionals làm đối số phương thức (xem giải pháp 1)? Nó có vẻ như là giải pháp dễ đọc nhất đối với tôi và làm cho rõ ràng nhất là các tham số có thể trống / null cho các nhà bảo trì trong tương lai. (Tôi biết rằng các nhà thiết kế Optionaldự định nó chỉ được sử dụng làm kiểu trả về, nhưng tôi không thể tìm thấy bất kỳ lý do logic nào để không sử dụng nó trong kịch bản này).


16
Nếu bạn đã sử dụng tùy chọn, bạn sẽ không phải kiểm tra xem tùy chọn được truyền dưới dạng tham số không null?
biziclop

17
Có, nhưng sẽ rõ ràng với một người khác đang duy trì mã trong tương lai rằng tham số có thể trống / null, do đó có khả năng tránh ngoại lệ con trỏ null trong tương lai
Neil Stevens

1
Ồ Các đối số Null được truyền vào một phương thức? Đó là Visual Basic. Nguyên tắc nào là vi phạm? SRP (có thể). Nó cũng vi phạm một nguyên tắc khác mà tên của tôi khiến tôi không thể đi theo dòng thông qua CHỈ thông tin cần thiết cho một phương thức hoặc chức năng để thực hiện công việc của mình.
Sáng

20
Về mặt lý thuyết, mọi thứ có thể là null giống như mọi phương thức trong thư viện có thể gọi System.exit (0). Bạn không thể kiểm tra chống lại tis và bạn không nên kiểm tra chống lại điều này. Tất cả mọi thứ bạn sẽ phải làm tất cả thời gian bạn thực tế nên (hầu như) không bao giờ làm. Giống như làm cho tất cả các tham số cuối cùng. Hãy để các công cụ của bạn giúp bạn ngăn chặn thay đổi giá trị tham số hoặc quên khởi tạo các trường thay vì làm cho mã của bạn không thể đọc được bằng hàng nghìn trận chung kết và càng nhiều kiểm tra null.
yeoman

6
Trên thực tế, chỉ cần sử dụng các chú thích NonNull / Nullable, đó là những gì bạn đang tìm kiếm trong tình huống này, không phải là tùy chọn.
Bill K

Câu trả lời:


202

Ồ, những kiểu mã hóa đó sẽ được thực hiện với một chút muối.

  1. (+) Truyền kết quả Tùy chọn sang phương pháp khác mà không cần phân tích ngữ nghĩa; để lại cho phương pháp, là khá ổn.
  2. (-) Sử dụng các tham số tùy chọn gây ra logic điều kiện bên trong các phương thức theo nghĩa đen là chống sản xuất.
  3. (-) Cần đóng gói một đối số trong Tùy chọn, là tối ưu cho trình biên dịch và thực hiện gói không cần thiết.
  4. (-) So với các tham số nullable Tùy chọn tốn kém hơn.

Nói chung: Tùy chọn thống nhất hai trạng thái, phải được làm sáng tỏ. Do đó phù hợp hơn cho kết quả hơn đầu vào, vì sự phức tạp của luồng dữ liệu.


38
Trên thực tế, có một Optionalsố đại diện cho một trong ba trạng thái: một nulltùy chọn, một tổ chức phi-null Optionalvới isPresent() == falsevà không null Optionalgói một giá trị thực tế.
biziclop

16
@biziclop vâng, một điểm không thể tránh khỏi đã bị chỉ trích. Nhưng ý định là chỉ có các biểu thức không null. Điều đó không mất nhiều thời gian, để nghe lời khuyên để tránh Tùy chọn trong một số trường hợp quá, là khá mỉa mai. Một @NotNull Optional.
Eggen

80
@biziclop Lưu ý rằng nếu bạn đang sử dụng Optionaltất cả, thì trạng thái 1 ( nullTùy chọn) thường chỉ ra lỗi lập trình, do đó bạn cũng có thể không xử lý (chỉ cần để nó ném NullPointerException)
user253751

50
"tối ưu cho trình biên dịch", "So với các tham số nullable Tùy chọn tốn kém hơn" - các đối số này có thể hợp lệ cho ngôn ngữ C và không phải cho ngôn ngữ Java. Các lập trình viên Java nên tập trung vào mã sạch, tính di động, khả năng kiểm tra, kiến ​​trúc tốt, tính mô đun, v.v., chứ không phải vào "Tùy chọn tốn kém hơn mà tham chiếu null". Và nếu bạn thấy rằng bạn cần tập trung vào tối ưu hóa vi mô, thì tốt hơn hết bạn nên bỏ qua Đối tượng, Danh sách, Generics và chuyển sang mảng và nguyên thủy (Tôi không muốn gây khó chịu ở đây, tôi chỉ chia sẻ ý kiến ​​của mình) .
Kacper86

15
"tối ưu cho trình biên dịch": Tối ưu hóa sớm là gốc rễ của mọi tội lỗi ... nếu bạn không viết mã quan trọng về hiệu suất (thường xảy ra khi cố gắng chỉ định giao diện sạch), điều này không phải là vấn đề đáng lo ngại.
Mohan

165

Bài viết hay nhất tôi từng thấy về chủ đề này được viết bởi Daniel Olszewski :

Mặc dù có thể rất hấp dẫn khi xem xét Tùy chọn cho các tham số phương thức không bắt buộc, một giải pháp như vậy nhạt so với các lựa chọn thay thế có thể khác. Để minh họa vấn đề, kiểm tra khai báo hàm tạo sau:

public SystemMessage(String title, String content, Optional<Attachment> attachment) {
    // assigning field values
}

Thoạt nhìn nó có thể là một quyết định thiết kế đúng đắn. Rốt cuộc, chúng tôi đánh dấu rõ ràng tham số đính kèm là tùy chọn. Tuy nhiên, như để gọi hàm tạo, mã máy khách có thể trở nên hơi vụng về.

SystemMessage withoutAttachment = new SystemMessage("title", "content", Optional.empty());
Attachment attachment = new Attachment();
SystemMessage withAttachment = new SystemMessage("title", "content", Optional.ofNullable(attachment));

Thay vì cung cấp sự rõ ràng, các phương thức xuất xưởng của lớp Tùy chọn chỉ làm người đọc mất tập trung. Lưu ý chỉ có một tham số tùy chọn, nhưng hãy tưởng tượng có hai hoặc ba. Chú Bob chắc chắn sẽ không tự hào về mã như vậy

Khi một phương thức có thể chấp nhận các tham số tùy chọn, tốt nhất nên áp dụng phương pháp đã được chứng minh và thiết kế trường hợp đó bằng cách sử dụng nạp chồng phương thức. Trong ví dụ về lớp SystemMessage, khai báo hai hàm tạo riêng biệt là tốt hơn so với sử dụng Tùy chọn.

public SystemMessage(String title, String content) {
    this(title, content, null);
}

public SystemMessage(String title, String content, Attachment attachment) {
    // assigning field values
}

Sự thay đổi đó làm cho mã máy khách đơn giản hơn và dễ đọc hơn.

SystemMessage withoutAttachment = new SystemMessage("title", "content");
Attachment attachment = new Attachment();
SystemMessage withAttachment = new SystemMessage("title", "content", attachment);

10
Đó là rất nhiều bản sao + dán khi chỉ có một hoặc hai đoạn có liên quan.
Garret Wilson

47
Thật không may, lời giải thích này không giải quyết được mối quan tâm khi người gọi ví dụ phân tích một số thông tin có thể là tùy chọn. Với quá tải phương thức (như được đề xuất ở trên), mã gọi phải nói: "Có phải là X không? Nếu vậy, tôi sẽ gọi phương thức bị quá tải A. Có phải là Y không? Tôi sẽ phải gọi phương thức quá tải B. Nếu X và Y có mặt, tôi sẽ phải gọi phương thức quá tải C. " Và như thế. Có thể có một câu trả lời tốt cho điều này, nhưng lời giải thích này về "tại sao" không bao gồm nó.
Garret Wilson

9
Ngoài ra, khi nói đến các bộ sưu tập, có một sự khác biệt rõ rệt giữa một bộ sưu tập trống và không có bộ sưu tập. Ví dụ, một bộ đệm. Có phải là một bộ nhớ cache bị mất? trống tùy chọn / null. Có một bộ nhớ cache xảy ra là một bộ sưu tập trống? đầy đủ tùy chọn / bộ sưu tập.
Ajax

1
@Ajax Tôi nghĩ bạn đang hiểu nhầm bài viết. Họ đang trích dẫn một điểm được ủng hộ bởi sách Java hiệu quả , nói rằng khi kiểu trả về của phương thức là Bộ sưu tập (không phải là một đối tượng tùy ý như bộ đệm sẽ trả về) thì bạn nên ưu tiên trả về một bộ sưu tập trống thay vì nullhoặc Optional.
Gili

4
Chắc chắn, bạn nên ủng hộ nó, nhưng bạn không luôn có quyền kiểm soát một số mã và, theo một nghĩa rất thực, bạn có thể muốn phân biệt giữa "có một giá trị và đó là một bộ sưu tập trống" so với "không có giá trị được xác định (chưa) ". Vì "ma thuật null" cũng là một cách làm nản lòng, nên tùy thuộc vào bạn, nhà phát triển, để chọn tùy chọn ít tệ nhất. Tôi thích trống tùy chọn để biểu thị một bộ nhớ cache thay vì bộ sưu tập trống, vì giá trị bộ đệm thực tế có thể một bộ sưu tập trống.
Ajax

86

Hầu như không có lý do chính đáng cho việc không sử dụng Optionallàm tham số. Các đối số chống lại điều này dựa trên các đối số từ chính quyền (xem Brian Goetz - đối số của anh ta là chúng ta không thể thực thi các tùy chọn không null) hoặc các Optionalđối số có thể là null (về cơ bản là cùng một đối số). Tất nhiên, bất kỳ tham chiếu nào trong Java đều có thể là null, chúng ta cần khuyến khích các quy tắc được thực thi bởi trình biên dịch, không phải bộ nhớ lập trình viên (có vấn đề và không mở rộng).

Ngôn ngữ lập trình chức năng khuyến khích Optionalcác tham số. Một trong những cách tốt nhất để sử dụng điều này là có nhiều tham số tùy chọn và sử dụng liftM2để sử dụng một hàm giả sử các tham số không trống và trả về một tùy chọn (xem http://www.feftaljava.org/javadoc/4.4/feftaljava/fj/ dữ liệu / Tùy chọn.html # nângM2-fj.F- ). Java 8 đã không may triển khai một thư viện rất hạn chế hỗ trợ tùy chọn.

Là lập trình viên Java, chúng ta chỉ nên sử dụng null để tương tác với các thư viện cũ.


3
Thay vào đó, làm thế nào về việc sử dụng @Nonnull@Nullabletừ javax.annotation? Tôi sử dụng chúng rộng rãi trong một thư viện do tôi phát triển và sự hỗ trợ do IDE (IntelliJ) cung cấp là rất tốt.
Rogério

1
Điều đó là tốt, nhưng bạn phải sử dụng chú thích này ở mọi nơi và bạn cần một thư viện để hỗ trợ các phương thức như map, FlatMap / bind, elevM2, trình tự, v.v.
Mark Perry

14
Ngôn ngữ lập trình chức năng khuyến khích các tham số tùy chọn. Cần dẫn nguồn. Hầu hết các chức năng không nên có một tùy chọn; thay vào đó, trách nhiệm của giao dịch (sử dụng các hàm bậc cao hơn phù hợp) với sự hiện diện hoặc vắng mặt của một giá trị nằm ở người gọi hàm được đề cập.
jub0bs

5
Điều này. Thật vậy, không có câu trả lời nào đủ thuyết phục và không ai trong số họ trả lời câu hỏi cụ thể này "tại sao sử dụng tùy chọn làm tham số phương thức được coi là thực tiễn xấu trong khi chú thích các tham số phương thức @NonNulllà hoàn toàn tốt nếu chúng phục vụ cùng một mục đích theo những cách khác nhau?" Đối với tôi, chỉ có một đối số có ý nghĩa ít nhất: "Nên sử dụng tùy chọn để cung cấp API tốt hơn có kiểu trả về rõ ràng. Tuy nhiên, đối với các đối số phương thức, có thể sử dụng các hàm quá tải." - tuy nhiên, tôi không nghĩ rằng đây là một đối số đủ mạnh.
Utku zdemir

1
Tất nhiên, thích hợp hơn với null, nhưng điều thậm chí còn tốt hơn là không cần nhiều giá trị tùy chọn ngay từ đầu vì thiết kế tốt: D
yeoman

12

Lời khuyên này là một biến thể của quy tắc "càng cụ thể càng tốt về đầu vào và càng cụ thể càng tốt về quy tắc đầu ra".

Thông thường nếu bạn có một phương thức lấy một giá trị không null đơn giản, bạn có thể ánh xạ nó qua Optional, vì vậy phiên bản đơn giản hoàn toàn không cụ thể hơn về các đầu vào. Tuy nhiên, có rất nhiều lý do có thể khiến bạn muốn yêu cầu một Optionalcuộc tranh luận:

  • bạn muốn chức năng của mình được sử dụng cùng với một API khác trả về Optional
  • Hàm của bạn sẽ trả về một cái gì đó khác hơn là một sản phẩm nào Optional nếu giá trị đã cho trống
  • Bạn nghĩ Optionalthật tuyệt vời khi bất cứ ai sử dụng API của bạn đều phải được yêu cầu tìm hiểu về nó ;-)

10

Các mô hình với Optionallà cho một để tránh trở lại null . Vẫn hoàn toàn có thể truyền vào nullmột phương thức.

Mặc dù chúng chưa thực sự chính thức, nhưng bạn có thể sử dụng các chú thích kiểu JSR-308 để cho biết liệu bạn có chấp nhận nullcác giá trị vào hàm hay không. Lưu ý rằng bạn phải có công cụ phù hợp để thực sự xác định nó và nó sẽ cung cấp nhiều kiểm tra tĩnh hơn là chính sách thời gian chạy có thể thi hành được, nhưng nó sẽ giúp ích.

public int calculateSomething(@NotNull final String p1, @NotNull final String p2) {}

Vấn đề ở đây là @NotNull không phải là trường hợp mặc định và phải được chú thích rõ ràng - do đó, bản tóm tắt và những thứ mà mọi người sẽ không làm do lười biếng.
bộ

1
@dwegener Có, nhưng Optionalcũng không khắc phục được sự cố, do đó, đề xuất của tôi cho các chú thích.
Makoto

2
Khó có thể bỏ qua Tùy chọn hơn là bỏ qua một chú thích mà bạn sẽ chỉ chú ý nếu đọc tài liệu / nguồn hoặc nếu công cụ của bạn có thể bắt được nó (phân tích tĩnh không thể luôn xác định chính xác không có giá trị).
Ajax

Lưu ý rằng @Nullable có thể không có nghĩa là bạn "chấp nhận" null là giá trị hợp lệ, nghĩa là không ném bất kỳ ngoại lệ nào. Nó có thể chỉ có nghĩa là bạn bảo vệ chống lại trường hợp này, nhưng vẫn sẽ đưa ra một ngoại lệ (Đây là ngữ nghĩa của Findbugs). Vì vậy, bạn có thể giới thiệu sự mơ hồ với các chú thích như vậy thay vì rõ ràng.
tkruse

Tôi không chắc có sự mơ hồ nào có @ user2986984. "Không xử lý null" chỉ có thể thực sự có nghĩa là một ngoại lệ được ném ra.
Makoto

7

Điều này có vẻ hơi ngớ ngẩn đối với tôi, nhưng lý do duy nhất tôi có thể nghĩ đến là các đối số đối tượng trong các tham số phương thức đã là tùy chọn theo cách - chúng có thể là null. Do đó, việc ép buộc ai đó lấy một đối tượng hiện có và bọc nó trong một tùy chọn là vô nghĩa.

Điều đó đang được nói, các phương pháp kết hợp các tùy chọn mất / trả lại là một điều hợp lý để làm, ví dụ Có thể là đơn nguyên.


7
Không thể trả về giá trị và các trường là null? Như vậy Optionallà vô nghĩa?
Phường Samuel Edwin


4

Tôi nghĩ rằng Tùy chọn phải là một Monad và những thứ này không thể hiểu được trong Java.

Trong lập trình chức năng, bạn xử lý các hàm thuần túy và bậc cao hơn, lấy và soạn thảo các đối số của chúng chỉ dựa trên "loại miền kinh doanh" của chúng. Soạn các hàm tạo ra hoặc phải báo cáo tính toán cho thế giới thực (được gọi là tác dụng phụ) yêu cầu ứng dụng các hàm tự động giải nén các giá trị ra khỏi các đơn vị đại diện cho thế giới bên ngoài (Trạng thái, Cấu hình, Tương lai, Có thể, Hoặc, Nhà văn, v.v ...); cái này được gọi là nâng. Bạn có thể nghĩ về nó như một loại tách biệt của mối quan tâm.

Trộn hai mức độ trừu tượng này không tạo điều kiện cho mức độ dễ đọc vì vậy tốt hơn hết là bạn nên tránh nó.


3

Một lý do khác cần cẩn thận khi truyền Optionaltham số dưới dạng là một phương thức nên thực hiện một điều ... Nếu bạn vượt qua một Optionaltham số bạn có thể ưu tiên thực hiện nhiều hơn một điều, nó có thể tương tự như vượt qua một tham số boolean.

public void method(Optional<MyClass> param) {
     if(param.isPresent()) {
         //do something
     } else {
         //do some other
     }
 }

Tôi nghĩ đó không phải là một lý do tốt. Logic tương tự có thể được áp dụng để kiểm tra nếu param là null khi không sử dụng tùy chọn. Trên thực tế, if(param.isPresent())đây không phải là cách tiếp cận tốt nhất để sử dụng Tùy chọn, thay vào đó bạn có thể sử dụng:param.forEach(() -> {...})
hdkrus

Tôi cũng không thích ý tưởng vượt qua các thông số vô giá trị. Dù sao, tôi đã nói rằng bạn có thể ủng hộ, tất nhiên có những trường hợp ngoại lệ bạn có thể sử dụng nó, tùy bạn, chỉ cần sử dụng nó một cách cẩn thận, đó là tất cả. Không có bất kỳ quy tắc nào áp dụng cho tất cả các kịch bản.
Pau

2

Chấp nhận Tùy chọn làm tham số gây ra gói không cần thiết ở cấp độ người gọi.

Ví dụ trong trường hợp:

public int calculateSomething(Optional<String> p1, Optional<BigDecimal> p2 {}

Giả sử bạn có hai chuỗi không null (nghĩa là được trả về từ một số phương thức khác):

String p1 = "p1"; 
String p2 = "p2";

Bạn buộc phải bọc chúng trong Tùy chọn ngay cả khi bạn biết chúng không trống.

Điều này thậm chí còn tồi tệ hơn khi bạn phải soạn thảo với các cấu trúc "có thể ánh xạ" khác, tức là. Eithers :

Either<Error, String> value = compute().right().map((s) -> calculateSomething(
< here you have to wrap the parameter in a Optional even if you know it's a 
  string >));

tham chiếu:

Các phương thức không nên mong đợi Tùy chọn làm tham số, đây hầu như luôn là mùi mã cho thấy sự rò rỉ của luồng điều khiển từ người gọi đến callee, trách nhiệm của người gọi là kiểm tra nội dung của Tùy chọn

tham chiếu https://github.com/teamdigitale/digital-citizenship-fifts/pull/148#discussion_r170862749


1

Tôi nghĩ đó là bởi vì bạn thường viết các hàm của mình để thao tác dữ liệu và sau đó nâng nó lên Optionalsử dụng mapvà các hàm tương tự. Điều này thêm Optionalhành vi mặc định cho nó. Tất nhiên, có thể có trường hợp, khi cần phải viết chức năng phụ trợ của riêng bạn hoạt động Optional.


1

Tôi tin rằng tiếng vang của sự tồn tại là trước tiên bạn phải kiểm tra xem Tùy chọn có tự rỗng hay không và sau đó thử đánh giá giá trị của nó. Quá nhiều xác nhận không cần thiết.


2
Vượt qua null Tham chiếu tùy chọn có thể được coi là lỗi lập trình (vì Tùy chọn cung cấp một cách rõ ràng để chuyển giá trị "trống"), do đó, không đáng để kiểm tra, trừ khi bạn muốn tránh ném ngoại lệ trong mọi trường hợp.
pvgoran

1

Tùy chọn không được thiết kế cho mục đích này, như được giải thích độc đáo bởi Brian Goetz .

Bạn luôn có thể sử dụng @Nullable để biểu thị rằng một đối số phương thức có thể là null. Sử dụng một tùy chọn không thực sự cho phép bạn viết logic phương thức của bạn gọn gàng hơn.


5
Tôi xin lỗi, nhưng tôi không thể đồng ý với lập luận "không được thiết kế" hoặc "ai đó khuyên bạn nên chống lại nó". Một kỹ sư chúng ta nên cụ thể. Sử dụng @Nullable tệ hơn nhiều khi sử dụng Tùy chọn, bởi vì Tùy chọn dài hơn nhiều so với quan điểm API. Tôi không thấy bất kỳ lý do chính đáng nào chống lại việc sử dụng Tùy chọn làm đối số (với điều kiện bạn không muốn sử dụng quá tải phương thức)
Kacper86

0

Một cách tiếp cận nữa, những gì bạn có thể làm là

// get your optionals first
Optional<String> p1 = otherObject.getP1();
Optional<BigInteger> p2 = otherObject.getP2();

// bind values to a function
Supplier<Integer> calculatedValueSupplier = () -> { // your logic here using both optional as state}

Khi bạn đã xây dựng một hàm (nhà cung cấp trong trường hợp này), bạn sẽ có thể chuyển xung quanh này như bất kỳ biến nào khác và có thể gọi nó bằng cách sử dụng

calculatedValueSupplier.apply();

Ý tưởng ở đây là liệu bạn có giá trị tùy chọn hay không sẽ là chi tiết nội bộ của hàm và sẽ không nằm trong tham số. Suy nghĩ các chức năng khi suy nghĩ về tùy chọn như tham số thực sự là kỹ thuật rất hữu ích mà tôi đã tìm thấy.

Đối với câu hỏi của bạn liệu bạn có thực sự nên làm điều đó hay không dựa trên sở thích của bạn, nhưng như những người khác nói, điều đó làm cho API của bạn trở nên xấu xí khi nói.


0

Lúc đầu, tôi cũng thích chuyển Tùy chọn làm tham số, nhưng nếu bạn chuyển từ phối cảnh Trình thiết kế API sang phối cảnh Người dùng API, bạn sẽ thấy những nhược điểm.

Ví dụ của bạn, trong đó mỗi tham số là tùy chọn, tôi sẽ đề nghị thay đổi phương thức tính toán thành một lớp riêng như sau:

Optional<String> p1 = otherObject.getP1();
Optional<BigInteger> p2 = otherObject.getP2();

MyCalculator mc = new MyCalculator();
p1.map(mc::setP1);
p2.map(mc::setP2);
int result = mc.calculate();

0

Tôi biết rằng câu hỏi này là về ý kiến ​​nhiều hơn là sự thật khó khăn. Nhưng gần đây tôi đã chuyển từ làm nhà phát triển .net sang java, vì vậy tôi mới chỉ tham gia vào nhóm Tùy chọn. Ngoài ra, tôi muốn nêu ý kiến ​​này như một nhận xét, nhưng vì mức điểm của tôi không cho phép tôi nhận xét, nên tôi buộc phải đặt câu hỏi này làm câu trả lời thay thế.

Những gì tôi đã và đang làm, đã phục vụ tôi tốt như một quy tắc của ngón tay cái. Là sử dụng Tùy chọn cho các loại trả về và chỉ sử dụng Tùy chọn làm tham số, nếu tôi yêu cầu cả giá trị của Tùy chọn và thời tiết hoặc không Tùy chọn có giá trị trong phương thức.

Nếu tôi chỉ quan tâm đến giá trị, tôi kiểm tra isPftime trước khi gọi phương thức, nếu tôi có một loại ghi nhật ký hoặc logic khác trong phương thức phụ thuộc vào giá trị tồn tại, thì tôi sẽ vui vẻ chuyển vào Tùy chọn.


0

Điều này là do chúng tôi có các yêu cầu khác nhau đối với người dùng API và nhà phát triển API.

Một nhà phát triển có trách nhiệm cung cấp một đặc điểm kỹ thuật chính xác và thực hiện đúng. Do đó, nếu nhà phát triển đã biết rằng một đối số là tùy chọn, thì việc triển khai phải giải quyết chính xác, cho dù đó là null hay là Tùy chọn. API phải đơn giản nhất có thể đối với người dùng và null là đơn giản nhất.

Mặt khác, kết quả được truyền từ nhà phát triển API đến người dùng. Tuy nhiên, đặc điểm kỹ thuật đã hoàn tất và dài dòng, vẫn có khả năng người dùng không biết về nó hoặc chỉ lười biếng để đối phó với nó. Trong trường hợp này, kết quả Tùy chọn buộc người dùng phải viết thêm một số mã để xử lý kết quả trống có thể.


0

Trước hết, nếu bạn đang sử dụng phương thức 3, bạn có thể thay thế 14 dòng mã cuối cùng này bằng:

int result = myObject.calculateSomething(p1.orElse(null), p2.orElse(null));

Bốn biến thể bạn đã viết là phương pháp thuận tiện . Bạn chỉ nên sử dụng chúng khi chúng thuận tiện hơn. Đó cũng là cách tiếp cận tốt nhất. Theo cách đó, API rất rõ ràng thành viên nào là cần thiết và không phải thành viên nào. Nếu bạn không muốn viết bốn phương thức, bạn có thể làm rõ mọi thứ bằng cách bạn đặt tên cho các tham số của mình:

public int calculateSomething(String p1OrNull, BigDecimal p2OrNull)

Bằng cách này, rõ ràng là giá trị null được cho phép.

Việc bạn sử dụng p1.orElse(null)minh họa cách mã dài dòng của chúng tôi nhận được khi sử dụng Tùy chọn, đó là một phần lý do tại sao tôi tránh nó. Tùy chọn đã được viết cho lập trình chức năng. Luồng cần nó. Các phương thức của bạn có lẽ không bao giờ nên trả về Tùy chọn trừ khi cần sử dụng chúng trong lập trình chức năng. Có các phương thức, giống như Optional.flatMap()phương thức, yêu cầu tham chiếu đến một hàm trả về Tùy chọn. Đây là chữ ký của nó:

public <U> Optional<U> flatMap(Function<? super T, ? extends Optional<? extends U>> mapper)

Vì vậy, đó thường là lý do chính đáng duy nhất để viết một phương thức trả về Tùy chọn. Nhưng ngay cả ở đó, nó có thể tránh được. Bạn có thể chuyển một getter không trả về Tùy chọn cho một phương thức như FlatMap (), bằng cách gói nó trong một phương thức khác để chuyển đổi hàm thành đúng loại. Phương thức trình bao bọc trông như thế này:

public static <T, U> Function<? super T, Optional<U>> optFun(Function<T, U> function) {
    return t -> Optional.ofNullable(function.apply(t));
}

Vì vậy, giả sử bạn có một getter như thế này: String getName()

Bạn không thể chuyển nó vào FlatMap như thế này:

opt.flatMap(Widget::getName) // Won't work!

Nhưng bạn có thể vượt qua nó như thế này:

opt.flatMap(optFun(Widget::getName)) // Works great!

Ngoài lập trình chức năng, nên tránh các tùy chọn.

Brian Goetz đã nói điều đó tốt nhất khi ông nói điều này:

Lý do Tùy chọn đã đượ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;

1
Chà, đó là MỘT trong những lý do nó được thêm vào Java. Có rất nhiều người khác. Thay thế các chuỗi dài các câu lệnh 'if' lồng vào nhau trong cấu trúc dữ liệu bằng một chuỗi các cuộc gọi được xâu chuỗi đến Options.map là sở thích cá nhân của tôi. Thế giới lập trình Ngôn ngữ chức năng có nhiều cách sử dụng thú vị cho Tùy chọn bên cạnh việc thay thế các kiểm tra null như thế này.
Một số Guy
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.