Java “?” Boş değeri kontrol etme operatörü - Nedir? (Üçlü değil!)


88

Bir slashdot hikayesinden bağlantılı bir makale okuyordum ve şu küçük haberle karşılaştım:

Sonsuz işaretçi testi için kısayol sözdizimi sunarak boş işaretçi denetimini kolaylaştırmaya çalışan en son Java sürümünü alın. Her yöntem çağrısına bir soru işareti eklemek, otomatik olarak boş işaretçiler için bir test içerir ve farenin if-then ifadeleri yuvasını değiştirir, örneğin:

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

Bununla:

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

İnterneti araştırdım (tamam, "java soru işareti" üzerinde en az 15 dakika googling varyasyonları harcadım) ve hiçbir şey alamadım. Öyleyse sorum: bununla ilgili herhangi bir resmi belge var mı? C # 'ın benzer bir operatörü ("??" operatörü) olduğunu buldum, ancak çalıştığım dil için dokümantasyonu almak istiyorum. Veya, bu sadece sahip olduğum üçlü operatörün bir kullanımı mı? daha önce hiç görülmemiş.

Teşekkürler!

DÜZENLEME: Makaleye bağlantı: http://infoworld.com/d/developer-world/12-programming-mistakes-avoid-292


3
En azından makaleye bir bağlantı verebilir miyiz?
Karl Knechtel

1
Ve pasajın kaynağı?
khachik

5
Makale yanlış. infoworld.com/print/145292 Bunun için bir Proje Parası gönderiminin gönderildiğine inanıyorum. Ancak seçilmedi (makalede bahsedilen nedenlerden dolayı - bu tür bir şey yapmak istiyorsanız, C # veya başka bir şey kullanın) ve kesinlikle Java dilinin şu anki sürümünde değil.
Tom Hawtin -

4
Bu C # ile aynı değil mi? Şebeke: ?? boşlukları birleştirir, yani A ?? B == (A != null) ? A : B. Bu, nesne başvurusu boş değilse, bir nesne üzerindeki bir özelliği değerlendiriyor gibi görünür A?.B == (A != null) ? A.B : null.
Rup

1
@Erty: Temelde yazdığım kodun her yerinde bulunan @NotNull ek açıklamasının büyük bir kullanıcısı olarak, artık NPE'lerin ne olduğunu çok iyi bilmiyorum (kötü tasarlanmış API'leri kullanmanın dışında). Yine de bu "kısayol gösterimini" sevimli ve ilginç buluyorum. Elbette makale şunu ifade ederken haklıdır: Sonuçta, sorunun kökenini ortadan kaldırmaz: hızlı ve gevşek programlama nedeniyle boş değerlerin çoğalması. OOA / OOD seviyesinde "boş" mevcut değil. Çoğunlukla üzerinde çalışılabilecek bir başka Java kendine özgü saçmalık. Bana göre her yerde @NotNull var.
SyntaxT3rr0r

Yanıtlar:


80

Orijinal fikir, harika bir fikirden geliyor. Project Coin'in bir parçası olarak Java 7 için önerildi: https://wiki.openjdk.java.net/display/Coin/2009+Proposals+TOC (Elvis ve Diğer Null-Safe Operatörler), ancak henüz kabul edilmedi .

İlgili Elvis operatörü x ?: y?: İçin bir kısaltma yapması önerildi x != null ? x : y, özellikle x karmaşık bir ifade olduğunda kullanışlı.


3
Java'da (boşa otomatik zorlamanın olmadığı yerlerde) x!=null ? x : y
şunun

@Michael Borgwardt: İyi nokta, harika anlambilim düşünüyordum.
ataylor

50
?Elvis Presley'in imzası olan quiff; :Her zamanki gibi bir çift göz temsil eder. Belki ?:-odaha anımsatıcıdır ...
Andrzej Doyle

4
?:0"Boş değil, ne de 0" operatörü olmalıdır. Yap o zaman.
azz

3
Teklife Elvis operatörü olarak atıfta bulunmak aslında bir hataydı. Mail.openjdk.java.net/pipermail/coin-dev/2009-July/002089.html adresindeki açıklama "Boş güvenlikli dereferencing" kullanmak daha iyi bir terimdir.
JustinKSU

63

Bu sözdizimi Java'da yoktur ve bildiğim gelecek sürümlerden herhangi birine dahil edilmesi planlanmamıştır.


9
Neden olumsuz oy? Bu cevap bildiğim kadarıyla% 100 doğru ... Eğer farklı bir şey biliyorsanız, lütfen söyleyin.
ColinD

