No es un bug, es una característica no documentada

domingo, 19 de abril de 2015

Programación. Desarrollo de software orientado a objetos (II)

20:49 Posted by Inazio , No comments

Ficheros en Java


Manejar ficheros en Java es muy sencillo. En el paquete java.io encontramos las herramientas que necesitamos.

1.      La clase File representa un fichero o directorio (que no tiene por qué existir todavía). File nos permite averiguar mucha información útil (ruta del fichero, si se puede escribir, etc).
2.      Por otro lado, si quieremos tratar con ficheros de texto usaremos streams basados en caracteres, para el resto de casos utilizaremos streams basados en bytes.

Para el uso de la clase File ver antes sección anterior.

El uso de los streams de ficheros dependerá de lo que queramos hacer…

Para la lectura o escritura en ficheros de texto usamos FileReader y FileWriter. Ejemplo:

FileReader fichLec = new FileReader(cadNombre1);
FileWriter fichEsc = new FileWriter(cadNombre2);
char caracter = fichLec.read();
fichEsc.write(carácter);

Para lectura o escritura de datos en ficheros usamos DataInputStream y DataOutputStream. Ejemplo:

DataInputStream fichLec = new DataInputStream(cad1);
DataOutoutStream fichEsc = new DataOutputStream(cad1);
int codArticulo = fichLec.readInt();
int numUnidades = fichLec.readInt();
double precio = fichLec.readDouble();
fichEsc.writeInt(codArticulo);
fichEsc.writeInt(numUnidades);
fichEsc.writeDouble(precio);

Todos los constructores de streams de ficheros admiten como parámetro de entrada un objeto de la clase File.

Se aconseja que el acceso a los ficheros se haga mediante un buffer de memoria para mejorar la eficiencia. Para ello debemos usar un stream con capacidad de almacenamiento.

Ejemplo:

File fichero = new File(“pedidos.dat”);
DataOutputStream dos = new DataOutputStream(fichero);
BufferedOutputStream stream = new BufferedOutputStream(dos);
stream.writeInt(codArticulo);
stream.writeDouble(precio);

Presistencia de objetos

Hasta ahora todos los objetos que hemos creado se han alojado en la memoria del ordenador. Ahora queremos almacenarlos en el disco duro, de manera que pueda recuperarlos cuando reinicie el ordenador.

La persistencia de objetos se ocupa de escribirlos y leerlos desde el disco duro.

Normalmente los objetos en:
è Ficheros binarios
è Bases de datos

Persistencia de objetos en ficheros

Partimos del siguiente ejemplo:

Persona p = new Persona(“Ana”, “Lopez Haro”);
Mascota m = new Mascota(“Tobi”);
Coche c = new Coche(“4324-FJT”);
p.compraMascota(m);
p.compraCoche(c);



Si ahora quieres escribir el objeto Persona al disco duro, se escribiría lo siguiente


A la operación de convertir un objeto en una hiera de bytes se le llama serialización.

Lo que ocurre después es que primero se escribe el árbol de objetos referenciados por el objeto y después el objeto propio.


Cuando se realiza la lectura del fichero, se hace la operación inversa y el objeto se reconstruye en memoria con todas sus referencias apuntado a los objetos adecuados.

Implementación en Java

Para que un objeto pueda ser escrito y leído a un fichoer debe implementar la interfaz Serializable. Si el objeto posee referencias a otros objetos, éstos también deben implementar la interfaz Serializable. En nuestro ejemplo:


La interfaz Serializable no contiene ningún método. Actúa como un marcador del objeto, indicando que éste puede ser serializado.

Por otro lado, necesiamos un stream de entrada y otro de salida capaces de trabajar con objetos:
è java.io.ObjectInputStream
è java.io.ObjectOutputStream

Ahora tenemos que hacer:
1.      Crear un objeto del stream que conecta con el fichero
2.      Conectar dicho stream con el stream de objetos
3.      Llamar al método que nos haga falta
a.       + void writeObject(Object obj) para escribir un objeto en disco
b.      + Object readObject() para leerlo del fichero
4.      Capturar las excepciones que puedan aparecer
5.      Cerrar los streams

import java.io.Serializable;
public class Coche implements Serializable{
   private String matricula;
   private double maxLitros;
}

