Como usar as anotações @Nullable e @Nonnull com mais eficiência?

140

Eu posso ver que @Nullablee @Nonnullanotações pode ser útil na prevenção NullPointerExceptions, mas eles não se propagam muito longe.

  • A eficácia dessas anotações diminui completamente após um nível de indireção, portanto, se você adicionar apenas algumas, elas não se propagam muito longe.
  • Como essas anotações não são bem aplicadas, existe o risco de assumir que um valor marcado com @Nonnullnão é nulo e, consequentemente, não executa verificações nulas.

O código abaixo faz com que um parâmetro marcado com @Nonnullseja nullsem apresentar nenhuma reclamação. Ele lança um NullPointerExceptionquando é executado.

public class Clazz {
    public static void main(String[] args){
        Clazz clazz = new Clazz();

        // this line raises a complaint with the IDE (IntelliJ 11)
        clazz.directPathToA(null);

        // this line does not
        clazz.indirectPathToA(null); 
    }

    public void indirectPathToA(Integer y){
        directPathToA(y);
    }

    public void directPathToA(@Nonnull Integer x){
        x.toString(); // do stuff to x        
    }
}

Existe uma maneira de tornar essas anotações mais rigorosamente aplicadas e / ou propagar mais?

Mike Rylander
fonte
1
Eu gosto da idéia de @Nullableou @Nonnull, mas se eles valem a pena é muito "provável que debate solicit"
Maarten Bodewes
Eu acho que a maneira de mudar para um mundo em que isso causa um erro ou aviso do compilador é exigir uma conversão ao @Nonnullchamar um @Nonnullmétodo com uma variável anulável. Obviamente, a conversão com uma anotação não é possível no Java 7, mas o Java 8 adicionará a capacidade de aplicar anotações ao uso de uma variável, incluindo projeções. Portanto, este pode tornar-se possível implementar em Java 8.
Theodore Murdock
1
@TheodoreMurdock, sim, no Java 8, uma conversão (@NonNull Integer) yé sintaticamente possível, mas um compilador não tem permissão para emitir nenhum código de byte específico com base na anotação. Para asserções de tempo de execução, pequenos métodos auxiliares são suficientes, conforme discutido em bugs.eclipse.org/442103 (por exemplo, directPathToA(assertNonNull(y))) - mas lembre-se, isso só ajuda a falhar rapidamente. A única maneira segura é executar uma verificação nula real (além de, esperançosamente, uma implementação alternativa no ramo else).
23715 Stephan Herrmann
1
Seria útil nesta questão de dizer que @Nonnulle @Nullableque você está falando, pois há vários annoations semelhantes (Veja esta pergunta ). Você está falando sobre as anotações no pacote javax.annotation?
James Dunn
1
@TJamesBoone Para o contexto desta pergunta, não importa, era sobre como usar qualquer uma delas de maneira eficaz.
Mike Rylander 06/02

Respostas:

66

Resposta curta: acho que essas anotações são úteis apenas para o seu IDE avisar sobre erros de ponteiro potencialmente nulos.

Como dito no livro "Código Limpo", você deve verificar os parâmetros do seu método público e também evitar a verificação de invariantes.

Outra boa dica é nunca retornar valores nulos, mas usar Null Object Pattern .

Pedro Boechat
fonte
10
Para valores de retorno, possivelmente, estar vazio, eu sugiro fortemente usando o Optionaltipo em vez de planícienull
Patrick
7
Opcional não é melhor que "nulo". # Get () opcional lança NoSuchElementException enquanto um uso do nulo lança NullPointerException. Ambos são RuntimeException sem descrição significativa. Eu prefiro variáveis ​​anuláveis.
30thh
4
@ 30thh, por que você usaria Optional.get () diretamente e não Optional.isPresent () ou Optional.map primeiro?
precisa saber é o seguinte
7
@GauravJ Por que você usaria uma variável anulável diretamente e não verificaria se ela era nula primeiro? ;-)
30th
5
A diferença entre Optionale nulo neste caso é que Optionalcomunica melhor que esse valor pode intencionalmente estar vazio. Certamente, não é uma varinha mágica e, no tempo de execução, pode falhar exatamente da mesma maneira que a variável anulável. No entanto, a recepção da API pelo programador é melhor Optionalna minha opinião.
user1053510
31

Além do seu IDE, fornecendo dicas quando você passa nullpara métodos que esperam que o argumento não seja nulo, há outras vantagens:

  • As ferramentas de análise de código estático podem testar o mesmo que o seu IDE (por exemplo, FindBugs)
  • Você pode usar o AOP para verificar essas afirmações

Isso pode ajudar seu código a ser mais sustentável (já que você não precisa de nullverificações) e menos propenso a erros.

