Java „?” Operator do sprawdzania null - co to jest? (Nie potrójny!)


86

Czytałem artykuł powiązany z historią slashdot i natknąłem się na tę małą ciekawostkę:

Weź najnowszą wersję Javy, która stara się ułatwić sprawdzanie wskaźnika null, oferując skróconą składnię do niekończących się testów wskaźników. Samo dodanie znaku zapytania do każdego wywołania metody automatycznie obejmuje test na zerowe wskaźniki, zastępując szczurzy zestaw instrukcji jeśli-to, takich jak:

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

Z tym:

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

Przeszukałem internet (ok, spędziłem co najmniej 15 minut na googlowaniu odmian „znaku zapytania java”) i nic nie znalazłem. A więc moje pytanie: czy jest jakaś oficjalna dokumentacja na ten temat? Odkryłem, że C # ma podobny operator (operator „??”), ale chciałbym uzyskać dokumentację dla języka, w którym pracuję. Czy jest to tylko użycie operatora trójskładnikowego, nigdy wcześniej nie widziane.

Dzięki!

EDYCJA: Link do artykułu: http://infoworld.com/d/developer-world/12-programming-mistakes-avoid-292


3
Czy moglibyśmy mieć przynajmniej link do artykułu?
Karl Knechtel

A źródło fragmentu?
chaczik

5
Artykuł jest nieprawidłowy. infoworld.com/print/145292 Wydaje mi się, że przesłano do niego zgłoszenie projektu monety. Ale nie został wybrany (z powodów wymienionych w artykule - jeśli chcesz to zrobić, użyj C # lub czegoś podobnego) i na pewno nie jest w aktualnej wersji języka Java.
Tom Hawtin - tackline

4
To nie to samo co C #? operator: ?? łączy null, tj A ?? B == (A != null) ? A : B. Wydaje się, że ocenia to właściwość obiektu, jeśli odniesienie do obiektu nie jest zerowe, tj A?.B == (A != null) ? A.B : null.
Rup

1
@Erty: jako ogromny użytkownik adnotacji @NotNull, która jest praktycznie wszędzie w kodzie, który piszę, nie wiem już zbyt dobrze, czym są NPE (z wyjątkiem sytuacji, gdy używam źle zaprojektowanych API). Jednak uważam ten „skrótowy zapis” za ładny i interesujący. Oczywiście artykuł ma rację, gdy stwierdza: w końcu nie eliminuje on źródła problemu: mnożenia się wartości zerowych z powodu szybkiego i luźnego programowania. Wartość „null” nie istnieje na poziomie OOA / OOD. Jest to kolejny idiosynchroniczny nonsens Java, który można w większości obejść. Dla mnie to @NotNull wszędzie.
Składnia T3rr0r

Odpowiedzi:


78

Oryginalny pomysł wywodzi się z groovy. Został zaproponowany dla Java 7 w ramach Project Coin: https://wiki.openjdk.java.net/display/Coin/2009+Prop Suggest+ TOC (Elvis i inni operatorzy Null-Safe), ale nie został jeszcze zaakceptowany .

Powiązany operator Elvisa?: Został zaproponowany jako x ?: yskrót dla x != null ? x : y, szczególnie przydatny, gdy x jest złożonym wyrażeniem.


3
W Javie (gdzie nie ma automatycznego przymusu do zera) skrót dlax!=null ? x : y
Michael Borgwardt

@Michael Borgwardt: słuszna uwaga, myślałem o świetnej semantyce.
ataylor

50
?jest podpisowym quiffem Elvisa Presleya; po :prostu reprezentuje parę oczu, jak zwykle. Może ?:-ojest bardziej sugestywny ...
Andrzej Doyle

4
?:0Powinien być operatorem „Nie null ani 0”. Zrób to tak.
azz

3
Nazywanie propozycji operatorem Elvisa było właściwie błędem. Wyjaśnienie pod adresem mail.openjdk.java.net/pipermail/coin-dev/2009-July/002089.html Lepszym terminem jest „dereferencja bez zabezpieczeń”.
JustinKSU

62

Ta składnia nie istnieje w Javie, ani nie ma być uwzględniona w żadnej z przyszłych wersji, które znam.


9
Dlaczego głos przeciw? O ile wiem, ta odpowiedź jest w 100% poprawna ... jeśli wiesz coś innego, powiedz to.
ColinD,

6
@Webinator: Nie będzie w Javie 7 ani 8, a obecnie nie ma innych „nadchodzących wersji”. Wydaje mi się też mało prawdopodobne, że się uda, ponieważ zachęca do raczej złych praktyk. Uważam też, że „jeszcze” nie jest konieczne, ponieważ „nie istnieje w Javie” to nie to samo, co „nigdy nie będzie istnieć w Javie”.
ColinD,

9
@Webinator: kilku posterów skomentowało, że propozycja została złożona, ale została odrzucona. Zatem odpowiedź jest w 100% trafna. Głosowanie za przeciwdziałaniem głosowaniu przeciw.
JeremyP

2
@ColinD zła praktyka polega na rezygnacji z tego brzydkiego kodu i zdecydowaniu się na użycie Optionali maptakie tam. Nie ukrywamy problemu, jeśli value dopuszcza wartość null, co oznacza, że ​​czasami oczekuje się, że będzie zerowa i musisz sobie z tym poradzić. czasami wartości domyślne są całkowicie rozsądne i nie jest to zła praktyka.
M.kazem Akhgary,

2
Java po prostu nie chce być nieco nowoczesna. Po prostu zakończ ten język już.
Plagon


19

Jednym ze sposobów obejścia braku znaku „?” Operator używający Javy 8 bez narzutu try-catch (który mógłby również ukryć NullPointerExceptionpochodzący z innego miejsca, jak wspomniano) polega na utworzeniu klasy do metod „potokowych” w stylu 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;
    }
}

