Herencia en Java.



Herencia, Clases Abstractas, Clases Finales, Casting, operador instanceof

La herencia es una de las características fundamentales de la Programación Orientada a Ojetos.
Mediante la herencia podemos definir una clase a partir de otra ya existente.
La clase nueva se llama clase derivada o subclase y la clase existente se llama clase base o superclase.
En UML la herencia se representa con una flecha apuntando desde la clase derivada a la clase base.
La clase derivada hereda los componentes (atributos y métodos) de la clase base.
La finalidad de la herencia es:
-           Extender la funcionalidad de la clase base: en la clase derivada se pueden añadir atributos y métodos nuevos.
-           Especializar el comportamiento de la clase base: en la clase derivada se pueden modificar (sobrescribir, override) los métodos heredados para adaptarlos a sus necesidades.
La herencia permite la reutilización del código, ya que evita tener que reescribir de nuevo una clase existente cuando necesitamos ampliarla en cualquier sentido. Todas las clases derivadas pueden utilizar el código de la clase base sin tener que volver a definirlo en cada una de ellas.
Reutilización de código: El código se escribe una vez en la clase base y se utiliza en todas las clases derivadas.
Una clase base puede serlo de tantas derivadas como se desee: Un solo padre, varios hijos.

Herencia múltiple en Java: Java no soporta la herencia múltiple. Una clase derivada solo puede tener una clase base.
Diagrama UML de herencia múltiple no permitida en Java

La herencia expresa una relación “ES UN/UNA” entre la clase derivada y la clase base.
Esto significa que un objeto de una clase derivada es también un objeto de su clase base.
Al contrario NO es cierto. Un objeto de la clase base no es un objeto de la clase derivada.
Por ejemplo, supongamos una clase Vehiculo como la clase base de una clase Coche. Podemos decir que un Coche es un Vehiculo pero un Vehiculo no siempre es un Coche, puede ser una moto, un camión, etc.
Un objeto de una clase derivada es a su vez un objeto de su clase base, por lo tanto se puede utilizar en cualquier lugar donde aparezca un objeto de la clase base. Si esto no fuese posible entonces la herencia no está bien planteada.
Ejemplo de herencia bien planteada:
A partir de una clase Persona que tiene como atributos el nif y el nombre, podemos obtener una clase derivada Alumno. Un Alumno es una Persona que tendrá como atributos nif, nombre y curso.
Ejemplo de herencia mal planteada:
Supongamos una clase Punto que tiene como atributos coordenadaX y coordenadaY.
Se puede crear una clase Linea a partir de la clase Punto. Simplificando mucho para este ejemplo, podemos considerar una línea como un punto de origen y una longitud. En ese caso podemos crear la Clase Linea como derivada de la clase Punto, pero el planteamiento no es correcto ya que no se cumple la relación ES UN
Una Linea NO ES un Punto. En este caso no se debe utilizar la herencia.


Una clase derivada a su vez puede ser clase base en un nuevo proceso de derivación, formando de esta manera una Jerarquía de Clases.

Las clases más generales se sitúan en lo más alto de la jerarquía. Cuánto más arriba en la jerarquía, menor nivel de detalle.
Cada clase derivada debe implementar únicamente lo que la distingue de su clase base.
En java todas las clases derivan directa o indirectamente de la clase Object.
Object es la clase base de toda la jerarquía de clases Java.
Todos los objetos en un programa Java son Object.
CARACTERÍSTICAS DE LAS CLASES DERIVADAS
Una clase derivada hereda de la clase base sus componentes (atributos y métodos).
Los constructores no se heredan. Las clases derivadas deberán implementar sus propios constructores.
Una clase derivada puede acceder a los miembros públicos y protegidos de la clase base como si fuesen miembros propios.
Una clase derivada no tiene acceso a los miembros privados de la clase base. Deberá acceder a través de métodos heredados de la clase base.
Si se necesita tener acceso directo a los miembros privados de la clase base se deben declarar protected en lugar de private en la clase base.
Una clase derivada puede añadir a los miembros heredados, sus propios atributos y métodos (extender la funcionalidad de la clase).
También puede modificar los métodos heredados (especializar el comportamiento de la clase base).
-           Una clase derivada puede, a su vez, ser una clase base, dando lugar a una jerarquía de clases. LOS MODIFICADORES DE ACCESO
MODIFICADORES DE ACCESO JAVA

ACESO EN
MODIFICADOR
PROPIA CLASE
PACKAGE
CLASE DERIVADA
RESTO
private
SI
NO
NO
NO
<Sin modificador>
SI
SI
NO
NO
protected
SI
SI
SI
NO
public
SI
SI
SI
SI