Uwe Plonus
fonte
9
Eu simpatizo com o OP aqui, porque mesmo que você cite essas duas vantagens, nos dois casos você usou a palavra "pode". Isso significa que não há garantia de que essas verificações realmente ocorram. Agora, essa diferença de comportamento pode ser útil para testes sensíveis ao desempenho que você gostaria de evitar a execução no modo de produção, para o qual temos assert. Acho @Nullablee @Nonnullpara ser idéias úteis, mas gostaria de ter mais força por trás delas, em vez de questionarmos o que se poderia fazer com elas, o que ainda deixa em aberto a possibilidade de não fazer nada com elas.
seh
2
A questão é por onde começar. No momento, suas anotações são opcionais. Somtimes Eu gostaria que eles não eram, pois, em algumas circunstâncias, seria útil para aplicá-las ...
Uwe Plonus
Posso perguntar o que é AOP que você está se referindo aqui?
Chris.Zou
@ Chris.Zou AOP significa programação orientada aspecto, por exemplo, AspectJ
Uwe Plonus
13

Eu acho que essa pergunta original aponta indiretamente para uma recomendação geral de que a verificação de ponteiro nulo em tempo de execução ainda é necessária, mesmo que o @NonNull seja usado. Consulte o seguinte link:

Novas anotações de tipo do Java 8

No blog acima, é recomendado que:

As anotações de tipo opcionais não substituem a validação de tempo de execução. Antes das anotações de tipo, o local principal para descrever itens como nulidade ou intervalos estava no javadoc. Com as anotações de tipo, essa comunicação entra no bytecode de maneira a verificar em tempo de compilação. Seu código ainda deve executar a validação de tempo de execução.

jonathanzh
fonte
1
Entendido, mas as verificações padrão do fiapo alertam que verificações nulas em tempo de execução são desnecessárias, o que, à primeira impressão, parece desencorajar esta recomendação.
swooby
1
@swooby Normalmente, ignoro os avisos de fiapos se tiver certeza de que meu código está correto. Esses avisos não são erros.
jonathanzh
12

Compilando o exemplo original no Eclipse na conformidade 1.8 e com a análise nula baseada em anotação ativada, obtemos este aviso:

    directPathToA(y);
                  ^
Null type safety (type annotations): The expression of type 'Integer' needs unchecked conversion to conform to '@NonNull Integer'

Esse aviso está redigido em analogia com os avisos que você recebe ao misturar código gerado com código herdado usando tipos brutos ("conversão não verificada"). Temos exatamente a mesma situação aqui: o método indirectPathToA()possui uma assinatura "herdada", pois não especifica nenhum contrato nulo. As ferramentas podem relatar isso facilmente, de modo que o perseguirão por todos os becos onde as anotações nulas precisam ser propagadas, mas ainda não são.

E ao usar um inteligente @NonNullByDefault , nem precisamos dizer isso o tempo todo.

Em outras palavras: se as anotações nulas "propagam-se muito longe" podem depender da ferramenta que você usa e de quão rigorosamente você atende a todos os avisos emitidos pela ferramenta. Com as anotações nulas TYPE_USE, você finalmente tem a opção de deixar a ferramenta avisá-lo sobre todos os NPE possíveis em seu programa, porque a nulidade se tornou uma propriedade intrínseca do sistema de tipos.

Stephan Herrmann
fonte
8

Concordo que as anotações "não se propagam muito longe". No entanto, vejo o erro do lado do programador.

Entendo a Nonnullanotação como documentação. O método a seguir expressa que requer (como pré-condição) um argumento não nulo x.

    public void directPathToA(@Nonnull Integer x){
        x.toString(); // do stuff to x        
    }

O seguinte trecho de código contém um erro. O método chama directPathToA()sem impor que ynão seja nulo (ou seja, não garante a pré-condição do método chamado). Uma possibilidade é adicionar uma Nonnullanotação também a indirectPathToA()(propagar a pré-condição). A possibilidade dois é verificar a nulidade de yin indirectPathToA()e evitar a chamada de directPathToA()quando yé nulo.

    public void indirectPathToA(Integer y){
        directPathToA(y);
    }
Andres
fonte
1
Propagar o @Nonnull que você tem indirectPathToA(@Nonnull Integer y)é uma prática ruim: você precisará fazer a propagação na pilha de chamadas completa (por isso, se você adicionar um nullcheck-in directPathToA(), precisará substituir @Nonnullpor @Nullablena pilha de chamadas completa). Isso seria um grande esforço de manutenção para grandes aplicações.
Julien Kronegg
A anotação @Nonnull apenas enfatiza que a verificação nula do argumento está do seu lado (você precisa garantir que passa um valor não nulo). Não é de responsabilidade do método.
Alexander Drobyshevsky
@Nonnull também é coisa sensata quando nulo de valor não faz qualquer sentido para este método
Alexander Drobyshevsky
5

O que faço em meus projetos é ativar a seguinte opção na inspeção de código "Condições e exceções constantes":
Sugira anotação @Nullable para métodos que possivelmente retornem nulos e relatem valores nulos passados ​​para parâmetros não anotados Inspeções

Quando ativado, todos os parâmetros não anotados serão tratados como não nulos e, portanto, você também verá um aviso em sua chamada indireta:

clazz.indirectPathToA(null); 

Para verificações ainda mais fortes, o Checker Framework pode ser uma boa opção (consulte este bom tutorial .
Nota : Eu ainda não o usei e pode haver problemas com o compilador Jack: consulte este relatório de erros

TmTron
fonte
4

Em Java, eu usaria o tipo opcional do Guava . Sendo um tipo real, você obtém garantias do compilador sobre seu uso. É fácil ignorá-lo e obter um NullPointerException, mas pelo menos a assinatura do método comunica claramente o que ele espera como argumento ou o que ele pode retornar.

Ionuț G. Stan
fonte
16
Você tem que ter cuidado com isso. Opcional deve ser usado apenas onde um valor é verdadeiramente opcional, e a ausência dele é usada como porta de decisão para lógica adicional. Eu já vi isso abusado com a substituição geral de objetos por opcionais e verificações nulas substituídas por verificações de presença que não atendem ao objetivo.
Christopher Perry
se você estiver segmentando o JDK 8 ou mais recente, prefira usar em java.util.Optionalvez da classe do Guava. Veja as notas / comparações da Goiaba para detalhes sobre as diferenças.
AndrewF 23/10/19
1
"verificações nulos substituídos por verificações de presença que perde o ponto" você pode elaborar, então, sobre o que o ponto é ? Essa não é a única razão para os Opcionais, na minha opinião, mas certamente é de longe a maior e a melhor.
scubbo 7/01
4

Se você usar o Kotlin, ele suportará essas anotações de nulidade em seu compilador e impedirá que você passe um nulo para um método java que requer um argumento não nulo. Mesmo que essa pergunta tenha sido direcionada originalmente para Java, mencionei esse recurso do Kotlin porque ele é direcionado especificamente para essas anotações em Java e a pergunta era "Existe uma maneira de tornar essas anotações mais rigorosamente aplicadas e / ou se propagarem mais?" e esse recurso torna essas anotações mais rigorosamente aplicadas .

Classe Java usando @NotNullanotação

public class MyJavaClazz {
    public void foo(@NotNull String myString) {
        // will result in an NPE if myString is null
        myString.hashCode();
    }
}

Classe Kotlin chamando a classe Java e passando nulo para o argumento anotado com @NotNull

class MyKotlinClazz {
    fun foo() {
        MyJavaClazz().foo(null)
    }
}  

Erro do compilador Kotlin ao aplicar a @NotNullanotação.

Error:(5, 27) Kotlin: Null can not be a value of a non-null type String

consulte: http://kotlinlang.org/docs/reference/java-interop.html#nullability-annotations

Mike Rylander
fonte
3
A pergunta aborda o Java, por sua primeira tag, e não o Kotlin.
SEH
1
@seh, veja a atualização para saber por que essa resposta é relevante para esta pergunta.
Mike Rylander
2
Justo. Essa é uma boa característica do Kotlin. Eu apenas acho que não vai satisfazer aqueles que vêm aqui para aprender sobre Java.
SEH
mas o acesso myString.hashCode()continuará lançando o NPE, mesmo que @NotNullnão seja adicionado ao parâmetro. Então, o que há de mais específico em adicioná-lo?
Kamol 17/10/19
@kAmol A diferença aqui é que, ao usar o Kotlin, você receberá um erro de tempo de compilação em vez de um erro de tempo de execução. A anotação é para notificar que o desenvolvedor precisa garantir que um nulo não seja passado. Isso não impedirá que um nulo seja passado em tempo de execução, mas impedirá que você escreva código que chama esse método com um nulo (ou com uma função que pode retornar nula).
Mike Rylander 18/10/19
-4

Desde o novo recurso Java 8, opcional, você não deve usar @Nullable ou @Notnull em seu próprio código . Veja o exemplo abaixo:

public void printValue(@Nullable myValue) {
    if (myValue != null) {
        System.out.print(myValue);
    } else {
        System.out.print("I dont have a value");
}

Pode ser reescrito com:

public void printValue(Optional<String> myValue) {
    if (myValue.ifPresent) {
        System.out.print(myValue.get());
    } else {
        System.out.print("I dont have a value");
}

Usar um opcional obriga a verificar o valor nulo . No código acima, você pode acessar o valor apenas chamando oget método

Outra vantagem é que o código fica mais legível . Com a adição do Java 9 ifPresentOrElse , a função pode até ser escrita como:

public void printValue(Optional<String> myValue) {
    myValue.ifPresentOrElse(
        v -> System.out.print(v),
        () -> System.out.print("I dont have a value"),
    )
}
Mathieu Gemard
fonte
Mesmo assim Optional, ainda existem muitas bibliotecas e estruturas que usam essas anotações, de forma que não é possível atualizar / substituir todas as suas dependências por versões atualizadas para usar Opcionais. Optionalno entanto, pode ajudar em situações em que você usa nulo em seu próprio código.
Mike Rylander 26/12/19