Cuando uso la nueva API de secuencias Java8 para encontrar un elemento específico en una colección, escribo código como este:
String theFirstString = myCollection.stream()
.findFirst()
.get();
Aquí IntelliJ advierte que se llama a get () sin verificar primero isPresent ().
Sin embargo, este código:
String theFirstString = myCollection.iterator().next();
... no da ninguna advertencia.
¿Hay alguna diferencia profunda entre las dos técnicas, que hace que el enfoque de transmisión sea más "peligroso" de alguna manera, que es crucial nunca llamar a get () sin llamar primero a isPresent ()? Buscando en Google, encuentro artículos que hablan sobre cómo los programadores son descuidados con Opcional <> y supongo que pueden llamar a get () siempre que sea necesario.
La cosa es que me gusta que me arrojen una excepción lo más cerca posible del lugar donde mi código tiene errores, que en este caso sería donde estoy llamando a get () cuando no se encuentra ningún elemento. No quiero tener que escribir ensayos inútiles 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 exista algún peligro en provocar excepciones de get () que desconozco?
Después de leer la respuesta de Karl Bielefeldt y un comentario de Hulk, me di cuenta de que mi código de excepción anterior era un poco torpe, aquí hay algo mejor:
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));
Esto parece más útil y probablemente será natural en muchos lugares. Todavía siento que querré poder elegir un elemento de una lista sin tener que lidiar con todo esto, pero eso podría ser que necesito acostumbrarme a este tipo de construcciones.
Respuestas:
En primer lugar, tenga en cuenta que la verificación es compleja y que probablemente sea relativamente lenta. Requiere un análisis estático, y puede haber bastante código entre las dos llamadas.
Segundo, el uso idiomático de las dos construcciones es muy diferente. Programo a tiempo completo en Scala, que usa con
Options
mucha más frecuencia que Java. No puedo recordar una sola instancia en la que necesitaba usar un.get()
en unOption
. Casi siempre debe devolver elOptional
de su función, o utilizarlo en suorElse()
lugar. Estas son formas muy superiores de manejar los valores perdidos que lanzar excepciones.Debido a
.get()
que rara vez se debe llamar en uso normal, tiene sentido que un IDE comience una comprobación compleja cuando vea un.get()
para ver si se usó correctamente. En contraste,iterator.next()
es el uso apropiado y ubicuo de un iterador.En otras palabras, no se trata de que uno sea malo y el otro no, se trata de que uno sea un caso común que es fundamental para su funcionamiento y es muy probable que arroje una excepción durante las pruebas del desarrollador, y el otro sea un uso poco frecuente. caso que puede no ejercerse lo suficiente como para lanzar una excepción durante las pruebas de desarrollador. Esos son los tipos de casos sobre los que desea que los IDE le avisen.
fuente
map
oflatMap
.Optional
En su mayoría, son una excelente manera de evitar tener que poner todo entre corchetesnull
.La clase Opcional está diseñada para usarse cuando no se sabe si el elemento que contiene está presente o no. La advertencia existe para notificar a los programadores que hay una posible ruta de código adicional que pueden manejar con gracia. La idea es evitar que se produzcan excepciones. El caso de uso que presenta no es para lo que está diseñado y, por lo tanto, la advertencia es irrelevante para usted: si realmente desea que se lance una excepción, continúe y desactívela (aunque solo para esta línea, no globalmente), debería ser tomar la decisión de manejar o no el caso no presente caso por caso, y la advertencia le recordará que debe hacerlo.
Podría decirse que se debe generar una advertencia similar para los iteradores. Sin embargo, simplemente nunca se ha hecho, y agregar una ahora probablemente causaría demasiadas advertencias en los proyectos existentes como una buena idea.
También tenga en cuenta que lanzar su propia excepción puede ser más útil que solo una excepción predeterminada, ya que incluye una descripción de qué elemento se esperaba que existiera, pero no puede ser muy útil para la depuración en un momento posterior.
fuente
Estoy de acuerdo en que no hay una diferencia inherente en estos, sin embargo, me gustaría señalar un defecto más grande.
En ambos casos, tiene un tipo, que proporciona un método que no se garantiza que funcione y se supone que debe llamar a otro método antes de eso. Si su IDE / compilador / etc. le advierte sobre un caso u otro solo depende de si es compatible con este caso y / o lo tiene configurado para hacerlo.
Dicho esto, sin embargo, ambos fragmentos de código son olores de código. Estas expresiones sugieren fallas de diseño subyacentes y producen código frágil.
Tienes razón al querer fallar rápido, por supuesto, pero la solución, al menos para mí, no puede ser simplemente ignorarlo y tirar las manos al aire (o una excepción en la pila).
Puede que se sepa que su colección no está vacía por ahora, pero en lo único en lo que puede confiar es en su tipo:
Collection<T>
- y eso no garantiza su falta de vacío. Aunque ahora sepa que no está vacío, un requisito modificado puede hacer que se cambie el código por adelantado y, de repente, su código se vea afectado por una colección vacía.Ahora puede retroceder y decirles a los demás que se suponía que debía obtener una lista no vacía. Puede estar feliz de encontrar ese "error" rápidamente. Sin embargo, tiene una pieza de software rota y necesita cambiar su código.
Compare esto con un enfoque más pragmático: dado que obtiene una colección y posiblemente puede estar vacía, se obliga a lidiar con ese caso mediante el uso de # map, #orElse o cualquier otro de estos métodos, excepto #get.
El compilador hace todo el trabajo posible para usted, de modo que puede confiar lo más posible en el contrato de tipo estático para obtener un
Collection<T>
. Cuando su código nunca viola este contrato (como a través de cualquiera de estas dos cadenas de llamadas malolientes), su código es mucho menos frágil a las condiciones ambientales cambiantes.En el escenario anterior, un cambio que produzca una colección de entrada vacía no tendría ninguna consecuencia para su código. Es solo otra entrada válida y, por lo tanto, aún se maneja correctamente.
El costo de esto es que tiene que invertir tiempo en mejores diseños, en lugar de a) arriesgar código frágil ob) complicar innecesariamente las cosas lanzando excepciones.
En una nota final: dado que no todos los IDE / compiladores advierten sobre las llamadas iterator (). Next () u optional.get () sin las verificaciones previas, dicho código generalmente se marca en las revisiones. Por lo que vale, no permitiría que dicho código permitiera pasar una revisión y exigir una solución limpia.
fuente