Wtedy podany przykład wyglądałby następująco:

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

[EDYTOWAĆ]

Po głębszym przemyśleniu doszedłem do wniosku, że to samo można osiągnąć tylko przy użyciu standardowych klas Java 8:

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

W takim przypadku możliwe jest nawet wybranie wartości domyślnej (np. "<no first name>") Zamiast nullprzekazywania jej jako parametru orElse.


Twoje pierwsze rozwiązanie bardziej mi się podoba. W jaki sposób ulepszysz swoją Pipeklasę, aby dostosować orElsefunkcjonalność, tak żebym mógł przekazać dowolny obiekt niezerowy wewnątrzorElse metody?
ThanosFisherman

@ThanosFisherman Dodałem orElsemetodę do Pipeklasy.
Helder Pereira



6

Java nie ma dokładnej składni, ale od JDK-8 mamy do dyspozycji opcjonalne API z różnymi metodami. A więc wersja C # z użyciem zerowego operatora warunkowego :

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

można zapisać w następujący sposób w Javie z opcjonalnym interfejsem API :

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

jeśli którykolwiek z person , getNamelub getGivenNamejest zerowa, a następnie jest zawracana puste.


2

Możliwe jest zdefiniowanie metod użytkowych, które rozwiązują ten problem w prawie ładny sposób za pomocą lambdy Java 8.

Jest to odmiana rozwiązania H-MAN, ale używa przeciążonych metod z wieloma argumentami do obsługi wielu kroków zamiast łapaniaNullPointerException .

Nawet jeśli uważam to rozwiązanie za fajne, myślę, że wolę sekundowe rozwiązanie Heldera Pereiry, ponieważ nie wymaga to żadnych metod użytkowych.

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

Nie jestem pewien, czy to w ogóle zadziała; jeśli, powiedzmy, odniesienie do osoby miało wartość null, czym zostało zastąpione przez środowisko wykonawcze? Nowa osoba? Wymagałoby to od osoby domyślnej inicjalizacji, której można by się spodziewać w tym przypadku. Możesz uniknąć zerowych wyjątków odwołań, ale nadal uzyskasz nieprzewidywalne zachowanie, jeśli nie planujesz tego typu konfiguracji.

?? operator w C # można najlepiej nazwać operatorem „koalescencji”; możesz połączyć w łańcuch kilka wyrażeń i zwróci to pierwsze, które nie jest null. Niestety Java tego nie ma. Myślę, że najlepsze, co możesz zrobić, to użyć operatora trójargumentowego do sprawdzenia wartości null i oceny alternatywy dla całego wyrażenia, jeśli którykolwiek element w łańcuchu jest pusty:

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

