Java “?” Operador para verificação de nulo - O que é? (Não Ternário!)

86

Eu estava lendo um artigo com link de uma história do slashdot e me deparei com este pequeno boato:

Pegue a versão mais recente do Java, que tenta tornar a verificação de ponteiro nulo mais fácil, oferecendo uma sintaxe abreviada para o teste de ponteiro sem fim. Apenas adicionar um ponto de interrogação a cada invocação de método inclui automaticamente um teste para ponteiros nulos, substituindo um ninho de rato de instruções if-then, como:

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

Com isso:

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

Eu vasculhei a internet (ok, eu gastei pelo menos 15 minutos pesquisando variações sobre "ponto de interrogação java") e não encontrei nada. Então, minha pergunta: existe alguma documentação oficial sobre isso? Descobri que C # tem um operador semelhante (o operador "??"), mas gostaria de obter a documentação da linguagem em que estou trabalhando. Ou isso é apenas um uso do operador ternário que eu nunca visto antes.

Obrigado!

EDITAR: Link para o artigo: http://infoworld.com/d/developer-world/12-programming-mistakes-avoid-292

Erty Seidohl
fonte
3
Podemos ter um link para o artigo, pelo menos?
Karl Knechtel
E a fonte do snippet?
khachik
5
O artigo está errado. infoworld.com/print/145292 Eu acredito que um projeto de moeda foi enviado para ele. Mas ela não foi selecionada (pelos motivos mencionados no artigo - se você quiser fazer esse tipo de coisa, use C # ou algo assim) e certamente não está na versão atual da linguagem Java.
Tom Hawtin - tackline
4
Isso não é o mesmo que o C # ?? operador: ?? coalesces nulos, ou seja A ?? B == (A != null) ? A : B. Isso parece avaliar uma propriedade em um objeto se a referência do objeto não for nula, ou seja A?.B == (A != null) ? A.B : null.
Rup
1
@Erty: como um grande usuário da anotação @NotNull, que está basicamente em todo o código que escrevo, não sei mais o que são NPEs (exceto quando uso APIs mal projetadas). Mesmo assim, acho essa "notação de atalho" fofa e interessante. Claro que o artigo tem razão quando afirma: Afinal, não elimina a raiz do problema: a proliferação de valores nulos devido à programação rápida e frouxa. 'null' não existe no nível OOA / OOD. É outro absurdo idiossincrático do Java que pode ser contornado. Para mim, é @NotNull em todos os lugares.
SintaxeT3rr0r

Respostas:

78

A ideia original vem do groovy. Foi proposto para Java 7 como parte do Projeto Coin: https://wiki.openjdk.java.net/display/Coin/2009+Proposals+TOC (Elvis e outros operadores nulos-seguros), mas ainda não foi aceito .

O operador Elvis relacionado?: Foi proposto como uma x ?: yabreviação de x != null ? x : y, especialmente útil quando x é uma expressão complexa.

Ataylor
fonte
3
Em java (onde não há coação automática para nulo), uma abreviação parax!=null ? x : y
Michael Borgwardt
@Michael Borgwardt: bom ponto, eu estava pensando na semântica groovy.
ataylor
50
?é o topete de assinatura de Elvis Presley; o :justo representa um par de olhos como de costume. Talvez ?:-oseja mais evocativo ...
Andrzej Doyle
4
?:0Deve ser um operador "Não é nulo, nem 0". Faça assim.
azz de
3
Na verdade, foi um erro referir-se à proposta como um operador de Elvis. Explicação em mail.openjdk.java.net/pipermail/coin-dev/2009-July/002089.html " Desreferenciação nula-segura" é um termo melhor para usar.
JustinKSU de
62

Essa sintaxe não existe em Java, nem está programada para ser incluída em nenhuma das versões futuras que eu conheço.

ColinD
fonte
9
Por que o downvote? Esta resposta está 100% correta até onde eu sei ... se você souber de algo diferente, por favor diga.
ColinD
6
@Webinator: Não será no Java 7 ou 8 e não há outras "próximas versões" no momento. Eu também acho meio improvável que isso aconteça, já que incentiva práticas bastante ruins. Também não acho que "ainda" seja necessário, pois "não existe em Java" não é o mesmo que "nunca existirá em Java".
ColinD
9
@Webinator: vários participantes comentaram que uma proposta foi enviada, mas rejeitada. Portanto, a resposta é 100% precisa. Votação positiva para anular o voto negativo.
JeremyP
2
A má prática da @ColinD é quando você desiste desse código feio e decide usá-lo Optionale mapoutras coisas. não estamos escondendo o problema se o valor for anulável, o que significa que às vezes é esperado que seja nulo e você terá que lidar com isso. às vezes, os valores padrão são perfeitamente razoáveis ​​e não é uma prática ruim.
M.kazem Akhgary
2
Java simplesmente se recusa a ser um tanto moderno. Basta encerrar este idioma já.
Plagon
19

Uma maneira de contornar a falta de "?" operador usando Java 8 sem a sobrecarga de try-catch (que também poderia ocultar um NullPointerExceptionoriginado em outro lugar, como mencionado) é criar uma classe para "canalizar" métodos em um estilo 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;
    }
}