public class Mascota implements Serializable{
   ...
}

public class PruebaSerializable{
   public static void main(String[] args){
     FileOutputStream fichSalida = null;
     ObjectOutputStream oos = null;
     FileinputStream fichEntrada = null;
     ObjectInputStream ois = null;

     try{
         fichSalida = new FileOutputStream(“c:/persona.obi”);
         oos = new ObjectOutputStream(fichSalida);
         oos.writeObject(p);
     }
     catch(IOException e){
         System.out.println(“Se produjo un error de apertura”);
     }
     finally{
         try{
              if(oos != null)
                   oos.close();
              if(fichSalida != null)
                   fichSalida.close();
         }
catch(IOException e){
     System.out.println(“No se consiguió cerrar el fichero correctamente”);
}

     }

     try{
         fichEntrada = new FileInputStream(“c:\persona.obi”);
         ois = new ObjectInputStream(fichEntrada);
         p = (Persona)ois.readObject();
     }
     catch(ClassNotFoundException e){
         System.out.println(“Contenido de fichero no esperado”);
     }
     finally{
         try{
              if (ois != null)
                   ois.close();
              if (fichEntrada != null)
                   fichEntrada.close();
         }
         catch(IOException e){
              System.out.println(“No se consiguió cerrar el fichero correctamente”);
         }
     }
     System.out.println(“Se leyó el fichero correctamente”);
   }
}

Persistencia de objetos en bases de datos

Según su relación con el enfoque orientado a objetos podemos clasificar las bases de datos en tres grupos:

è Relacionales puras
·         Tecnología madura y eficiente
·         Problema: Se basan en un modelo plano de datos. Los objetos pueden contener a su vez otros objetos y éstos, otros a su vez.
·         Solución: Bastante costosa en tiempo y esfuerzo. Hay que establecer una correspondencia entre objetos y tablas haciendo un buen trabajo de diseño
è Orientadas a objetos puras
·         Se soluciona el problema anterior. Ahora la correspondencia entre objeto de programación y el objeto guardado en disco es directa, creándose un grafo de objetos interrelacionados y navegables.
·         Tecnología lenta, ineficiente y poco flexible a la hora de hacer consultas (se pierde la flexibilidad del SQL).
è Objeto-Relacionales o mixtas:
·         Combina las ventajas de las dos anteriores
·         Son BBDD relacionales, así se conserva la velocidad y la eficiencia. Sin embargo incorporan nuevos tipos de datos que permiten modelar los objetos a elementos de una base de datos relacionales.
·         A partir de la versión 8i de Oracle se incorporan los nuevos tipos abstractos de datos que pueden relacionarse mediante herencia y poseen métodos que pueden ser implementados en Java o SQL.

El framework Collection


Tablas, listas, pilas, colas, árboles, conjuntos, diccionarios…

Las colecciones o contenedores de objetos son estructuras de datos muy recurridas y necesarias. Su utilización implica normalmente:
è La adaptación de la colección al tipo de elemento que queremos almacenar
è La escritura de los algoritmos que nos hagan falta: ordenación, búsqueda, recorrido…

Estas tareas son repetitivas, tediosas y propensas a errores.

Java soluciona el problema utilizando las siguientes herramientas:
è Tipos genéricos. Permite escribir el código de una colección sin especificar el tipo del objeto que almacena.
è FrameWork Collection. Es una arquitectura unificada para la representación y manipulación de colecciones de objetos.

Tipos génericos


Vamos a crear un contenedor muy simple capaz de guardar un objeto de una clase cualquiera:

public class Caja{
   private Object object;
   public void set(Object object){
     this.object = object;
   }
   public Object get(){
     return object;
   }
}

Si quisiera usar la caja para almacenar SÓLO objetos de la clase Bombilla podríamos hacer:

public class PruebaCajaBombillas{
   public static void main(String args[]){
     Caja cajaBombillas = new Caja();
     // Creamos una bombilla y la guardamos
     cajaBombillas.set(new Bombilla());
     // Al recuperarla hay que hacer un casting
     Bombilla b = (Bombilla) cajaBombillas.get();
     // Nada nos impide guardar una cadena
     cajaBombillas.set(new String(“hola”));
     // Que provocaría una ClassCastException al hacer
     b = (Bombilla)cajaBombillas.get();
   }
}

