4 Clases abstractas e interfaces¶
Clases abstractas¶
Cuando se crea una estructura con herencia, puede darse el caso de que algún método del padre no se pueda implementar porque los detalles de la implementación dependan de cada uno de los hijos. Entonces, dicho método se declara como abstracto en el padre y solamente se define su firma, no se implementa código en él. Los hijos pueden hacer dos cosas:
- Implementar el código de dicho método.
- Declararlo también como abstracto.
Si la clase contiene algún método abstracto, se convierte en una clase abstracta. Una clase abstracta puede contener métodos no abstractos pero al menos uno de los métodos debe ser abstracto.
Para indicar en Java que un método o una clase son abstractos, se utiliza la palabra reservada abstract.
public class AbstractClass {
public void show(){
Car car = new Car(4, "rojo");
System.out.printf("La velocidad del coche es %.2f km/h\n", car.getSpeed());
car.refuel(40.35);
System.out.printf("El coche tiene %.2f litros de gasolina\n", car.getGasoline());
car.accelerate(100);
System.out.printf("La velocidad del coche es %.2f km/h\n", car.getSpeed());
System.out.printf("El coche tiene %.2f litros de gasolina\n", car.getGasoline());
}
public static void main(String[] args) {
new AbstractClass().show();
}
}
Una clase abstracta no se puede instanciar, es decir, no se pueden crear objetos de ella haciendo uso del new:
Haciendo uso del polimorfismo, se puede declarar una variable de una clase abstracta que referencie a un objeto hijo, de tal forma que si se invoca el método abstracto, se ejecutará la implementación realizada por el hijo de dicho método abstracto:
Vehicle vehicle = new Car(4, "rojo");
vehicle.accelerate(50.89); //(1)!
System.out.printf("La velocidad del coche es %.2f km/h\n", car.getSpeed());
- Se ejecuta la implementación realizada en Car del método abstracto accelerate
Interfaces¶
Una interfaz en Java es una colección de métodos abstractos, es decir, en una interfaz se especifica qué se debe hacer pero no cómo hacerlo. Serán las clases que implementen estas interfaces las que describen la lógica del comportamiento de los métodos.
Una clase puede implementar más de una interfaz, lo que implica que debe realizar todos los métodos de cada una de ellas. Si algún método lo deja como abstracto, entonces se convierte en una clase abstracta.
En una interfaz también se pueden declarar constantes que luego puedan ser utilizadas por las clases que implementen dicha interfaz.
Una interfaz se define en un archivo con el mismo nombre de la interfaz y con extensión .java.
Las clases que quieran implementarla, tienen que añadir la palabra reservada implements detrás del nombre de la clase.
Veamos un ejemplo de una interfaz que va a contener acciones que pueda realizar un vehículo, como por ejemplo, acelerar y frenar:
Para comprobar si una clase implementa una interfaz se puede usar instanceof
:
Herencia entre interfaces¶
Las interfaces también pueden heredar de otras interfaces. En este caso, la clase que implemente la interfaz hija tendrá que realizar los métodos de la interfaz hija y los métodos de la interfaz padre. Por ejemplo, vamos a crear una interfaz GasolineMotor que va a contener métodos de un vehículo con motor de gasolina y que herede de la interfaz ActionsVehicle:
Métodos por defecto¶
A partir de Java 8, se pueden definir métodos con una implementación por defecto dentro de las interfaces. Las clases que implementan la interfaz pueden usar dicho método o anularlo si les interesa otra implementación diferente.
Para indicar que un método es por defecto se utiliza la palabra reservada default.
Por ejemplo, si definimos la siguiente interfaz con el siguiente método por defecto:
y ahora queremos crear una clase que implemente la interfaz, dicha clase no estará obligada a implementar el método method()
. Por ejemplo, el siguiente código es totalmente válido:
public class Class1 implements Interfaz1{
// No estamos obligados a implementar method(), aunque
// tenemos la posibilidad de anularlo
}
Pero ¿qué ocurre si una clase implementa dos interfaces que tienen el mismo método con implementación por defecto? En ese caso la clase estará obligada a anular dicho método, porque no se puede decidir qué implementación por defecto usar. Por ejemplo:
public interface Interfaz1{
default void method1(String str){
//Implementación
}
}
public interface Interfaz2{
default void method1(String str){
//Implementación
}
}
public class Class1 implements Interfaz1, Interfaz2{
@Override // Estamos oblogado a deshacer la ambigüedad
void method1(String str){
//Implementación en Clase1
}
}
¡Cuidado!
Un método con implementación por defecto no puede anular a un método de la clase java.lang.Object
, ya que es la clase base de todas las clases.
Métodos estáticos¶
A partir de Java 8, las interfaces también puede contener métodos estáticos con implementación por defecto, cuya característica principal es que pueden ser anulados por las clases que implementan la interfaz para evitar implementaciones no correctas en dichas clases. Se usan sobre todo para métodos de utilidad. Veamos un ejemplo:
public interface Interfaz3 {
default void print(String str){
// Se llama al método estático de la interfaz desde otro
// método default de la interfaz
if(!isNull(str)){
System.out.println("Cadena: " + str);
}
}
static boolean isNull(String str){
System.out.println("Interface Null Check");
return str == null ? true : "".equals(str) ? true : false;
}
}
Un método estático con implementación por defecto de una interfaz, como el método isNull()
anterior, puede ser llamado desde otro método de la propia interfaz , por ejemplo desde el método print()
.
También puede ser llamado estáticamente usando el nombre de la interfaz, como en:
Pero no puede ser llamado a través de una instancia de una clase que implemente la interfaz. Por ejemplo, el siguiente código da un error de compilación:
No se pueden definir en una interfaz métodos estáticos con implementación que tengan la misma firma que métodos de la clase java.lang.Object , ya que es la clase base de todas las clases.
Métodos privados¶
A partir de Java 9, las interfaces pueden contener métodos privados, que sólo pueden ser llamados desde métodos default de dicha interfaz u otros métodos privados de la misma. Sirven, básicamente, para poder separar el código de métodos con implementación por defecto. Veamos un ejemplo:
public interface Interfaz4{
// Este método privado sólo puede ser llamado por métodos default
// de la misma interfaz.
private int getNumeroAleatorio(){
return (new Random()).nextInt(100);
}
default String method1(String s){
// Un método default puede llamar a un método privado de la interfaz
return s + getNumeroAleatorio();
}
}
A partir de Java 9 también podemos definir en una interfaz métodos estáticos privados, que sólo podrán ser llamados desde otros métodos estáticos de la interfaz. Sirven, básicamente, para poder separar el código de métodos estáticos de la interfaz. Por ejemplo:
public interface Interfaz5{
private static String getPrefix(String p){
return p.equals("male") ? "Mr. " : "Ms. "
}
public static String getName(String n, String p){
return getPrefix(p) + n;
}
}
Diferencias entre una interfaz y una clase abstracta¶
Pero entonces, si las interfaces pueden tener métodos con implementación por defecto y métodos privados ¿qué diferencia hay entre una interfaz con métodos por defecto y una clase abstracta? La diferencia principal es que una interfaz no tiene estado, es decir no podemos almacenar atributos en ella, mientras que una clase abstracta sí.
Utilización de una interfaz como un tipo de dato¶
Al declarar una interfaz, se declara un nuevo tipo de datos, lo que significa que se puede declarar una variable cuyo tipo es una interfaz. Pero, ¿qué va a contener dicha variable? Puede contener un objeto de cualquier clase que implemente dicha interfaz. Con dicha variable, las acciones que se pueden realizar son los métodos de la interfaz.
public class Interfaces {
public void show(){
Vehicle vehicle = new Vehicle(2, "rojo");
ActionVehicle actionVehicle;
System.out.printf("La velocidad del vehículo es %.2f km/h\n", vehicle.getSpeed());
actionVehicle = vehicle;
actionVehicle.accelerate(100.30);
System.out.printf("La velocidad del vehículo es %.2f km/h\n", vehicle.getSpeed());
}
public static void main(String[] args) {
new Interfaces().show();
}
}
¡OJO!
Con una variable de tipo interfaz, las acciones que se pueden realizar son los métodos de la interfaz, es decir, con una variable de tipo ActionsVehicle lo que se puede realizar son los métodos accelerate y brake que son los métodos que pertenecen a la interfaz. Si intentáramos realizar un getSpeed que pertenece a la clase Vehicle y no se encuentra entre los métodos de la interfaz, nos daría un error de compilación.