2012-04-30 2 views
25

Представьте, что у меня есть класс Family. Он содержит список лиц. Каждый (класс) человек содержит (класс) адрес. Каждый (класс) Адрес содержит (класс) PostalCode. Любой «промежуточный» класс может быть нулевым.Java: избегайте проверки нулевого значения в вложенных классах (проверка глубины)

Итак, есть ли простой способ добраться до PostalCode без проверки на нуль на каждом шагу? то есть есть ли способ избежать следующего кода цепи ввода-вывода? Я знаю, что нет «родного» Java-решения, но надеялся, что кто-нибудь знает библиотеку или что-то в этом роде. (Проверено Commons & Guava и ничего не видел)

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

Нет, не может изменить структуру. Это из службы, которой я не контролирую.

Нет, я не могу использовать Groovy, и это удобно для оператора «Элвис».

Нет, я бы предпочел не ждать Java 8: D

Я не могу поверить, что я первый DEV когда-либо заболеть н устал от написания кода, как это, но я гавань ' найти решение.

Идеи?

Благодаря

-
llappall

+0

Извините, вы застряли. Некоторые люди используют оператор условного оператора trinary, чтобы сделать его немного менее плотным, но это все тот же байт-код, который сложнее читать. –

+4

«Я не могу поверить, что я первый девственник, который когда-либо болел», устал писать код вроде этого * «Ну, это не так. – user1329572

+0

Несомненно. Но я не верю, что вы можете больше бить код! Сожалею! –

ответ

1

Не такая классная идея, но как насчет отлов исключения:

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

Ближайший вы можете получить, чтобы воспользоваться короткой правила обрезки в условных выражениях:

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

} 

Кстати, ловить исключение вместо проверки состояния заранее - это ужасная идея.

+0

у вас есть доступный '}' там (забыл удалить их при рефакторинге) – amit

+0

Спасибо, @amit. Исправлено. –

11

Ваш код ведет себя так же, как

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 
} 

Благодаря short circuiting evaluation, это также безопасен, так как второе условие не будет оцениваться, если первое ложно, третьего не будет оцениваться, если второй - false, .... и вы не получите NPE, потому что если это.

+0

'null' allways хочет оставаться в наших кодах! –

3

Если это редкий случай, вы можете игнорировать null чеки и полагаться на NullPointerException. «Редкий» из-за возможной проблемы с производительностью (зависит, как правило, будет заполнять трассировку стека, которая может быть дорогостоящей).

Кроме того, что 1) конкретного вспомогательного метода, который проверяет нуль, чтобы очистить этот код или 2) Сделать общий подход с помощью отражения и строки типа:

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

Осуществление в качестве упражнения.

+0

Отражение не совсем дешево. –

+0

Да, действительно, это может быть медленным, а также @paul. Особенно поиск методов и т. Д. Фактический вызов метода может быть довольно быстрым, но опять же он зависит (другие оптимизации могут быть сложнее для виртуальной машины). Поэтому кэширование поиска методов/полей обычно важно из моего опыта, если это необходимо. Больше всего это, конечно, зависит от того, как часто код используется вообще. –

1

Вместо того, чтобы использовать null, вы можете использовать некоторую версию шаблона проектирования «нулевой объект».Например:

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 

Тогда ваш, если заявление может стать:

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

Это неоптимальным с:

  • Вы застряли, что делает NULL объекты для каждого класса,
  • Это трудно чтобы эти объекты были родовыми, поэтому вы застряли в создании нулевой версии каждого списка, карты и т. д., которые вы хотите использовать, и
  • Есть потенциально некоторые смешные проблемы с подклассом и какой NULL использовать.

Но если вы действительно ненавидите == null s, это выход.

0

Я просто искал одно и то же (мой контекст: куча автоматически созданных классов JAXB, и как-то у меня эти длинные цепочки-гильзы .getFoo().getBar().... Неизменно, время от времени один из вызовов в середине возвращается null, вызывающий NPE.

Что-то, с чего я начинал возиться с возвращением, основано на отражении. Я уверен, что мы можем сделать это красивее и эффективнее (кэшируя отражение, с одной стороны, и определяя «магические» методы например ._all, чтобы автоматически выполнять итерацию по всем элементам коллекции, если какой-либо метод в середине возвращает коллекцию). Не красиво, но, возможно, кто-то может сказать нам, что там уже что-то лучше:

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

Если в случае, если вы используете java8, то вы можете использовать;

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

Это решение основано на улавливании NPE, который будет работать очень плохо. – mojoken

0

Хотя этот пост почти пять лет, я мог бы иметь другое решение вековой вопрос о том, как справиться с NullPointerException с.

В двух словах:

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

Поскольку существует много унаследованного кода все еще в использовании, с помощью Java 8 и Optional не всегда вариант.

Если у вас есть глубоко вложенные классы (JAXB, SOAP, JSON, вы его называете ...) и Law of Demeter не применяется, вам в основном нужно проверить все и посмотреть, есть ли поблизости потенциальные NPE.

Мое предлагаемое решение стремится к удобочитаемости и не должно использоваться, если не задействовано не менее 3 или более вложенных классов (когда я говорю вложенные, я не имею в виду Nested classes в формальном контексте). Поскольку код читается больше, чем он написан, быстрый взгляд на левую часть кода сделает его смысл более понятным, чем использование глубоко вложенных операторов if-else.

Если вам нужна еще часть, вы можете использовать этот шаблон:

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

Некоторые Иды сломается это форматирование, если вы не проинструктировать их не (см this question).

Ваши условные обозначения должны быть инвертированы - вы указываете код, когда он должен ломаться, а не когда он должен продолжаться.

Еще одна вещь - ваш код по-прежнему подвержен поломке. Вы должны использовать if(family.getPeople() != null && !family.getPeople().isEmpty()) в качестве первой строки в вашем коде, иначе пустой список будет вызывать NPE.