Tại sao người ta nên sử dụng Object.requireNonNull ()?


213

Tôi đã lưu ý rằng nhiều phương thức Java 8 trong Oracle JDK sử dụng Objects.requireNonNull(), mà bên trong sẽ ném NullPointerExceptionnếu đối tượng (đối số) đã cho là null.

public static <T> T requireNonNull(T obj) {
    if (obj == null)
        throw new NullPointerException();
    return obj;
}

Nhưng NullPointerExceptiondù sao cũng sẽ bị ném nếu một nullđối tượng bị hủy đăng ký. Vì vậy, tại sao một người nên làm thêm kiểm tra null và ném NullPointerException?

Một câu trả lời rõ ràng (hoặc lợi ích) là nó làm cho mã dễ đọc hơn và tôi đồng ý. Tôi muốn biết bất kỳ lý do nào khác để sử dụng Objects.requireNonNull()khi bắt đầu phương pháp.




1
Cách tiếp cận đó để kiểm tra đối số cho phép bạn gian lận khi bạn viết bài kiểm tra đơn vị. Spring cũng có các tiện ích như thế (xem docs.spring.io/spring/docs/civerse/javadoc-api/org/ mẹo ). Nếu bạn có "nếu" và muốn có phạm vi kiểm tra đơn vị cao, bạn phải bảo hiểm cả hai nhánh: khi điều kiện được đáp ứng và khi điều kiện không được đáp ứng. Nếu bạn sử dụng Objects.requireNonNull, mã của bạn không có phân nhánh, do đó, một lần kiểm tra đơn vị sẽ giúp bạn được bảo hiểm 100% :-)
xorcus

Câu trả lời:


214

Bởi vì bạn có thể làm cho mọi thứ rõ ràng bằng cách làm như vậy. Giống:

public class Foo {
  private final Bar bar;

  public Foo(Bar bar) {
    Objects.requireNonNull(bar, "bar must not be null");
    this.bar = bar;
  }

Hoặc ngắn hơn:

  this.bar = Objects.requireNonNull(bar, "bar must not be null");

Bây giờ bạn biết :

  • khi một đối tượng Foo được tạo thành công bằng cách sử dụngnew()
  • sau đó trường thanh của nó được đảm bảo là không null.

So sánh điều đó với: bạn tạo một đối tượng Foo hôm nay và ngày mai bạn gọi một phương thức sử dụng trường đó và ném. Rất có thể, bạn sẽ không biết ngày mai tại sao tài liệu tham khảo đó không có giá trị vào ngày hôm qua khi nó được chuyển cho nhà xây dựng!

Nói cách khác: bằng cách sử dụng rõ ràng phương pháp này để kiểm tra các tham chiếu đến, bạn có thể kiểm soát thời điểm khi ngoại lệ sẽ được ném. Và hầu hết thời gian, bạn muốn thất bại nhanh nhất có thể !

Những ưu điểm chính là:

  • như đã nói, hành vi được kiểm soát
  • gỡ lỗi dễ dàng hơn - bởi vì bạn ném lên trong bối cảnh tạo đối tượng. Tại một thời điểm mà bạn có một cơ hội nhất định rằng nhật ký / dấu vết của bạn cho bạn biết điều gì đã xảy ra!
  • và như được hiển thị ở trên: sức mạnh thực sự của ý tưởng này mở ra kết hợp với các lĩnh vực cuối cùng . Bởi vì bây giờ bất kỳ mã nào khác trong lớp của bạn đều có thể cho rằng barkhông phải là null - và do đó bạn không cần bất kỳ if (bar == null)kiểm tra nào ở những nơi khác!

23
Bạn có thể làm cho mã của mình gọn hơn bằng cách viếtthis.bar = Objects.requireNonNull(bar, "bar must not be null");
Kirill Rakhman

3
@KirillRakhman Dạy GhostCat một cái gì đó tuyệt vời mà tôi không biết về -> giành được một vé trong xổ số upvote GhostCat.
GhostCat

1
Tôi nghĩ bình luận số 1 ở đây là lợi ích thực tế của Objects.requireNonNull(bar, "bar must not be null");. Cảm ơn vì điều này.
Kawu

1
Cái nào là đầu tiên, và tại sao "ngôn ngữ" mọi người nên theo các tác giả của một số thư viện?! Tại sao ổi không theo hành vi lang Java?!
GhostCat

1
Điều this.bar = Objects.requireNonNull(bar, "bar must not be null");này là ổn trong các hàm tạo, nhưng có khả năng nguy hiểm trong các phương thức khác nếu hai hoặc nhiều biến được đặt trong cùng một phương thức. Ví dụ: talkwards.com/2018/11/03/ trộm
Erk

99

Thất bại nhanh

Mã sẽ sụp đổ càng sớm càng tốt. Nó không nên thực hiện một nửa công việc và sau đó hủy bỏ null và chỉ sụp đổ sau đó để lại một nửa công việc được thực hiện khiến hệ thống ở trạng thái không hợp lệ.

Điều này thường được gọi là "thất bại sớm" hoặc "thất bại nhanh" .


40

Nhưng NullPulumException dù sao cũng sẽ bị ném nếu một đối tượng null bị hủy đăng ký. Vì vậy, tại sao người ta phải làm thêm kiểm tra null này và ném NullPulumException?

Nó có nghĩa là bạn phát hiện vấn đề ngay lập tứcđáng tin cậy .

Xem xét:

  • Tham chiếu có thể không được sử dụng cho đến sau này trong phương thức, sau khi mã của bạn đã thực hiện một số tác dụng phụ
  • Tham chiếu có thể không được bỏ qua trong phương pháp này
    • Nó có thể được chuyển đến mã hoàn toàn khác nhau (nghĩa là nguyên nhân và lỗi nằm cách xa nhau trong không gian mã)
    • Nó có thể được sử dụng nhiều sau đó (nghĩa là nguyên nhân và lỗi cách xa nhau về thời gian)
  • Nó có thể được sử dụng ở đâu đó rằng tham chiếu null hợp lệ, nhưng có tác dụng ngoài ý muốn

.NET làm cho điều này tốt hơn bằng cách tách NullReferenceException("bạn đã xác định giá trị null") khỏi ArgumentNullException("bạn không nên chuyển thành null dưới dạng đối số - và đó là cho tham số này ). Tôi ước Java cũng làm điều tương tự, nhưng ngay cả với chỉ là NullPointerException, việc sửa mã vẫn dễ dàng hơn nhiều nếu lỗi được đưa ra tại điểm sớm nhất có thể phát hiện ra.


12

Sử dụng requireNonNull()như các câu lệnh đầu tiên trong một phương thức cho phép xác định ngay / nhanh nguyên nhân của ngoại lệ.
Ngăn xếp chỉ ra rõ ràng rằng ngoại lệ đã được ném ngay khi nhập phương thức vì người gọi không tôn trọng các yêu cầu / hợp đồng. Truyền một nullđối tượng sang một phương thức khác thực sự có thể gây ra một ngoại lệ tại một thời điểm nhưng nguyên nhân của vấn đề có thể phức tạp hơn để hiểu vì ngoại lệ sẽ được đưa vào một lời gọi cụ thể trên nullđối tượng có thể xa hơn.


Dưới đây là một ví dụ cụ thể và thực tế cho thấy lý do tại sao chúng ta phải ưu tiên thất bại nhanh nói chung và đặc biệt hơn là sử dụng Object.requireNonNull()hoặc bất kỳ cách nào để thực hiện kiểm tra không null đối với các tham số được thiết kế là không null.

Giả sử một Dictionarylớp mà soạn một LookupServicevà một Listsố Stringđại diện từ chứa trong. Những trường này được thiết kế để không nullvà một trong những được truyền trong Dictionary constructor.

Bây giờ, giả sử một triển khai "xấu" Dictionarymà không nullkiểm tra trong mục nhập phương thức (ở đây là hàm tạo):

public class Dictionary {

    private final List<String> words;
    private final LookupService lookupService;

    public Dictionary(List<String> words) {
        this.words = this.words;
        this.lookupService = new LookupService(words);
    }

    public boolean isFirstElement(String userData) {
        return lookupService.isFirstElement(userData);
    }        
}


public class LookupService {

    List<String> words;

    public LookupService(List<String> words) {
        this.words = words;
    }

    public boolean isFirstElement(String userData) {
        return words.get(0).contains(userData);
    }
}

Bây giờ, hãy gọi hàm Dictionarytạo với nulltham chiếu cho wordstham số:

Dictionary dictionary = new Dictionary(null); 

// exception thrown lately : only in the next statement
boolean isFirstElement = dictionary.isFirstElement("anyThing");

JVM ném NPE vào câu lệnh này:

return words.get(0).contains(userData); 
Ngoại lệ trong luồng "chính" java.lang.NullPulumException
    tại Tra cứu dịch vụ.isFirstE bổ sung (Tra cứu dịch vụ.java)
    tại Dictionary.isFirstEuity (Dictionary.java:15)
    tại Dictionary.main (Dictionary.java:22)

Ngoại lệ được kích hoạt trong LookupServicelớp trong khi nguồn gốc của nó sớm hơn (hàm Dictionarytạo). Nó làm cho phân tích vấn đề tổng thể ít rõ ràng hơn nhiều.
words null? Là words.get(0) null? Cả hai? Tại sao cái này, cái kia hoặc có thể cả hai null? Đây có phải là một lỗi mã hóa trong Dictionary(phương thức khởi tạo? Phương thức được gọi không?)? Có phải là một lỗi mã hóa trong LookupService? (phương thức khởi tạo? gọi phương thức?)?
Cuối cùng, chúng ta sẽ phải kiểm tra thêm mã để tìm nguồn gốc lỗi và trong một lớp phức tạp hơn, thậm chí có thể sử dụng trình gỡ lỗi để hiểu dễ dàng hơn những gì nó đã xảy ra.
Nhưng tại sao một điều đơn giản (thiếu kiểm tra null) lại trở thành một vấn đề phức tạp?
Bởi vì chúng tôi cho phép lỗi ban đầu / thiếu nhận dạng trên một rò rỉ thành phần cụ thể trên các thành phần thấp hơn.
Hãy tưởng tượng rằng đó LookupServicekhông phải là một dịch vụ cục bộ mà là một dịch vụ từ xa hoặc thư viện của bên thứ ba với một vài thông tin gỡ lỗi hoặc tưởng tượng rằng bạn không có 2 lớp mà là 4 hoặc 5 lớp yêu cầu đối tượng trước khi nullphát hiện ra? Vấn đề sẽ vẫn phức tạp hơn để phân tích.

Vì vậy, cách để ủng hộ là:

public Dictionary(List<String> words) {
    this.words = Objects.requireNonNull(words);
    this.lookupService = new LookupService(words);
}

Theo cách này, không phải đau đầu: chúng tôi nhận được ngoại lệ ngay khi nhận được:

// exception thrown early : in the constructor 
Dictionary dictionary = new Dictionary(null);

// we never arrive here
boolean isFirstElement = dictionary.isFirstElement("anyThing");
Ngoại lệ trong luồng "chính" java.lang.NullPulumException
    tại java.util.Objects.requireNonNull (Object.java:203)
    tại com.Dixi. (Dictionary.java:15)
    tại com.Dixi.main (Dictionary.java:24)

Lưu ý rằng ở đây tôi đã minh họa vấn đề với một hàm tạo nhưng một lời gọi phương thức có thể có cùng một ràng buộc kiểm tra không null.


Ồ Giải thích tuyệt vời.
Jonathas Nascimento

2

Như một lưu ý phụ, điều này thất bại nhanh trước khi Object#requireNotNullđược triển khai hơi khác trước java-9 bên trong một số lớp jre. Giả sử trường hợp:

 Consumer<String> consumer = System.out::println;

Trong java-8, phần này biên dịch thành (chỉ các phần có liên quan)

getstatic Field java/lang/System.out
invokevirtual java/lang/Object.getClass

Về cơ bản là một hoạt động như: yourReference.getClass- sẽ thất bại nếu yourRefercence null.

Mọi thứ đã thay đổi trong jdk-9 trong đó cùng một mã biên dịch thành

getstatic Field java/lang/System.out
invokestatic java/util/Objects.requireNonNull

Hoặc về cơ bản Objects.requireNotNull (yourReference)


1

Việc sử dụng cơ bản là kiểm tra và ném NullPointerExceptionngay lập tức.

Một lựa chọn tốt hơn (shortcut) để phục vụ cho yêu cầu này cũng @NonNull chú thích bởi Lombok.


1
Chú thích không phải là sự thay thế cho phương thức Đối tượng, chúng hoạt động cùng nhau. Bạn không thể nói @ NonNull x = mightBeNull, bạn sẽ nói @ NonNull x = Object.requireNonNull (mightBeNull, "không thể tưởng tượng được!");
Bill K

@BillK xin lỗi, tôi không hiểu bạn
Supun Wijerathne

Tôi chỉ nói rằng chú thích Nonnull hoạt động với allowNonNull, nó không phải là một thay thế, nhưng chúng hoạt động khá tốt.
Bill K

để thực hiện bản chất thất bại nhanh chóng, nó là một thay thế phải không? Yh tôi đồng ý nó không phải là một thay thế cho nhiệm vụ.
Supun Wijerathne

Tôi đoán tôi sẽ gọi requestNonNull () một "chuyển đổi" từ @ Nullable sang @ NonNull. Nếu bạn không sử dụng các chú thích, phương thức này không thực sự thú vị (vì tất cả những gì nó làm là ném một NPE giống như mã mà nó bảo vệ) - mặc dù nó thể hiện khá rõ ý định của bạn.
Bill K

1

Ngoại lệ con trỏ Null được ném khi bạn truy cập vào một thành viên của một đối tượng nullở điểm sau đó. Objects.requireNonNull()ngay lập tức kiểm tra giá trị và ném ngoại lệ ngay lập tức mà không cần di chuyển về phía trước.


0

Tôi nghĩ rằng nó nên được sử dụng trong các hàm tạo sao chép và một số trường hợp khác như DI có tham số đầu vào là một đối tượng, bạn nên kiểm tra xem tham số có rỗng không. Trong trường hợp như vậy, bạn có thể sử dụng phương pháp tĩnh này một cách thuận tiện.

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.