HERENCIA EN JAVA. SINTAXIS
La herencia en Java se expresa mediante la palabra extends
Por ejemplo, para declarar una clase B que hereda de una clase A:
public class B extends A{
.
}
Ejemplo de herencia Java: Disponemos de una clase Persona con los atributos nif y nombre.
//Clase Persona. Clase Base
public class Persona {
    private String nif;
    private String nombre;
    public String getNif() {
        return nif;
    }
    public void setNif(String nif) {
        this.nif = nif;
    }
    public String getNombre() {
        return nombre;
    }
    public void setNombre(String nombre) {
        this.nombre = nombre;
    }
}
Queremos crear ahora una clase Alumno con los atributos nif, nombre y curso. Podemos crear la clase Alumno como derivada de la clase Persona. La clase Alumno contendrá solamente el atributo curso, el nombre y el nif son los heredados de Persona:
//Clase Alumno. Clase derivada de Persona
public class Alumno extends Persona{
    private String curso;
    public String getCurso() {
        return curso;
    }
    public void setCurso(String curso) {
        this.curso = curso;
    }  
}
La clase alumno hereda los atributos nombre y nif de la clase Persona y añade el atributo propio curso. Por lo tanto:
Los atributos de la clase Alumno son nif, nombre y curso.
Los métodos de la clase Alumno son: getNif(), setNif(String nif), getNombre(), setNombre(String nombre), getCurso(), setCurso(String curso).
La clase Alumno aunque hereda los atributos nif y nombre, no puede acceder a ellos de forma directa ya que son privados a la clase Persona. Se acceden a través de los métodos heredados de la clase base.
La clase Alumno puede utilizar los componentes public y protected de la clase Persona como si fueran propios.
Ejemplo de uso de la clase Alumno:

En una jerarquía de clases, cuando un objeto invoca a un método:
1. Se busca en su clase el método correspondiente.
2. Si no se encuentra, se busca en su clase base.
3. Si no se encuentra se sigue buscando hacia arriba en la jerarquía de clases hasta que el método se encuentra.
4. Si al llegar a la clase base raíz el método no se ha encontrado se producirá un error.
REDEFINIR MIEMBROS DE LA CLASE BASE EN LAS CLASES DERIVADAS
Redefinir o modificar métodos de la clase Base (Override)
Los métodos heredados de la clase base se pueden redefinir (también se le llama modificar o sobreescribir) en las clases derivadas.
El método en la clase derivada se debe escribir con el mismo nombre, el mismo número y tipo de parámetros y el mismo tipo de retorno que en la clase base. Si no fuera así estaríamos sobrecargando el método, no redefiniéndolo.
El método sobrescrito puede tener un modificador de acceso menos restrictivo que el de la clase base. Si por ejemplo el método heredado es protected se puede redefinir como public pero no como private porque sería un acceso más restrictivo que el que tiene en la clase base.
Cuando en una clase derivada se redefine un método de una clase base, se oculta el método de la clase base y todas las sobrecargas del mismo en la clase base.
Si queremos acceder al método de la clase base que ha quedado oculto en la clase derivada utilizamos:
super.metodo();
Si se quiere evitar que un método de la clase Base sea modificado en la clase derivada se debe declarar como método final. Por ejemplo:
public final void metodo(){
........
}
Ejemplo:
Vamos a añadir a la clase Persona un método leer() para introducir por teclado los valores a los atributos de la clase. La clase Persona queda así:
public class Persona {
   private String nif;
    private String nombre;
    public String getNif() {
        return nif;
    }
    public void setNif(String nif) {
        this.nif = nif;
    }
    public String getNombre() {
        return nombre;
    }
    public void setNombre(String nombre) {
        this.nombre = nombre;
    }
    public void leer(){
        Scanner sc = new Scanner(System.in);
        System.out.print("Nif: ");
        nif = sc.nextLine();
        System.out.print("Nombre: ");
        nombre = sc.nextLine();     
    }
}
La clase Alumno que es derivada de Persona, heredará este método leer() y lo puede usar como propio.
Podemos crear un objeto Alumno:
Alumno a = new Alumno();
y utilizar este método:
a.leer();
pero utilizando este método solo se leen por teclado el nif y el nombre. En la clase Alumno se debe sobreescribir o modificar este método heredado para que también se lea el curso. El método leer() de Alumno invocará al método leer() de Persona para leer el nif y nombre y a continuación se leerá el curso.
La clase Alumno con el método leer() modificado queda así:
//Clase Alumno. Clase derivada de Persona
public class Alumno extends Persona{
    private String curso;
    public String getCurso() {
        return curso;
    }
    public void setCurso(String curso) {
        this.curso = curso;
    }
    @Override  //indica que se modifica un método heredado
    public void leer(){
        Scanner sc = new Scanner(System.in);
        super.leer(); //se llama al método leer de Persona para leer nif y nombre
        System.out.print("Curso: ");
        curso = sc.nextLine(); //se lee el curso
    }  
}
Como se ha dicho antes, cuando en una clase derivada se redefine un método de una clase base, se oculta el método de la clase base y todas las sobrecargas del mismo en la clase base. Por eso para ejecutar el método leer() de Persona se debe escribir super.leer();
Redefinir atributos de la clase Base
Una clase derivada puede volver a declarar un atributo heredado (Atributo public o protected en la clase base). En este caso el atributo de la clase base queda oculto por el de la clase derivada.
Para acceder a un atributo de la clase base que ha quedado oculto en la clase derivada se escribe: super.atributo;
CONSTRUCTORES Y HERENCIA EN JAVA. CONSTRUCTORES EN CLASES DERIVADAS
Los constructores no se heredan. Cada clase derivada tendrá sus propios constructores.
La clase base es la encargada de inicializar sus atributos.
La clase derivada se encarga de inicializar solo los suyos.
Cuando se crea un objeto de una clase derivada se ejecutan los constructores en este orden:
1. Primero se ejecuta el constructor de la clase base
2. Después se ejecuta el constructor de la clase derivada.
Esto lo podemos comprobar si añadimos a las clases Persona y Alumno sus constructores por defecto y hacemos que cada constructor muestre un mensaje:
public class Persona {
    private String nif;
    private String nombre;
    public Persona() {
        System.out.println("Ejecutando el constructor de Persona");
    }
    /////// Resto de métodos
}

