Một nhà thông thái đã từng nói rằng bạn không phải là một lập trình viên Java thực sự cho đến khi bạn xử lý xong một ngoại lệ con trỏ null. Đùa sang một bên, tham chiếu null là nguồn gốc của nhiều vấn đề vì nó thường được sử dụng để biểu thị việc không có giá trị. Java SE 8 giới thiệu một lớp mới có tên là
String version =
computer?.getSoundcard[]?.getUSB[]?.getVersion[] ?: "UNKNOWN";
7 có thể giảm bớt một số vấn đề nàyHãy bắt đầu với một ví dụ để thấy sự nguy hiểm của null. Hãy xem xét cấu trúc đối tượng lồng nhau cho một
String version =
computer?.getSoundcard[]?.getUSB[]?.getVersion[] ?: "UNKNOWN";
8, như được minh họa trong Hình 1Hình 1. Một cấu trúc lồng nhau để đại diện cho một
String version =
computer?.getSoundcard[]?.getUSB[]?.getVersion[] ?: "UNKNOWN";
8Điều gì có thể có vấn đề với đoạn mã sau?
public class Computer {
private Optional soundcard;
public Optional getSoundcard[] { .. }
...
}
public class Soundcard {
private Optional usb;
public Optional getUSB[] { .. }
}
public class USB{
public String getVersion[]{ .. }
}
0Mã này có vẻ khá hợp lý. Tuy nhiên, nhiều máy tính [ví dụ: Raspberry Pi] không thực sự đi kèm với card âm thanh. Vậy kết quả của
public class Computer {
private Optional soundcard;
public Optional getSoundcard[] { .. }
...
}
public class Soundcard {
private Optional usb;
public Optional getUSB[] { .. }
}
public class USB{
public String getVersion[]{ .. }
}
1 là gì?Một thực tế phổ biến [xấu] là trả về tham chiếu null để cho biết không có card âm thanh. Thật không may, điều này có nghĩa là lệnh gọi tới
public class Computer {
private Optional soundcard;
public Optional getSoundcard[] { .. }
...
}
public class Soundcard {
private Optional usb;
public Optional getUSB[] { .. }
}
public class USB{
public String getVersion[]{ .. }
}
2 sẽ cố gắng trả lại cổng USB của một tham chiếu null, điều này sẽ dẫn đến một
public class Computer {
private Optional soundcard;
public Optional getSoundcard[] { .. }
...
}
public class Soundcard {
private Optional usb;
public Optional getUSB[] { .. }
}
public class USB{
public String getVersion[]{ .. }
}
3 trong thời gian chạy và ngăn chương trình của bạn tiếp tục chạy. Hãy tưởng tượng nếu chương trình của bạn đang chạy trên máy của khách hàng; Để đưa ra một số bối cảnh lịch sử, Tony Hoare—một trong những người khổng lồ của khoa học máy tính—đã viết, "Tôi gọi đó là sai lầm tỷ đô của mình. Đó là phát minh của tài liệu tham khảo null vào năm 1965. Tôi không thể cưỡng lại sự cám dỗ để đưa vào một tài liệu tham khảo null, đơn giản vì nó rất dễ thực hiện. "
Bạn có thể làm gì để ngăn chặn các ngoại lệ con trỏ null ngoài ý muốn?
Sao chép
String version = "UNKNOWN";
if[computer != null]{
Soundcard soundcard = computer.getSoundcard[];
if[soundcard != null]{
USB usb = soundcard.getUSB[];
if[usb != null]{
version = usb.getVersion[];
}
}
}
Liệt kê 1
Tuy nhiên, bạn có thể thấy rằng mã trong Liệt kê 1 nhanh chóng trở nên rất xấu do các kiểm tra lồng nhau. Thật không may, chúng tôi cần rất nhiều mã soạn sẵn để đảm bảo rằng chúng tôi không nhận được một
public class Computer {
private Optional soundcard;
public Optional getSoundcard[] { .. }
...
}
public class Soundcard {
private Optional usb;
public Optional getUSB[] { .. }
}
public class USB{
public String getVersion[]{ .. }
}
3. Ngoài ra, thật khó chịu khi những kiểm tra này cản trở logic kinh doanh. Trên thực tế, chúng đang làm giảm khả năng đọc tổng thể của chương trình của chúng tôiHơn nữa, đây là một quá trình dễ bị lỗi; . Những gì chúng ta cần là một cách tốt hơn để mô hình hóa sự vắng mặt và hiện diện của một giá trị
Để đưa ra một số bối cảnh, chúng ta hãy xem xét ngắn gọn những gì các ngôn ngữ lập trình khác cung cấp
Có những lựa chọn thay thế nào cho Null?
Các ngôn ngữ như Groovy có toán tử điều hướng an toàn được đại diện bởi "
public class Computer {
private Optional soundcard;
public Optional getSoundcard[] { .. }
...
}
public class Soundcard {
private Optional usb;
public Optional getUSB[] { .. }
}
public class USB{
public String getVersion[]{ .. }
}
5" để điều hướng an toàn qua các tham chiếu null tiềm ẩn. [Lưu ý rằng nó cũng sẽ sớm được đưa vào C# và nó đã được đề xuất cho Java SE 7 nhưng không được đưa vào bản phát hành đó. ] Nó hoạt động như sau
public class Computer {
private Optional soundcard;
public Optional getSoundcard[] { .. }
...
}
public class Soundcard {
private Optional usb;
public Optional getUSB[] { .. }
}
public class USB{
public String getVersion[]{ .. }
}
6Trong trường hợp này, biến
public class Computer {
private Optional soundcard;
public Optional getSoundcard[] { .. }
...
}
public class Soundcard {
private Optional usb;
public Optional getUSB[] { .. }
}
public class USB{
public String getVersion[]{ .. }
}
7 sẽ được gán là null nếu
public class Computer {
private Optional soundcard;
public Optional getSoundcard[] { .. }
...
}
public class Soundcard {
private Optional usb;
public Optional getUSB[] { .. }
}
public class USB{
public String getVersion[]{ .. }
}
8 là null hoặc
public class Computer {
private Optional soundcard;
public Optional getSoundcard[] { .. }
...
}
public class Soundcard {
private Optional usb;
public Optional getUSB[] { .. }
}
public class USB{
public String getVersion[]{ .. }
}
1 trả về null hoặc
public class Computer {
private Optional soundcard;
public Optional getSoundcard[] { .. }
...
}
public class Soundcard {
private Optional usb;
public Optional getUSB[] { .. }
}
public class USB{
public String getVersion[]{ .. }
}
2 trả về null. Bạn không cần phải viết các điều kiện lồng nhau phức tạp để kiểm tra nullNgoài ra, Groovy còn bao gồm toán tử Elvis "
String version =
computer?.getSoundcard[]?.getUSB[]?.getVersion[] ?: "UNKNOWN";
51" [nhìn nghiêng sẽ nhận ra mái tóc nổi tiếng của Elvis], có thể sử dụng cho các trường hợp đơn giản khi cần giá trị mặc định. Trong phần sau đây, nếu biểu thức sử dụng toán tử điều hướng an toàn trả về null, thì giá trị mặc định
String version =
computer?.getSoundcard[]?.getUSB[]?.getVersion[] ?: "UNKNOWN";
52 được trả về; Sao chép
String version =
computer?.getSoundcard[]?.getUSB[]?.getVersion[] ?: "UNKNOWN";
Các ngôn ngữ chức năng khác, chẳng hạn như Haskell và Scala, có cách nhìn khác. Haskell bao gồm một loại
String version =
computer?.getSoundcard[]?.getUSB[]?.getVersion[] ?: "UNKNOWN";
53, về cơ bản gói gọn một giá trị tùy chọn. Giá trị của loại
String version =
computer?.getSoundcard[]?.getUSB[]?.getVersion[] ?: "UNKNOWN";
53 có thể chứa giá trị của loại nhất định hoặc không có gì. Không có khái niệm về tham chiếu null. Scala có một cấu trúc tương tự được gọi là
String version =
computer?.getSoundcard[]?.getUSB[]?.getVersion[] ?: "UNKNOWN";
55 để gói gọn sự hiện diện hoặc vắng mặt của một giá trị kiểu
String version =
computer?.getSoundcard[]?.getUSB[]?.getVersion[] ?: "UNKNOWN";
56. Sau đó, bạn phải kiểm tra rõ ràng xem một giá trị có tồn tại hay không bằng cách sử dụng các thao tác có sẵn trên loại
String version =
computer?.getSoundcard[]?.getUSB[]?.getVersion[] ?: "UNKNOWN";
57, thao tác này thực thi ý tưởng "kiểm tra null. " Bạn không còn có thể "quên làm việc đó" vì nó được thực thi bởi hệ thống loạiOK, chúng tôi đã chuyển hướng một chút và tất cả điều này nghe có vẻ khá trừu tượng. Bây giờ bạn có thể tự hỏi, "vậy, còn Java SE 8 thì sao?"
Tóm tắt về
String version =
computer?.getSoundcard[]?.getUSB[]?.getVersion[] ?: "UNKNOWN";
58
String version =
computer?.getSoundcard[]?.getUSB[]?.getVersion[] ?: "UNKNOWN";
Java SE 8 giới thiệu một lớp mới có tên là j
String version =
computer?.getSoundcard[]?.getUSB[]?.getVersion[] ?: "UNKNOWN";
59 được lấy cảm hứng từ ý tưởng của Haskell và Scala. Nó là một lớp đóng gói một giá trị tùy chọn, như được minh họa trong Liệt kê 2 bên dưới và trong Hình 1. Bạn có thể xem
String version =
computer?.getSoundcard[]?.getUSB[]?.getVersion[] ?: "UNKNOWN";
58 dưới dạng vùng chứa một giá trị có chứa giá trị hoặc không [khi đó giá trị này được cho là "trống"], như được minh họa trong Hình 2Hình 2. Card âm thanh tùy chọn
Chúng tôi có thể cập nhật mô hình của mình để sử dụng
String version =
computer?.getSoundcard[]?.getUSB[]?.getVersion[] ?: "UNKNOWN";
58, như trong Liệt kê 2Sao chép
public class Computer {
private Optional soundcard;
public Optional getSoundcard[] { .. }
...
}
public class Soundcard {
private Optional usb;
public Optional getUSB[] { .. }
}
public class USB{
public String getVersion[]{ .. }
}
Liệt kê 2
Mã trong Liệt kê 2 ngay lập tức cho thấy rằng một máy tính có thể có hoặc không có card âm thanh [card âm thanh là tùy chọn]. Ngoài ra sound card có thể tùy chọn thêm cổng USB. Đây là một cải tiến, bởi vì mô hình mới này hiện có thể phản ánh rõ ràng liệu một giá trị nhất định có được phép thiếu hay không. Lưu ý rằng những ý tưởng tương tự đã có sẵn trong các thư viện như Guava
Nhưng bạn thực sự có thể làm gì với một đối tượng
String version =
computer?.getSoundcard[]?.getUSB[]?.getVersion[] ?: "UNKNOWN";
82? . Tóm lại, lớp
String version =
computer?.getSoundcard[]?.getUSB[]?.getVersion[] ?: "UNKNOWN";
58 bao gồm các phương thức để xử lý rõ ràng các trường hợp có hoặc không có giá trị. Tuy nhiên, lợi thế so với tham chiếu null là lớp
String version =
computer?.getSoundcard[]?.getUSB[]?.getVersion[] ?: "UNKNOWN";
58 buộc bạn phải suy nghĩ về trường hợp không có giá trị. Kết quả là, bạn có thể ngăn chặn các ngoại lệ con trỏ null ngoài ý muốnĐiều quan trọng cần lưu ý là mục đích của lớp
String version =
computer?.getSoundcard[]?.getUSB[]?.getVersion[] ?: "UNKNOWN";
58 không phải là thay 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 một phương thức, bạn có thể biết liệu bạn có thể mong đợi một giá trị tùy chọn hay không. Điều này buộc bạn phải tích cực mở một
String version =
computer?.getSoundcard[]?.getUSB[]?.getVersion[] ?: "UNKNOWN";
58 để xử lý việc không có giá trịMô hình áp dụng
String version =
computer?.getSoundcard[]?.getUSB[]?.getVersion[] ?: "UNKNOWN";
58
String version =
computer?.getSoundcard[]?.getUSB[]?.getVersion[] ?: "UNKNOWN";
Nói đủ rồi; . Đầu tiên chúng ta sẽ khám phá cách viết lại các mẫu kiểm tra null điển hình bằng cách sử dụng
String version =
computer?.getSoundcard[]?.getUSB[]?.getVersion[] ?: "UNKNOWN";
58. Đến cuối bài viết này, bạn sẽ hiểu cách sử dụng
String version =
computer?.getSoundcard[]?.getUSB[]?.getVersion[] ?: "UNKNOWN";
58, như được hiển thị bên dưới, để viết lại mã trong Liệt kê 1 đang thực hiện một số kiểm tra null lồng nhauSao chép
String version =
computer?.getSoundcard[]?.getUSB[]?.getVersion[] ?: "UNKNOWN";
5Ghi chú. Hãy chắc chắn rằng bạn đã hiểu rõ về lambdas Java SE 8 và cú pháp tham chiếu phương thức [xem "Java 8. Lambdas"] cũng như các khái niệm đường ống dẫn luồng của nó [xem "Xử lý dữ liệu với luồng Java SE 8"]
Tạo đối tượng
String version =
computer?.getSoundcard[]?.getUSB[]?.getVersion[] ?: "UNKNOWN";
58
String version =
computer?.getSoundcard[]?.getUSB[]?.getVersion[] ?: "UNKNOWN";
Đầu tiên, làm thế nào để bạn tạo các đối tượng
String version =
computer?.getSoundcard[]?.getUSB[]?.getVersion[] ?: "UNKNOWN";
58? Đây là một
String version =
computer?.getSoundcard[]?.getUSB[]?.getVersion[] ?: "UNKNOWN";
58 trống
public class Computer {
private Optional soundcard;
public Optional getSoundcard[] { .. }
...
}
public class Soundcard {
private Optional usb;
public Optional getUSB[] { .. }
}
public class USB{
public String getVersion[]{ .. }
}
03Và đây là một
String version =
computer?.getSoundcard[]?.getUSB[]?.getVersion[] ?: "UNKNOWN";
58 với giá trị khác nullSao chép
String version =
computer?.getSoundcard[]?.getUSB[]?.getVersion[] ?: "UNKNOWN";
8Nếu
public class Computer {
private Optional soundcard;
public Optional getSoundcard[] { .. }
...
}
public class Soundcard {
private Optional usb;
public Optional getUSB[] { .. }
}
public class USB{
public String getVersion[]{ .. }
}
05 là null, thì một
public class Computer {
private Optional soundcard;
public Optional getSoundcard[] { .. }
...
}
public class Soundcard {
private Optional usb;
public Optional getUSB[] { .. }
}
public class USB{
public String getVersion[]{ .. }
}
3 sẽ bị ném ngay lập tức [thay vì nhận được lỗi tiềm ẩn khi bạn cố truy cập các thuộc tính của
public class Computer {
private Optional soundcard;
public Optional getSoundcard[] { .. }
...
}
public class Soundcard {
private Optional usb;
public Optional getUSB[] { .. }
}
public class USB{
public String getVersion[]{ .. }
}
05]Ngoài ra, bằng cách sử dụng
public class Computer {
private Optional soundcard;
public Optional getSoundcard[] { .. }
...
}
public class Soundcard {
private Optional usb;
public Optional getUSB[] { .. }
}
public class USB{
public String getVersion[]{ .. }
}
08, bạn có thể tạo đối tượng
String version =
computer?.getSoundcard[]?.getUSB[]?.getVersion[] ?: "UNKNOWN";
58 có thể chứa giá trị null
public class Computer {
private Optional soundcard;
public Optional getSoundcard[] { .. }
...
}
public class Soundcard {
private Optional usb;
public Optional getUSB[] { .. }
}
public class USB{
public String getVersion[]{ .. }
}
80Nếu soundcard null, đối tượng
String version =
computer?.getSoundcard[]?.getUSB[]?.getVersion[] ?: "UNKNOWN";
58 kết quả sẽ trốngLàm điều gì đó nếu có giá trị
Bây giờ bạn đã có một đối tượng
String version =
computer?.getSoundcard[]?.getUSB[]?.getVersion[] ?: "UNKNOWN";
58, bạn có thể truy cập các phương thức có sẵn để giải quyết rõ ràng sự hiện diện hoặc vắng mặt của các giá trị. Thay vì phải nhớ thực hiện kiểm tra null, như sauSao chép
public class Computer {
private Optional soundcard;
public Optional getSoundcard[] { .. }
...
}
public class Soundcard {
private Optional usb;
public Optional getUSB[] { .. }
}
public class USB{
public String getVersion[]{ .. }
}
0Bạn có thể sử dụng phương pháp
public class Computer {
private Optional soundcard;
public Optional getSoundcard[] { .. }
...
}
public class Soundcard {
private Optional usb;
public Optional getUSB[] { .. }
}
public class USB{
public String getVersion[]{ .. }
}
83, như sauSao chép
public class Computer {
private Optional soundcard;
public Optional getSoundcard[] { .. }
...
}
public class Soundcard {
private Optional usb;
public Optional getUSB[] { .. }
}
public class USB{
public String getVersion[]{ .. }
}
8Bạn không cần phải thực hiện kiểm tra null rõ ràng nữa; . Nếu đối tượng
String version =
computer?.getSoundcard[]?.getUSB[]?.getVersion[] ?: "UNKNOWN";
58 trống, sẽ không có gì được inBạn cũng có thể sử dụng phương thức
public class Computer {
private Optional soundcard;
public Optional getSoundcard[] { .. }
...
}
public class Soundcard {
private Optional usb;
public Optional getUSB[] { .. }
}
public class USB{
public String getVersion[]{ .. }
}
85 để tìm hiểu xem một giá trị có trong đối tượng
String version =
computer?.getSoundcard[]?.getUSB[]?.getVersion[] ?: "UNKNOWN";
58 hay không. Ngoài ra, có một phương thức
public class Computer {
private Optional soundcard;
public Optional getSoundcard[] { .. }
...
}
public class Soundcard {
private Optional usb;
public Optional getUSB[] { .. }
}
public class USB{
public String getVersion[]{ .. }
}
87 trả về giá trị chứa trong đối tượng
String version =
computer?.getSoundcard[]?.getUSB[]?.getVersion[] ?: "UNKNOWN";
58, nếu nó hiện diện. Nếu không, nó ném một
public class Computer {
private Optional soundcard;
public Optional getSoundcard[] { .. }
...
}
public class Soundcard {
private Optional usb;
public Optional getUSB[] { .. }
}
public class USB{
public String getVersion[]{ .. }
}
89. Hai phương pháp có thể được kết hợp, như sau, để ngăn chặn các trường hợp ngoại lệSao chép
public class Computer {
private Optional soundcard;
public Optional getSoundcard[] { .. }
...
}
public class Soundcard {
private Optional usb;
public Optional getUSB[] { .. }
}
public class USB{
public String getVersion[]{ .. }
}
0Tuy nhiên, đây không phải là cách sử dụng được khuyến nghị của
String version =
computer?.getSoundcard[]?.getUSB[]?.getVersion[] ?: "UNKNOWN";
58 [nó không cải thiện nhiều so với kiểm tra null lồng nhau] và có nhiều lựa chọn thay thế thành ngữ hơn mà chúng tôi khám phá bên dướiGiá trị và hành động mặc định
Một mẫu điển hình là trả về một giá trị mặc định nếu bạn xác định rằng kết quả của một thao tác là null. Nói chung, bạn có thể sử dụng toán tử bậc ba, như sau, để đạt được điều này
Sao chép
public class Computer {
private Optional soundcard;
public Optional getSoundcard[] { .. }
...
}
public class Soundcard {
private Optional usb;
public Optional getUSB[] { .. }
}
public class USB{
public String getVersion[]{ .. }
}
2Sử dụng đối tượng
String version =
computer?.getSoundcard[]?.getUSB[]?.getVersion[] ?: "UNKNOWN";
58, bạn có thể viết lại mã này bằng cách sử dụng phương thức
public class Computer {
private Optional soundcard;
public Optional getSoundcard[] { .. }
...
}
public class Soundcard {
private Optional usb;
public Optional getUSB[] { .. }
}
public class USB{
public String getVersion[]{ .. }
}
02, phương thức này cung cấp giá trị mặc định nếu
String version =
computer?.getSoundcard[]?.getUSB[]?.getVersion[] ?: "UNKNOWN";
58 trống
public class Computer {
private Optional soundcard;
public Optional getSoundcard[] { .. }
...
}
public class Soundcard {
private Optional usb;
public Optional getUSB[] { .. }
}
public class USB{
public String getVersion[]{ .. }
}
04Tương tự, bạn có thể sử dụng phương thức
public class Computer {
private Optional soundcard;
public Optional getSoundcard[] { .. }
...
}
public class Soundcard {
private Optional usb;
public Optional getUSB[] { .. }
}
public class USB{
public String getVersion[]{ .. }
}
05, thay vì cung cấp giá trị mặc định nếu
String version =
computer?.getSoundcard[]?.getUSB[]?.getVersion[] ?: "UNKNOWN";
58 trống, sẽ đưa ra một ngoại lệSao chép
public class Computer {
private Optional soundcard;
public Optional getSoundcard[] { .. }
...
}
public class Soundcard {
private Optional usb;
public Optional getUSB[] { .. }
}
public class USB{
public String getVersion[]{ .. }
}
9Từ chối một số giá trị bằng phương pháp
public class Computer {
private Optional soundcard;
public Optional getSoundcard[] { .. }
...
}
public class Soundcard {
private Optional usb;
public Optional getUSB[] { .. }
}
public class USB{
public String getVersion[]{ .. }
}
07
public class Computer {
private Optional soundcard;
public Optional getSoundcard[] { .. }
...
}
public class Soundcard {
private Optional usb;
public Optional getUSB[] { .. }
}
public class USB{
public String getVersion[]{ .. }
}
Thường thì bạn cần gọi một phương thức trên một đối tượng và kiểm tra một số thuộc tính. Ví dụ: bạn có thể cần kiểm tra xem cổng USB có phải là phiên bản cụ thể không. Để thực hiện việc này một cách an toàn, trước tiên bạn cần kiểm tra xem tham chiếu trỏ đến đối tượng USB có phải là null hay không và sau đó gọi phương thức
public class Computer {
private Optional soundcard;
public Optional getSoundcard[] { .. }
...
}
public class Soundcard {
private Optional usb;
public Optional getUSB[] { .. }
}
public class USB{
public String getVersion[]{ .. }
}
08, như sauSao chép
String version =
computer?.getSoundcard[]?.getUSB[]?.getVersion[] ?: "UNKNOWN";
0Mẫu này có thể được viết lại bằng phương pháp
public class Computer {
private Optional soundcard;
public Optional getSoundcard[] { .. }
...
}
public class Soundcard {
private Optional usb;
public Optional getUSB[] { .. }
}
public class USB{
public String getVersion[]{ .. }
}
07 trên đối tượng
String version =
computer?.getSoundcard[]?.getUSB[]?.getVersion[] ?: "UNKNOWN";
58, như sauSao chép
String version =
computer?.getSoundcard[]?.getUSB[]?.getVersion[] ?: "UNKNOWN";
1Phương thức
public class Computer {
private Optional soundcard;
public Optional getSoundcard[] { .. }
...
}
public class Soundcard {
private Optional usb;
public Optional getUSB[] { .. }
}
public class USB{
public String getVersion[]{ .. }
}
07 lấy một vị từ làm đối số. Nếu một giá trị có mặt trong đối tượng
String version =
computer?.getSoundcard[]?.getUSB[]?.getVersion[] ?: "UNKNOWN";
58 và nó khớp với vị ngữ, thì phương thức
public class Computer {
private Optional soundcard;
public Optional getSoundcard[] { .. }
...
}
public class Soundcard {
private Optional usb;
public Optional getUSB[] { .. }
}
public class USB{
public String getVersion[]{ .. }
}
07 trả về giá trị đó; . Bạn có thể đã thấy một mẫu tương tự nếu bạn đã sử dụng phương pháp
public class Computer {
private Optional soundcard;
public Optional getSoundcard[] { .. }
...
}
public class Soundcard {
private Optional usb;
public Optional getUSB[] { .. }
}
public class USB{
public String getVersion[]{ .. }
}
07 với giao diện
public class Computer {
private Optional soundcard;
public Optional getSoundcard[] { .. }
...
}
public class Soundcard {
private Optional usb;
public Optional getUSB[] { .. }
}
public class USB{
public String getVersion[]{ .. }
}
26Trích xuất và chuyển đổi giá trị bằng phương pháp
public class Computer {
private Optional soundcard;
public Optional getSoundcard[] { .. }
...
}
public class Soundcard {
private Optional usb;
public Optional getUSB[] { .. }
}
public class USB{
public String getVersion[]{ .. }
}
27
public class Computer {
private Optional soundcard;
public Optional getSoundcard[] { .. }
...
}
public class Soundcard {
private Optional usb;
public Optional getUSB[] { .. }
}
public class USB{
public String getVersion[]{ .. }
}
Một mô hình phổ biến khác là trích xuất thông tin từ một đối tượng. Ví dụ: từ một đối tượng
public class Computer {
private Optional soundcard;
public Optional getSoundcard[] { .. }
...
}
public class Soundcard {
private Optional usb;
public Optional getUSB[] { .. }
}
public class USB{
public String getVersion[]{ .. }
}
28, bạn có thể muốn trích xuất đối tượng
public class Computer {
private Optional soundcard;
public Optional getSoundcard[] { .. }
...
}
public class Soundcard {
private Optional usb;
public Optional getUSB[] { .. }
}
public class USB{
public String getVersion[]{ .. }
}
29 và sau đó kiểm tra thêm xem nó có đúng phiên bản hay không. Bạn thường viết đoạn mã sauSao chép
String version =
computer?.getSoundcard[]?.getUSB[]?.getVersion[] ?: "UNKNOWN";
2Chúng ta có thể viết lại mô hình "kiểm tra null và giải nén" [ở đây, đối tượng
public class Computer {
private Optional soundcard;
public Optional getSoundcard[] { .. }
...
}
public class Soundcard {
private Optional usb;
public Optional getUSB[] { .. }
}
public class USB{
public String getVersion[]{ .. }
}
28] bằng cách sử dụng phương thức
public class Computer {
private Optional soundcard;
public Optional getSoundcard[] { .. }
...
}
public class Soundcard {
private Optional usb;
public Optional getUSB[] { .. }
}
public class USB{
public String getVersion[]{ .. }
}
27
public class Computer {
private Optional soundcard;
public Optional getSoundcard[] { .. }
...
}
public class Soundcard {
private Optional usb;
public Optional getUSB[] { .. }
}
public class USB{
public String getVersion[]{ .. }
}
92Có sự song song trực tiếp với phương thức
public class Computer {
private Optional soundcard;
public Optional getSoundcard[] { .. }
...
}
public class Soundcard {
private Optional usb;
public Optional getUSB[] { .. }
}
public class USB{
public String getVersion[]{ .. }
}
27 được sử dụng với các luồng. Ở đó, bạn truyền một hàm cho phương thức
public class Computer {
private Optional soundcard;
public Optional getSoundcard[] { .. }
...
}
public class Soundcard {
private Optional usb;
public Optional getUSB[] { .. }
}
public class USB{
public String getVersion[]{ .. }
}
27, phương thức này áp dụng hàm này cho từng phần tử của luồng. Tuy nhiên, không có gì xảy ra nếu luồng trốngPhương thức
public class Computer {
private Optional soundcard;
public Optional getSoundcard[] { .. }
...
}
public class Soundcard {
private Optional usb;
public Optional getUSB[] { .. }
}
public class USB{
public String getVersion[]{ .. }
}
27 của lớp
String version =
computer?.getSoundcard[]?.getUSB[]?.getVersion[] ?: "UNKNOWN";
58 thực hiện chính xác như vậy. giá trị chứa bên trong
String version =
computer?.getSoundcard[]?.getUSB[]?.getVersion[] ?: "UNKNOWN";
58 được "biến đổi" bởi hàm được truyền dưới dạng đối số [ở đây, tham chiếu phương thức để trích xuất cổng USB], trong khi không có gì xảy ra nếu
String version =
computer?.getSoundcard[]?.getUSB[]?.getVersion[] ?: "UNKNOWN";
58 trốngCuối cùng, chúng ta có thể kết hợp phương pháp
public class Computer {
private Optional soundcard;
public Optional getSoundcard[] { .. }
...
}
public class Soundcard {
private Optional usb;
public Optional getUSB[] { .. }
}
public class USB{
public String getVersion[]{ .. }
}
27 với phương pháp
public class Computer {
private Optional soundcard;
public Optional getSoundcard[] { .. }
...
}
public class Soundcard {
private Optional usb;
public Optional getUSB[] { .. }
}
public class USB{
public String getVersion[]{ .. }
}
07 để từ chối cổng USB có phiên bản khác với 3. 0Sao chép
String version =
computer?.getSoundcard[]?.getUSB[]?.getVersion[] ?: "UNKNOWN";
3Đáng kinh ngạc;
Xếp tầng các đối tượng
String version =
computer?.getSoundcard[]?.getUSB[]?.getVersion[] ?: "UNKNOWN";
58 bằng phương pháp
String version =
computer?.getSoundcard[]?.getUSB[]?.getVersion[] ?: "UNKNOWN";
02
String version =
computer?.getSoundcard[]?.getUSB[]?.getVersion[] ?: "UNKNOWN";
String version =
computer?.getSoundcard[]?.getUSB[]?.getVersion[] ?: "UNKNOWN";
Bạn đã thấy một số mẫu có thể được cấu trúc lại để sử dụng
String version =
computer?.getSoundcard[]?.getUSB[]?.getVersion[] ?: "UNKNOWN";
58. Vậy làm thế nào chúng ta có thể viết đoạn mã sau một cách an toàn?
public class Computer {
private Optional soundcard;
public Optional getSoundcard[] { .. }
...
}
public class Soundcard {
private Optional usb;
public Optional getUSB[] { .. }
}
public class USB{
public String getVersion[]{ .. }
}
0Lưu ý rằng tất cả những gì đoạn mã này thực hiện là trích xuất một đối tượng từ một đối tượng khác, đây chính xác là những gì mà phương thức
public class Computer {
private Optional soundcard;
public Optional getSoundcard[] { .. }
...
}
public class Soundcard {
private Optional usb;
public Optional getUSB[] { .. }
}
public class USB{
public String getVersion[]{ .. }
}
27 dành cho. Trước đó trong bài viết, chúng tôi đã thay đổi mô hình của mình để một
String version =
computer?.getSoundcard[]?.getUSB[]?.getVersion[] ?: "UNKNOWN";
8 có một
String version =
computer?.getSoundcard[]?.getUSB[]?.getVersion[] ?: "UNKNOWN";
82 và một
public class Computer {
private Optional soundcard;
public Optional getSoundcard[] { .. }
...
}
public class Soundcard {
private Optional usb;
public Optional getUSB[] { .. }
}
public class USB{
public String getVersion[]{ .. }
}
28 có một
String version =
computer?.getSoundcard[]?.getUSB[]?.getVersion[] ?: "UNKNOWN";
09, vì vậy chúng tôi có thể viết như sauSao chép
String version =
computer?.getSoundcard[]?.getUSB[]?.getVersion[] ?: "UNKNOWN";
4Thật không may, mã này không biên dịch. Tại sao? . Tuy nhiên,
public class Computer {
private Optional soundcard;
public Optional getSoundcard[] { .. }
...
}
public class Soundcard {
private Optional usb;
public Optional getUSB[] { .. }
}
public class USB{
public String getVersion[]{ .. }
}
1 trả về một đối tượng kiểu
String version =
computer?.getSoundcard[]?.getUSB[]?.getVersion[] ?: "UNKNOWN";
82. Điều này có nghĩa là kết quả của thao tác bản đồ là một đối tượng thuộc loại
String version =
computer?.getSoundcard[]?.getUSB[]?.getVersion[] ?: "UNKNOWN";
14. Do đó, lệnh gọi tới
public class Computer {
private Optional soundcard;
public Optional getSoundcard[] { .. }
...
}
public class Soundcard {
private Optional usb;
public Optional getUSB[] { .. }
}
public class USB{
public String getVersion[]{ .. }
}
2 không hợp lệ vì
String version =
computer?.getSoundcard[]?.getUSB[]?.getVersion[] ?: "UNKNOWN";
58 ngoài cùng chứa giá trị của nó là một
String version =
computer?.getSoundcard[]?.getUSB[]?.getVersion[] ?: "UNKNOWN";
58 khác, tất nhiên là không hỗ trợ phương thức
public class Computer {
private Optional soundcard;
public Optional getSoundcard[] { .. }
...
}
public class Soundcard {
private Optional usb;
public Optional getUSB[] { .. }
}
public class USB{
public String getVersion[]{ .. }
}
2. Hình 3 minh họa cấu trúc
String version =
computer?.getSoundcard[]?.getUSB[]?.getVersion[] ?: "UNKNOWN";
58 lồng nhau mà bạn sẽ nhận đượcHình 3. Một
String version =
computer?.getSoundcard[]?.getUSB[]?.getVersion[] ?: "UNKNOWN";
58 hai cấpVậy làm thế nào chúng ta có thể giải quyết vấn đề này? . phương pháp
String version =
computer?.getSoundcard[]?.getUSB[]?.getVersion[] ?: "UNKNOWN";
02. Với các luồng, phương thức
String version =
computer?.getSoundcard[]?.getUSB[]?.getVersion[] ?: "UNKNOWN";
02 lấy một hàm làm đối số, trả về một luồng khác. Hàm này được áp dụng cho từng thành phần của luồng, điều này sẽ dẫn đến một luồng các luồng. Tuy nhiên,
String version =
computer?.getSoundcard[]?.getUSB[]?.getVersion[] ?: "UNKNOWN";
02 có tác dụng thay thế từng luồng được tạo bằng nội dung của luồng đó. Nói cách khác, tất cả các luồng riêng biệt do hàm tạo ra sẽ được hợp nhất hoặc "làm phẳng" thành một luồng duy nhất. Những gì chúng tôi muốn ở đây là một cái gì đó tương tự, nhưng chúng tôi muốn "làm phẳng" một
String version =
computer?.getSoundcard[]?.getUSB[]?.getVersion[] ?: "UNKNOWN";
58 hai cấp thành mộtVâng, đây là tin tốt.
String version =
computer?.getSoundcard[]?.getUSB[]?.getVersion[] ?: "UNKNOWN";
58 cũng hỗ trợ phương pháp
String version =
computer?.getSoundcard[]?.getUSB[]?.getVersion[] ?: "UNKNOWN";
02. Mục đích của nó là áp dụng hàm chuyển đổi trên giá trị của một
String version =
computer?.getSoundcard[]?.getUSB[]?.getVersion[] ?: "UNKNOWN";
58 [giống như thao tác bản đồ] và sau đó làm phẳng
String version =
computer?.getSoundcard[]?.getUSB[]?.getVersion[] ?: "UNKNOWN";
58 hai cấp kết quả thành một cấp duy nhất. Hình 4 minh họa sự khác biệt giữa
public class Computer {
private Optional soundcard;
public Optional getSoundcard[] { .. }
...
}
public class Soundcard {
private Optional usb;
public Optional getUSB[] { .. }
}
public class USB{
public String getVersion[]{ .. }
}
27 và
String version =
computer?.getSoundcard[]?.getUSB[]?.getVersion[] ?: "UNKNOWN";
02 khi hàm chuyển đổi trả về một đối tượng
String version =
computer?.getSoundcard[]?.getUSB[]?.getVersion[] ?: "UNKNOWN";
58hinh 4. Sử dụng
public class Computer {
private Optional soundcard;
public Optional getSoundcard[] { .. }
...
}
public class Soundcard {
private Optional usb;
public Optional getUSB[] { .. }
}
public class USB{
public String getVersion[]{ .. }
}
27 so với
String version =
computer?.getSoundcard[]?.getUSB[]?.getVersion[] ?: "UNKNOWN";
02 với
String version =
computer?.getSoundcard[]?.getUSB[]?.getVersion[] ?: "UNKNOWN";
58Vì vậy, để làm cho mã của chúng tôi chính xác, chúng tôi cần viết lại mã như sau bằng cách sử dụng
String version =
computer?.getSoundcard[]?.getUSB[]?.getVersion[] ?: "UNKNOWN";
02Sao chép
String version =
computer?.getSoundcard[]?.getUSB[]?.getVersion[] ?: "UNKNOWN";
5
String version =
computer?.getSoundcard[]?.getUSB[]?.getVersion[] ?: "UNKNOWN";
02 đầu tiên đảm bảo rằng một
String version =
computer?.getSoundcard[]?.getUSB[]?.getVersion[] ?: "UNKNOWN";
82 được trả lại thay vì một
String version =
computer?.getSoundcard[]?.getUSB[]?.getVersion[] ?: "UNKNOWN";
14 và
String version =
computer?.getSoundcard[]?.getUSB[]?.getVersion[] ?: "UNKNOWN";
02 thứ hai đạt được mục đích tương tự để trả lại một
String version =
computer?.getSoundcard[]?.getUSB[]?.getVersion[] ?: "UNKNOWN";
09. Lưu ý rằng lệnh gọi thứ ba chỉ cần là một
String version =
computer?.getSoundcard[]?.getUSB[]?.getVersion[] ?: "UNKNOWN";
41 vì
public class Computer {
private Optional soundcard;
public Optional getSoundcard[] { .. }
...
}
public class Soundcard {
private Optional usb;
public Optional getUSB[] { .. }
}
public class USB{
public String getVersion[]{ .. }
}
08 trả về một
String version =
computer?.getSoundcard[]?.getUSB[]?.getVersion[] ?: "UNKNOWN";
43 chứ không phải một đối tượng
String version =
computer?.getSoundcard[]?.getUSB[]?.getVersion[] ?: "UNKNOWN";
58Ồ. Chúng ta đã trải qua một chặng đường dài từ việc viết các kiểm tra null lồng nhau đau đớn sang viết mã khai báo có thể kết hợp được, có thể đọc được và được bảo vệ tốt hơn khỏi các ngoại lệ con trỏ null
Sự kết luận
Trong bài viết này, chúng ta đã thấy cách bạn có thể áp dụng Java SE 8
String version =
computer?.getSoundcard[]?.getUSB[]?.getVersion[] ?: "UNKNOWN";
45 mới. Mục đích của
String version =
computer?.getSoundcard[]?.getUSB[]?.getVersion[] ?: "UNKNOWN";
58 không phải là để thay thế mọi tham chiếu null đơn lẻ trong cơ sở mã của bạn mà là để giúp thiết kế các API tốt hơn trong đó—chỉ bằng cách đọc chữ ký của một phương thức—người dùng có thể biết liệu có nên mong đợi một giá trị tùy chọn hay không. Ngoài ra,
String version =
computer?.getSoundcard[]?.getUSB[]?.getVersion[] ?: "UNKNOWN";
58 buộc bạn phải chủ động mở một
String version =
computer?.getSoundcard[]?.getUSB[]?.getVersion[] ?: "UNKNOWN";
58 để xử lý việc không có giá trị; Xem thêm
- Chương 9, "Tùy chọn. một giải pháp thay thế tốt hơn cho null," từ Java 8 in Action. Lambdas, Luồng và Lập trình kiểu chức năng
- "Monadic Java" của Mario Fusco
Sự nhìn nhận
Cảm ơn Alan Mycroft và Mario Fusco đã trải qua cuộc phiêu lưu viết Java 8 in Action. Lambdas, Luồng và Lập trình kiểu chức năng với tôi
Thông tin về các Tác giả
Raoul-Gabriel Urma [@raoulUK] hiện đang hoàn thành bằng tiến sĩ về khoa học máy tính tại Đại học Cambridge, nơi anh nghiên cứu về ngôn ngữ lập trình. Anh ấy là đồng tác giả của cuốn sách sắp ra mắt Java 8 in Action. Lambdas, Luồng và Lập trình kiểu chức năng, do Manning xuất bản. Anh ấy cũng là diễn giả thường xuyên tại các hội nghị lớn về Java [ví dụ: Devoxx và Fosdem] và là người hướng dẫn. Ngoài ra, anh ấy đã làm việc tại một số công ty nổi tiếng—bao gồm nhóm Python của Google, nhóm Nền tảng Java của Oracle, eBay và Goldman Sachs—cũng như cho một số dự án khởi nghiệp