2012-04-30 41 views
25

Hãy tưởng tượng tôi có một lớp học Gia đình. Nó chứa một danh sách người. Mỗi (lớp) Người có một (lớp) Địa chỉ. Mỗi (lớp) Địa chỉ chứa một Mã Bưu chính (lớp). Bất kỳ lớp "trung gian" nào đều có thể là null.Java: tránh kiểm tra null trong các lớp lồng nhau (Kiểm tra sâu Null)

Vì vậy, có cách nào đơn giản để đến Mã Bưu điện mà không phải kiểm tra null trong mỗi bước không? tức là, có cách nào để tránh mã chuỗi nối tiếp sau không? Tôi biết không có giải pháp Java "bản xứ", nhưng hy vọng nếu có ai biết thư viện hay gì đó. (đã chọn Commons & Ổi và không thấy gì cả)

if(family != null) { 
    if(family.getPeople() != null) { 
     if(family.people.get(0) != null) { 
      if(people.get(0).getAddress() != null) { 
       if(people.get(0).getAddress().getPostalCode() != null) { 
        //FINALLY MADE IT TO DO SOMETHING!!! 
       } 
      } 
     } 
    } 
} 

Không, không thể thay đổi cấu trúc. Đó là từ một dịch vụ mà tôi không có quyền kiểm soát.

Không, tôi không thể sử dụng Groovy và toán tử "Elvis" tiện dụng của nó.

Không, tôi không muốn chờ đợi cho Java 8: D

Tôi không thể tin rằng tôi là người dev đầu tiên bao giờ bị ốm 'n mệt mỏi viết code như thế này, nhưng tôi thiên đường' t có thể tìm ra một giải pháp.

Ý tưởng?

Cảm ơn

-
llappall

+0

Xin lỗi, bạn đang bị kẹt. Một số người sử dụng toán tử điều kiện trinary để làm cho nó ít dày đặc hơn một chút, nhưng nó vẫn là cùng một bytecode, khó đọc hơn. –

+4

"* Tôi không thể tin rằng tôi là người đầu tiên bị bệnh" mệt mỏi vì viết mã như thế này * "Vâng, bạn không biết. – user1329572

+0

Chắc chắn. Nhưng tôi không tin rằng bạn có thể đánh bại mã! Lấy làm tiếc! –

Trả lời

1

Không như là một ý tưởng tuyệt vời, nhưng làm thế nào về bắt ngoại lệ:

try 
    { 
     PostalCode pc = people.get(0).getAddress().getPostalCode(); 
    } 
    catch(NullPointerException ex) 
    { 
     System.out.println("Gotcha"); 
    } 
6

Gần nhất bạn có thể nhận được là để tận dụng thời gian ngắn các quy tắc cắt trong các điều kiện:

if(family != null && family.getPeople() != null && family.people.get(0) != null && family.people.get(0).getAddress() != null && family.people.get(0).getAddress().getPostalCode() != null) { 
        //FINALLY MADE IT TO DO SOMETHING!!! 

} 

Nhân tiện, bắt một ngoại lệ thay vì kiểm tra điều kiện trước là một ý tưởng khủng khiếp.

+0

bạn đã có một số truy cập '}' ở đó (quên xóa chúng khi được cấu trúc lại) – amit

+0

Cảm ơn, @amit. Đã sửa lỗi. –

11

Mã của bạn cư xử giống như

if(family != null && 
    family.getPeople() != null && 
    family.people.get(0) != null && 
    family.people.get(0).getAddress() != null && 
    family.people.get(0).getAddress().getPostalCode() != null) { 
     //My Code 
} 

Nhờ short circuiting evaluation, đây cũng là an toàn, vì điều kiện thứ hai sẽ không được đánh giá nếu là người đầu tiên là sai, thứ 3 sẽ không được đánh giá nếu thứ hai là sai, .... và bạn sẽ không nhận được NPE vì nếu nó.

+0

'null' allways muốn ở lại trong mã của chúng tôi! –

3

Nếu hiếm khi bạn có thể bỏ qua kiểm tra null và dựa trên NullPointerException. "Hiếm" do vấn đề hiệu suất có thể (phụ thuộc, thường sẽ điền vào dấu vết ngăn xếp có thể tốn kém).

Ngoài ra 1) một phương pháp helper cụ thể để kiểm tra cho null để dọn dẹp mã hoặc 2) Thực hiện cách tiếp cận chung sử dụng phản xạ và một chuỗi như:

checkNonNull(family, "people[0].address.postalcode") 

Thực hiện trái như một bài tập.