public class Alumno extends Persona{
    private String curso;
    public Alumno() {
        System.out.println("Ejecutando el constructor de Alumno");
    }
    /////// Resto de métodos
}
Si creamos un objeto Alumno:
Alumno a = new Alumno();
Se muestra por pantalla:
Ejecutando el constructor de Persona
Ejecutando el constructor de Alumno
Cuando se invoca al constructor de la clase Alumno se invoca automáticamente al constructor de la clase Persona y después continúa la ejecución del constructor de la clase Alumno.
El constructor por defecto de la clase derivada llama al constructor por defecto de la clase base.
La instrucción para invocar al constructor por defecto de la clase base es:   super();
Todos los constructores en las clases derivadas contienen de forma implícita la instrucción super() como primera instrucción.
public Alumno() {
        super(); //esta instrucción se ejecuta siempre. No es necesario escribirla
        System.out.println("Ejecutando el constructor de Alumno");
}  
Cuando se crea un objeto de la clase derivada y queremos asignarle valores a los atributos heredados de la clase base:
-           La clase derivada debe tener un constructor con parámetros adecuado que reciba los valores a asignar a los atributos de la clase base.
-           La clase base debe tener un constructor con parámetros adecuado.
-           El constructor de la clase derivada invoca al constructor con parámetros de la clase base y le envía los valores iniciales de los atributos. Debe ser la primera instrucción.
-           La clase base es la encargada de asignar valores iniciales a sus atributos.
-           A continuación el constructor de la clase derivada asigna valores a los atributos de su clase.
Ejemplo: en las clases Persona y Alumno anteriores añadimos constructores con parámetros:

Ahora se pueden crear objetos de tipo Alumno y asignarles valores iniciales. Por ejemplo:
Alumno a = new Alumno("12345678-Z","Eliseo Gonzáles Manzano","1DAW");
CLASES FINALES
Si queremos evitar que una clase tenga clases derivadas debe declararse con el modificador final delante de class:
public final class A{
         ........
}
Esto la convierte en clase final. Una clase final no se puede heredar.
Si intentamos crear una clase derivada de A se producirá un error de compilación:
public class B extends A{ //extends A producirá un error de compilación
         ........
}
CLASES ABSTRACTAS
Una clase abstracta es una clase que NO se puede instanciar, es decir, no se pueden crear objetos de esa clase.
Se diseñan solo para que otras clases hereden de ella.
La clase abstracta normalmente es la raíz de una jerarquía de clases y contendrá el comportamiento general que deben tener todas las subclases. En las clases derivadas se detalla la implementación.
Las clases abstractas:
- Pueden contener cero o más métodos abstractos.
- Pueden contener métodos no abstractos.                                      
- Pueden contener atributos.
Todas las clases que hereden de una clase abstracta deben implementar todos los métodos abstractos heredados.
Si una clase derivada de una clase abstracta no implementa algún método abstracto se convierte en abstracta y tendrá que declararse como tal (tanto la clase como los métodos que siguen siendo abstractos).
Aunque no se pueden crear objetos de una clase abstracta, sí pueden tener constructores para inicializar sus atributos que serán invocados cuando se creen objetos de clases derivadas.
La forma general de declarar una clase abstracta en Java es:
[modificador] abstract class nombreClase{
    ……….
}
Ejemplo de clase Abstracta en Java: Clase Polígono.
//Clase abstracta Poligono
public abstract class Poligono {
  
