2012-04-30 39 views
25

Wyobraź sobie, że mam klasową rodzinę. Zawiera listę osób. Każda (klasa) osoba zawiera adres (klasy). Każdy (klasa) adres zawiera (klasa) kod pocztowy. Każda klasa "pośrednia" może mieć wartość zerową.Java: unikaj sprawdzania wartości pustej w klasach zagnieżdżonych (sprawdzanie głębokiego zera)

Czy istnieje prosty sposób na uzyskanie kodu pocztowego bez konieczności sprawdzania wartości zerowej w każdym kroku? tzn. czy istnieje sposób na uniknięcie następującego kodu połączenia łańcuchowego? Wiem, że nie ma "natywnego" rozwiązania Java, ale liczyłem na to, że ktokolwiek wie o bibliotece lub czymś podobnym. (Zaznaczone Commons & Guava i nie widzi nic)

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!!! 
       } 
      } 
     } 
    } 
} 

Nie, nie może zmienić strukturę. To z usługi, nad którą nie mam kontroli.

Nie, nie mogę używać Groovy i jest to przydatny operator "Elvisa".

Nie, ja nie wolą czekać na Java 8: D

Nie mogę uwierzyć, że jestem pierwszym dev nigdy chorować „n zmęczony pisania kodu jak ten, ale haven” t udało się znaleźć rozwiązanie.

Pomysły?

Dzięki

-
llappall

+0

Przepraszam, utknąłeś. Niektórzy używają warunkowego operatora trójwymiarowego, aby uczynić go nieco mniej gęstym, ale nadal jest to ten sam kod bajtowy, tylko trudniejszy do odczytania. –

+4

"* Nie mogę uwierzyć, że jestem pierwszym dev, który kiedykolwiek zachorował, znużony pisaniem kodu takiego jak ten *" Cóż, nie jesteś. – user1329572

+0

Pewnie. Ale nie wierzę, że możesz jeszcze bardziej pobić kod! Przepraszam! –

Odpowiedz

1

Nie taki fajny pomysł, ale jak o połowu wyjątek:

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

najbliżej można dostać jest skorzystać z krótkiej -cut reguły w warunkach warunkowych:

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!!! 

} 

Nawiasem mówiąc, złapanie wyjątku zamiast testowania warunku z góry jest okropnym pomysłem.

+0

masz jakieś accessive '}' tam (zapomniałeś usunąć je po refaktoryzacji) – amit

+0

Dzięki, @amit. Naprawiono teraz. –

11

Twój kod zachowuje się tak samo jak

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 
} 

Dzięki short circuiting evaluation, to również bezpieczne, ponieważ drugi warunek nie zostanie oceniona, jeśli pierwsza jest fałszywe, 3. nie będą oceniane, jeśli druga jest fałszywa, .... a nie otrzymasz NPE, ponieważ jeśli tak.

+0

'null' zawsze chce pozostać w naszych kodach! –

3

Jeśli jest to rzadkie, można zignorować kontrole null i polegać na NullPointerException. "Rzadko" ze względu na możliwy problem z wydajnością (zależy zwykle od wypełnienia śladu stosu, który może być kosztowny).

Inny niż że: 1) określonej metody pomocnika, który sprawdza, czy wartość null, aby oczyścić ten kod lub 2) Złóż ogólne podejście przy użyciu odbicia i ciąg jak:

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

Wykonanie pozostawiamy jako ćwiczenie.

+0

Refleksja również nie jest tania. –

+0

Tak, rzeczywiście może być również powolny @paul. Zwłaszcza metoda wyszukiwania itp. Rzeczywiste wywołanie metody może być dość szybkie, ale znowu to zależy (inne optymalizacje mogą być trudniejsze dla maszyny wirtualnej). Tak więc buforowanie wyszukiwania metod/pól jest zazwyczaj ważne z mojego doświadczenia, jeśli jest potrzebne. Przede wszystkim zależy to oczywiście od tego, jak często kod jest w ogóle używany. –

1

Zamiast używać wartości null, można użyć jakiejś wersji wzoru "obiekt zerowy".Na przykład:

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 

wówczas, gdy oświadczenie może stać:

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

To suboptimal od:

  • utkniesz czyni NULL przedmioty dla każdej klasy,
  • Trudno aby uczynić te obiekty ogólnymi, więc utkniesz tworząc wersję o wartości NULL każdej listy, mapy itp., której chcesz użyć, i
  • Istnieje potencjalnie kilka zabawnych problemów z podklasami i których NULL używać.

Ale jeśli naprawdę nienawidzisz swoich == null s, jest to wyjście.

0

Po prostu szukałem tego samego (mój kontekst: grupa automatycznie tworzonych klas JAXB, i jakoś mam te długie łańcuchy daisy .getFoo().getBar().... Niezmiennie, od czasu do czasu jedno z połączeń w środku null, powodując NPE:

Coś, z czym zacząłem się bawić chwilę, opiera się na refleksji - jestem pewien, że możemy sprawić, że będzie to ładniejsze i bardziej wydajne (buforowanie refleksji, dla jednej rzeczy, a także definiowanie "magicznych" metod np. ._all, aby automatycznie wykonywać iteracje na wszystkich elementach kolekcji, jeśli pewna metoda w środku zwraca kolekcję) .Nie ładne, ale może ktoś mógłby nam powiedzieć, czy jest tam już coś lepszego:

/** 
* 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

Jeśli na wypadek, gdy używasz java8, możesz użyć;

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

To rozwiązanie polega na wychwyceniu NPE, który będzie działał bardzo słabo. – mojoken

0

Chociaż ten post jest prawie pięć lat, może mam inne rozwiązanie starości pytanie, jak radzić NullPointerException s.

W skrócie:

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"); 
} 

Ponieważ istnieje wiele starszych kod nadal w użyciu, przy użyciu języka Java 8 i Optional nie zawsze jest to możliwe.

Zawsze, gdy są zaangażowane głęboko zagnieżdżone klasy (JAXB, SOAP, JSON, nazywasz to ...) i Law of Demeter nie jest zastosowany, musisz po prostu sprawdzić wszystko i sprawdzić, czy w okolicy czają się NPE.

Proponowane przeze mnie rozwiązanie dąży do czytelności i nie powinno być używane, jeśli nie ma w nim co najmniej 3 lub więcej zagnieżdżonych klas (kiedy mówię "zagnieżdżony", nie mam na myśli Nested classes w kontekście formalnym). Ponieważ kod jest czytany bardziej niż jest napisany, szybkie spojrzenie do lewej części kodu sprawi, że jego znaczenie będzie wyraźniejsze niż w przypadku użycia głęboko zagnieżdżonych instrukcji if-else.

Jeśli potrzebujesz części innego, można użyć tego wzoru:

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"); 
} 

Pewne IDE złamie to formatowanie, jeśli nie uniemożliwić im (patrz this question).

Twoje warunki muszą być odwrócone - mówisz kodowi, kiedy powinien się złamać, a nie kiedy powinien być kontynuowany.

Jeszcze jedno - twój kod jest nadal podatny na złamanie. Musisz użyć if(family.getPeople() != null && !family.getPeople().isEmpty()) jako pierwszej linii kodu, w przeciwnym razie pusta lista wyrzuci NPE.

Powiązane problemy