+0

Phản ánh không chính xác là giá rẻ. –

+0

Có thực sự nó có thể được làm chậm cũng @paul. Đặc biệt phương pháp tra cứu vv Thực tế phương pháp gọi mặc dù có thể khá nhanh, nhưng một lần nữa nó phụ thuộc (tối ưu hóa khác có thể khó khăn hơn cho VM). Vì vậy, việc lưu vào bộ nhớ cache các tra cứu phương pháp/trường thường quan trọng từ trải nghiệm của tôi nếu cần. Hầu hết tất cả nó tất nhiên phụ thuộc vào mức độ thường xuyên mã được sử dụng ở tất cả. –

1

Thay vì sử dụng null, bạn có thể sử dụng một số phiên bản mẫu thiết kế "đối tượng rỗng".Ví dụ:

public class Family { 
    private final PersonList people; 
    public Family(PersonList people) { 
     this.people = people; 
    } 

    public PersonList getPeople() { 
     if (people == null) { 
      return PersonList.NULL; 
     } 
     return people; 
    } 

    public boolean isNull() { 
     return false; 
    } 

    public static Family NULL = new Family(PersonList.NULL) { 
     @Override 
     public boolean isNull() { 
      return true; 
     } 
    }; 
} 


import java.util.ArrayList; 

public class PersonList extends ArrayList<Person> { 
    @Override 
    public Person get(int index) { 
     Person person = null; 
     try { 
      person = super.get(index); 
     } catch (ArrayIndexOutOfBoundsException e) { 
      return Person.NULL; 
     } 
     if (person == null) { 
      return Person.NULL; 
     } else { 
      return person; 
     } 
    } 
    //... more List methods go here ... 

    public boolean isNull() { 
     return false; 
    } 

    public static PersonList NULL = new PersonList() { 
     @Override 
     public boolean isNull() { 
      return true; 
     } 
    }; 
} 

public class Person { 
    private Address address; 

    public Person(Address address) { 
     this.address = address; 
    } 

    public Address getAddress() { 
     if (address == null) { 
      return Address.NULL; 
     } 
     return address; 
    } 
    public boolean isNull() { 
     return false; 
    } 

    public static Person NULL = new Person(Address.NULL) { 
     @Override 
     public boolean isNull() { 
      return true; 
     } 
    }; 
} 

etc etc etc 

Sau đó bạn câu lệnh if có thể trở thành:

if (!family.getPeople().get(0).getAddress().getPostalCode.isNull()) {...} 

Đó là tối ưu từ:

  • bạn đang mắc kẹt khiến đối tượng NULL cho mỗi lớp,
  • Thật khó để làm cho các đối tượng này chung chung, vì vậy bạn đang mắc kẹt tạo phiên bản đối tượng null của từng Danh sách, Bản đồ, v.v. mà bạn muốn sử dụng và
  • Có khả năng một số vấn đề buồn cười với subclassing và NULL để sử dụng.

Nhưng nếu bạn thực sự ghét == null s, đây là cách thoát.

0