    private int numLados;

    public Poligono() {
    }

    public Poligono(int numLados) {
        this.numLados = numLados;
    }

    public int getNumLados() {
        return numLados;
    }

    public void setNumLados(int numLados) {
        this.numLados = numLados;
    }
    //Declaración del método abstracto area()
    public abstract double area();
}
La clase Poligono contiene un único atributo nublados. Es una clase abstracta porque contiene el método abstracto area().
A partir de la clase Poligono vamos a crear dos clases derivadas Rectangulo y Triangulo. Ambas deberán implementar el método area(). De lo contrario también serían clases abstractas.
El diagrama UML es el siguiente:


En UML las clases abstractas y métodos abstractos se escriben con su nombre en cursiva.

//Clase Rectangulo
public class Rectangulo extends Poligono{

    private double lado1;
    private double lado2;

    public Rectangulo() {
    }

    public Rectangulo(double lado1, double lado2) {
        super(2);
        this.lado1 = lado1;
        this.lado2 = lado2;
    }

    public double getLado1() {
        return lado1;
    }

    public void setLado1(double lado1) {
        this.lado1 = lado1;
    }

    public double getLado2() {
        return lado2;
    }

    public void setLado2(double lado2) {
        this.lado2 = lado2;
    }

    //Implementación del método abstracto area()
    //heredado de la clase Polígono
    @Override
    public double area(){
        return lado1 * lado2;
    }
}

//Clase Triangulo
public class Triangulo extends Poligono{

    private double lado1;
    private double lado2;
    private double lado3;

    public Triangulo() {
    }

    public Triangulo(double lado1, double lado2, double lado3) {
        super(3);
        this.lado1 = lado1;
        this.lado2 = lado2;
        this.lado3 = lado3;
    }


    public double getLado1() {
        return lado1;
    }

    public void setLado1(double lado1) {
        this.lado1 = lado1;
    }

    public double getLado2() {
        return lado2;
    }
    public void setLado2(double lado2) {
        this.lado2 = lado2;
    }

    public double getLado3() {
        return lado3;
    }

    public void setLado3(double lado3) {
        this.lado3 = lado3;
    }
   