Możesz także użyć try-catch:

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

1
„czym środowisko wykonawcze mogłoby to zastąpić?” ... przeczytanie pytania może pomóc :-P Zastąpiłoby je wartością null. Ogólnie rzecz biorąc, wydaje się, że pomysłem jest ta osoba? .GetName zwraca wartość null, jeśli osoba jest pusta, lub do person.getName, jeśli nie. Jest to więc prawie jak zamiana „” na null we wszystkich przykładach.
podsub

1
Może również zostać zgłoszony wyjątek NullReferenceException getName()lub getGivenName()którego nie będziesz wiedzieć, jeśli po prostu zwrócisz pusty ciąg dla wszystkich wystąpień.
Jimmy T.

1
W Javie jest to NullPointerException.
Tuupertunut

w C # person?.getName()?.getGivenName() ?? ""jest odpowiednikiem twojego pierwszego przykładu, z wyjątkiem tego, że jeśli getGivenName()zwróci wartość null, nadal da""
Austin_Anderson

0

Masz to, wywołanie bezpieczne null w Javie 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;
    }
}

Będę musiał tego spróbować. SI mają poważne wątpliwości co do tej całej „opcjonalnej” sprawy. Wygląda na paskudny hack.
ggb667,

3
Celem propozycji Elvis Operator było zrobienie tego w jednej linii. To podejście nie jest lepsze niż powyższe podejście "if (! = Null). W rzeczywistości uważałbym, że jest gorsze, ponieważ nie jest proste. Dodatkowo należy unikać rzucania i łapania błędów z powodu narzutu."
JustinKSU

Jak @Darron powiedział w innej odpowiedzi, to samo dotyczy tutaj: „Problem z tym stylem polega na tym, że wyjątek NullPointerException mógł nie pochodzić z miejsca, w którym się tego spodziewałeś. A zatem może ukryć prawdziwy błąd”.
Helder Pereira

0

Jeśli ktoś szuka alternatywy dla starych wersji java, może wypróbować tę, którą napisałem:

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

Możesz przetestować kod, który dostarczyłeś, a spowoduje to błąd składniowy, więc nie jest obsługiwany w Javie. Groovy to obsługuje i został zaproponowany dla Java 7 (ale nigdy nie został uwzględniony).

Możesz jednak użyć opcji Opcjonalnej dostępnej w Javie 8. Może to pomóc w osiągnięciu czegoś podobnego. https://docs.oracle.com/javase/8/docs/api/java/util/Optional.html http://www.oracle.com/technetwork/articles/java/java8-optional-2175753.html

Przykładowy kod opcjonalny


0

Ponieważ Android nie obsługuje funkcji Lambda, chyba że zainstalowany system operacyjny jest> = 24, musimy użyć refleksji.

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

Jeśli nie jest to dla Ciebie kwestia wydajności, możesz napisać

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

8
Problem z tym stylem polega na tym, że wyjątek NullPointerException mógł nie pochodzić z oczekiwanego miejsca. W ten sposób może ukryć prawdziwy błąd.
Darron

2
@Darron, Czy możesz podać przykład kodu pobierającego, który napisałeś, który może wyrzucić NPE i jak chciałbyś inaczej sobie z tym poradzić?
Peter Lawrey

2
uruchamiasz go w osobnej zmiennej i testujesz za pomocą if jak zwykle. Ideą tego operatora jest wyeliminowanie tej brzydoty. Darron ma rację, twoje rozwiązanie może ukryć i wyrzucić wyjątki, które chcesz wyrzucić. Na przykład, gdybyś getName()wyrzucił wewnętrznie wyjątek, którego nie chcesz wyrzucić.
Mike Miller

2
Nie jest to żaden wyjątek, musiałby to być wyjątek NullPointerException. Próbujesz uchronić się przed sytuacją, w której nie zacząłeś wyjaśniać, jak może się to zdarzyć w prawdziwej aplikacji.
Peter Lawrey

1
W prawdziwej aplikacji Personmoże być proxy uzyskującym dostęp do bazy danych lub jakiejś pamięci innej niż sterta i gdzieś może być błąd ... Niezbyt realistyczne, ale Peter, założę się, że nigdy nie napisałeś fragmentu kodu takiego jak powyżej .
maaartinus
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.