Então, o exemplo dado seria:

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

[EDITAR]

Pensando melhor, descobri que é realmente possível conseguir o mesmo apenas usando classes Java 8 padrão:

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

Neste caso, é ainda possível escolher um valor padrão (like "<no first name>") em vez de nullpassá-lo como parâmetro de orElse.

Helder Pereira
fonte
Eu gosto mais da sua primeira solução. Como você melhoraria sua Pipeclasse para adaptar uma orElsefuncionalidade para que eu pudesse passar um objeto não nulo arbitrário dentro do orElsemétodo?
ThanosFisherman de
@ThanosFisherman Eu adicionei o orElsemétodo à Pipeclasse.
Helder Pereira de
7

Na verdade, esse é o operador de desreferência segura do Groovy . Você não pode usá-lo em Java puro (infelizmente), então essa postagem está simplesmente errada (ou mais provavelmente um pouco enganosa, se estiver alegando que o Groovy é a "versão mais recente do Java").

Andrzej Doyle
fonte
2
Portanto, o artigo estava errado - a sintaxe não existe em java nativo. Hmm.
Erty Seidohl
mas o link está quebrado
bvdb 01 de
6

Java não tem a sintaxe exata, mas a partir do JDK-8, temos a API opcional com vários métodos à nossa disposição. Portanto, a versão C # com o uso do operador condicional nulo :

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

pode ser escrito da seguinte forma em Java com a API opcional :

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

se algum de person, getNameou getGivenNamefor nulo, será retornado nulo.

Ousmane D.
fonte
2

É possível definir métodos util que resolvem isso de uma maneira quase bonita com Java 8 lambda.

Esta é uma variação da solução H-MANs, mas usa métodos sobrecarregados com vários argumentos para lidar com várias etapas em vez de captura NullPointerException.

Mesmo que eu ache que essa solução seja legal, eu prefiro a segunda de Helder Pereira, já que ela não requer nenhum método utilitário.

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; }
}
Lii
fonte
1

Não tenho certeza se isso funcionaria; se, digamos, a referência de pessoa fosse nula, pelo que o tempo de execução a substituiria? Uma nova pessoa? Isso exigiria que a pessoa tivesse alguma inicialização padrão que você esperaria neste caso. Você pode evitar exceções de referência nula, mas ainda obteria um comportamento imprevisível se não planejasse esses tipos de configurações.

O ?? operador em C # pode ser denominado melhor o operador "coalescer"; você pode encadear várias expressões e ele retornará a primeira que não for nula. Infelizmente, o Java não tem. Acho que o melhor que você pode fazer é usar o operador ternário para realizar verificações de nulos e avaliar uma alternativa para a expressão inteira se algum membro da cadeia for nulo:

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