    //Implementación del método abstracto area()
    //heredado de la clase Polígono
    @Override
    public double area(){
        double p = (lado1+lado2+lado3)/2;
        return Math.sqrt(p * (p-lado1) * (p-lado2) * (p-lado3));
    }
}
Ejemplo de uso de las clases:
public static void main(String[] args) {
        Triangulo t = new Triangulo(3.25,4.55,2.71);
        System.out.printf("Área del triángulo: %.2f %n" , t.area());
        Rectangulo r = new Rectangulo(5.70,2.29);
        System.out.printf("Área del rectángulo: %.2f %n" , r.area());
}
CASTING: CONVERSIONES ENTRE CLASES
UpCasting: Conversiones implícitas
La herencia establece una relación ES UN entre clases. Esto quiere decir que un objeto de una clase derivada es también un objeto de la clase base.
Por esta razón:
Se puede asignar de forma implícita una referencia a un objeto de una clase derivada a una referencia de la clase base. Son tipos compatibles.
También se llaman conversiones ascendentes o upcasting.
En el ejemplo anterior un triángulo en un Poligono y un cuadrado es un Poligono.
Poligono p;  
Triangulo t = new Triangulo(3,5,2);
Como un triángulo es un polígono, se puede hacer esta asignación:
p = t;
La variable p de tipo Poligono puede contener la referencia de un objeto Triangulo ya que son tipos compatibles.
Cuando manejamos un objeto a través de una referencia a una superclase (directa o indirecta) solo se pueden ejecutar métodos disponibles en la superclase.
En el ejemplo, la instrucción
p.getLado1();
provocará un error ya que p es de tipo Poligono y el método getLado1() no es un método de esa clase.
Cuando manejamos un objeto a través de una referencia a una superclase (directa o indirecta) y se invoca a un método que está redefinido en las subclases se ejecuta el método de la clase a la que pertenece el objeto no el de la referencia.
En el ejemplo, la instrucción
p.area();
ejecutará el método area() de Triángulo.
DownCasting: Conversiones explícitas
Se puede asignar una referencia de la clase base a una referencia de la clase derivada, siempre que la referencia de la clase base sea a un objeto de la misma clase derivada a la que se va a asignar o de una clase derivada de ésta.
También se llaman conversiones descendentes o downcasting.
Esta conversión debe hacerse mediante un casting.
Siguiendo con el ejemplo anterior:
Poligono p = new Triangulo(1,3,2);  //upcasting
Triangulo t;
t = (Triangulo) p; //downcasting
Esta asignación se puede hacer porque p contiene la referencia de un objeto triángulo.
Las siguientes instrucciones provocarán un error de ejecución del tipo ClassCastException:
Triangulo t;
Poligono p1 = new Rectangulo(3,2);
t = (Triangulo)p1;   //----> Error de ejecución
p1 contiene la referencia a un objeto Rectangulo y no se puede convertir en una referencia a un objeto Triangulo. No son tipos compatibles.
EL OPERADOR instanceof
Las operaciones entre clases y en particular el downcasting requieren que las clases sean de tipos compatibles. Para asegurarnos de ello podemos utilizar el operador instanceof.
instanceof devuelve true si el objeto es instancia de la clase y false en caso contrario.
La sintaxis es:
Objeto instanceof Clase
Ejemplo:
Triangulo t;
Poligono p1 = new Rectangulo(3,2);
if(p1 instanceof Triangulo)
   t = (Triangulo)p1;
else
   System.out.println("Objetos incompatibles");

1. Serialización y persistencia de objetos
La serialización es la transformación de un objeto en una secuencia de bytes que pueden ser posteriormente leídos para reconstruir el objeto original.
El objeto serializado pueda guardarse en un fichero o puede enviarse por red para reconstruirlo en otro lugar. Puede crearse en un sistema Windows y enviarlo. por ejemplo, a otro sistema que utilice Linux.
Guardar objetos de forma que existan cuando la aplicación haya terminado se conoce como persistencia.
Para poder transformar el objeto en una secuencia de bytes, el objeto debe ser serializable.
Un objeto es serializable si su clase implementa la interface Serializable.
La interface Serializable se encuentra en el paquete java.io Es una interface vacía. No contiene ningún método.
public interface Serializable{
}
Sirve para indicar que los objetos de la clase que lo implementa se pueden serializar. Solo es necesario que una clase la implemente para que la máquina virtual pueda serializar los objetos.
Si un objeto contiene atributos que son referencias a otros objetos éstos a su vez deben ser serializables.
Todos los tipos básicos Java son serializables, así como los arrays y los String.
2. Persistencia de Objetos en Ficheros
Para escribir objetos en un fichero binario en Java se utiliza la clase ObjectOutputStream derivada de OutputStream.
Un objeto ObjectOutputStream se crea a partir de un objeto FileOutputStream asociado al fichero.
El constructor de la clase es:
ObjectOutputStream(OutputStream nombre);
Lanza una excepción IOException.
Por ejemplo, las instrucciones para crear el fichero personas.dat para escritura de objetos serían éstas:
FileOutputStream fos = new FileOutputStream ("/ficheros/personas.dat");
ObjectOutputStream salida = new ObjectOutputStream (fos);
La clase proporciona el método writeObject(Object objeto) para escribir el objeto en el fichero. Lanza una IOException
El método defaultWriteObject() de la clase ObjectOutputStream realiza de forma automática la serialización de los objetos de una clase. Este método se invoca en el método writeObject().
defaultWriteObject() escribe en el stream de salida todo lo necesario para reconstruir los objetos:
- La clase del objeto.
- Los miembros de la clase (atributos).
- Los valores de los atributos que no sean static o transient.
El método defaultReadObject() de la clase ObjectInputStream realiza la deserialización de los objetos de una clase. Este método se invoca en el método readObject().
Ejemplo 1:
Ejemplo de persistencia de objetos en fichero en Java
Programa que escribe 3 objetos de tipo Persona en el fichero personas.dat.
La clase Persona es la siguiente:
//Clase Persona
import java.io.Serializable;
public class Persona implements Serializable{
    private String nif;
    private String nombre;
    private int edad;