6
@Webinator: Java 7 veya 8'de olmayacak ve şu anda başka "gelecek sürümler" de yok. Ayrıca, oldukça kötü uygulamaları teşvik ettiği için, içeri girme ihtimalini de düşük buluyorum. "Java'da yok" ile "Java'da asla var olmayacak" ile aynı şey olmadığı için "henüz" nin gerekli olduğunu da düşünmüyorum.
ColinD

9
@Webinator: Birkaç poster bir teklifin gönderildiği ancak reddedildiği yorumunu yaptı. Dolayısıyla cevap% 100 doğrudur. Olumsuz oyu etkisiz hale getirmek için oylama.
JeremyP

2
@ColinD kötü uygulama bu çirkin kod vazgeçmek ve kullanımına karar verirken olduğunu Optionalve mapmalzeme. Değer null yapılabilir ise sorunu gizlemiyoruz, bu da bazen boş olmasının beklendiği ve bunu halletmeniz gerektiği anlamına gelir. bazen varsayılan değerler tamamen mantıklıdır ve kötü bir uygulama değildir.
M.kazem Akhgary

2
Java sadece biraz modern olmayı reddediyor. Sadece bu dili şimdiden sonlandırın.
Plagon


19

"?" Eksikliğini geçici olarak çözmenin bir yolu Try-catch ek yükü olmadan Java 8 kullanan operatör ( NullPointerExceptionbahsedildiği gibi, başka bir yerde ortaya çıkmış olanı da gizleyebilir ), Java-8-Stream stilinde yöntemleri "yönlendirmek" için bir sınıf oluşturmaktır.

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

Ardından, verilen örnek şöyle olur:

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

[DÜZENLE]

Daha fazla düşündükten sonra, aynı şeyi yalnızca standart Java 8 sınıflarını kullanarak elde etmenin aslında mümkün olduğunu anladım:

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

Bu durumda, parametresi olarak geçmek "<no first name>"yerine varsayılan bir değer (gibi ) seçmek bile mümkündür .nullorElse


İlk çözümünüzü daha çok beğendim. Metodun içine rastgele boş olmayan bir nesneyi geçirebilmem için Pipebir orElseişlevselliği uyarlamak için sınıfınızı nasıl geliştirirsiniz orElse?
ThanosFisherman

@ThanosFisherman orElseYöntemi Pipesınıfa ekledim .
Helder Pereira


7

