Đặt đối tượng null javascript

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ày

Hã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 1

Hì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[]{ .. }
}

0

Mã 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ôi

Hơ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[]{ .. }
}

6

Trong 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 null

Ngoà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ại

OK, 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

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 2

Hì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ê 2

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[]{ .. }
}

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

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 nhau

Sao chép


String version = 
    computer?.getSoundcard[]?.getUSB[]?.getVersion[] ?: "UNKNOWN";

5

Ghi 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

Đầ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[]{ .. }
}

03

Và đây là một


String version = 
    computer?.getSoundcard[]?.getUSB[]?.getVersion[] ?: "UNKNOWN";

58 với giá trị khác null

Sao chép


String version = 
    computer?.getSoundcard[]?.getUSB[]?.getVersion[] ?: "UNKNOWN";

8

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[]{ .. }
}

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[]{ .. }
}

80

Nếu soundcard null, đối tượng


String version = 
    computer?.getSoundcard[]?.getUSB[]?.getVersion[] ?: "UNKNOWN";

58 kết quả sẽ trống

Là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ư sau

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[]{ .. }
}

0

Bạ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ư sau

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[]{ .. }
}

8

Bạ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 in

Bạ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[]{ .. }
}

0

Tuy 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ưới

Giá 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[]{ .. }
}

2

Sử 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[]{ .. }
}

04

Tươ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[]{ .. }
}

9

Từ 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

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ư sau

Sao chép


String version = 
    computer?.getSoundcard[]?.getUSB[]?.getVersion[] ?: "UNKNOWN";

0

Mẫ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ư sau

Sao chép


String version = 
    computer?.getSoundcard[]?.getUSB[]?.getVersion[] ?: "UNKNOWN";

1

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 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[]{ .. }
}

26

Trí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

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ã sau

Sao chép


String version = 
    computer?.getSoundcard[]?.getUSB[]?.getVersion[] ?: "UNKNOWN";

2

Chú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[]{ .. }
}

92

Có 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ố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 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ống

Cuố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. 0

Sao 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

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[]{ .. }
}

0

Lư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ư sau

Sao chép


String version = 
    computer?.getSoundcard[]?.getUSB[]?.getVersion[] ?: "UNKNOWN";

4

Thậ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 được

Hình 3. Một


String version = 
    computer?.getSoundcard[]?.getUSB[]?.getVersion[] ?: "UNKNOWN";

58 hai cấp

Vậ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ột

Vâ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";

58

hinh 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";

58

Vì 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";

02

Sao 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

Chủ Đề