    public Persona() {
    }

    public Persona(String nif, String nombre, int edad) {
        this.nif = nif;
        this.nombre = nombre;
        this.edad = edad;
    }

    public int getEdad() {
        return edad;
    }

    public void setEdad(int edad) {
        this.edad = edad;
    }

    public String getNif() {
        return nif;
    }

    public void setNif(String nif) {
        this.nif = nif;
    }

    public String getNombre() {
        return nombre;
    }

    public void setNombre(String nombre) {
        this.nombre = nombre;
    }
}



//Clase Principal

import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;

public class Serial1 {

    public static void main(String[] args) {

        FileOutputStream fos = null;
        ObjectOutputStream salida = null;
        Persona p;

        try {
            //Se crea el fichero
            fos = new FileOutputStream("/ficheros/personas.dat");
            salida = new ObjectOutputStream(fos);
            //Se crea el primer objeto Persona
            p = new Persona("12345678A","Lucas González", 30);
            //Se escribe el objeto en el fichero
            salida.writeObject(p);
            //Se crea el segundo objeto Persona
            p = new Persona("98765432B","Anacleto Jiménez", 28);
            //Se escribe el objeto en el fichero
            salida.writeObject(p);
            //Se crea el tercer objeto Persona
            p = new Persona("78234212Z","María Zapata", 35);
            //Se escribe el objeto en el fichero
            salida.writeObject(p);
           
        } catch (FileNotFoundException e) {
            System.out.println("1"+e.getMessage());
        } catch (IOException e) {
            System.out.println("2"+e.getMessage());
        } finally {
            try {
                if(fos!=null) fos.close();
                if(salida!=null) salida.close();
            } catch (IOException e) {
                System.out.println("3"+e.getMessage());
            }
        }

    }
}

3. Serialización y Herencia
Para serializar objetos de una jerarquía solamente la clase base tiene que implementar el interface Serializable. No es necesario que las clases derivadas implementen la interfaz.
Si una clase es serializable lo son también todas sus clases derivadas.
Ejemplo 2:
Ejemplo de herencia y serialización:
Programa Java que escribe en un fichero tres objetos de tipo Empleado. Empleado es una clase derivada de la clase Persona del ejemplo anterior. Como la clase Persona es serializable, no es necesario indicar que la clase Empleado también lo es. Empleado es serializable por el hecho de heredar de Persona.

//Clase Empleado
public class Empleado extends Persona{
    private double sueldo;
    public Empleado(String nif, String nombre, int edad, double sueldo) {
        super(nif, nombre, edad);
        this.sueldo = sueldo;
    }

    public Empleado() {
    }

    public double getSueldo() {
        return sueldo;
    }
    public void setSueldo(double sueldo) {
        this.sueldo = sueldo;
    }
}


//Clase principal
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;

public class Serial3 {

    public static void main(String[] args) {
        FileOutputStream fos = null;
        ObjectOutputStream salida = null;
        Empleado emp;
        try {
            fos = new FileOutputStream("/ficheros/personas.dat");
            salida = new ObjectOutputStream(fos);
            emp = new Empleado("12345678A","Lucas González", 30, 1200.40);
            salida.writeObject(emp);
            emp = new Empleado("98765432B","Anacleto Jiménez", 28, 1000);
            salida.writeObject(emp);
            emp = new Empleado("78234212Z","María Zapata", 35, 1100.25);
            salida.writeObject(emp);
           
        } catch (FileNotFoundException e) {
            System.out.println(e.getMessage());
        } catch (IOException e) {
            System.out.println(e.getMessage());
        } finally {
            try {
                if(fos!=null) fos.close();
                if(salida!=null) salida.close();
            } catch (IOException e) {
                System.out.println(e.getMessage());
            }
        }
    }
}


4. Serialización y clases compuestas
Cuando una clase contiene un atributo que es una referencia a otro objeto, la clase a la que pertenece dicho atributo también debe ser serializable.  
Ejemplo 3:
Ejemplo de serialización de una clase compuesta:
Programa java que escribe en un fichero tres objetos de tipo Alumno. Alumno es una clase derivada de Persona y contiene un atributo Fecha:

//Clase Fecha
import java.io.Serializable;
public class Fecha implements Serializable{
    private int dia;
    private int mes;
    private int año;

    public Fecha(int dia, int mes, int año) {
        this.dia = dia;
        this.mes = mes;
        this.año = año;
    }
    public Fecha() {
    }
    public int getAño() {
        return año;
    }

