Java “?” Оператор для перевірки нуля - що це? (Не потрійний!)


86

Я читав статтю, пов’язану з історії slashdot, і натрапив на цей маленький шматочок:

Візьміть останню версію 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 хвилин на гугл варіацій на "знак питання Java") і нічого не отримав. Отже, моє запитання: чи існує якась офіційна документація з цього приводу? Я виявив, що C # має подібний оператор (оператор "??"), але я хотів би отримати документацію щодо мови, якою я працюю. Або це просто використання тернарного оператора, який я ніколи раніше не бачив.

Дякую!

EDIT: Посилання на статтю: http://infoworld.com/d/developer-world/12-programming-mistakes-avoid-292


3
Чи могли б ми мати посилання на статтю принаймні?
Karl Knechtel

А джерело фрагмента?
khachik

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

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 (Елвіс та інші нульові безпечні оператори), але ще не прийнятий .

Пов’язаний оператор Елвіса?: Було запропоновано зробити x ?: yскороченням x != null ? x : y, особливо корисним, коли х - складний вираз.


3
У Java (там, де немає автоматичного примусу до нуля) скороченняx!=null ? x : y
Michael Borgwardt

@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інше. ми не приховуємо проблему, якщо значення має значення NULL, що означає, що іноді очікується, що воно буде нульовим, і ви повинні це впоратись. іноді значення за замовчуванням є цілком розумними, і це не погана практика.
M.kazem Akhgary

2
Java просто відмовляється бути дещо сучасною. Просто припиніть цю мову вже.
Плагон


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 Я додав orElseметод до Pipeкласу.
Helder Pereira


7

Це насправді оператор безпечного перенаправлення Groovy . Ви не можете використовувати його на чистій Java (на жаль), тому цей пост просто помилковий (або, швидше за все, трохи вводить в оману, якщо він стверджує, що Groovy є "останньою версією Java").


2
Тож стаття помилилася - синтаксис не існує в рідній Java. Хм
Erty Seidohl

але посилання порушено
bvdb

6

Java не має точного синтаксису, але станом на JDK-8 у нас є необов’язковий API з різними методами. Отже, версія C # із використанням нульового умовного оператора :

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

можна записати наступним чином на Java з необов’язковим API :

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

якщо будь-який з person, getNameабо getGivenNameв нуль , то нуль повертається.


2

Можна визначити утилічні методи, які вирішують це майже досить добре за допомогою лямбда-Java 8.

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

Навіть якщо я вважаю, що це рішення якось круте, я вважаю, що віддаю перевагу секундному Helder Pereira, оскільки для цього не потрібні будь-які утилітні методи.

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

Я не впевнений, що це навіть спрацювало б; якщо, скажімо, посилання на особу було нульовим, чим би його замінив час виконання? Нова людина? Для цього потрібно, щоб особа мала якусь ініціалізацію за замовчуванням, яку ви очікували б у цьому випадку. Ви можете уникнути нульових винятків посилань, але ви все одно отримаєте непередбачувану поведінку, якщо не планували подібних типів установок.

?? оператора в 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. Загалом, здається, ідея полягає в тому, що людина? .GetName обчислює нуль, якщо особа нульова, або person.getName, якщо ні. Тож це майже схоже на заміну "" на null у всіх ваших прикладах.
subub

1
NullReferenceException також може бути вкинутий getName()або getGivenName()який ви не будете знати, якщо просто повернете порожній рядок для всіх випадків.
Jimmy T.

1
У Java це NullPointerException.
Туупертунут

в 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;
    }
}

Мені доведеться спробувати це. SI мають серйозні сумніви щодо всього цього "факультативного" бізнесу. Здається, це неприємний хак.
ggb667

3
Вся мета пропозиції Оператора Елвіса полягала в тому, щоб зробити це в один рядок. Цей підхід не кращий за підхід "if (! = Null)". Насправді я б стверджував, що він гірший, оскільки це не прямий
рух

Як сказав @ Даррон в іншій відповіді, те саме стосується і цього: "Проблема цього стилю полягає в тому, що NullPointerException, можливо, прийшов не з того місця, де ти очікував. І, отже, він може приховати справжню помилку."
Helder Pereira

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 (але так і не було включено).

Тим не менш, ви можете скористатися додатковим, що надається в 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, можливо, з’явився не звідки ви очікували. А отже, це може приховати справжню помилку.
Даррон,

2
@Darron, чи можете ви навести приклад написаного вами геттера, який може кинути NPE, і як ви хотіли б поводитися з цим по-іншому?
Пітер Лорі

2
ви запускаєте його в окрему змінну і перевіряєте, якщо як зазвичай. Ідея цього оператора полягає в усуненні цієї потворності. Даррон правий, ваше рішення може приховати та викинути винятки, які ви хочете кинути. Такі, як якщо б getName()викинули внутрішнє виняток, який ви не хочете викидати.
Mike Miller

2
Не будь-який виняток, це повинен бути NullPointerException. Ви намагаєтеся захистити себе від ситуації, яку ви не почали пояснювати, як це може відбуватися в реальному додатку.
Пітер Лорі

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