Bu aslında Groovy'nin güvenli referans operatörü . Onu saf Java'da (ne yazık ki) kullanamazsınız, bu yüzden bu gönderi tamamen yanlıştır (veya Groovy'nin "Java'nın en son sürümü" olduğunu iddia ediyorsa, muhtemelen biraz yanıltıcıdır).


2
Yani makale yanlıştı - sözdizimi yerel java'da mevcut değil. Hmm.
Erty Seidohl

ancak bağlantı bozuk
bvdb

6

Java'nın tam sözdizimi yoktur, ancak JDK-8'den itibaren, çeşitli yöntemlerle isteğe bağlı API'ye sahibiz . Bu nedenle, boş koşullu operatörün kullanıldığı C # sürümü :

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

İsteğe Bağlı API ile Java'da aşağıdaki şekilde yazılabilir :

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

herhangi eğer person, getNameya getGivenNameboş sonra boş döndürülür edilir.


2

Java 8 lambda ile bunu oldukça güzel bir şekilde çözen util yöntemleri tanımlamak mümkündür.

Bu, H-MAN çözümünün bir varyasyonudur, ancak yakalamak yerine birden çok adımı işlemek için birden çok argümana sahip aşırı yüklenmiş yöntemler kullanır NullPointerException.

Bu çözümün harika olduğunu düşünsem bile Helder Pereira'nın ikinci saniyesini tercih ettiğimi düşünüyorum çünkü bu herhangi bir kullanım yöntemi gerektirmiyor.

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

Bunun işe yarayacağından bile emin değilim; diyelim ki kişi referansı boş olsaydı, çalışma zamanı onu neyle değiştirirdi? Yeni Kişi? Bu, Kişinin bu durumda bekleyeceğiniz bazı varsayılan başlatmaya sahip olmasını gerektirir. Boş referans istisnalarından kaçınabilirsiniz, ancak bu tür kurulumları planlamadıysanız yine de öngörülemeyen davranışlar elde edersiniz.

?? C # operatörü en iyi "birleştirme" operatörü olarak adlandırılabilir; birkaç ifadeyi zincirleyebilirsiniz ve boş olmayan ilk ifadeyi döndürür. Maalesef Java'da yok. Bence yapabileceğiniz en iyi şey, üç terimli operatörü kullanarak boş kontroller yapmak ve zincirdeki herhangi bir üye boş ise tüm ifadeye bir alternatifi değerlendirmek:

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

Ayrıca try-catch'i de kullanabilirsiniz:

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

1
"çalışma zamanı onu ne ile değiştirir?" ... soruyu okumak yardımcı olabilir: -P Onu null ile değiştirirdi. Genel olarak, fikir o kişi gibi görünüyor? .GetName, kişi boşsa boş olarak değerlendirilir veya değilse person.getName olarak değerlendirilir. Bu nedenle, tüm örneklerinizde "" null yerine null koymak gibidir.
subsub

1
Bir NullReferenceException da atılmış olabilir getName()veya getGivenName()tüm olaylar için yalnızca boş bir dize döndürürseniz bilemeyeceksiniz.
Jimmy T.

1
Java'da NullPointerException'dır.
Tuupertunut

C # 'da, person?.getName()?.getGivenName() ?? ""ilk örneğinizin eşdeğeridir getGivenName(), ancak null döndürürse, yine de verir""
Austin_Anderson

0

İşte Java 8'de boş güvenlikli çağrı var:

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

Bunu denemek zorunda kalacağım. SI'nın tüm bu "İsteğe Bağlı" iş hakkında ciddi şüpheleri var. Kötü bir hack gibi görünüyor.
ggb667

3
Elvis Operatör teklifinin tüm amacı, bunu tek bir satırda yapmaktı. Bu yaklaşım, yukarıdaki "eğer (! = Null) yaklaşımından daha iyi değildir. Aslında, basit olmadığı için daha kötü olduğunu iddia ediyorum. Ek olarak, ek
yükten

@Darron'un başka bir cevapta söylediği gibi, burada da aynı şey geçerlidir: "Bu tarzdaki sorun, NullPointerException'ın beklediğiniz yerden gelmemiş olabileceğidir. Ve bu nedenle gerçek bir hatayı gizleyebilir."
Helder Pereira

0

Birisi eski java sürümleri için bir alternatif arıyorsanız, yazdığım şunu deneyebilirsiniz:

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

Verdiğiniz kodu test edebilirsiniz ve bu sözdizimi hatası verecektir, bu yüzden Java'da desteklenmemektedir. Groovy bunu destekliyor ve Java 7 için önerildi (ancak dahil edilmedi).

Bununla birlikte, Java 8'de sağlanan İsteğe Bağlı'yı kullanabilirsiniz. Bu, benzer bir hat üzerinde bir şey elde etmenize yardımcı olabilir. https://docs.oracle.com/javase/8/docs/api/java/util/Optional.html http://www.oracle.com/technetwork/articles/java/java8-optional-2175753.html

İsteğe Bağlı Örnek Kod


0

Android, kurulu işletim sisteminiz> = 24 olmadığı sürece Lambda İşlevlerini desteklemediğinden, yansıma kullanmamız gerekir.

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

Bu sizin için bir performans sorunu değilse yazabilirsiniz

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

8
Bu stille ilgili sorun, NullPointerException'ın beklediğiniz yerden gelmemiş olabileceğidir. Ve böylece gerçek bir hatayı gizleyebilir.
Darron

2
@Darron, NPE'yi atabilecek yazdığınız bir alıcıya ve onu farklı şekilde nasıl ele almak isteyeceğinize bir örnek verebilir misiniz?
Peter Lawrey

2
bunu ayrı bir değişkenle çalıştırır ve normal bir if ile test edersiniz. Bu operatörün arkasındaki fikir, bu çirkinliği ortadan kaldırmaktır. Darron haklı, çözümünüz atmak istediğiniz istisnaları saklayabilir ve atabilir. Örneğin, getName()atmak istemediğiniz bir istisna içeriden atmış gibi .
Mike Miller

2
Herhangi bir istisna değil, bir NullPointerException olması gerekir. Kendinizi gerçek bir uygulamada nasıl olabileceğini açıklamaya başlamadığınız bir durumdan korumaya çalışıyorsunuz.
Peter Lawrey

1
Gerçek bir uygulamada Person, bir DB'ye veya yığın olmayan belleğe erişen bir proxy olabilir ve bir yerde bir hata olabilir ... Pek gerçekçi değil, ama Peter, bahse girerim yukarıdaki gibi bir kod parçası yazmamışsındır. .
maaartinus
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.