Esta forma de usar la caja nos plantea los siguientes problemas:
·         Tenemos que acordarnos de hacer el casting al recuperar el objeto guardado
·         Podemos guardar otro tipo de objeto por equivocación y el compilador no se da cuenta. Esto provoca que se lance una excepción en tiempo de ejecución.

Ahora vamos a rescribir nuestra clase Caja usando tipos genéricos y se resuelven problemas de un golpe:

public class Caja<Tipo>{
   private Tipo objeto;
   public void set(Tipo objeto){
     this.objeto = objeto;
   }
   public Tipo get(){
     return objeto;
   }
}

Indicamos que en la caja va a aparecer un tipo de datos que todavía no se ha concretado, y después lo usamos como si se conociera.

public class PruebaCajaBombillas{
   public static void main(String args[]){
     // Al declarar la referencia y crear el objeto de la clase cala concreatamos el tipo genérico
     Caja<Bombilla> cajaBombillas = new Caja<Bombilla>();
     cajaBombillas.set(new Bombilla());
     // Ya no tenemos que hacer el casting
     Bombilla b = cajaBombillas.get();
     // El compilador identifica el error
     cajaBombillas.set(new String(“Hola”));
   }
}

Más sobre los tipos genéricos

Se puede usar el tipo genérico como parámetro al crear constructores. Ejemplo:

public class Caja(Tipo o){
   this.objeto = o;
}

Además podemos usar más de un tipo genérico en una clase. Ejemplo:

public class Armario<Tipo1, Tipo2>{
   Tipo1 cajonSuperior;
   Tipo2 cajonInferior;
   …
}

Podemos limitar las clases que pueden concretar al tipo genérico. Ejemplo:

public class Jaula<Tipo extends Animal>{
   // Admite enjaular cualquier objeto Animal sea de la clase que sea: Tigre, Mosquito, Ballena. Ya no podríamos enjaular un objeto String
}

Se puede usar con interfaces

public interface Contenedor<Tipo>{
   public void introducir(Tipo cosa);
   public void extraer();
   public Tipo consultarConenido();
}

Se pueden establecer relaciones de herencia entre clases o interfaces genéricas, formando jerarquías.

El FrameWork Collection


Lo forman un conjunto de clases e interfaces ubicadas mayoritariamente en el paquete java.util y que se estructuran de la siguiente manera:

·         Las interfaces definen las operaciones de cada colección de elementos
·         Las clases pueden ser de dos tipos
o   Las que implementan las interfaces anteriores
o   Las que implementan los algoritmos “clásicos” de las colecciones: ordenar, buscar un elemento, buscar máximo…


Veamos que hace cada tipo de interfaz:

·         set(conjunto). Es una colección de elementos en la que no existe un orden y que no admite duplicados
·         list(lista). Es una colección de elementos ordenada y en la que se admiten duplicados
·         queue(cola). Es una colección de elementos ordenada con comportamiento FIFO (First In, First Out).
·         map(mapa o diccionario). Es una colección de pares de objetos Clave – Valor que funciona como un diccionario en el que se busca una palabra (clave) para encontrar su definición (Valor).

Las claves no pueden repetirse pero sí los valores. Ejemplo: ‘súbito’ y ‘repentino’ podrían tener la misma definición.
·         sortedSet y sortedMap. Además, almacena sus elementos según un criterio de orden.

Las implementaciones de las interfaces se basan en distintas estructuras de datos.

·         ArrayList. Tabla capaz de redimensionarse
·         TreeMap y TreeSet. Árbol
·         LinkedList. Lista enlazada
·         HashMap y Haztree. Tabla de dispersión (o tabla Hash).
·         LinkedHashMap y LinkedHashSet. Tabla de dispersión combinada con una lista enlazada que mantiene el orden en el que se insertaron los pares Clave – Valor.

Programa de ejemplo. PruebaHashMap.java

import java.util.HashMap;
import java.io.*;

