Como devemos gerenciar o fluxo jdk8 para valores nulos

88

Olá colegas desenvolvedores Java,

Eu sei que o assunto pode ser um pouco in advanceporque o JDK8 ainda não foi lançado (e não por agora, de qualquer maneira ..) mas eu estava lendo alguns artigos sobre as expressões Lambda e particularmente a parte relacionada à nova API de coleção conhecida como Stream.

Aqui está o exemplo fornecido no artigo da Java Magazine (é um algoritmo de população de lontras ..):

Set<Otter> otters = getOtters();
System.out.println(otters.stream()
    .filter(o -> !o.isWild())
    .map(o -> o.getKeeper())
    .filter(k -> k.isFemale())
    .into(new ArrayList<>())
    .size());

Minha pergunta é o que acontecerá se no meio da iteração interna Set, uma das lontras for nula?

Eu esperaria que um NullPointerException fosse lançado, mas talvez eu ainda esteja preso no paradigma de desenvolvimento anterior (não funcional), alguém pode me esclarecer como isso deve ser tratado?

Se isso realmente gerar um NullPointerException, considero o recurso bastante perigoso e terá que ser usado apenas como abaixo:

  • O desenvolvedor deve garantir que não haja nenhum valor nulo (talvez usando um filtro anterior (o -> o! = Nulo))
  • O desenvolvedor deve garantir que o aplicativo nunca está gerando lontra nula ou um objeto NullOtter especial para lidar.

Qual é a melhor opção, ou alguma outra opção?

Obrigado!

clemente
fonte
3
Eu diria que depende do programador fazer a coisa certa aqui; a JVM e o compilador podem fazer muito. Observe que algumas implementações de coleção não permitirão valores nulos, entretanto.
fge
18
Você pode usar filter(Objects::nonNull)com Objectsdejava.utils
Benj

Respostas:

45

O pensamento atual parece ser "tolerar" os nulos, ou seja, permiti-los em geral, embora algumas operações sejam menos tolerantes e possam acabar gerando NPE. Veja a discussão sobre nulos na lista de discussão do grupo de especialistas em Bibliotecas Lambda, especificamente esta mensagem . Posteriormente, surgiu um consenso em torno da opção nº 3 (com uma objeção notável de Doug Lea). Então, sim, a preocupação do OP com a explosão de pipelines com a NPE é válida.

Não é à toa que Tony Hoare se refere aos valores nulos como o "Erro de um bilhão de dólares". Lidar com nulos é uma verdadeira dor. Mesmo com coleções clássicas (sem considerar lambdas ou fluxos), os nulos são problemáticos. Como fge mencionou em um comentário, algumas coleções permitem nulos e outras não. Com coleções que permitem nulos, isso introduz ambigüidades na API. Por exemplo, com Map.get () , um retorno nulo indica que a chave está presente e seu valor é nulo ou que a chave está ausente. É preciso fazer um trabalho extra para eliminar a ambigüidade desses casos.

O uso usual de nulo é denotar a ausência de um valor. A abordagem para lidar com isso proposto para Java SE 8 é introduzir um novo java.util.Optionaltipo, que encapsula a presença / ausência de um valor, junto com comportamentos de fornecer um valor padrão, ou lançar uma exceção, ou chamar uma função, etc. se o valor está ausente. Optionalé usado apenas por novas APIs, entretanto, todo o resto no sistema ainda tem que suportar a possibilidade de nulos.

Meu conselho é evitar referências nulas reais o máximo possível. É difícil ver pelo exemplo dado como pode haver uma lontra "nula". Mas, se necessário, as sugestões do OP de filtrar valores nulos ou mapeá-los para um objeto sentinela (o padrão de objeto nulo ) são boas abordagens.

Stuart Marks
fonte
3
Por que evitar nulos? Eles são usados ​​em bancos de dados extensivamente.
Raffi Khatchadourian
5
@RaffiKhatchadourian Sim, nulos são usados ​​em bancos de dados, mas são igualmente problemáticos. Veja isto e isto e leia todas as respostas e comentários. Considere também o impacto de SQL null em expressões booleanas: en.wikipedia.org/wiki/… ... esta é uma fonte rica de erros de consulta.
Stuart Marks
1
@RaffiKhatchadourian Porque nullé uma merda, é por isso. Acc. para uma pesquisa que vi (não consigo encontrar), NPE é a exceção número 1 em Java. SQL não é uma linguagem de programação de alto nível, certamente não funcional, que despreza null, então não se importa.
Abhijit Sarkar
91

Embora as respostas sejam 100% corretas, uma pequena sugestão para melhorar o nulltratamento de casos da própria lista com Opcional :

 List<String> listOfStuffFiltered = Optional.ofNullable(listOfStuff)
                .orElseGet(Collections::emptyList)
                .stream()
                .filter(Objects::nonNull)
                .collect(Collectors.toList());

A parte Optional.ofNullable(listOfStuff).orElseGet(Collections::emptyList)permitirá que você trate bem o caso quando listOfStuffé nulo e retorne uma emptyList em vez de falhar com NullPointerException.

Johnny
fonte
2
Eu gosto disso, evita checar explicitamente por null.
Chris,
1
parece melhor claro .. bom e exatamente o que eu precisava
Abdullah Al Noman
Ele está verificando explicitamente se há nulo, apenas de uma maneira diferente. Se for permitido ser nulo, deve ser um Opcional, essa é a ideia, certo? Banir todos os valores nulos com opcionais. Além disso, é uma má prática retornar nulo em vez de uma lista vazia, então ele mostra dois odores de código se eu vir isso: nenhum opcional sendo usado e nenhum fluxo vazio sendo retornado. E este último está disponível há mais de 20 anos, então está maduro ...
Koos Gadellaa
e se eu quiser retornar uma lista nula em vez de vazia?
Ashburn RK
@AshburnRK é uma prática ruim. Você deve retornar uma lista vazia.
Johnny
69

A resposta de Stuart fornece uma ótima explicação, mas gostaria de fornecer outro exemplo.

Encontrei esse problema ao tentar executar um reduceem um fluxo contendo valores nulos (na verdade, era LongStream.average(), que é um tipo de redução). Como o Average () retorna OptionalDouble, presumi que o Stream poderia conter nulos, mas em vez disso, foi lançada uma NullPointerException. Isso se deve à explicação de Stuart de nulo x vazio.

Então, como o OP sugere, adicionei um filtro assim:

list.stream()
    .filter(o -> o != null)
    .reduce(..);

Ou como tangens apontado abaixo, use o predicado fornecido pela API Java:

list.stream()
    .filter(Objects::nonNull)
    .reduce(..);

Da discussão da lista de e-mails, Stuart linkou: Brian Goetz em nulos em Streams

bparry
fonte
19

Se você deseja apenas filtrar valores nulos de um fluxo, pode simplesmente usar uma referência de método para java.util.Objects.nonNull (Object) . De sua documentação:

Este método existe para ser usado como um predicado ,filter(Objects::nonNull)

Por exemplo:

List<String> list = Arrays.asList( null, "Foo", null, "Bar", null, null);

list.stream()
    .filter( Objects::nonNull )  // <-- Filter out null values
    .forEach( System.out::println );

Isso irá imprimir:

Foo
Bar
Andy Thomas
fonte
7

Um exemplo de como evitar null, por exemplo, use o filtro antes de agrupar por

Filtre as instâncias nulas antes de agrupar por.

Aqui está um exemplo

MyObjectlist.stream()
            .filter(p -> p.getSomeInstance() != null)
            .collect(Collectors.groupingBy(MyObject::getSomeInstance));
Ilan M
fonte