    public void setAño(int año) {
        this.año = año;
    }

    public int getDia() {
        return dia;
    }

    public void setDia(int dia) {
        this.dia = dia;
    }

    public int getMes() {
        return mes;
    }

    public void setMes(int mes) {
        this.mes = mes;
    }
   
}


//Clase Alumno
public class Alumno extends Persona{
    private Fecha fechaMatricula;

    public Alumno(String nif, String nombre, int edad, Fecha fechaMatricula) {
        super(nif, nombre, edad);
        this.fechaMatricula = new Fecha();
        setFechaMatricula(fechaMatricula);
    }

    public Alumno() {
    }

    public Fecha getFechaMatricula() {
        return fechaMatricula;
    }

    public void setFechaMatricula(Fecha fechaMatricula) {
        this.fechaMatricula.setDia(fechaMatricula.getDia());
        this.fechaMatricula.setMes(fechaMatricula.getMes());
        this.fechaMatricula.setAño(fechaMatricula.getAño());
    }  
   
}


//Clase principal
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;

public class Serial5 {

    public static void main(String[] args) {
        FileOutputStream fos = null;
        ObjectOutputStream salida = null;
        Alumno a;
        Fecha f;
        try {
            fos = new FileOutputStream("/ficheros/alumnos.dat");
            salida = new ObjectOutputStream(fos);
            f = new Fecha(5,9,2011);
            a = new Alumno("12345678A","Lucas González", 20, f);
            salida.writeObject(a);
            f = new Fecha(7,9,2011);
            a = new Alumno("98765432B","Anacleto Jiménez", 19, f);
            salida.writeObject(a);
            f = new Fecha(8,9,2011);
            a = new Alumno("78234212Z","María Zapata", 21, f);
            salida.writeObject(a);
           
        } catch (FileNotFoundException e) {
            System.out.println(e.getMessage());
        } catch (IOException e) {
            System.out.println(e.getMessage());
        } finally {
            try {
                if(fos!=null) fos.close();
                if(salida!=null) salida.close();
            } catch (IOException e) {
                System.out.println(e.getMessage());
            }
        }
    }
}

5. Leer objetos de ficheros
Para leer los objetos contenidos en un fichero binario que han sido almacenados mediante ObjectOutputStream se utiliza la clase ObjectInputStream derivada de InputStream.
Un objeto ObjectInputStream se crea a partir de un objeto FileInputStream asociado al fichero.
El constructor de la clase es:
ObjectInputStream(InputStream nombre);
Lanza una excepción IOException.
Por Ejemplo,  las instrucciones para crear el objeto ObjectInputStream para lectura de objetos del fichero personas.dat serían éstas:
FileInputStream fis = new FileInputStream ("/ficheros/personas.dat");
ObjectInputStream entrada = new ObjectInputStream (fis);
La clase proporciona el método readObject() que devuelve el objeto del fichero (tipo Object).
Es necesario hacer un casting para guardarlo en una variable del tipo adecuado.
Lanza una IOException
Ejemplo4:
Ejemplo de lectura de objetos contenidos en un fichero en Java
Programa que lee los objetos del fichero creado en el Ejemplo 1:
//Clase principal

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.ObjectInputStream;

public class Serial2 {

    public static void main(String[] args) {

        FileInputStream fis = null;
        ObjectInputStream entrada = null;
        Persona p;

        try {

            fis = new FileInputStream("/ficheros/personas.dat");
            entrada = new ObjectInputStream(fis);
            p = (Persona) entrada.readObject(); //es necesario el casting
            System.out.println(p.getNif() + " " + p.getNombre() + " " + p.getEdad());
            p = (Persona) entrada.readObject();
            System.out.println(p.getNif() + " " + p.getNombre() + " " + p.getEdad());
            p = (Persona) entrada.readObject();
            System.out.println(p.getNif() + " " + p.getNombre() + " " + p.getEdad());
        } catch (FileNotFoundException e) {
            System.out.println(e.getMessage());
        } catch (ClassNotFoundException e) {
            System.out.println(e.getMessage());
        } catch (IOException e) {
            System.out.println(e.getMessage());
        } finally {
            try {
                if (fis != null) {
                    fis.close();
                }
                if (entrada != null) {
                    entrada.close();
                }
            } catch (IOException e) {
                System.out.println(e.getMessage());
            }
        }

    }
}

Ejemplo 5:
Programa Java que lee los objetos del fichero creado en el Ejemplo 2:
//Clase Principal
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.ObjectInputStream;