public class PruebaHashMap{
   public static void main(String [] args) throws IOException{
     HashMap<String, String> diccionario = new HashMap<String, String>();
     String palabra, respuesta;
     Teclado t = new Teclado();

     diccionario.put(“Hola”, “Hello”);
     System.out.println(“Mini diccionario español - inglés”);
     do{
         palabra = t.leerString(“dime una palabra”);
         if (diccionario.containKey(palabra))
              System.out.println(palabra + “ se dice en inglés ” + diccionario.get(palabra));
         else
              System.out.println(palabra + “ no se encuentra en el diccionario”);
         respuesta = t.leerString(“Quieres buscar otra palabra(S/N):”);
     }while (respuesta.equalsIgnoreCase(“S”); // equalsIgnoreCase lee texto no case sensitive
   }
}

Recorrido de colecciones de elementos


Ahora queremos recorrer una colección (de cualquier tipo) desde el primer hasta el último elemento.

Para ello Java aplica el patrón de diseño iterador que se basa en las siguienter interfaces:

·         La interfaz Iterable posee sólo un método que devuelve un objeto que implementar la interfaz Iterator
·         La interfaz Iterator posee los métodos hasNext y next que nos permite recorrer una colección.


import java.util.*;

public class PruebaIteradores{
   public static void main(String[] args){
     int i;
     Interator iter;

     LinkedList<String> lista = new LinkedList<String>();
     HashSet<String> conjunto = new HashSet<String>();
     TreeSet<String> arbol = new TreeSet<String>();

     for (i = 1; i <= 10; i++){
         lista.add(“Elemento ” + i + “ de la lista”);
         conjunto.add(“Elemento ” + i + “ de la lista”);
         arbol.add(“Elemento ” + i + “ de la lista”);
     }

     iter = lista.iterator();
     while(iter.hasNext())
         System.out.println(inter.next);

     iter = conjunto.iterator();
     while(iter.hasNext())
         System.out.println(inter.next);

     iter = arbol.iterator();
     while(iter.hasNext())
         System.out.println(inter.next);

   }
}

Las clases que implementan los algoritmos “clásicos” de las colecciones son:
è Collections. Capaz de operar con objetos que implementan la interfaz Collection.
è Arrays. Capaz de trabajar con objetos de tipo tabla

Podemos encontrar algoritmos para ordenar, desordenar, buscar el máximo o mínimo, invertir el orden de los elementos, rellenar todo los elementos con un valor, convertir de Collection a tabla y viceversa…

Estos algoritmos se presentan como métodos estáticos y que usan tipos genéricos.

Nos fijamos en una de las versiones del método de ordenación de la clase Collections:

public static void sort(List<T> lista);

La documentación de Java dice que ordena una lista según el orden natural de los elementos… ¿y qué orden es ese?

Para establecer el orden natural de los objetos de una clase Java obliga a implementar la interfaz Comparable.

Si hacemos

objeto1.compareTo(objeto2)

Se debe obtener
·         -1 si objeto1 < objeto2
·         0 si objeto1 = objeto2
·         1 si objeto1 > objeto2

Programa de ejemplo:

public class Persona implements Comparable{
   // Propiedades
   private String nombre;
   private String apellido;
   private String dni;
  
   // Métodos de comparación
   public int compareTo(Object o){
     String nomCompleto = apellido + “ ” + nombre;
     Persona p = Persona(o);
     return nombreCompleto.compareToIgnoreCase.p.getApellido() + “ ” + p.getNombre();
   }
}

import java.util.*;
public class PruebaOrdenacion{
   public static void main(String[] args){
     Iterator iter;
     LinkedList<Persona> lista = new LinkedList<Persona>();

     lista.add(new Persona(“Juan”, “Lopez Torres”);
     lista.add(new Persona(“Ana”, “Lopez Torres”);
     lista.add(new Persona(“Paula”, “Zambrano Sanchez”);
     lista.add(new Persona(“Ambrosio”, “Aguilar Ramos”);
     lista.add(new Persona(“Maria”, “Lopez Torres”);

     iter = lista.iterator();
     while (iter.hasNext())
         System.out.println(iter.next());
    
     System.out.println(“Y ahora ordenados…”);
     Collections.sort(lista);

     iter = lista.iterator();
     while (iter.hasNext())
         System.out.println(iter.next());
   }
}

La clase Arrays se comporta de la misma forma que Collections pero para objeto de tipo tablas (arrays).

0 comentarios:

Publicar un comentario