Imprimir

Introduce la capacidad de extender clases, donde la clase original se denomina clase padre, clase base o superclase, y la nueva clase se denomina clase hija, derivada, extendida o subclase.

La nueva clase tiene (hereda) los atributos y métodos de la clase padre, excepto los que se hayan definido como privados.

Se emplea la palabra extends en la clase hija seguida del nombre de la clase padre.

Ejemplo:

//Superclase 
class Persona {   
    private String nombre;   
    private String apellidos;   
    private int añoDeNacimiento;   

    public void imprime() {   
        System.out.println("Datos personales: " + nombre + " " +   
            apellidos + " (" + añoDeNacimiento + ")");   
    }   
}   

//Subclase 
class Alumno extends Persona {   
    //Hereda las propiedades nombre, apelidos y añoDeNacimiento 
    private String grupo;   

    public void setGrupo(String grupo) {   
        this.grupo = grupo;   
    }   
     
    //También hereda el método imprime 

En el ejemplo, la clase Alumno hereda los atributos nombre, apellidos y añoDeNacimiento de la clase Persona, así como el método imprime. Por tanto, sobre un objeto de la clase Alumno es posible, por ejemplo, llamar al método imprime.

//Creación de un nuevo alumno 
Alumno alumno1 = new Alumno(); 
//... 
//Llamada al método imprime heredado de la clase Persona 
alumno1.imprime(); 

Sobreescritura de métodos y atributos

En una clase derivada se puede volver a escribir los métodos o los atributos de la clase padre.

Se puede ampliar el nivel de acceso, haciéndolo más público, pero no restringirlo haciéndolo más privado.

Es muy común que al reescribir en la clase hija algunos métodos de la clase padre, sea necesario invocar los métodos originales de la clase padre. Para ello se dispone de la referencia super.

Ejemplo:

class Alumno extends Persona { 

    //... 

    //Sobreescribir el método imprime en la clase Alumno 
    public void imprime() { 
        //Mostrar la información como persona (datos personales) 
        super.imprime(); 
        //Mostrar también información del grupo 
        System.out.println("Es un estudiante del grupo: " + grupo); 
    } 
}

Con este ejemplo anterior, si se hiciera una llamada al método imprime() con un objeto Alumno, se mostrarán sus datos personales (lo que hace el método imprime de la clase Persona) y también se mostrará el grupo en el que se encuentre el alumno (lo que se ha añadido al método imprime).

Métodos y clases finales

En algunos puede ser conveniente impedir que se pueda sobreescribir un método. Para ello se le antepone el modificador final.

Por ejemplo, si la clase Persona tuviera un método getAñoDeNacimiento() del cual se quisiera asegurar que ninguna clase heredada pudiera sobreescribirlo, se debería declarar de la siguiente manera:

class Persona { 

    //... 
     
    public final int getAñoDeNacimiento() { 
        return añoDeNacimiento; 
    } 
}

Así, si en la clase Alumno se quisiera cambiar su funcionamiento no se podría, obteniendo un error en la compilación del código. Es decir, no se podría hacer algo como esto, que pretendía obtener un año más del real:

class Alumno extends Persona { 

    //... 
     
    //Se obtiene un error al intentar sobreescribir este método 
    public int getAñoDeNacimiento() { 
        return super.getAñoDeNacimiento()+1
    }     
}

De manera similar, si se quiere asegurar que no se puedan hacer subclases de una clase dada, se debe usar ese mismo modificador final en la declaración de la clase. Por ejemplo, si se quisiera impedir que se hicieran clases heredadas de la clase Persona se declararía como:

public final class Persona { 
    //... 
}

Herencia forzada. Clases abstractas

Se puede obligar que para usar una clase haya que hacerlo escribiendo una nueva clase que herede de ella.

Para ello se utiliza el modificador abstract delante de la definición de la clase.

Ejemplo:

public abstract class NombreDeClase { ... } 

Una clase abstracta no se puede instanciar (no se puede crear objetos). Se debe instanciar la clase derivada.

Si la clase Persona que se ha empleado en los ejemplos se declarara de esa manera, 

public abstract class Persona { 
    //... 
}

no se podrían crear objetos de tipo Persona, pero sí de la clase Alumno.

public class Main { 

    public static void main(String[] args) { 
        //Correcto 
        Alumno alumno1 = new Alumno();  
        //Error 
        Persona persona1 = new Persona(); 
    } 
     

Interfaces

Un interfaz permite diseñar la estructura básica de una serie de clases implementadas en función de los métodos declarados en dicho interfaz. El código de interfaz únicamente podrá contener la descripción de los métodos, sin ningún código dentro de ellos, que deberán ser forzosamente desarrollados (escribir su código) en las clases que implementen el interfaz.

Un interfaz se declará siguiendo un formato similar al siguiente, usando la palabra interface y en los métodos no se indican ni siquiera las llaves, tan sólo terminando en punto y coma:

public interface NombreInterfaz 
     
    public ClaseRetorno nombreMetodo1(); 
    public ClaseRetorno nombreMetodo2(); 
    public ClaseRetorno nombreMetodo3();     
     
}

La clase o clases que implementen un interfaz deberán usar la palabra clave implements junto con el nombre del interfaz, después del nombre de la clase. Estas clases deberán declarar obligatoriamente todos los métodos declarados en el interfaz con todo su código incluído.

public class NombreClase implements NombreInterfaz { 
     
    public ClaseRetorno nombreMetodo1() { 
         //Código propio del método 
    } 

    public ClaseRetorno nombreMetodo2() { 
         //Código propio del método 
    } 

    public ClaseRetorno nombreMetodo3() { 
         //Código propio del método 
    }     
     
}

Por ejemplo, podemos crear un interfaz llamado SerVivo y una clase Persona que sea una implementación de dicho interfaz:

public interface SerVivo { 
     
    public String getEstructura(); 
    public String getNacimiento(); 
    public String getAlimentacion();     
     



public class Persona implements SerVivo { 
    private String nombre;    
    private String apellidos;    
    private int añoDeNacimiento;  
     
    @Override 
    public String getEstructura() { 
        return "Vertebrado"
    } 

    @Override 
    public String getNacimiento() { 
        return "Mamífero"
    } 

    @Override 
    public String getAlimentacion() { 
        return "Omnívoro?"
    } 
         
    public void imprime() {    
        System.out.println("Datos personales: " + nombre + " " +    
            apellidos + " (" + añoDeNacimiento + ")");    
    }      
}