2009-01-19 12 views
5

en Java una clase puede implementar Iterable que le permite utilizar la instrucción foreach() y el azúcar sintáctico de iteración:En Java, ¿cómo escribirías el equivalente de Iterable que arrojaría excepciones?

for(T t:ts) ... 

Sin embargo, esto no le permiten lanzar excepciones en la construcción de un iterador. Si estaba iterando de una red, archivo, base de datos, etc., sería bueno poder lanzar excepciones. Los candidatos obvios son java.io.InputStream, Reader y el código java.nio.Channel, pero nada de esto puede usar Generics como la interfaz Iterable.

¿Hay una lengua común o API de Java para esta situación?

Aclaración: Esto pregunta si hay un patrón o una interfaz alternativa para iterar para objetos de una fuente que no es de memoria. Como dijeron los que respondieron, no se recomienda lanzar RuntimeExceptions para evitar el problema o lo que estaba buscando.

Editar 2: Gracias por las respuestas hasta el momento. El consenso parece ser "no se puede". Entonces, ¿puedo extender la pregunta a "¿Qué haces en esta situación, cuando esta situación sería útil?" ¿Solo escribe tu propia interfaz?

Respuesta

4

Lamentablemente no se puede. Hay dos problemas:

  • La API iterador no declarar las excepciones que se lance, por lo que tendría que tirar RuntimeExceptions (o no throwables excepción)
  • El bucle for mejorado no hace cualquier cosa para tratar de liberar recursos al final del ciclo

Esto es muy molesto. En C#, por ejemplo, puede escribir muy fácilmente el código para iterar a través de las líneas de un archivo de texto:

public static IEnumerable<string> ReadLines(string filename) 
{ 
    using (TextReader reader = File.OpenText(filename)) 
    { 
     string line; 
     while ((line=reader.ReadLine()) != null) 
     { 
      yield return line; 
     } 
    } 
} 

Utilizar como:

foreach (string line in ReadLines("foo.txt")) 

El bucle foreach llama Dispose en el IEnumerator en un fin bloque, que se traduce en "verificar si necesitamos hacer algo en el bloque del iterador finalmente (desde la instrucción using)". Obviamente no hay excepciones marcadas en C#, por lo que ese lado tampoco es un problema.

Una frase entera (¡útil!) Es prácticamente inviable en Java debido a esto.

0

Yo diría que no puede, incluso si pudiera, probablemente no debería. Obtienes bytes de estas cosas, si se usaran en un ciclo for, es probable que cada byte termine encuadrado.

Lo que puede hacer es ajustar las excepciones comprobadas en las excepciones no verificadas y cumplir con la interfaz iterable, aunque una vez más esto no es aconsejable.

4

Las transmisiones como una red no son realmente iterables en el sentido tradicional. Los datos pueden aparecer en cualquier momento, por lo que no tiene sentido tener un para cada ciclo.

Para una lectura de archivo, o una instantánea de base de datos (como una consulta de selección) no hay ninguna razón para no tomar esos datos, segmentarlos en fragmentos lógicos e implementar una interfaz iterable.

También puede llamar primero a un método de inicialización que captará cualquier excepción, si eso es un problema.

try{ 
    ts.initializeIOIterator(); 
}catch(...) 

for(T t:ts) 
...  
+0

Gracias por la respuesta. Pero si llama primero a initialise, primero tiene que leer todo en la memoria y perder el beneficio de la transmisión, o tiene que hacer frente a las excepciones –

+0

Yup. Tienes que elegir una metáfora o la otra. Si está tratando con una transmisión verdadera, entonces es mejor que se quede con la metáfora de la corriente. – patros

1

Lo mejor que puedes hacer es crear RuntimeIOException que lanzarás desde tu implementación de hasNext/next en caso de errores.

try { 
    for (...) { 
    // do my stuff here 
    } 
catch (RuntimeIOException e) { 
    throw e.getCause(); // rethrow IOException 
} 

RuntimeIOException será una excepción en tiempo de ejecución, envolviendo su IOException:

class RuntimeIOException extends RuntimeException { 
    RuntimeIOException(IOException e) { 
    super(e); 
    } 

    IOException getCause() { 
    return (IOException) super.getCause(); 
    } 
} 

a veces no hay otra manera.

0

Generalmente en esta situación, lanzaría una subclase apropiada de RuntimeException en la implementación de Iterable.

En términos de limpieza de recursos, un bloque try - finally funciona igual de bien envolviendo un bloque foreach como lo hace con cualquier otro bit de código, por lo que desde la perspectiva del cliente puede usarlo fácilmente para limpiar cualquier recurso. Si desea administrar recursos dentro de Iterable, puede ser más complicado, ya que no hay puntos obvios de inicio y finalización del ciclo de vida.

En este caso la mejor que probablemente se podría hacer es crear los recursos de la demanda (es decir, la primera llamada a next()), y luego los destruye ya sea cuando una llamada al next() está a punto de volver false, o cuando se lanza una excepción en el cuerpo de next(). Hacer esto significaría por supuesto que cuando su método next() sale con una excepción, el iterador ya no se puede usar; esta no es una restricción irrazonable para colocar (considere la excepción como una versión más de error de devolver false) pero es algo que debe documentar ya que no está cubierto estrictamente por la interfaz.

Dicho esto, lo anterior supone que está creando algo únicamente como Iterable. En la práctica, cuando implemente Iterable en una clase, es más como un "súper captador" (es decir, una manera para que los clientes accedan de manera conveniente a la información almacenada en él), que el objetivo de la clase en sí. La mayoría de las veces estos objetos se configurarán de forma independiente y se accederá a ellos a través de otros métodos, por lo que su ciclo de vida se puede administrar completamente por separado de su existencia como Iterable.

Esto puede parecer tangencial a la pregunta, pero la respuesta inmediata a la pregunta es directa ("use excepciones de tiempo de ejecución"): la parte difícil es mantener un estado apropiado en presencia de estas excepciones.

Cuestiones relacionadas