public class Serial4 {

    public static void main(String[] args) {
        FileInputStream fis = null;
        ObjectInputStream entrada = null;
        Empleado emp;
        try {
            fis = new FileInputStream("/ficheros/personas.dat");
            entrada = new ObjectInputStream(fis);
            emp = (Empleado) entrada.readObject();
            System.out.println(emp.getNif() + " "
                                  + emp.getNombre() + " " + emp.getEdad() + " " + emp.getSueldo());
            emp = (Empleado) entrada.readObject();
            System.out.println(emp.getNif() + " "
                                  + emp.getNombre() + " " + emp.getEdad() + " " + emp.getSueldo());
            emp = (Empleado) entrada.readObject();
            System.out.println(emp.getNif() + " "
                                  + emp.getNombre() + " " + emp.getEdad() + " " + emp.getSueldo());
        } catch (FileNotFoundException e) {
            System.out.println(e.getMessage());
        } catch (ClassNotFoundException e) {
            System.out.println(e.getMessage());
        } catch (IOException e) {
            System.out.println(e.getMessage());
        } finally {
            try {
                if (fis != null) {
                    fis.close();
                }
                if (entrada != null) {
                    entrada.close();
                }
            } catch (IOException e) {
                System.out.println(e.getMessage());
            }
        }
    }
}
Ejemplo 6:
Programa Java que lee los objetos del fichero creado en el Ejemplo 3:
//Clase Principal
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.ObjectInputStream;
import persona.Alumno;

public class Serial6 {

    public static void main(String[] args) {
        FileInputStream fis = null;
        ObjectInputStream entrada = null;
        Alumno a;
        try {
            fis = new FileInputStream("/ficheros/alumnos.dat");
            entrada = new ObjectInputStream(fis);
            a = (Alumno) entrada.readObject();
            System.out.println(a.getNif() + " " + a.getNombre() + " " + a.getEdad()
                                       + " " + a.getFechaMatricula().getDia() + "-"
                                       + a.getFechaMatricula().getMes() + "-"
                                       + a.getFechaMatricula().getAño());
            a = (Alumno) entrada.readObject();
            System.out.println(a.getNif() + " " + a.getNombre() + " " + a.getEdad()
                                       + " " + a.getFechaMatricula().getDia() + "-"
                                       + a.getFechaMatricula().getMes() + "-"
                                       + a.getFechaMatricula().getAño());
            a = (Alumno) entrada.readObject();
            System.out.println(a.getNif() + " " + a.getNombre() + " " + a.getEdad()
                                       + " " + a.getFechaMatricula().getDia() + "-"
                                       + a.getFechaMatricula().getMes() + "-"
                                       + a.getFechaMatricula().getAño());
        } catch (FileNotFoundException e) {
            System.out.println(e.getMessage());
        } catch (ClassNotFoundException e) {
            System.out.println(e.getMessage());
        } catch (IOException e) {
            System.out.println(e.getMessage());
        } finally {
            try {
                if (fis != null) {
                    fis.close();
                }
                if (entrada != null) {
                    entrada.close();
                }
            } catch (IOException e) {
                System.out.println(e.getMessage());
            }
        }
    }
}

6. El modificador transient
Un atributo transient no se serializa.
En el siguiente ejemplo, la clase Cliente tiene dos atributos: el nombre del cliente y la contraseña. El atributo contraseña es transient por lo tanto si se serializa para escribir un objeto Cliente en un fichero solo se escribirá el atributo nombre.
//Clase Cliente
public class Cliente implements Serializable{
     private String nombre;
     private transient String passWord;
     public Cliente(String nombre, String passWord) {
              this.nombre=nombre;
              this.passWord= passWord;
     }
}

7 comentarios:

  1. gracias enrique, otra vez utilizando tus apuntes, después de estudiar programación en el instituto en tu clase, ahora en la universidad, también hay una asignatura de java.

    ResponderEliminar
    Respuestas
    1. Gracias por el comentario, Espero que te vaya muy bien en la Universidad, pero... me gustaría saber quién eres!!???? Un saludo

      Eliminar
  2. Muchas gracias muy bien explicado, entendi a la perfeccion. Más personas como vos.

    ResponderEliminar
  3. es de gran ayuda la informacion que han aportado en este blog puesto que esta muy explicito y ayuda a entender dicho tema

    ResponderEliminar
    Respuestas
    1. Me alegro que te haya sido útil Eduardo. Saludos y gracias por seguir el blog

      Eliminar