Ява "?" Оператор проверки нуля - что это? (Не троичный!)


86

Я читал статью, на которую ссылается история со слэшдотом, и наткнулся на этот небольшой лакомый кусочек:

Возьмем последнюю версию Java, которая пытается упростить проверку нулевого указателя, предлагая сокращенный синтаксис для тестирования бесконечного указателя. Простое добавление вопросительного знака к каждому вызову метода автоматически включает тест на нулевые указатели, заменяя крысиное множество операторов if-then, таких как:

    public String getPostcode(Person person) {
      String ans= null;
      if (person != null) {
        Name nm= person.getName();
        if (nm!= null) {
          ans= nm.getPostcode();
        }
      }
      return ans
    } 

С этим:

public String getFirstName(Person person) {
      return person?.getName()?.getGivenName();
    } 

Я рыскал в Интернете (ладно, я потратил как минимум 15 минут на поиск в Google вариантов "вопросительного знака java") и ничего не нашел. Итак, мой вопрос: есть ли на это официальная документация? Я обнаружил, что в C # есть аналогичный оператор (оператор "??"), но я хотел бы получить документацию для языка, на котором я работаю. Или это просто использование тернарного оператора, который у меня никогда раньше не видел.

Благодарность!

РЕДАКТИРОВАТЬ: ссылка на статью: http://infoworld.com/d/developer-world/12-programming-mistakes-avoid-292


3
Можно хоть ссылку на статью?
Карл Кнехтель

А источник фрагмента?
хачик

