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
A ?? B == (A != null) ? A : B
. Wydaje się, że ocenia to właściwość obiektu, jeśli odniesienie do obiektu nie jest zerowe, tjA?.B == (A != null) ? A.B : null
.Odpowiedzi:
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 ?: y
skrót dlax != null ? x : y
, szczególnie przydatny, gdy x jest złożonym wyrażeniem.źródło
x!=null ? x : y
?
jest podpisowym quiffem Elvisa Presleya; po:
prostu reprezentuje parę oczu, jak zwykle. Może?:-o
jest bardziej sugestywny ...?:0
Powinien być operatorem „Nie null ani 0”. Zrób to tak.Ta składnia nie istnieje w Javie, ani nie ma być uwzględniona w żadnej z przyszłych wersji, które znam.
źródło
Optional
imap
takie 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.Była taka propozycja w Javie 7, ale została odrzucona:
http://tech.puredanger.com/java7/#null
źródło
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ć
NullPointerException
pochodzą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>"
) Zamiastnull
przekazywania jej jako parametruorElse
.źródło
Pipe
klasę, aby dostosowaćorElse
funkcjonalność, tak żebym mógł przekazać dowolny obiekt niezerowy wewnątrzorElse
metody?orElse
metodę doPipe
klasy.Zobacz: https://blogs.oracle.com/darcy/project-coin:-the-final-five-or-so (konkretnie „Elvis i inni null safe operatorzy”).
W rezultacie ta funkcja była rozważana dla języka Java 7, ale nie została uwzględniona.
źródło
To właściwie operator bezpiecznego wyłuskiwania Groovy'ego . Nie możesz go używać w czystej Javie (niestety), więc ten post jest po prostu błędny (lub bardziej prawdopodobne, że wprowadza w błąd, jeśli twierdzi, że Groovy jest „najnowszą wersją Javy”).
źródło
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
,getName
lubgetGivenName
jest zerowa, a następnie jest zawracana puste.źródło
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 łapania
NullPointerException
.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; } }
źródło
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 ""; }
źródło
getName()
lubgetGivenName()
którego nie będziesz wiedzieć, jeśli po prostu zwrócisz pusty ciąg dla wszystkich wystąpień.person?.getName()?.getGivenName() ?? ""
jest odpowiednikiem twojego pierwszego przykładu, z wyjątkiem tego, że jeśligetGivenName()
zwróci wartość null, nadal da""
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; } }
źródło
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; } }
źródło
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
źródło
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; } } }
źródło
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; } }
źródło
getName()
wyrzucił wewnętrznie wyjątek, którego nie chcesz wyrzucić.Person
moż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 .