Você também pode usar try-catch:

try
{
   return person.getName().getGivenName();
}
catch(NullReferenceException)
{
   return "";
}
KeithS
fonte
1
"pelo que o tempo de execução o substituiria?" ... ler a pergunta pode ajudar :-P Ele iria substituí-lo por nulo. Em geral, a ideia parece ser aquela pessoa? .GetName avalia como nulo se pessoa for nula ou para pessoa.getNome se não for. Portanto, é quase como substituir "" por nulo em todos os seus exemplos.
subsub
1
Uma NullReferenceException também pode ser lançada getName()ou getGivenName()você não saberá se simplesmente retornar uma string vazia para todas as ocorrências.
Jimmy T.
1
Em Java, é NullPointerException.
Tuupertunut
em C #, person?.getName()?.getGivenName() ?? ""é o equivalente ao seu primeiro exemplo, com a exceção de que se getGivenName()retornar nulo, ainda dará""
Austin_Anderson
0

Aí está, invocação null-safe em 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;
    }
}
H-MAN
fonte
Eu terei que tentar isso. A SI tem sérias dúvidas sobre todo esse negócio de "Opcionais". Parece um hack desagradável.
ggb667
3
Todo o propósito da proposta do Operador Elvis era fazer isso em uma linha. Esta abordagem não é melhor do que a abordagem "if (! = Null) acima. Na verdade, eu diria que é pior, pois não é uma abordagem direta. Além disso, você deve evitar lançar e capturar erros devido à sobrecarga.
JustinKSU
Como @Darron disse em outra resposta, o mesmo se aplica aqui: "O problema com esse estilo é que a NullPointerException pode não ter vindo de onde você esperava. E, portanto, pode ocultar um bug real."
Helder Pereira
0

Se alguém estiver procurando uma alternativa para versões antigas do java, você pode tentar esta que escrevi:

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

}
irobson
fonte
0

Você pode testar o código que forneceu e ele apresentará um erro de sintaxe. Portanto, não é compatível com Java. Groovy oferece suporte e foi proposto para Java 7 (mas nunca foi incluído).

No entanto, você pode usar o opcional fornecido em Java 8. Isso pode ajudá-lo a alcançar algo em linha semelhante. https://docs.oracle.com/javase/8/docs/api/java/util/Optional.html http://www.oracle.com/technetwork/articles/java/java8-optional-2175753.html

Exemplo de código para opcional

SK -
fonte
0

Como o Android não oferece suporte a Funções Lambda, a menos que seu sistema operacional instalado seja> = 24, precisamos usar reflexão.

// 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;
        }
    }
}
LanDenLabs
fonte
-4

Se este não for um problema de desempenho para você, você pode escrever

public String getFirstName(Person person) {
  try {
     return person.getName().getGivenName();
  } catch (NullPointerException ignored) {
     return null;
  }
} 
Peter Lawrey
fonte
8
O problema com esse estilo é que NullPointerException pode não ter vindo de onde você esperava. E assim pode esconder um bug real.
Darron
2
@Darron, você pode dar um exemplo de getter que você escreveu que poderia lançar um NPE e como você gostaria de lidar com isso de forma diferente?
Peter Lawrey
2
você o executa em uma variável separada e o testa com um if normal. A ideia por trás dessa operadora é eliminar essa feiura. Darron está certo, sua solução pode ocultar e descartar as exceções que você deseja lançar. Como se tivesse getName()lançado uma exceção internamente que você não quer jogar fora.
Mike Miller
2
Sem exceção, teria que ser um NullPointerException. Você está tentando se proteger de uma situação que ainda não começou a explicar como pode ocorrer em um aplicativo real.
Peter Lawrey
1
Em um aplicativo real, Personpode ser um proxy acessando um banco de dados ou alguma memória não heap e pode haver um bug em algum lugar ... Não é muito realista, mas, Peter, aposto que você nunca escreveu um trecho de código como o acima .
maaartinus de