5
Статья неправильная. infoworld.com/print/145292 Я считаю, что для этого была отправлена заявка на участие в проекте. Но он не был выбран (по причинам, указанным в статье - если вы хотите сделать что-то подобное, используйте C # или что-то в этом роде), и, конечно же, его нет в текущей версии языка Java.
Том Хотин - tackline

4
Это не то же самое, что C # ?? оператор: ?? срастается обнуляет, то есть A ?? B == (A != null) ? A : B. Это , как представляется , оценить свойство на объекте , если ссылка на объект не является нулевым, то есть A?.B == (A != null) ? A.B : null.
Rup

1
@Erty: как большой пользователь аннотации @NotNull, которая практически присутствует везде в коде, который я пишу, я уже не очень хорошо знаю, что такое NPE (кроме случаев использования плохо спроектированных API). И все же я нахожу эту "сокращенную запись" милой и интересной. Конечно, в статье правильно сказано: в конце концов, она не устраняет корень проблемы: распространение нулевых значений из-за быстрого и свободного программирования. 'null' не существует на уровне OOA / OOD. Это еще одна Java-идиосинхратическая чепуха, которую в основном можно обойти. Для меня везде @NotNull .
SyntaxT3rr0r

Ответы:


78

Первоначальная идея пришла от groovy. Он был предложен для Java 7 как часть Project Coin: https://wiki.openjdk.java.net/display/Coin/2009+Proposals+TOC (Elvis и другие Null-Safe Operators), но еще не был принят .

Связанный оператор Элвиса?: Был предложен для x ?: yсокращения x != null ? x : y, особенно полезного, когда x - сложное выражение.


3
В java (где нет автоматического принуждения к нулю) сокращение дляx!=null ? x : y
Майкл Боргвардт

@Michael Borgwardt: хорошая мысль, я думал о отличной семантике.
ataylor

50
?фирменная причёска Элвиса Пресли; :просто представляет собой пару глаз , как обычно. Возможно ?:-o, более запоминающийся ...
Анджей Дойл

4
?:0Должен быть оператором «Ни нуль, ни 0». Сделай это так.
azz

3
На самом деле было ошибкой называть предложение оператором Элвиса. Пояснение на mail.openjdk.java.net/pipermail/coin-dev/2009-July/002089.html "Нулевое разыменование" - лучший термин для использования.
JustinKSU

62

Этого синтаксиса нет в Java, и его не планируется включать ни в одну из будущих версий, о которых я знаю.


9
Почему голос против? Насколько я знаю, этот ответ на 100% правильный ... если вы знаете что-то другое, скажите, пожалуйста.
ColinD

6
@Webinator: Этого не будет в Java 7 или 8, и в настоящее время нет других «будущих версий». Я также считаю, что это маловероятно, поскольку оно поощряет довольно плохие практики. Я также не думаю, что «пока» необходимо, поскольку «не существует в Java» - не то же самое, что «никогда не будет существовать в Java».
ColinD

9
@Webinator: несколько авторов отметили, что предложение было отправлено, но отклонено. Таким образом, ответ точен на 100%. Голосование за противодействие голосованию против.
JeremyP

2
Плохая практика @ColinD - это когда вы отказываетесь от этого уродливого кода и решаете использовать Optionalи mapпрочее. мы не скрываем проблему, если value допускает значение NULL, что означает, что иногда ожидается, что оно будет равно NULL, и вам нужно с этим справиться. иногда значения по умолчанию вполне разумны, и это неплохая практика.
М.казем Ахгары

2
Java просто отказывается быть чем-то современным. Просто закройте уже этот язык.
Plagon


19

Один из способов обойти отсутствие символа "?" Оператор с использованием Java 8 без накладных расходов на try-catch (который NullPointerException, как уже упоминалось, может также скрыть источник, созданный в другом месте), заключается в создании класса для «перенаправления» методов в стиле Java-8-Stream.

public class Pipe<T> {
    private T object;

    private Pipe(T t) {
        object = t;
    }

    public static<T> Pipe<T> of(T t) {
        return new Pipe<>(t);
    }

    public <S> Pipe<S> after(Function<? super T, ? extends S> plumber) {
        return new Pipe<>(object == null ? null : plumber.apply(object));
    }

    public T get() {
        return object;
    }

    public T orElse(T other) {
        return object == null ? other : object;
    }
}

Тогда данный пример будет выглядеть следующим образом:

public String getFirstName(Person person) {
    return Pipe.of(person).after(Person::getName).after(Name::getGivenName).get();
}

[РЕДАКТИРОВАТЬ]

Поразмыслив, я понял, что добиться того же можно только с помощью стандартных классов Java 8:

public String getFirstName(Person person) {
    return Optional.ofNullable(person).map(Person::getName).map(Name::getGivenName).orElse(null);
}

В этом случае можно даже выбрать значение по умолчанию (например "<no first name>") вместо того null, чтобы передавать его в качестве параметра orElse.


Мне больше нравится ваше первое решение. Как бы вы улучшили свой Pipeкласс, чтобы адаптировать orElseфункциональность, чтобы я мог передавать произвольный ненулевой объект внутри orElseметода?
ThanosFisherman

@ThanosFisherman Я добавил orElseметод в Pipeкласс.
Helder Pereira


7

На самом деле это оператор безопасного разыменования Groovy . Вы не можете использовать его на чистой Java (к сожалению), поэтому этот пост просто неверен (или, что более вероятно, слегка вводит в заблуждение, если он утверждает, что Groovy является «последней версией Java»).


2
Итак, статья была неправильной - синтаксиса нет в родном java. Хм.
Эрти Сейдол

но ссылка не работает
bvdb 01

6

В Java нет точного синтаксиса, но, начиная с JDK-8, в нашем распоряжении есть дополнительный API с различными методами. Итак, версия C # с использованием условного оператора null :

return person?.getName()?.getGivenName(); 

можно записать на Java с помощью Optional API следующим образом :

 return Optional.ofNullable(person)
                .map(e -> e.getName())
                .map(e -> e.getGivenName())
                .orElse(null);

если любой из person,getName или getGivenNameв нуль , то нуль возвращается.


2

С помощью лямбда-выражения Java 8 можно определить методы утилиты, которые решают эту проблему почти красиво.

Это вариант решения H-MAN, но он использует перегруженные методы с несколькими аргументами для обработки нескольких шагов вместо перехватаNullPointerException .

Даже если я считаю это решение крутым, я предпочитаю второе решение Хелдера Перейры, поскольку оно не требует каких-либо дополнительных методов.

void example() {
    Entry entry = new Entry();
    // This is the same as H-MANs solution 
    Person person = getNullsafe(entry, e -> e.getPerson());    
    // Get object in several steps
    String givenName = getNullsafe(entry, e -> e.getPerson(), p -> p.getName(), n -> n.getGivenName());
    // Call void methods
    doNullsafe(entry, e -> e.getPerson(), p -> p.getName(), n -> n.nameIt());        
}

/** Return result of call to f1 with o1 if it is non-null, otherwise return null. */
public static <R, T1> R getNullsafe(T1 o1, Function<T1, R> f1) {
    if (o1 != null) return f1.apply(o1);
    return null; 
}

public static <R, T0, T1> R getNullsafe(T0 o0, Function<T0, T1> f1, Function<T1, R> f2) {
    return getNullsafe(getNullsafe(o0, f1), f2);
}

public static <R, T0, T1, T2> R getNullsafe(T0 o0, Function<T0, T1> f1, Function<T1, T2> f2, Function<T2, R> f3) {
    return getNullsafe(getNullsafe(o0, f1, f2), f3);
}


/** Call consumer f1 with o1 if it is non-null, otherwise do nothing. */
public static <T1> void doNullsafe(T1 o1, Consumer<T1> f1) {
    if (o1 != null) f1.accept(o1);
}

public static <T0, T1> void doNullsafe(T0 o0, Function<T0, T1> f1, Consumer<T1> f2) {
    doNullsafe(getNullsafe(o0, f1), f2);
}

public static <T0, T1, T2> void doNullsafe(T0 o0, Function<T0, T1> f1, Function<T1, T2> f2, Consumer<T2> f3) {
    doNullsafe(getNullsafe(o0, f1, f2), f3);
}


class Entry {
    Person getPerson() { return null; }
}

class Person {
    Name getName() { return null; }
}

class Name {
    void nameIt() {}
    String getGivenName() { return null; }
}

1

Я не уверен, что это вообще сработает; если, скажем, ссылка на человека была пустой, чем среда выполнения заменила бы ее? Новый человек? Это потребует от Person инициализации по умолчанию, которую вы ожидаете в этом случае. Вы можете избежать исключений с нулевой ссылкой, но вы все равно получите непредсказуемое поведение, если не планируете эти типы настроек.

?? Оператор в C # лучше всего назвать оператором «объединения»; вы можете связать несколько выражений, и оно вернет первое, которое не равно нулю. К сожалению, в Java этого нет. Я думаю, что лучшее, что вы могли бы сделать, - это использовать тернарный оператор для выполнения нулевых проверок и оценки альтернативы всему выражению, если какой-либо член в цепочке имеет значение NULL:

return person == null ? "" 
    : person.getName() == null ? "" 
        : person.getName().getGivenName();

Вы также можете использовать try-catch:

try
{
   return person.getName().getGivenName();
}
catch(NullReferenceException)
{
   return "";
}

1
"чем среда выполнения заменит его?" ... чтение вопроса может помочь :-P Он заменит его на null. В общем, идея, кажется, заключается в том, что person? .GetName оценивается как null, если person имеет значение null, или как person.getName, если нет. Это очень похоже на замену "" на ноль во всех ваших примерах.
Subsub 04

1
NullReferenceException может также был брошен в getName()или getGivenName()которые вы не будете знать , если вы просто возвращаете пустую строку для всех случаев.
Джимми Т.

1
В Java это исключение NullPointerException.
Tuupertunut

в C #, person?.getName()?.getGivenName() ?? ""является эквивалентом вашего первого примера, за исключением того, что если getGivenName()возвращает null, он все равно выдаст""
Austin_Anderson

0

Вот он, нулевой безопасный вызов в Java 8:

public void someMethod() {
    String userName = nullIfAbsent(new Order(), t -> t.getAccount().getUser()
        .getName());
}

static <T, R> R nullIfAbsent(T t, Function<T, R> funct) {
    try {
        return funct.apply(t);
    } catch (NullPointerException e) {
        return null;
    }
}

Придется попробовать. У СИ есть серьезные сомнения по поводу всего этого «необязательного» бизнеса. Похоже на противный взлом.
ggb667 05

3
Вся цель предложения оператора Элвиса заключалась в том, чтобы сделать это одной строкой. Этот подход не лучше, чем подход «if (! = Null), описанный выше. На самом деле, я бы сказал, что он хуже, так как он не прямолинейный. Кроме того, вам следует избегать выброса и
перехвата

Как сказал @Darron в другом ответе, то же самое применимо и здесь: «Проблема с этим стилем в том, что исключение NullPointerException могло появиться не там, где вы ожидали. И, таким образом, оно может скрыть настоящую ошибку».
Хелдер Перейра

0

Если кто-то ищет альтернативу старым версиям java, вы можете попробовать эту, которую я написал:

/**
 * Strong typed Lambda to return NULL or DEFAULT VALUES instead of runtime errors. 
 * if you override the defaultValue method, if the execution result was null it will be used in place
 * 
 * 
 * Sample:
 * 
 * It won't throw a NullPointerException but null.
 * <pre>
 * {@code
 *  new RuntimeExceptionHandlerLambda<String> () {
 *      @Override
 *      public String evaluate() {
 *          String x = null;
 *          return x.trim();
 *      }  
 *  }.get();
 * }
 * <pre>
 * 
 * 
 * @author Robson_Farias
 *
 */

public abstract class RuntimeExceptionHandlerLambda<T> {

    private T result;

    private RuntimeException exception;

    public abstract T evaluate();

    public RuntimeException getException() {
        return exception;
    }

    public boolean hasException() {
        return exception != null;
    }

    public T defaultValue() {
        return result;
    }

    public T get() {
        try {
            result = evaluate();
        } catch (RuntimeException runtimeException) {
            exception = runtimeException;
        }
        return result == null ? defaultValue() : result;
    }

}

0

Вы можете протестировать предоставленный код, и он выдаст синтаксическую ошибку, поэтому он не поддерживается в Java. Groovy поддерживает его, и он был предложен для Java 7 (но так и не был включен).

Однако вы можете использовать опцию Optional, предусмотренную в Java 8. Это может помочь вам в достижении чего-то подобного. https://docs.oracle.com/javase/8/docs/api/java/util/Optional.html http://www.oracle.com/technetwork/articles/java/java8-optional-2175753.html

Пример кода для необязательного


0

Поскольку Android не поддерживает лямбда-функции, если у вас установленная ОС не> = 24, нам нужно использовать отражение.

// Example using doIt function with sample classes
public void Test() {
    testEntry(new Entry(null));
    testEntry(new Entry(new Person(new Name("Bob"))));
}

static void testEntry(Entry entry) {
    doIt(doIt(doIt(entry,  "getPerson"), "getName"), "getName");
}

// Helper to safely execute function 
public static <T,R> R doIt(T obj, String methodName) {
    try {
       if (obj != null) 
           return (R)obj.getClass().getDeclaredMethod(methodName).invoke(obj);
    } catch (Exception ignore) {
    }
    return null;
}
// Sample test classes
    static class Entry {
        Person person;
        Entry(Person person) { this.person = person; }
        Person getPerson() { return person; }
    }

    static class Person {
        Name name;
        Person(Name name) { this.name = name; }
        Name getName() { return name; }
    }

    static class Name {
        String name;
        Name(String name) { this.name = name; }
        String getName() {
            System.out.print(" Name:" + name + " ");
            return name;
        }
    }
}