Tôi chỉ đang tìm kiếm cùng một điều (ngữ cảnh của tôi: một loạt các lớp JAXB được tạo tự động, và bằng cách nào đó tôi có các chuỗi dài này là .getFoo().getBar().... Luôn luôn, thỉnh thoảng một trong các cuộc gọi ở giữa null, gây ra NPE

Một điều gì đó mà tôi bắt đầu nghịch ngợm trong một thời gian dài dựa trên sự phản chiếu Tôi chắc chắn chúng ta có thể làm cho hiệu ứng này đẹp hơn và hiệu quả hơn (lưu vào bộ nhớ đệm). chẳng hạn như ._all để tự động lặp lại trên tất cả các phần tử của bộ sưu tập, nếu một số phương thức ở giữa trả về một bộ sưu tập. Không đẹp, nhưng có lẽ ai đó có thể cho chúng tôi biết nếu có điều gì đó tốt hơn trên đó:

/** 
* Using {@link java.lang.reflect.Method}, apply the given methods (in daisy-chain fashion) 
* to the array of Objects x. 
* 
* <p>For example, imagine that you'd like to express: 
* 
* <pre><code> 
* Fubar[] out = new Fubar[x.length]; 
* for (int i=0; {@code i<x.length}; i++) { 
* out[i] = x[i].getFoo().getBar().getFubar(); 
* } 
* </code></pre> 
* 
* Unfortunately, the correct code that checks for nulls at every level of the 
* daisy-chain becomes a bit convoluted. 
* 
* <p>So instead, this method does it all (checks included) in one call: 
* <pre><code> 
* Fubar[] out = apply(new Fubar[0], x, "getFoo", "getBar", "getFubar"); 
* </code></pre> 
* 
* <p>The cost, of course, is that it uses Reflection, which is slower than 
* direct calls to the methods. 
* @param type the type of the expected result 
* @param x the array of Objects 
* @param methods the methods to apply 
* @return 
*/ 
@SuppressWarnings("unchecked") 
public static <T> T[] apply(T[] type, Object[] x, String...methods) { 
    int n = x.length; 
    try { 
     for (String methodName : methods) { 
      Object[] out = new Object[n]; 
      for (int i=0; i<n; i++) { 
       Object o = x[i]; 
       if (o != null) { 
        Method method = o.getClass().getMethod(methodName); 
        Object sub = method.invoke(o); 
        out[i] = sub; 
       } 
      } 
      x = out; 
     } 
    T[] result = (T[])Array.newInstance(type.getClass().getComponentType(), n); 
    for (int i=0; i<n; i++) { 
      result[i] = (T)x[i]; 
    } 
      return result; 
    } catch (NoSuchMethodException | IllegalAccessException | IllegalArgumentException | InvocationTargetException e) { 
      throw new RuntimeException(e); 
    } 
} 
0

Nếu, trong trường hợp, bạn đang sử dụng java8 thì bạn có thể sử dụng;

resolve(() -> people.get(0).getAddress().getPostalCode()); 
    .ifPresent(System.out::println); 

: 
public static <T> Optional<T> resolve(Supplier<T> resolver) { 
    try { 
     T result = resolver.get(); 
     return Optional.ofNullable(result); 
    } 
    catch (NullPointerException e) { 
     return Optional.empty(); 
    } 
} 

REF: avoid null checks

+0

Giải pháp này dựa vào việc bắt NPE sẽ hoạt động rất kém. – mojoken

0

Mặc dù bài này là gần năm tuổi, tôi có thể có một giải pháp cho câu hỏi tuổi già về cách xử lý NullPointerException s.

Tóm lại:

end: { 
    List<People> people = family.getPeople();   if(people == null || people.isEmpty()) break end; 
    People person = people.get(0);      if(person == null) break end; 
    Address address = person.getAddress();    if(address == null) break end; 
    PostalCode postalCode = address.getPostalCode();  if(postalCode == null) break end; 

    System.out.println("Do stuff"); 
} 

Vì có rất nhiều mã di sản vẫn còn được sử dụng, sử dụng Java 8 và Optional không phải luôn luôn là một lựa chọn.

Bất cứ khi nào có các lớp lồng nhau liên quan (JAXB, SOAP, JSON, bạn đặt tên cho nó ...) và Law of Demeter không được áp dụng, về cơ bản bạn phải kiểm tra mọi thứ và xem có NPE có thể ẩn hay không.

Giải pháp đề xuất của tôi cố gắng đọc và không nên sử dụng nếu không có ít nhất 3 hoặc nhiều lớp lồng nhau tham gia (khi tôi nói lồng nhau, tôi không có nghĩa là Nested classes trong ngữ cảnh chính thức). Vì mã được đọc nhiều hơn nó được viết, một cái nhìn nhanh về phần bên trái của mã sẽ làm cho ý nghĩa của nó rõ ràng hơn là sử dụng các câu lệnh if-else lồng nhau sâu sắc.

Nếu bạn cần một phần khác, bạn có thể sử dụng mô hình này:

boolean prematureEnd = true; 

end: { 
    List<People> people = family.getPeople();   if(people == null || people.isEmpty()) break end; 
    People person = people.get(0);      if(person == null) break end; 
    Address address = person.getAddress();    if(address == null) break end; 
    PostalCode postalCode = address.getPostalCode();  if(postalCode == null) break end; 

    System.out.println("Do stuff"); 
    prematureEnd = false; 
} 

if(prematureEnd) { 
    System.out.println("The else part"); 
} 

Một số IDE sẽ phá vỡ định dạng này, trừ khi bạn hướng dẫn họ không (xem this question).

Điều kiện của bạn phải được đảo ngược - bạn cho biết mã khi nào cần ngắt, không phải khi nào nó nên tiếp tục.

Một điều nữa - mã của bạn vẫn dễ bị vỡ. Bạn phải sử dụng if(family.getPeople() != null && !family.getPeople().isEmpty()) làm dòng đầu tiên trong mã của mình, nếu không danh sách trống sẽ ném NPE.

Các vấn đề liên quan