Skip to content

8 Clases e interfaces selladas

Introducción

Una jerarquía de clases nos permite reutilizar el código mediante la herencia. Sin embargo, la jerarquía de clases también puede cumplir otros propósitos. La reutilización del código es excelente, pero no siempre es nuestro objetivo principal.

Un propósito alternativo de una jerarquía de clases puede ser modelar varias posibilidades que existen en un dominio.

Como ejemplo, imaginemos un dominio empresarial que solo trabaja con automóviles y camiones, no con motocicletas. Al crear la clase abstracta Vehicle en Java, deberíamos poder permitir que solo las clases Car y Truck la extiendan. Por lo tanto, queremos asegurarnos de que no se haga un uso indebido de la clase abstracta Vehicle dentro de nuestro dominio.

En este ejemplo, nos interesa más la claridad del código que maneja subclases conocidas que la defensa contra todas las subclases desconocidas .

En versiones anteriores, Java proporcionaba opciones limitadas en el área de control de herencia.

Una clase final no puede tener subclases. Una clase friendly solo puede tener subclases en el mismo paquete.

Una superclase desarrollada con un conjunto de sus subclases debería poder documentar su uso previsto, sin restringir sus subclases. Además, tener subclases restringidas no debería limitar la accesibilidad de su superclase.

Sealed

Una clase o interfaz sellada (sealed) permite crear una clase o interfaz que solo puedan ser heredadas por las clases que el mismo programador que las crea, permite.

Las clases e interfaces selladas, fueron añadidas en su versión alpha en la versión 15 de Java, y de forma estable en la versión 17.

Para crear una clase o interfaz sellada, se usa el modificador de acceso sealed seguido del nombre la clase o interfaz. Luego, se usa el modificar permit seguido del nombre de las clases o interfaces que pueden heredar o implementar dicha clase.

Veamos un ejemplo

public abstract sealed class Vehicle permits Car, Truck {

    protected final String registrationNumber;

    public Vehicle(String registrationNumber) {
        this.registrationNumber = registrationNumber;
    }

    public String getRegistrationNumber() {
        return registrationNumber;
    }

}
public sealed interface Service permits Car, Truck {

    int getMaxServiceIntervalInMonths();

    default int getMaxDistanceBetweenServicesInKilometers() {
        return 100000;
    }

}

Ahora, al crear la clase Car o Truck ambas pueden heredar de la clase Vehicle y/o implementar la interface Service:

public final class Car extends Vehicle{
    //...
}

Sin embargo, una clase diferente a Car o Truck no podrá ni heredar ni implementar la clase o interfaz sellada:

public class OtherClass implements Service{ // ❌ ERROR DE COMPILACIÓN ❌
    // ...
}

Clase permitida

Una clase o interfaz permitida es aquella a la que se le permite heredar o implementar una clase o interfaz sellada. Sin embargo, esto debe tener una restricción, ya que si Vehicle permite que la clase Car pueda heredar de ella, no es una buena práctica que cualquier otra clase pueda heredar de la clase Car.

Para ello, la sealed class o sealed interface requieren de forma obligatoria que la clase o interfaz heredada o implementada utilice un modificador de acceso, final, sealed o non-sealed.

Si la clase o interfaz permitida utiliza el modificador de acceso final, esta clase no puede tener herencia. Por lo que, estaríamos evitando que cualquier otra clase o interfaz no permitida herede o implemente de nuestra clase o interfaz sellada.

public final class Car extends Vehicle implements Service{
    //...
}

Por otro lado, si una clase o interfaz utiliza el modificar sealed también quedará sellada, por lo que podrán heredar o implementar de ella, las clases que ésta permite.

public sealed class Car extends Vehicle implements Service permits Class1{
    // ...
}

También se puede utilizar el modificador non-sealed. En este caso, la clase permitida también permite su implementación y su extensión:

public non-sealed class Car extends Vehicle implements Service{
    // ...
}
Ejercicio

Crea una clase sellada Shape que permita su implementación en la clase Circle, Square y Triangle. Teniendo en cuenta las siguientes restricciones:

  • La clase Circle no permitirá ninguna tipo de herencia.
  • La clase Square permitirá herencia únicamente de la clase Rectangle.
  • Cualquier clase puede heredar de la clase Triangle.