-4

Если для вас это не проблема производительности, вы можете написать

public String getFirstName(Person person) {
  try {
     return person.getName().getGivenName();
  } catch (NullPointerException ignored) {
     return null;
  }
} 

8
Проблема с этим стилем в том, что исключение NullPointerException могло появиться не там, где вы ожидали. И таким образом может скрыть настоящую ошибку.
Darron

2
@Darron, не могли бы вы привести пример написанного вами геттера, который может генерировать NPE, и как бы вы хотели обработать его по-другому?
Питер Лоури

2
вы запускаете его в отдельную переменную и проверяете с помощью if как обычно. Идея этого оператора состоит в том, чтобы устранить это уродство. Даррон прав, ваше решение может скрывать и отбрасывать исключения, которые вы хотите выбросить. Например, если getName()вы внутренне выбросили исключение, которое вы не хотите выбрасывать.
Майк Миллер,

2
Не исключение, это должно быть исключение NullPointerException. Вы пытаетесь защитить себя от ситуации, которую еще не начали объяснять, как она может произойти в реальном приложении.
Питер Лоури

1
В реальном приложении Personможет быть прокси-сервер, обращающийся к БД или некоторой памяти, отличной от кучи, и где-то может быть ошибка ... Не очень реалистично, но, Питер, держу пари, вы никогда не писали кусок кода, как указано выше .
maaartinus
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.