Ao usar a nova API Java de fluxos Java8 para encontrar um elemento específico em uma coleção, escrevo um código como este:
String theFirstString = myCollection.stream()
.findFirst()
.get();
Aqui, o IntelliJ avisa que get () é chamado sem verificar primeiro isPresent ().
No entanto, este código:
String theFirstString = myCollection.iterator().next();
... não dá aviso.
Existe alguma diferença profunda entre as duas técnicas, que torna a abordagem de streaming mais "perigosa" de alguma forma, que é crucial nunca chamar get () sem chamar isPresent () primeiro? Pesquisando no Google, encontro artigos falando sobre como os programadores são descuidados com o Opcional <> e presumo que eles possam chamar get () sempre.
O problema é que eu gosto de receber uma exceção o mais próximo possível do local onde meu código está com bugs, que nesse caso seria o local para onde estou chamando get () quando não há nenhum elemento a ser encontrado. Não quero escrever ensaios inúteis como:
Optional<String> firstStream = myCollection.stream()
.findFirst();
if (!firstStream.isPresent()) {
throw new IllegalStateException("There is no first string even though I totally expected there to be! <sarcasm>This exception is much more useful than NoSuchElementException</sarcasm>");
}
String theFirstString = firstStream.get();
... a menos que haja algum perigo em provocar exceções de get () das quais eu não conheço?
Depois de ler a resposta de Karl Bielefeldt e um comentário de Hulk, percebi que meu código de exceção acima era um pouco desajeitado, aqui está algo melhor:
String errorString = "There is no first string even though I totally expected there to be! <sarcasm>This exception is much more useful than NoSuchElementException</sarcasm>";
String firstString = myCollection.stream()
.findFirst()
.orElseThrow(() -> new IllegalStateException(errorString));
Isso parece mais útil e provavelmente virá natural em muitos lugares. Ainda sinto que desejo escolher um elemento de uma lista sem ter que lidar com tudo isso, mas pode ser que eu precise me acostumar com esses tipos de construções.
Respostas:
Antes de tudo, considere que a verificação é complexa e provavelmente é relativamente demorada. Requer alguma análise estática e pode haver um pouco de código entre as duas chamadas.
Segundo, o uso idiomático das duas construções é muito diferente. Eu programo em tempo integral no Scala, que usa
Options
muito mais frequentemente do que Java. Não me lembro de uma única instância em que precisei usar um.get()
em umOption
. Você quase sempre deve retornar oOptional
de sua função ou usar em seuorElse()
lugar. Essas são maneiras muito superiores de lidar com valores ausentes do que lançar exceções.Como
.get()
raramente deve ser chamado em uso normal, faz sentido que um IDE inicie uma verificação complexa quando vê um.get()
para ver se foi usado corretamente. Por outro lado,iterator.next()
é o uso adequado e onipresente de um iterador.Em outras palavras, não se trata de um ser ruim e o outro não, é um caso comum que é fundamental para sua operação e é altamente provável que crie uma exceção durante os testes do desenvolvedor, e o outro seja um raramente usado caso que pode não ser exercido o suficiente para gerar uma exceção durante o teste do desenvolvedor. Esses são os tipos de casos que você deseja que os IDEs o avisem.
fonte
map
ouflatMap
.Optional
são uma ótima maneira de evitar ter que colocar tudo emnull
cheques.A classe Opcional deve ser usada quando não se sabe se o item que ela contém está presente ou não. O aviso existe para notificar os programadores de que existe um caminho de código possível adicional que eles podem manipular normalmente. A idéia é evitar que exceções sejam lançadas. O caso de uso que você apresenta não é para o qual foi projetado e, portanto, o aviso é irrelevante para você - se você realmente deseja que uma exceção seja lançada, vá em frente e desative-a (embora apenas para esta linha, e não globalmente), você deve tomar a decisão de tratar ou não o caso não presente em uma instância por instância, e o aviso o lembrará de fazer isso.
Indiscutivelmente, um aviso semelhante deve ser gerado para os iteradores. No entanto, isso nunca foi feito, e adicionar um agora provavelmente faria com que muitos avisos nos projetos existentes fossem uma boa idéia.
Observe também que lançar sua própria exceção pode ser mais útil do que apenas uma exceção padrão, pois incluir uma descrição de qual elemento deveria existir, mas não pode ser muito útil na depuração posteriormente.
fonte
Concordo que não há nenhuma diferença inerente a elas, no entanto, gostaria de apontar uma falha maior.
Nos dois casos, você tem um tipo, que fornece um método que não é garantido que funcione e que você deve chamar outro método antes disso. Se o seu IDE / compilador / etc avisa sobre um caso ou outro, depende apenas se ele suporta esse caso e / ou se você está configurado para isso.
Dito isto, os dois pedaços de código são cheiros de código. Essas expressões sugerem falhas de design subjacentes e produzem código quebradiço.
Você está certo em querer fracassar rapidamente, é claro, mas a solução, pelo menos para mim, não pode ser simplesmente encolher os ombros e jogar as mãos no ar (ou uma exceção na pilha).
Sabe-se que sua coleção não está vazia por enquanto, mas tudo em que você pode confiar é no seu tipo:
Collection<T>
- e isso não garante que você não seja vazio. Mesmo sabendo que agora não está vazio, um requisito alterado pode levar à alteração antecipada do código e, de repente, seu código é atingido por uma coleção vazia.Agora você é livre para recuar e dizer aos outros que deveria ter uma lista não vazia. Você pode ficar feliz por encontrar esse 'erro' rapidamente. No entanto, você tem um software quebrado e precisa alterar seu código.
Compare isso com uma abordagem mais pragmática: como você obtém uma coleção e ela pode estar vazia, você se força a lidar com esse caso usando o # map opcional, #orElse ou qualquer outro desses métodos, exceto #get.
O compilador faz o máximo de trabalho possível para você, de modo que você pode confiar, tanto quanto possível, no contrato estático de obter a
Collection<T>
. Quando seu código nunca viola esse contrato (como por meio de uma dessas duas cadeias de chamadas fedorentas), seu código fica muito menos fragilizado com a alteração das condições ambientais.No cenário acima, uma alteração que produza uma coleção de entradas vazia não terá nenhuma importância para o seu código. É apenas mais uma entrada válida e, portanto, ainda é tratada corretamente.
O custo disso é que você deve investir tempo em projetos melhores, em vez de a) arriscar código quebradiço ou b) complicar desnecessariamente as coisas lançando exceções.
Em uma nota final: como nem todos os IDEs / compiladores alertam sobre as chamadas iterator (). Next () ou optional.get () sem as verificações anteriores, esse código geralmente é sinalizado nas revisões. Pelo que vale, eu não permitiria que esse código fosse aprovado em uma revisão e exigisse uma solução limpa.
fonte