Herencia, interfaces y polimorfismo en Java

Aprende cómo las clases de Java se basan unas en otras. Extiende una clase con extends y super, sobrescribe métodos heredados y dota a tus clases de métodos útiles toString, equals y hashCode de Object. Define comportamientos compartidos con clases abstractas e interfaces, predice qué método se ejecuta mediante el polimorfismo y el enlace dinámico, y modela opciones fijas y datos sencillos con enumeraciones y registros. Los ejemplos de código desarrollados muestran cada paso y los errores más frecuentes de quienes empiezan.

PrincipianteUnidad 8 de 11 del curso Curso de Programación orientada a objetos con Java

Lo que aprenderás

  1. Construye una clase nueva a partir de otra existente con extends, identifica qué hereda la nueva clase y asegúrate de que la parte heredada de cada objeto se inicialice correctamente con super.
    • Crea una subclase con extends
      Declara una subclase, determina qué miembros hereda y puede utilizar, y decide mediante la prueba de «es un» si la herencia es adecuada.
    • Inicializa objetos con constructores de la superclase
      Pasa valores al constructor de la superclase con super(...), predice el orden en que se ejecutan los constructores y evita los errores clásicos de constructores.
  2. Cambia el comportamiento heredado mediante la sobrescritura de métodos, amplía la versión de la superclase con super y dota a tus clases de métodos toString, equals y hashCode de Object con un significado útil.
    • Sobrescribe métodos heredados
      Sustituye o amplía un método heredado mediante una sobrescritura correcta, llama a la versión de la superclase con super y distingue la sobrescritura de la sobrecarga.
    • Utiliza y sobrescribe los métodos de Object
      Explica por qué todos los objetos tienen toString, equals, hashCode y getClass, interpreta su comportamiento predeterminado y escribe un toString útil.
    • Sobrescribe equals y hashCode correctamente
      Define cuándo dos objetos son iguales con un equals(Object) correcto, implementa hashCode de forma coherente y gestiona la igualdad en una jerarquía de clases.
  3. Utiliza clases abstractas para compartir código entre clases estrechamente relacionadas, utiliza interfaces para garantizar un comportamiento en clases no relacionadas y elige la herramienta adecuada para un diseño.
    • Escribe clases abstractas y métodos abstractos
      Declara clases y métodos abstractos, complétalos en subclases concretas y utiliza una clase abstracta para compartir campos y código.
    • Define e implementa interfaces
      Escribe una interfaz como contrato, impleméntala en una o más clases y combina interfaces con herencia.
    • Añade métodos predeterminados, estáticos y privados a las interfaces
      Dota a las interfaces de comportamiento compartido mediante métodos predeterminados, añade métodos auxiliares estáticos y privados, y resuelve conflictos entre métodos predeterminados.
    • Elige entre una clase abstracta y una interfaz
      Compara las clases abstractas y las interfaces y elige la opción adecuada, o ambas juntas, para un diseño determinado.
  4. Haz referencia a objetos mediante tipos generales, predice qué método se ejecuta en tiempo de ejecución, convierte tipos de forma segura y escribe código que siga funcionando cuando se añadan nuevas subclases.
    • Haz referencia a objetos mediante tipos de superclase y de interfaz
      Distingue el tipo declarado de una variable del tipo real del objeto y utiliza tipos de superclase y de interfaz para variables, parámetros y listas.
    • Predice qué método se ejecuta con el enlace dinámico
      Sigue las llamadas a métodos a través de una jerarquía de clases y distingue lo que decide el compilador de lo que se decide en tiempo de ejecución.
    • Convierte entre tipos con conversiones explícitas e instanceof
      Realiza conversiones descendentes de forma segura con instanceof y coincidencia de patrones, reconoce ClassCastException e identifica cuándo una comprobación de tipo indica que falta un método sobrescrito.
    • Diseña código flexible con polimorfismo
      Escribe métodos y colecciones usando tipos generales para que las clases nuevas encajen sin cambios, y haz que las clases puedan ordenarse con Comparable.
  5. Representa un conjunto fijo de opciones con una enumeración y un dato sencillo con un registro, utilizando los miembros que el compilador genera por ti.
    • Representa conjuntos fijos de valores con enumeraciones
      Declara enumeraciones, utiliza sus métodos integrados y switch, y dota a las constantes de enumeración de campos, constructores y comportamiento.
    • Modela datos con registros
      Declara registros para datos sencillos, utiliza el constructor generado, los métodos de acceso, equals, hashCode y toString, y valida datos en un constructor compacto.

Guía de estudio de Herencia, interfaces y polimorfismo en Java

5 capítulos

1

Extiende clases mediante herencia

Una subclase parte de los campos y el comportamiento de una clase existente y añade lo que la distingue. Esa relación afecta tanto a la forma en que otro código puede utilizar el objeto como a la forma en que Java lo construye. Sigue a una persona y a un estudiante a través de sus declaraciones y constructores: los datos privados permanecen bajo el control de la superclase y las llamadas a constructores proporcionan los valores que necesita el estado heredado.

1.1 Construye una subclase y protege sus datos

La herencia crea una subclase a partir de una superclase existente. En class Student extends Person, todo estudiante es una persona. Un objeto estudiante contiene su estado de persona y su estado adicional de estudiante juntos en un solo objeto.

Añade las partes específicas del estudiante

Aquí tienes una pequeña superclase y una subclase:

class Person {
    private String name;
    public Person(String name) { this.name = name; }
    public String getName() { return name; }
}

class Student extends Person {
    private int credits;
    public Student(String name) { super(name); }
    public void study() { credits += 5; }
    public int getCredits() { return credits; }
}

Student hereda getName(). Añade un campo de créditos y dos métodos. Después de Student s = new Student("Ada Lovelace");, el campo de créditos comienza en 0. Dos llamadas a s.study() lo aumentan primero a 5 y luego a 10. s.getName() sigue devolviendo «Ada Lovelace»: el método heredado lee el campo privado declarado en Person.

La sentencia super(name) pasa el nombre al constructor de Person. Los constructores no se heredan, por lo que Student declara su propio constructor. Una subclase puede añadir métodos y sobrescribir el comportamiento heredado, pero no puede eliminar un método público heredado de su interfaz pública.

Identifica a qué miembros se puede acceder
Modificador de accesoDónde se puede acceder al miembro
publicCualquier clase con acceso al tipo que lo declara
protectedEl mismo paquete, además del acceso de subclases entre paquetes
Sin modificadorEl mismo paquete
privateLa clase que lo declara

Los miembros privados no se heredan. El campo privado name sigue existiendo dentro de cada estudiante, pero Student no puede leerlo directamente. Mantener el campo privado y proporcionar un método de acceso permite que Person controle sus propios datos. Entre paquetes, el acceso a instancias protected tiene una restricción adicional: el código de la subclase accede al miembro a través de sí mismo o de una referencia de su propio tipo de subclase, no a través de una referencia arbitraria de la superclase.

Comprueba la relación antes de extender

Java permite una superclase directa por clase. Una cadena como GraduateStudent extends Student y Student extends Person es válida; extends Student, Teacher no lo es. Una clase sin superclase explícita extiende Object. El propio Object no tiene superclase.

Utiliza la prueba de «es un»: ¿puede utilizarse cada objeto de la subclase donde se espere la superclase? Un camión es un vehículo. Un cliente tiene una dirección, pero un cliente no es una dirección. Para esa segunda relación, utiliza la composición:

class Customer {
    private Address address;
}

Aquí Address es otra clase. El campo permite que un cliente utilice una dirección sin afirmar que es una. Extender una clase solo para aprovechar un método útil también hereda todo su contrato público. Por último, una clase final no puede extenderse. Como String es final, class ShortString extends String no compila.

Puntos clave

  • Un objeto de subclase incluye el estado de la superclase y cualquier estado que añada la subclase.
  • Se accede a los campos privados de la superclase mediante los métodos de esta.
  • Una clase extiende una superclase directa; los constructores no se heredan.
  • Utiliza herencia para una relación de «es un» y composición para una relación de «tiene un».
  • Una clase final no puede extenderse.

1.2 Pasa valores mediante una cadena de constructores

Un constructor de subclase debe organizar la construcción de la parte de la superclase del mismo objeto. Puede llamar a un constructor de la superclase con super(...) o delegar en otro constructor de su propia clase con this(...).

Elige el constructor de la superclase
class Person {
    private final String name;
    Person(String name) { this.name = name; }
}
class Student extends Person {
    private final String studentId;
    Student(String name, String studentId) {
        super(name);
        this.studentId = studentId;
    }
    Student(String name) {
        this(name, "no id yet");
    }
}

El constructor de dos argumentos pasa el nombre a Person y almacena él mismo el identificador. El constructor de un argumento delega en él. Un constructor contiene como máximo una invocación explícita de constructor: this(...) o super(...). Al seguir la cadena de this(...) se llega finalmente a un constructor que invoca super(...).

Si no se escribe ninguna de las dos, Java inserta super(). Aquí falla porque Person solo tiene un constructor que requiere un nombre. Declarar cualquier constructor impide que Java proporcione un constructor predeterminado sin argumentos. Una clase sin constructores declarados recibe uno predeterminado que llama a super().

Escribir new Person(name) dentro de Student hace otra cosa: construye un objeto independiente. No inicializa el estado de persona del estudiante actual ni sustituye la llamada obligatoria al constructor de la superclase.

Sigue la inicialización en orden
class Base {
    Base() { System.out.println("Base ready"); }
}
class Middle extends Base {
    Middle() { System.out.println("Middle ready"); }
}
class Top extends Middle {
    Top() { super(); System.out.println("Top ready"); }
}

new Top() entra en la cadena de constructores hasta llegar a Object. A medida que retornan las llamadas, la salida es «Base ready», «Middle ready» y luego «Top ready». Los inicializadores de campos de cada clase se ejecutan después de que retorne el constructor de su superclase y antes del resto del cuerpo del constructor de esa clase.

Un método sobrescribible llamado por un constructor de la superclase ya puede ejecutar una sobrescritura de la subclase. Si esa sobrescritura lee un campo de referencia de la subclase cuya asignación aún no se ha ejecutado, encuentra null, incluso si el campo tiene un inicializador en su declaración. Limita el trabajo de los constructores a inicializar el estado; evita llamar a métodos que las subclases puedan sobrescribir.

Reconoce el cambio de Java 25

En Java 25, un constructor puede validar parámetros antes de su invocación explícita de constructor:

Student(String name, String studentId, int credits) {
    if (credits < 0) {
        throw new IllegalArgumentException("negative credits");
    }
    super(name);
    this.studentId = studentId;
}

Java 25 también permite asignaciones simples anticipadas a campos declarados en la clase actual sin inicializadores en su declaración. Sigue prohibiendo leer los campos del objeto actual, llamar a sus métodos de instancia o pasar this antes de la invocación. Para los niveles estándar del lenguaje anteriores a Java 25, mantén super(...) o this(...) en primer lugar. Java 22–24 ofrecía formas anteriores como funciones de vista previa.

Puntos clave

  • super(...) construye la parte de la superclase del objeto actual.
  • Un super() implícito necesita un constructor sin argumentos accesible en la superclase.
  • this(...) delega dentro de la misma clase; su cadena debe llegar a un constructor de la superclase.
  • Los inicializadores de campos se ejecutan después de que retorne el constructor de la superclase.
  • Java 25 permite realizar operaciones limitadas antes de una invocación explícita de constructor.
  • Una sobrescritura llamada durante la construcción puede observar campos de la subclase antes de sus asignaciones.
2

Sobrescribe métodos y utiliza la clase Object

Los métodos heredados dan a los objetos relacionados un punto de partida común. La sobrescritura permite que cada subclase responda a la misma llamada a su manera, mientras que los métodos heredados de Object controlan cómo se imprime un objeto y cómo se compara con otros. Aquí importan las pequeñas diferencias de firma: un método puede parecer correcto al leerlo y, aun así, no sustituir al método que realmente llama el código de biblioteca. La igualdad también tiene compromisos de los que dependen las colecciones basadas en hash.

2.1 Sustituye el comportamiento heredado mediante una sobrescritura

Una sobrescritura proporciona a una subclase su propia implementación de un método de instancia heredado. Su nombre y los tipos de sus parámetros coinciden con los del método original, por lo que quienes lo llaman pueden seguir utilizando la misma operación.

Amplía un método que funciona

Supón que el salario anual de un empleado equivale a doce pagos mensuales. Un gerente también recibe una bonificación anual:

class Employee {
    private final double monthlySalary;
    Employee(double monthlySalary) {
        this.monthlySalary = monthlySalary;
    }
    public double yearlyPay() { return 12 * monthlySalary; }
}
class Manager extends Employee {
    private final double bonus;
    Manager(double monthlySalary, double bonus) {
        super(monthlySalary);
        this.bonus = bonus;
    }
    @Override
    public double yearlyPay() {
        return super.yearlyPay() + bonus;
    }
}

Para new Manager(3000.0, 5000.0), la llamada a la superclase devuelve 36 000,0. Al sumar 5000,0 se obtiene 41 000,0. super.yearlyPay() selecciona deliberadamente la implementación de la superclase. El método de la superclase sigue disponible dentro de la subclase, aunque las llamadas ordinarias sobre el gerente ejecutan la sobrescritura.

Escribir return yearlyPay() + bonus; vuelve a llamar al propio método del gerente. Ninguna llamada termina, por lo que la ejecución acaba fallando con StackOverflowError. La ausencia de super. cambia el significado de la llamada.

Deja que el compilador compruebe la firma

Pon @Override encima de cada método que sobrescriba otro. Un error de escritura como yearlypay() o un parámetro adicional provoca entonces un error de compilación. Sin la anotación, Java puede aceptar un método nuevo que quienes llaman al método nunca utilizan.

Una sobrescritura válida sigue estas reglas:

  • Sus tipos de parámetros coinciden exactamente.
  • Su tipo de retorno es el mismo o un subtipo cuando devuelve un tipo de objeto. Esa reducción permitida es un tipo de retorno covariante.
  • Su acceso es al menos tan abierto como el del método heredado.
  • No añade excepciones comprobadas que la declaración heredada prohíba.

Por ejemplo, public String toString() no puede sustituirse por String toString() con acceso de paquete. Quienes ya lo llaman deben conservar el acceso.

Distingue la sobrescritura de la sobrecarga y la ocultación
DeclaraciónQué hace
Misma firma de método de instancia en una subclaseSobrescribe el comportamiento heredado
Mismo nombre, distinta lista de parámetrosAñade una sobrecarga
Misma firma de método estático en una subclaseOculta un método estático

role() y role(String language) son métodos sobrecargados; el segundo no sustituye al primero. La elección de la sobrecarga utiliza los tipos declarados de los argumentos en tiempo de compilación. Los métodos estáticos también se seleccionan a partir del tipo utilizado en tiempo de compilación, por lo que no se sobrescriben dinámicamente.

Un método privado no se hereda; un método de la subclase con el mismo nombre es independiente. Un método final no puede sobrescribirse y los constructores no pueden sobrescribirse. Además de compilar, el método debe seguir cumpliendo su propósito original. Una sobrescritura de gerente que devolviera el salario mensual haría fallar el código que suma salarios anuales, aunque su firma fuera correcta.

Puntos clave

  • Una sobrescritura coincide con el nombre y los tipos de parámetros de un método de instancia heredado.
  • @Override detecta sobrecargas accidentales y errores de escritura.
  • super.method() reutiliza la implementación de la superclase.
  • Una sobrescritura no puede reducir el acceso ni ampliar las excepciones comprobadas permitidas.
  • La sobrecarga cambia los parámetros; los métodos estáticos se ocultan y los métodos final no pueden sobrescribirse.

2.2 Dota a los objetos de texto útil e identifica su clase

Toda clase de Java extiende Object directa o indirectamente. Los arreglos también son objetos. Esta raíz común proporciona métodos como toString(), equals(Object), hashCode() y getClass(), incluso cuando una clase no declara ninguno de ellos.

Sustituye la descripción impresa predeterminada

El método heredado Object.toString() produce un nombre de clase, @ y el código hash del objeto en hexadecimal. Un texto como «school.Person@1b6d3586» identifica la clase, pero no explica los datos de la persona. El número puede cambiar entre ejecuciones y Java no garantiza que sea una dirección de memoria.

Una sobrescritura útil muestra los campos que necesitan quienes leen la salida. Aquí un estudiante ha heredado getName() y almacena un campo credits:

@Override
public String toString() {
    return getName() + " (" + credits + " credits)";
}

Para un estudiante llamado Ada Lovelace con 10 créditos, System.out.println(s) imprime «Ada Lovelace (10 credits)». La llamada de impresión obtiene automáticamente el texto del objeto. La concatenación de cadenas también lo hace: "Student: " + s incluye la misma descripción.

Para una referencia nula, la impresión y la concatenación producen «null». Una llamada explícita s.toString() cuando s es null lanza una NullPointerException, porque no hay ningún objeto que reciba esa llamada.

Si Person.toString() ya devuelve el nombre de la persona, una subclase puede basarse en él:

@Override
public String toString() {
    return super.toString() + ", student id " + studentId;
}

Para el identificador «S-17», eso produce «Ada Lovelace, student id S-17». La utilidad de super.toString() depende de lo que devuelva realmente la superclase. Si sigue utilizando la versión de Object, aporta el texto predeterminado de clase y hash.

Distingue la identidad de la información sobre la clase

Object.equals(Object) compara la identidad: devuelve verdadero cuando ambas referencias hacen referencia al mismo objeto. Dos objetos new Person("Ada") independientes no son iguales según esa implementación heredada, aunque tengan el mismo nombre. Clases como String, Integer y ArrayList proporcionan sobrescrituras basadas en valores.

getClass() indica la clase real en tiempo de ejecución:

Person p = new Student("Ada Lovelace");
System.out.println(p.getClass().getSimpleName());

Esto imprime «Student» cuando Student tiene el constructor mostrado. El tipo declarado de la variable no altera la clase del objeto. getClass() es final y no puede sobrescribirse.

hashCode() da soporte a las colecciones basadas en hash. Otros métodos de Object incluyen el protegido clone() y los métodos de coordinación de hilos wait(), notify() y notifyAll().

Comprueba la escritura exacta

Java distingue toString() de tostring(). Un método toString(String prefix) es una sobrecarga, por lo que la impresión no lo llama. @Override detecta ambos errores. Una representación textual tampoco define la igualdad: dos objetos pueden imprimir el mismo texto y aun así compararse como desiguales.

Puntos clave

  • Object proporciona métodos comunes a todos los objetos Java, incluidos los arreglos.
  • La impresión y la concatenación de cadenas utilizan toString() y representan una referencia nula como «null».
  • Object.equals compara la identidad hasta que una clase lo sobrescribe.
  • getClass() devuelve la clase real y es final.

2.3 Mantén la coherencia entre igualdad y códigos hash

Dos objetos pueden representar el mismo valor sin ser el mismo objeto. Un catálogo de libros puede considerar iguales dos objetos libro cuando coinciden su código de catálogo y su edición. Define esa regla sobrescribiendo equals(Object) y mantén el código hash coherente con ella.

Compara los campos que definen el valor
import java.util.Objects;

final class Book {
    private final String code;
    private final int edition;
    Book(String code, int edition) {
        this.code = code;
        this.edition = edition;
    }
    @Override
    public boolean equals(Object other) {
        if (this == other) return true;
        if (!(other instanceof Book b)) return false;
        return edition == b.edition && Objects.equals(code, b.code);
    }
    @Override
    public int hashCode() {
        return Objects.hash(code, edition);
    }
}

La comprobación de identidad acepta rápidamente el mismo objeto. La comprobación de tipo rechaza un objeto no relacionado o null y luego introduce b como un Book. Las ediciones primitivas utilizan ==; Objects.equals compara los códigos de forma segura incluso cuando un código es null. Dos libros con el código «B-1042» y la edición 2 se comparan como iguales. La edición 3 constituye un valor diferente.

El parámetro debe ser Object. Escribir equals(Book other) añade una sobrecarga; deja intacto el equals(Object) heredado. El código de biblioteca que llame a esa firma seguiría comparando la identidad. @Override hace que la declaración incorrecta falle en tiempo de compilación.

Cumple los compromisos de igualdad

Para referencias no nulas, la igualdad debe ser:

  • Reflexiva: un objeto es igual a sí mismo.
  • Simétrica: intercambiar los dos objetos da la misma respuesta.
  • Transitiva: si uno es igual a un segundo y el segundo es igual a un tercero, el primero es igual al tercero.
  • Consistente: las comparaciones repetidas coinciden mientras los datos comparados no cambien.

Un objeto también debe compararse como desigual a null. Los objetos iguales deben devolver códigos hash iguales. Los objetos desiguales pueden compartir un código hash: una colisión de hash no establece la igualdad. Deriva el hash de los mismos campos que la igualdad, como hace aquí Objects.hash(code, edition).

Una colección basada en hash utiliza el hash para localizar candidatos antes de comprobar la igualdad. Sobrescribir solo equals puede hacer que un conjunto conserve libros iguales independientes o que un mapa no encuentre una clave igual. Cambiar un campo utilizado en el cálculo del hash mientras su objeto está almacenado como clave puede dejar ese objeto guardado según su hash anterior.

Protege la simetría entre subclases

Supón que un Book extensible acepta cualquier instanceof Book, mientras que Ebook añade el tamaño del archivo a la igualdad y solo acepta libros electrónicos. Un libro puede aceptar un libro electrónico con el mismo código y edición, pero el libro electrónico rechaza el libro. La comparación ha perdido la simetría.

Una opción exige que las clases en tiempo de ejecución coincidan y comprueba null antes de getClass(). Otra hace que la igualdad de la superclase sea final para que las subclases no puedan ampliar la regla de igualdad. Una clase de valor final, como la anterior, evita por completo la igualdad entre subclases. Los registros generan la igualdad y los códigos hash a partir de sus componentes; las constantes de enumeración ya tienen una igualdad basada en la identidad adecuada para sus valores fijos.

Puntos clave

  • Sobrescribe equals(Object), no una sobrecarga con la clase como parámetro.
  • La igualdad debe ser reflexiva, simétrica, transitiva y consistente; la comparación con null es falsa.
  • Los objetos iguales necesitan códigos hash iguales; un hash compartido no establece la igualdad.
  • Mantén estables los campos que determinan la igualdad mientras un objeto sea una clave de una colección basada en hash o un elemento de un conjunto.
  • Una superclase y una subclase no deben utilizar reglas de igualdad que discrepen al invertir los operandos.
  • Los registros y las enumeraciones proporcionan un comportamiento de igualdad integrado adecuado.
3

Define contratos con clases abstractas e interfaces

Algunas clases relacionadas comparten campos y métodos funcionales; otras clases solo comparten una capacidad. Las clases abstractas y las interfaces expresan esas relaciones diferentes. Un programa de dibujo puede mantener los datos comunes de las figuras en una clase abstracta, mientras que un programa de carga trabaja con dispositivos no relacionados mediante una interfaz. Ambos enfoques permiten que el compilador compruebe el comportamiento requerido antes de ejecutar el programa, y Java proporciona reglas precisas cuando compiten implementaciones heredadas.

3.1 Comparte estado y exige comportamiento a las subclases

Una clase abstracta es una clase base que no puede instanciarse directamente. Puede contener campos comunes y métodos funcionales, y dejar determinadas operaciones para que las subclases las completen.

Deja abstracta una operación requerida
abstract class Shape {
    private final String name;
    Shape(String name) { this.name = name; }
    public abstract double area();
    public String describe() {
        return name + " with area " + area();
    }
}
class Rectangle extends Shape {
    private final double width;
    private final double height;
    Rectangle(double width, double height) {
        super("rectangle");
        this.width = width;
        this.height = height;
    }
    @Override
    public double area() { return width * height; }
}

area() tiene una firma y un punto y coma, sin cuerpo. Indica lo que debe proporcionar cada figura concreta. Como Shape contiene un método abstracto, la clase también debe ser abstracta. Rectangle proporciona la implementación que falta y puede instanciarse.

Para new Rectangle(3.0, 4.0), el constructor de la superclase almacena «rectangle». El constructor de la subclase almacena las dimensiones. Su área es de 12,0 metros cuadrados cuando las dimensiones están en metros. Llamar a describe() devuelve «rectangle with area 12.0». La llamada a area() del método heredado ejecuta la implementación del rectángulo.

Utiliza constructores y métodos concretos normalmente

Una clase abstracta puede tener campos, constructores y métodos con cuerpo, con los niveles de acceso habituales. Su constructor se ejecuta como parte de la creación de un objeto de subclase. new Shape() está prohibido incluso si existe un constructor correspondiente; new Rectangle(3.0, 4.0) es válido.

Una segunda subclase puede utilizar la misma descripción heredada mientras calcula el área de otra manera. Una implementación de círculo devuelve Math.PI * radius * radius. Para un radio de 1,5 m, el cálculo es π por 2,25, aproximadamente 7,07 metros cuadrados. El método compartido describe() no necesita una rama específica para el círculo.

Una variable puede seguir teniendo el tipo abstracto: Shape s = new Rectangle(3.0, 4.0);. Java prohíbe construir la clase abstracta, no hacer referencia a un objeto concreto a través de ella.

Haz que el trabajo pendiente produzca un error de compilación

Toda subclase concreta debe proporcionar o heredar implementaciones de todos los métodos abstractos. Una subclase que deja algunos sin terminar también debe declararse abstracta. Devolver 0 desde un area() provisional permitiría que una subclase concreta incompleta compilara y diera resultados incorrectos sin avisar. Declarar area() abstracto hace que el trabajo pendiente sea visible para el compilador.

Los métodos abstractos no pueden ser privados, estáticos ni final: cada uno impediría la sobrescritura de la subclase que proporciona la operación. Una clase abstracta puede no contener métodos abstractos; entonces el modificador solo impide la construcción directa. Utiliza este tipo de clase base cuando sus subclases superen la prueba de «es un» y se beneficien de compartir estado o código.

Puntos clave

  • Una clase abstracta no puede construirse directamente, pero su constructor se ejecuta para los objetos de subclase.
  • Un método abstracto no tiene cuerpo y requiere una implementación concreta.
  • Una subclase concreta debe completar todos los métodos abstractos heredados.
  • Un tipo abstracto puede utilizarse para variables, parámetros y listas.
  • Las clases abstractas pueden combinar campos comunes, métodos funcionales y operaciones requeridas.

3.2 Dota a clases no relacionadas de un contrato compartido

Una interfaz da nombre a una capacidad que varias clases pueden proporcionar. Establece los métodos disponibles mediante esa capacidad sin exigir que las clases compartan los mismos campos de instancia o la misma superclase.

Declara e implementa los métodos
interface Chargeable {
    int batteryPercent();
    void charge(int minutes);
}
class Phone implements Chargeable {
    private int battery;
    Phone(int battery) { this.battery = battery; }
    @Override
    public int batteryPercent() { return battery; }
    @Override
    public void charge(int minutes) {
        battery = Math.min(100, battery + 2 * minutes);
    }
}

Los métodos abstractos de la interfaz son implícitamente públicos. Los métodos que los implementan deben ser explícitamente públicos; omitir ese modificador reduciría el acceso y haría fallar la compilación. Una clase concreta debe proporcionar o heredar implementaciones públicas adecuadas de todos los métodos abstractos. Una clase abstracta que implemente la interfaz puede dejar algunos para sus subclases.

Para un teléfono al 35 %, charge(20) añade 40 puntos porcentuales y lo deja al 75 %. Otros 20 minutos lo llevarían a 115 antes de aplicar el límite, por lo que Math.min almacena 100. Un ElectricScooter puede implementar la misma interfaz y añadir solo un punto por minuto: si empieza al 50 %, alcanza el 70 después de 20 minutos. La interfaz establece la operación disponible, mientras que la clase proporciona su comportamiento.

Utiliza la interfaz como tipo
Chargeable device = new Phone(35);
device.charge(20);
System.out.println(device.batteryPercent());

Esto imprime 75. El código que llama puede utilizar los métodos de la interfaz y los métodos públicos disponibles en todos los objetos. Las operaciones exclusivas del teléfono no están disponibles a través de device, salvo que la interfaz declarada las exponga.

Las interfaces también pueden ser tipos de parámetros, valores de retorno y elementos de listas. Un método que acepta Chargeable puede trabajar con un teléfono o un patinete sin conocer la disposición de sus campos. La biblioteca de Java utiliza el mismo enfoque: List tiene implementaciones como ArrayList y LinkedList; Runnable declara run(); Comparable declara la comparación según el orden natural.

Combina más de una capacidad

Una clase extiende una clase y puede implementar varias interfaces. El orden es class Phone extends Device implements Chargeable, Connectable. Una interfaz puede extender varias interfaces: interface SmartDevice extends Chargeable, Connectable. Quienes la implementen deben entonces proporcionar todas las operaciones abstractas heredadas.

Una interfaz no tiene constructores ni campos de instancia. new Chargeable() no compila. Un campo declarado como int FULL = 100; es implícitamente una constante public static final, a la que se accede como Chargeable.FULL.

Un método abstracto de interfaz no aporta código funcional. Su implementación puede proceder de la propia clase o de un método público heredado de la superclase. Los métodos predeterminados son los métodos de interfaz que pueden proporcionar comportamiento de instancia heredado.

Puntos clave

  • Una interfaz es un tipo que describe una capacidad compartida.
  • Una clase concreta que implementa una interfaz debe proporcionar o heredar implementaciones públicas adecuadas.
  • Una clase puede implementar varias interfaces mientras extiende una clase.
  • Las interfaces no tienen constructores ni campos de instancia; sus campos son constantes.
  • El tipo de interfaz permite utilizar su contrato con clases no relacionadas.

3.3 Utiliza comportamiento compartido de interfaces y resuelve conflictos

Las interfaces pueden proporcionar código funcional además de declaraciones de métodos abstractos. El modificador del método determina si los objetos que implementan la interfaz lo heredan, si se utiliza mediante el nombre de la interfaz o si solo puede utilizarlo el código dentro de ella.

Construye comportamiento predeterminado a partir del contrato
interface Chargeable {
    int batteryPercent();
    void charge(int minutes);
    default boolean needsCharge() {
        return batteryPercent() < 20;
    }
    static int clamp(int percent) {
        return Math.max(0, Math.min(100, percent));
    }
}

Un método predeterminado es un método público de instancia con cuerpo. Un teléfono que indica un 15 % hereda needsCharge() y devuelve verdadero. Al 20 % devuelve falso. El método predeterminado llama a la operación abstracta batteryPercent(); no lee un campo privado del teléfono. Una clase puede sobrescribir el método con su propia implementación pública.

Un método estático de interfaz pertenece a la interfaz: Chargeable.clamp(130) devuelve 100 y Chargeable.clamp(-5) devuelve 0. Las clases que implementan la interfaz no lo heredan, por lo que Phone.clamp(130) no es una llamada válida a este método. Los métodos predeterminados y estáticos de interfaz se introdujeron en Java 8.

Desde Java 9, una interfaz también puede declarar métodos privados para compartir código auxiliar entre sus propios métodos. Un método auxiliar privado de instancia sirve de apoyo a los métodos predeterminados; uno estático privado también puede servir de apoyo a los métodos estáticos. Las clases que implementan la interfaz no pueden llamar directamente a estos métodos auxiliares.

Resuelve conflictos entre métodos predeterminados

Dos interfaces no relacionadas pueden proporcionar la misma firma:

interface Lamp {
    default String describe() { return "lamp"; }
}
interface Speaker {
    default String describe() { return "speaker"; }
}
class SmartLamp implements Lamp, Speaker {
    @Override
    public String describe() {
        return Lamp.super.describe() + " and "
             + Speaker.super.describe();
    }
}

Sin la sobrescritura, SmartLamp tiene un conflicto y no compila. Las llamadas cualificadas InterfaceName.super.method() seleccionan los métodos predeterminados individuales de sus superinterfaces directas. Esta implementación devuelve «lamp and speaker».

Implementaciones en conflictoRegla
Método público concreto heredado de la superclase y método predeterminado de interfazGana el método de la clase
Método predeterminado de una interfaz y de la interfaz que la extiendeGana la interfaz más específica
Métodos predeterminados de interfaces no relacionadasLa clase debe resolver el conflicto

Una declaración abstracta de la superclase también puede exigir que una subclase concreta implemente el método; un método predeterminado de interfaz no elimina esa obligación.

Ten en cuenta las clases que ya implementan la interfaz

Añadir un método abstracto exige que cada clase concreta que implementa la interfaz proporcione o herede una implementación correspondiente. Un método predeterminado suele evitar ese cambio al proporcionar comportamiento funcional, pero puede introducir un conflicto con otro método heredado. Los métodos predeterminados ayudan a que una interfaz evolucione; las reglas de herencia siguen determinando si las clases resultantes compilan.

Puntos clave

  • Un método predeterminado proporciona comportamiento público de instancia que se puede sobrescribir.
  • Llama a un método estático de interfaz mediante el nombre de la interfaz.
  • Los métodos privados de interfaz son métodos auxiliares visibles solo dentro de la interfaz.
  • Un método concreto de clase tiene prioridad sobre un método predeterminado de interfaz.
  • Los métodos predeterminados en conflicto de interfaces no relacionadas requieren una sobrescritura; en caso contrario, gana el de la interfaz más específica.
  • Añadir un método predeterminado puede introducir conflictos entre métodos heredados.

3.4 Elige una base compartida, una capacidad o ambas

Tanto una clase abstracta como una interfaz pueden definir métodos obligatorios. La elección depende de la relación entre los objetos y de si su implementación necesita estado de instancia compartido.

Compara lo que proporciona cada una
PreguntaClase abstractaInterfaz
¿Puede almacenar campos por objeto?SíNo
¿Puede tener constructores?SíNo
¿Puede proporcionar métodos de instancia funcionales?SíSí, con métodos predeterminados
¿Cuántas puede utilizar directamente una clase?Una superclaseVarias interfaces implementadas
¿Qué niveles de acceso están disponibles?Todos los niveles habitualesMétodos públicos y métodos auxiliares privados

Los campos de interfaz son constantes public static final, no datos de cada objeto. Los métodos auxiliares privados de una interfaz sirven de apoyo a sus propios métodos funcionales; no crean estado privado para cada objeto que la implementa.

Coloca el estado estrechamente relacionado en una clase abstracta

Un programa de dibujo tiene círculos, rectángulos y triángulos. Cada figura almacena un nombre y una posición, y cada una calcula su área de forma diferente. Una Shape abstracta puede mantener los campos comunes y el constructor, proporcionar métodos funcionales de movimiento o descripción y declarar area() como abstracto.

Todas las figuras concretas son tipos de Shape, por lo que otro código puede pedir una figura y recibir cualquiera de ellas. Del mismo modo, una Enemy abstracta podría tener puntos de salud y un método compartido takeDamage(int points), mientras deja el movimiento a las clases de enemigos concretas. El estado compartido da a estas clases base una función útil.

Coloca una capacidad transversal en una interfaz

El mismo programa de dibujo puede exportar figuras, etiquetas de texto e imágenes. Exportar no convierte una etiqueta de texto en una figura, y estos objetos pueden necesitar ya superclases diferentes. Una interfaz Exportable puede declarar String exportText(). Cada clase implementa la operación utilizando sus propios datos.

Una clase puede combinar las dos funciones: class Circle extends Shape implements Exportable. La superclase proporciona el estado de la figura y el código compartido; la interfaz permite utilizar el círculo desde el código de exportación. Una clase que implementa varias interfaces se compromete a ofrecer varias capacidades sin heredar campos de instancia de ellas.

Combina un contrato con una implementación parcial

Una clase abstracta puede implementar una interfaz y completar solo algunos métodos. Las subclases concretas terminan las operaciones restantes. List y AbstractList de Java utilizan esta organización; ArrayList extiende AbstractList. El código que llama puede depender de la interfaz, mientras que quienes la implementan reutilizan una clase base cuando resulta adecuada.

Por ejemplo, List<String> names = new ArrayList<>(); permite añadir, leer y eliminar nombres mediante List. Sustituir el inicializador por new LinkedList<>() conserva esas llamadas. Un parámetro declarado como ArrayList<String> rechazaría un LinkedList<String> aunque solo necesite operaciones de lista.

Una clase abstracta que solo contiene métodos abstractos sigue ocupando la única posición de superclase de la subclase. No es intercambiable con una interfaz. Elige la interfaz cuando solo se necesite el contrato común y añade una clase base cuando las implementaciones relacionadas se beneficien de compartir estado o código.

Puntos clave

  • Utiliza una clase abstracta para tipos relacionados de objetos que comparten estado o código.
  • Utiliza una interfaz para una capacidad que pueden proporcionar clases no relacionadas.
  • Una clase puede extender una base abstracta e implementar interfaces a la vez.
  • Una implementación abstracta parcial puede respaldar una interfaz sin restringir a quienes la utilizan a esa clase base.
4

Escribe programas flexibles con polimorfismo

El tipo de una variable y la clase de un objeto responden a preguntas diferentes. El tipo declarado de la variable controla qué llamadas compilan; la clase real del objeto controla qué sobrescritura se ejecuta. Mantener separadas esas decisiones permite seguir la ejecución de código desconocido, utilizar conversiones explícitas de forma segura y escribir bucles que funcionan con nuevas implementaciones. El mismo enfoque sirve para interfaces de biblioteca como Comparable, que proporciona a los objetos un orden natural.

4.1 Distingue el tipo de referencia de la clase del objeto

El polimorfismo permite que una referencia de un tipo general apunte a objetos de distintos subtipos. El objeto conserva todo su estado y comportamiento; la referencia ofrece a quienes la utilizan una vista concreta de las operaciones disponibles.

Identifica los dos tipos
Person p = new Student("Ada Lovelace", "S-17");

El tipo declarado, también llamado tipo estático, es Person: aparece en la declaración de la variable. El tipo real, o tipo en tiempo de ejecución, es Student: esa es la clase instanciada por new. La clase real del objeto no cambia después de su construcción.

Asignar una referencia de estudiante a una referencia de persona es una conversión ascendente. Es automática y segura porque todo estudiante es una persona. También se puede hacer referencia al mismo estudiante mediante una variable Object. Un objeto que implementa una interfaz puede convertirse de forma ascendente a esa interfaz, como en Chargeable c = new Phone(35);.

La asignación no elimina ningún dato del estudiante. Sigue habiendo un solo objeto estudiante con su identificador, los datos de persona heredados y los métodos sobrescritos. Darle una referencia más general cambia aquello a lo que el compilador permite acceder mediante esa referencia.

Comprueba las llamadas según el tipo declarado

Si Person declara getName(), entonces p.getName() compila. Si solo Student declara getStudentId(), entonces p.getStudentId() no compila. El compilador comprueba la interfaz pública de Person incluso cuando el inicializador crea claramente un estudiante.

La asignación inversa Student s = p; no se acepta sin una conversión explícita. Una referencia de persona podría apuntar a una persona que no sea estudiante. Esa restricción impide que el código trate un objeto como un subtipo al que no pertenece.

Tipo declaradoLlamadas disponibles mediante la referencia
ObjectOperaciones públicas proporcionadas por Object
PersonOperaciones accesibles de Person, incluidas las heredadas
StudentOperaciones accesibles de estudiante y operaciones heredadas
Permite que los parámetros y las listas acepten varios subtipos

Un método static void printCard(Person person) puede recibir un objeto Person, Student o Teacher, suponiendo que los dos últimos extiendan Person. Su código puede apoyarse en el contrato de persona. Un tipo de retorno también puede ser general, por ejemplo, un método que devuelve un Shape.

Una lista con un tipo general de elemento puede almacenar distintas clases de figuras:

List<Shape> shapes = new ArrayList<>();
shapes.add(new Circle(1.5));
shapes.add(new Rectangle(3.0, 4.0));
for (Shape shape : shapes) {
    System.out.println(shape.area());
}

Aquí List y ArrayList proceden de java.util, y cada figura concreta proporciona area(). El bucle utiliza únicamente esa operación común.

Una List<Circle> no es una List<Shape>. Si lo fuera, el código que la recibiera como lista de figuras podría añadir un rectángulo a una lista que debe contener solo círculos. Cuando un método requiera List<Shape>, crea la lista con ese tipo de elemento desde el principio.

Puntos clave

  • El tipo declarado controla a qué operaciones puede acceder una referencia.
  • El tipo real es la clase del objeto y no cambia después de su construcción.
  • La conversión ascendente a una superclase o a una interfaz implementada es automática y segura.
  • Un tipo general de parámetro o elemento de lista puede admitir varios subtipos concretos.
  • List<Circle> no puede utilizarse como List<Shape>.

4.2 Sigue la selección de sobrecargas y el enlace dinámico

Una llamada a un método se resuelve en dos etapas. En tiempo de compilación, Java comprueba los tipos declarados y elige una firma de método. En tiempo de ejecución, el enlace dinámico elige la implementación sobrescrita del método de instancia para la clase real del objeto.

Sigue una llamada dentro de un método heredado
class A {
    String who() { return "A"; }
    String hello() { return "Hello from " + who(); }
}
class B extends A {
    @Override String who() { return "B"; }
}
class C extends B { }

Para A x = new C();, x.hello() está permitido porque A declara hello(). La clase C no sobrescribe hello(), y B tampoco, por lo que Java ejecuta la implementación de A.

Dentro de ese método, who() significa this.who(). El objeto receptor sigue siendo el objeto C. Java busca desde C y encuentra la implementación más cercana de who() en B. El resultado es «Hello from B». Entrar en un método heredado no convierte el objeto receptor en un objeto de la superclase.

Elige una sobrecarga antes de elegir una sobrescritura
static String kind(Object value) { return "object"; }
static String kind(String value) { return "string"; }

Dentro de una clase que contiene estos métodos, Object value = "hi"; kind(value) selecciona kind(Object) y devuelve «object». El tipo declarado de la variable es Object; Java no vuelve a considerar las sobrecargas utilizando la cadena real en tiempo de ejecución.

Para una llamada de instancia, el despacho en tiempo de ejecución todavía puede elegir una sobrescritura de la firma ya seleccionada. No puede cambiar a otra sobrecarga que simplemente parezca más específica para el argumento real. Identifica primero la firma y después busca su sobrescritura.

Reconoce las llamadas y los miembros que no se despachan de esta manera
Miembro o llamadaQué lo selecciona
Método de instancia sobrescritoClase real del objeto receptor, después de seleccionar la firma
Método estáticoTipo utilizado para la llamada en tiempo de compilación
Campo ocultoTipo declarado de la referencia
Método privadoCódigo de la clase que lo declara
super.method()Implementación de la superclase

Si P declara String label = "P"; y Q extends P declara su propio String label = "Q";, entonces P p = new Q(); p.label lee «P». Los campos se ocultan, no se sobrescriben. Ambos campos existen en el objeto. Evita reutilizar nombres de campos heredados porque el tipo de referencia puede hacer que accesos de aspecto idéntico lean estados diferentes.

super.method() selecciona la implementación de la superclase, incluida una que esa superclase herede. Si el propio método seleccionado hace una llamada ordinaria sobre this, esa llamada interna todavía puede despacharse a una sobrescritura de la subclase.

Puntos clave

  • En tiempo de compilación se fija la firma que puede llamarse, incluida la elección de sobrecarga.
  • El enlace dinámico selecciona la sobrescritura más cercana de esa firma, buscando desde la clase real hacia arriba.
  • Una llamada ordinaria dentro de un método heredado sigue utilizando el objeto receptor real.
  • Los campos, los métodos estáticos, los métodos privados y las llamadas explícitas a super no utilizan el despacho ordinario de sobrescrituras.

4.3 Comprueba un subtipo antes de utilizar sus métodos específicos

Una referencia general a veces necesita acceder a una operación que solo pertenece a un subtipo. Una conversión descendente indica al compilador que trate la referencia como ese subtipo. En tiempo de ejecución, Java comprueba si el objeto ya tiene ese tipo.

Comprende qué cambia una conversión explícita

Supón que Person p hace referencia a un estudiante. Student s = (Student) p; permite llamar a s.getStudentId(). Ambas variables siguen haciendo referencia al mismo objeto. La conversión no lo copia, no cambia su clase real ni añade campos de estudiante a una persona que no lo sea.

Si p hace referencia en realidad a new Person("Grace Hopper"), esa conversión lanza una ClassCastException. El compilador acepta la posible relación entre persona y estudiante, pero el objeto en tiempo de ejecución debe cumplirla. Una conversión entre clases que no pueden estar relacionadas, como una referencia Person convertida explícitamente a la clase final Integer, falla en tiempo de compilación.

Esto difiere de una conversión primitiva: (int) 3.9 crea el valor entero 3. Una conversión explícita de referencia solo comprueba y cambia el tipo a través del cual se utiliza una referencia existente.

Combina la comprobación y la variable nueva

Desde Java 16, la coincidencia de patrones hace que el caso seguro sea compacto:

static void showStudentId(Person person) {
    if (person instanceof Student student) {
        System.out.println(student.getStudentId());
    }
}

La comprobación es verdadera para un estudiante o una subclase de estudiante. La variable student está disponible dentro del bloque, donde se sabe que la comprobación ha tenido éxito. Una persona que no sea estudiante omite el bloque. Una referencia nula también hace que la comprobación sea falsa, por lo que el bloque nunca intenta llamar a un método sobre null.

La forma anterior comprueba person instanceof Student y luego declara Student student = (Student) person; dentro de la rama donde la comprobación tuvo éxito. Ambas formas protegen la conversión. Comprobar otro objeto en otro lugar no protege una conversión de person; la comprobación y la conversión deben referirse a la misma referencia.

Una conversión ascendente no necesita una conversión explícita. Person person = student; ya compila y no puede fallar por incompatibilidad de tipos. Una conversión explícita de null a un tipo de referencia compatible produce null; utilizar ese resultado para llamar a un método de instancia sigue fallando con una NullPointerException.

Decide si se necesita una comprobación de tipo

Las comprobaciones de tipo son adecuadas para código que recibe un Object, como equals(Object other), o necesita una operación exclusiva de un subtipo, como un identificador de estudiante. También pueden ayudar al trabajar con clases que no pueden modificarse.

Una cadena repetida de comprobaciones de tipo para una operación compartida es un problema diferente. Si el código comprueba cada clase de figura para calcular su área, cada figura añadida requiere otra rama. Declarar area() en Shape y dejar que las subclases lo implementen permite llamar a shape.area().

Una conversión explícita no puede hacer que una operación común ausente esté disponible en todos los subtipos. Coloca el comportamiento compartido en el contrato común y reserva la comprobación de subtipo para el trabajo que realmente requiera ese subtipo concreto.

Puntos clave

  • Una conversión descendente comprueba un objeto existente; no lo transforma.
  • Una conversión posible puede compilar y aun así lanzar una ClassCastException en tiempo de ejecución.
  • instanceof es falso para null y verdadero para instancias del tipo o de sus subtipos.
  • Un patrón de instanceof introduce una variable con un tipo seguro donde se sabe que la comprobación es verdadera.
  • Las operaciones compartidas pertenecen al tipo común; las exclusivas de un subtipo pueden necesitar una comprobación de tipo.

4.4 Escribe bucles generales y define un orden natural

Un método sigue siendo útil con clases nuevas cuando su parámetro expone exactamente las operaciones que necesita. Quien llama proporciona los objetos; el enlace dinámico proporciona su comportamiento individual.

Suma áreas sin ramas específicas de clase
static double totalArea(List<Shape> shapes) {
    double total = 0;
    for (Shape shape : shapes) {
        total += shape.area();
    }
    return total;
}

Con un círculo de radio 1,5 m, un rectángulo de dimensiones 3,0 m y 4,0 m, y un triángulo de base 4,0 m y altura 2,5 m, las áreas son aproximadamente 7,06858, 12,0 y 5,0 metros cuadrados. Al sumar el área del círculo sin redondear se obtienen aproximadamente 24,07 metros cuadrados. Cada objeto proporciona su propio area().

Añadir una clase Triangle no requiere modificar el bucle. La nueva clase implementa la operación existente de las figuras. Un resultado negativo incumpliría el contrato del área aunque la sobrescritura compilara; el código general depende tanto del significado de la operación como de su firma.

El mismo enfoque funciona con una interfaz. Un método chargeAll(List<Chargeable> devices, int minutes) recorre los dispositivos y llama a device.charge(minutes). Durante 20 minutos, el teléfono que empieza al 35 % alcanza el 75 % y el patinete que empieza al 50 % alcanza el 70 %. El bucle nunca selecciona por sí mismo una regla de carga.

Deja que quien llama elija una implementación de lista

Un parámetro List<Shape> acepta tanto un ArrayList<Shape> como un LinkedList<Shape>. Un parámetro ArrayList<Shape> rechaza la lista enlazada aunque solo necesite operaciones de lista. Utiliza el tipo más general que proporcione los métodos requeridos; declarar todo como Object sería demasiado general porque carece de area() y de operaciones de lista.

Haz que los objetos puedan ordenarse con Comparable

Comparable<T> define el orden natural de un objeto. Su resultado de compareTo es negativo cuando este objeto va antes que el otro, cero cuando hay un empate en la ordenación y positivo cuando va después. Importa el signo del resultado; no tiene que ser exactamente −1 o 1.

record Runner(String name, int seconds)
        implements Comparable<Runner> {
    @Override
    public int compareTo(Runner other) {
        return Integer.compare(seconds, other.seconds);
    }
}

Una lista modificable con Mia en 3605 s, Ken en 3490 s y Ana en 3720 s pasa a ser Ken, Mia, Ana después de Collections.sort(runners). list.sort(null) también utiliza el orden natural. Construye una lista modificable con new ArrayList<>(List.of(...)); intentar ordenar el resultado no modificable de List.of(...) lanza una UnsupportedOperationException.

Utiliza Integer.compare en lugar de restar los valores. La resta puede desbordarse para valores grandes de signos opuestos y devolver el signo incorrecto. La misma idea se aplica a Double.compare para campos decimales. En este ejemplo de corredores, los tiempos iguales empatan en la ordenación aunque los nombres sean diferentes, por lo que un empate en la comparación y la igualdad de objetos no tienen por qué significar lo mismo. Prefiere un orden natural cuyo resultado cero concuerde con la igualdad; documenta cualquier diferencia intencionada.

Puntos clave

  • El código general llama a una operación compartida y deja que cada objeto proporcione su implementación.
  • Un parámetro de interfaz puede aceptar distintas implementaciones del mismo contrato.
  • compareTo utiliza un resultado negativo, cero o positivo para expresar el orden.
  • Integer.compare evita el riesgo de desbordamiento al restar campos enteros.
  • La ordenación requiere una lista modificable; List.of(...) por sí sola no es modificable.
  • Un empate en la ordenación puede diferir de la igualdad por valor cuando la comparación utiliza menos campos.
5

Modela valores fijos y datos con enumeraciones y registros

Algunos programas necesitan un conjunto fijo de opciones, mientras que otros necesitan un pequeño grupo de valores que pertenecen juntos. Las enumeraciones dan a las opciones un tipo propio; los registros proporcionan gran parte del código rutinario para objetos de datos. Ambos encajan en el sistema de tipos de Java y pueden implementar interfaces. Su comportamiento integrado ahorra trabajo, pero elegir entre ellos y una clase ordinaria sigue dependiendo de lo que puede cambiar y de lo que hace que dos valores sean iguales.

5.1 Representa opciones fijas con una enumeración

Una enumeración da un tipo propio a un conjunto fijo de opciones con nombre. enum Size { SMALL, MEDIUM, LARGE } permite Size size = Size.MEDIUM;. Impide que se pase la cadena mal escrita «MEDUIM» o el entero 7 a un método que requiere Size.

Utiliza nombres, constantes y switch

Cada constante de enumeración es un único objeto, por lo que size == Size.LARGE es una comparación adecuada. Una variable de enumeración también puede ser null. Size.values() devuelve todas las constantes en el orden de declaración; Size.valueOf("LARGE") encuentra el nombre exacto. La búsqueda distingue mayúsculas de minúsculas: «large» provoca una IllegalArgumentException.

name() devuelve el nombre declarado. ordinal() devuelve la posición contando desde cero, por lo que Size.MEDIUM.ordinal() es 1. Insertar una constante cambia las posiciones posteriores, así que una posición ordinal es una mala opción como código almacenado o valor de negocio. Por defecto, toString() también devuelve el nombre.

Una expresión switch, estándar desde Java 14, puede devolver un valor:

String label = switch (size) {
    case SMALL -> "S";
    case MEDIUM -> "M";
    case LARGE -> "L";
};

Nombrar todas las constantes hace que la expresión sea exhaustiva sin una rama predeterminada. Estos casos suponen que size no es null; enumerar las constantes no gestiona null.

Dota a cada constante de datos significativos
enum CupSize {
    SMALL(250), MEDIUM(350), LARGE(500);
    private final int milliliters;
    CupSize(int milliliters) { this.milliliters = milliliters; }
    public int milliliters() { return milliliters; }
}

El punto y coma separa la lista de constantes de los miembros. El constructor se ejecuta una vez por cada constante cuando se inicializa la enumeración. Es implícitamente privado y no se pueden crear constantes nuevas con new CupSize(400).

Dos vasos grandes y uno pequeño contienen 1250 mL, o 1,25 L. Ese cálculo utiliza el campo milliliters, no las posiciones de las constantes. Recorrer CupSize.values() e imprimir cada constante y su capacidad da «SMALL: 250 mL», «MEDIUM: 350 mL» y «LARGE: 500 mL».

Dota a las constantes de su propio comportamiento

Una enumeración puede implementar una interfaz y puede dar a cada constante un cuerpo de método diferente:

enum Operation {
    ADD { int apply(int a, int b) { return a + b; } },
    MULTIPLY { int apply(int a, int b) { return a * b; } };
    abstract int apply(int a, int b);
}

Operation.MULTIPLY.apply(6, 7) devuelve 42. Cada constante completa la operación abstracta. Las enumeraciones extienden implícitamente java.lang.Enum; no pueden extender otra clase y las clases ordinarias no pueden extenderlas.

Utiliza una enumeración para opciones fijas al escribir el programa, como direcciones o estados de un pedido. La lista cambiante de productos de una tienda pertenece a los datos: añadir productos no debería requerir reescribir y recompilar una declaración de enumeración.

Puntos clave

  • Una enumeración da un tipo propio a un conjunto fijo de constantes.
  • values() conserva el orden de declaración y valueOf busca el nombre exacto.
  • Almacena los números significativos en campos, porque las posiciones ordinales pueden cambiar.
  • Los constructores de enumeración inicializan las constantes; no se puede utilizar new para añadir constantes.
  • Las enumeraciones pueden implementar interfaces y dar a las constantes comportamientos diferentes.

5.2 Contén valores en un registro y valídalos

Un registro es una clase de datos concisa, estándar desde Java 16. Su cabecera enumera los valores, llamados componentes, que definen cada instancia: record Point(int x, int y) { }.

Utiliza los miembros generados

El compilador proporciona un campo privado final para cada componente, un constructor canónico con esos componentes en orden y métodos de acceso públicos con sus nombres. También proporciona equals, hashCode y toString basados en los componentes.

Point a = new Point(3, 4);
Point b = new Point(3, 4);
System.out.println(a.x());
System.out.println(a.equals(b));
System.out.println(a == b);

Esto imprime 3, true y false en líneas separadas. Los valores coincidentes de los componentes hacen que los registros sean iguales, pero las dos expresiones new crean objetos independientes. Los registros iguales tienen códigos hash iguales. Su texto generado útil incluye los nombres del registro y de los componentes; los JDK actuales muestran «Point[x=3, y=4]». Ese formato de texto exacto no está garantizado, así que no lo analices como formato de datos.

Los métodos de acceso son x() e y(), no getX() y getY(). Un método modificador que asigna this.x = x no compila porque el campo del componente es final. En su lugar, un método puede devolver un valor nuevo: Point moveRight(int dx) { return new Point(x + dx, y); }.

Valida en un constructor compacto
record Temperature(double celsius) {
    Temperature {
        if (celsius < -273.15) {
            throw new IllegalArgumentException("below absolute zero");
        }
    }
}

Un constructor compacto repite el nombre del registro sin una lista de parámetros. Comprueba o normaliza los parámetros recibidos; Java asigna los campos de los componentes después de su cuerpo. Esta comprobación rechaza −300 °C y acepta 21,5 °C. Un componente de texto podría normalizarse con name = name.trim(); antes de la asignación automática.

Un registro público necesita un constructor compacto público. Los registros también pueden declarar campos estáticos, métodos estáticos y métodos de instancia. Un constructor adicional delega en el constructor canónico con this(...). Se prohíben los campos de instancia adicionales a los componentes.

Protege los objetos mutables de los componentes

Los campos final de los componentes hacen que la inmutabilidad sea superficial. Un registro que contiene una lista mutable sigue haciendo referencia a esa lista:

record Team(String name, List<String> members) {
    Team {
        members = List.copyOf(members);
    }
}

Con java.util.List importada, esto almacena una copia no modificable. Las adiciones posteriores a la lista original no afectan a la pertenencia al equipo, y team.members().add("Lee") lanza una UnsupportedOperationException. La copia protege la estructura de la lista; no hace profundamente inmutables los objetos mutables de sus elementos.

Los registros extienden implícitamente java.lang.Record y son final. No pueden extender otra clase ni ser extendidos, pero pueden implementar interfaces como Comparable. Utilízalos para datos definidos por sus valores; utiliza una clase ordinaria para estado cambiante, campos de instancia ocultos adicionales o una jerarquía de clases extensible.

Puntos clave

  • La cabecera de un registro define los componentes y el constructor, los métodos de acceso y los métodos de valor generados.
  • Los métodos de acceso de un registro utilizan nombres de componentes como x().
  • Un constructor compacto comprueba o normaliza parámetros antes de la asignación automática de campos.
  • Las referencias de los componentes de un registro son final, pero los objetos referenciados pueden seguir siendo mutables.
  • Una copia no modificable de la lista protege la estructura de lista de un registro.
  • Un registro es final, no puede extender explícitamente una clase y puede implementar interfaces.

Ejemplos de Herencia, interfaces y polimorfismo en Java

5 Lecciones Interactivas

1

Construye paso a paso un dispositivo mediante herencia

12 pasos
Crea un dispositivo con etiqueta, añade un subtipo de sensor y sigue la construcción del mismo objeto.
  1. 1
    IntroducciónUn objeto con una función general y otra específica

    Un sensor es un tipo de dispositivo. Todo dispositivo necesita una etiqueta, mientras que un sensor también necesita una unidad de medida. La herencia permite que el dispositivo sea responsable de su etiqueta y que el sensor añada su unidad.

    Construiremos esa relación, utilizaremos un método heredado y seguiremos cómo las llamadas a constructores inicializan cada parte. La pregunta clave es qué clase es responsable de cada campo y, por tanto, qué constructor debe asignarlo.

  2. 2
    EjemploEmpieza con el estado que necesita todo dispositivo
    class Device {
        private final String label;
        Device(String label) { this.label = label; }
        public String label() { return label; }
    }
    

    Device controla su etiqueta privada. Otro código la lee mediante label(). Una subclase heredará ese método público, pero no puede asignar el campo privado directamente. Así se mantiene la inicialización de la etiqueta en un solo lugar.

  3. 3
    EjemploAñade solo el estado específico del sensor
    class Sensor extends Device {
        private final String unit;
        Sensor(String label, String unit) {
            super(label);
            this.unit = unit;
        }
        public String unit() { return unit; }
    }
    

    Sensor extiende una clase y añade un campo. Su invocación super(label) pasa la responsabilidad de la etiqueta a Device; el constructor del sensor almacena él mismo la unidad.

  4. 4
    Idea claveLee ambas partes a través del sensor

    Después de Sensor s = new Sensor("Room", "C");, s.label() devuelve «Room» y s.unit() devuelve «C». El primer método se hereda; el segundo pertenece a Sensor.

    Ambas llamadas se refieren al mismo objeto. Extender Device no creó un segundo objeto dispositivo junto al sensor. La etiqueta privada está presente, aunque el código del sensor acceda a ella mediante un método.

  5. 5
    DesafíoElige la relación para un laboratorio

    Un Lab contiene varios sensores. ¿Debe Lab extender Sensor o mantener referencias a sensores? Explica la relación.

    ---reveal---

    Mantén referencias a sensores en campos o en una colección. Un laboratorio tiene sensores, pero no es un sensor, por lo que la composición es adecuada. Extender Sensor prometería que un laboratorio puede utilizarse donde se requiera un sensor, incluidas las operaciones que describen la unidad de un sensor.

  6. 6
    ExplicaciónEntra en el constructor de la superclase

    Llamar a new Sensor("Room", "C") selecciona el constructor de sensor de dos argumentos. Su super(label) invoca Device(String) para ese mismo objeto.

    Device llega primero a su propia superclase, Object, y luego asigna la etiqueta. Cuando Device retorna, se ejecutan la inicialización de los campos del sensor y el resto del cuerpo de su constructor. Por último, la nueva referencia queda disponible para quien hizo la llamada.

  7. 7
    ConsejoEncuentra el constructor sin argumentos que falta

    Elimina super(label) del constructor del sensor y Java inserta super(). Esa llamada no tiene un constructor correspondiente: Device solo declara Device(String).

    El error del constructor se refiere a los argumentos, no al campo privado. Sustituir la invocación por new Device(label) crea un objeto independiente y deja sin resolver el problema de super() implícito. Pasa la etiqueta al constructor de la superclase.

  8. 8
    EjemploDelega para ofrecer una unidad predeterminada opcional

    Añade este constructor dentro de Sensor:

    Sensor(String label) {
        this(label, "unknown");
    }
    

    new Sensor("Hall") delega en el constructor existente de dos argumentos, que invoca super(label). El objeto resultante tiene la etiqueta «Hall» y la unidad «unknown». No escribas también super(...) en el constructor que delega: un constructor tiene como máximo una invocación explícita de constructor.

  9. 9
    ConexiónValida un parámetro antes de la construcción

    Java 25 estándar permite realizar operaciones limitadas antes de una invocación explícita de constructor. Por ejemplo, un constructor de sensor puede rechazar una etiqueta recibida vacía antes de llamar a super(label).

    Ese código inicial puede utilizar parámetros, pero no puede leer los campos del objeto ni llamar a sus métodos de instancia. También se permiten asignaciones simples limitadas a campos de la clase actual sin inicializadores en su declaración. Para los niveles estándar anteriores del lenguaje, mantén la invocación en primer lugar.

  10. 10
    DesafíoPredice un mensaje de constructor

    En otra versión, Device imprime «device» después de su propia llamada a la superclase. Sensor imprime «sensor» después de super(label). ¿Qué mensaje aparece primero al crear un sensor y por qué?

    ---reveal---

    «device» aparece primero. El constructor del sensor invoca el constructor del dispositivo antes de continuar con su propia sentencia de impresión. El objeto es un sensor durante toda la construcción; estos mensajes muestran el orden del trabajo de los constructores, no cambios en su clase.

  11. 11
    DesafíoCorrige una asignación a un campo heredado

    Una subclase nueva intenta inicializar la etiqueta privada del dispositivo con this.label = label; y omite super(label). ¿Qué dos problemas relacionados crea esto?

    ---reveal---

    La subclase no puede asignar directamente el campo privado. El super() implícito tampoco tiene un constructor de dispositivo correspondiente. Utiliza super(label) para que Device asigne su propio estado privado y después asigna solo los campos declarados en la subclase.

  12. 12
    RepasoLocaliza un fallo de construcción

    Cuando no se puede construir un subtipo, localiza la primera llamada a un constructor que no tenga un destino accesible correspondiente. Después comprueba que cada clase inicialice los campos de los que es responsable.

    Mantén la prueba de la relación junto a ese seguimiento: un subtipo puede utilizarse como su tipo base; un objeto contenido pertenece a un campo. Estas comprobaciones abordan distintos tipos de errores de diseño y de compilación.

2

Imprime y compara correctamente entradas de catálogo

14 pasos
Sobrescribe comportamiento, construye un método de igualdad por valor y relaciónalo con el cálculo del código hash.

Guarda esta unidad para seguir cada Lección Interactiva paso a paso.

  1. 1
    IntroducciónElige qué debe significar una entrada de catálogo
  2. 2
    EjemploAmplía una descripción básica
  3. 3
    ConsejoEvita que la sobrescritura se llame a sí misma
  4. 4
    DesafíoComprueba una sustitución aparente
  5. 5
    EjemploProporciona a la impresión un método exacto al que llamar
  6. 6
    Idea claveLee la clase real sin cambiarla
  7. 7
    DesafíoDiagnostica un texto impreso de aspecto predeterminado
  8. 8
    EjemploDeclara los datos que determinan la igualdad
  9. 9
    EjemploRechaza el objeto incorrecto antes de comparar campos
  10. 10
    EjemploCalcula el hash con los mismos campos de igualdad
  11. 11
    DesafíoDetecta la sobrecarga que una colección no utiliza
  12. 12
    Idea claveProtege las comparaciones en ambas direcciones
  13. 13
    DesafíoDistingue el texto igual de los valores iguales
  14. 14
    RepasoComprueba la interfaz pública de una clase de valor
3

Construye un contrato y comparte su implementación

18 pasos
Utiliza una fuente abstracta, añade una interfaz y resuelve un conflicto entre métodos predeterminados heredados.

Guarda esta unidad para seguir cada Lección Interactiva paso a paso.

  1. 1
    IntroducciónDistingue los datos comunes de la capacidad común
  2. 2
    EjemploEmpieza una fuente abstracta de mensajes
  3. 3
    EjemploCompleta la operación que falta
  4. 4
    DesafíoDecide si una subclase puede ser concreta
  5. 5
    EjemploDefine la lectura como una capacidad
  6. 6
    ConexiónHaz que la base existente cumpla la interfaz
  7. 7
    EjemploEscribe código que llame mediante el contrato
  8. 8
    DesafíoCorrige una implementación con acceso más restringido
  9. 9
    EjemploAñade una operación predeterminada útil
  10. 10
    ExplicaciónMantén las funciones de los métodos auxiliares estáticos y privados
  11. 11
    EjemploCrea un conflicto real entre métodos predeterminados
  12. 12
    EjemploElige explícitamente un método predeterminado
  13. 13
    DesafíoDa prioridad a la implementación de la clase
  14. 14
    Idea claveOfrece una base sin exigir que todas las implementaciones la utilicen
  15. 15
    DesafíoColoca una etiqueta y un constructor en el tipo adecuado
  16. 16
    ConsejoComprueba los nuevos métodos predeterminados frente a los heredados
  17. 17
    DesafíoAñade un lector nuevo sin cambiar su superclase existente
  18. 18
    RepasoEvalúa un tipo común propuesto
4

Sigue una llamada de informe mediante tipos generales

17 pasos
Sigue un objeto receptor a través de la elección de sobrecarga, el despacho en tiempo de ejecución y una comprobación segura de subtipo; después construye un bucle general.

Guarda esta unidad para seguir cada Lección Interactiva paso a paso.

  1. 1
    IntroducciónSigue lo que promete la variable y lo que hace el objeto
  2. 2
    EjemploDa una referencia general a un informe especializado
  3. 3
    ExplicaciónComprueba lo que expone la referencia
  4. 4
    DesafíoElige tipos para una lista mixta de informes
  5. 5
    EjemploResuelve primero la llamada externa
  6. 6
    EjemploResuelve la llamada interna sobre el mismo objeto receptor
  7. 7
    EjemploElige una sobrecarga según el tipo declarado del argumento
  8. 8
    DesafíoDistingue dos decisiones en una llamada
  9. 9
    ConsejoMarca por separado los campos y las llamadas estáticas en un seguimiento
  10. 10
    EjemploSolicita de forma segura la operación exclusiva de ventas
  11. 11
    DesafíoPredice una conversión explícita cuando cambia el objeto
  12. 12
    EjemploObtén encabezados mediante la operación común
  13. 13
    ConexiónUtiliza la comparación como otra operación compartida
  14. 14
    DesafíoOrdena valores eligiendo una implementación
  15. 15
    Idea claveReserva las comprobaciones de subtipo para tareas específicas del subtipo
  16. 16
    DesafíoAdmite un encabezado de auditoría sin una rama nueva
  17. 17
    RepasoSigue en orden una llamada desconocida
5

Construye una opción fija y un valor de datos protegido

15 pasos
Añade datos a una enumeración, crea un registro e impide que sus componentes de lista mutable cambien sin que el registro lo controle.

Guarda esta unidad para seguir cada Lección Interactiva paso a paso.

  1. 1
    IntroducciónElige entre opciones con nombre y datos agrupados
  2. 2
    EjemploDa un tipo propio a las opciones de servicio
  3. 3
    EjemploAsocia números útiles a las opciones
  4. 4
    ConsejoMantén los datos de servicio independientes del orden de declaración
  5. 5
    DesafíoPredice una búsqueda y un campo útil
  6. 6
    ConexiónMantén el comportamiento junto a la opción fija
  7. 7
    EjemploDeclara los componentes de una solicitud de entrega
  8. 8
    EjemploNormaliza los datos recibidos antes de la asignación
  9. 9
    DesafíoCompara las solicitudes normalizadas
  10. 10
    EjemploEncuentra el objeto mutable dentro de un componente final
  11. 11
    EjemploAlmacena una copia no modificable
  12. 12
    ConsejoDevuelve un registro nuevo cuando deban cambiar los valores
  13. 13
    DesafíoElige la corrección adecuada para una ruta cambiante
  14. 14
    DesafíoModela nuevas opciones de servicio y solicitudes de clientes
  15. 15
    RepasoInspecciona el comportamiento generado antes de añadir código

Ejercicios de Herencia, interfaces y polimorfismo en Java

50 Preguntas de Práctica en 5 sets

Set 1: Construye clases y sigue el comportamiento heredado10 Preguntas de Práctica

Pregunta 1 de 10

Una Library almacena varios objetos Shelf y cada estante almacena libros. ¿Cómo debe representar Library su relación con Shelf?
  1. Extender Shelf para heredar métodos de estante.
  2. Almacenar estantes en campos o una colección.
  3. Convertir Shelf en una subclase de Library.
  4. Convertir Library en una subclase final de Shelf.
Mostrar respuestaOcultar respuesta
Respuesta: B
Una biblioteca tiene estantes, por lo que los campos o una colección modelan la relación mediante composición. Extender Shelf también afirmaría que toda biblioteca es un estante y que podría utilizarse donde se espera uno. Reutilizar métodos de estante no hace verdadera esa relación de «es un».

Pregunta 2 de 10

Account declara un campo privado balance y un método público getBalance(). SavingsAccount extends Account. ¿Cómo puede un método de SavingsAccount leer el estado heredado de la cuenta?
  1. Leer el campo como this.balance.
  2. Leer el campo como super.balance.
  3. Crear una cuenta y leer su saldo.
  4. Llamar al método heredado getBalance().
Mostrar respuestaOcultar respuesta
Respuesta: D
El método de acceso público se hereda y puede leer el campo privado dentro de Account. El estado privado sigue perteneciendo al objeto de cuenta de ahorro, pero el código de la subclase no puede acceder directamente a ese campo. Añadir super. no elude la regla de acceso privado.

Pregunta 3 de 10

¿Qué ocurre al compilar este código?

class Machine {
    Machine(int serial) { }
}
class Printer extends Machine {
    Printer() { }
}
  1. La compilación falla porque super() no tiene un constructor correspondiente.
  2. La compilación tiene éxito y utiliza Machine(0) por defecto.
  3. La compilación tiene éxito sin construir la parte de máquina.
  4. La compilación falla porque las subclases no pueden tener constructores sin argumentos.
Mostrar respuestaOcultar respuesta
Respuesta: A
El constructor vacío de la impresora llama implícitamente a super(). Machine solo declara un constructor que recibe un entero, por lo que no existe un constructor sin argumentos. Java no inventa un valor de argumento; Printer() puede seguir sin argumentos si su cuerpo proporciona explícitamente un número de serie a super.

Pregunta 4 de 10

¿Qué imprime new Leaf();?

class Root {
    Root() { System.out.print("R"); }
}
class Branch extends Root {
    Branch() { System.out.print("B"); }
}
class Leaf extends Branch {
    Leaf() { this(7); System.out.print("L"); }
    Leaf(int n) { System.out.print(n); }
}
  1. L7BR
  2. RBL7
  3. RB7L
  4. RRB7L
Mostrar respuestaOcultar respuesta
Respuesta: C
Leaf() delega en Leaf(int), cuya llamada implícita a la superclase entra en Branch y luego en Root. Sus cuerpos imprimen «R» y «B», después Leaf(int) imprime 7 y el constructor que delega imprime «L». La delegación no construye la parte raíz por segunda vez.

Pregunta 5 de 10

Meter declara public int read(). Una subclase escribe @Override public int read(int offset). ¿Por qué falla la compilación?
  1. El tipo de retorno debe cambiar en toda sobrescritura de subclase.
  2. La anotación impide que se hereden los métodos públicos.
  3. La lista de parámetros cambia, así que el método es una sobrecarga.
  4. La subclase debe redeclarar cada campo antes de sobrescribir.
Mostrar respuestaOcultar respuesta
Respuesta: C
El método heredado no recibe argumentos, mientras que la nueva declaración recibe un entero. Es una firma diferente, por lo que sobrecarga read en lugar de sobrescribirlo. @Override detecta la discrepancia; devolver int es compatible con el método original.

Pregunta 6 de 10

¿Qué imprime la última sentencia?

class Parcel {
    public String toString() { return "parcel"; }
}
class ChilledParcel extends Parcel {
    @Override public String toString() {
        return super.toString() + " cold";
    }
}
Parcel item = new ChilledParcel();
System.out.println("Send " + item);
  1. «Send parcel»
  2. «Send parcel cold»
  3. «Send ChilledParcel cold»
  4. «Send null cold»
Mostrar respuestaOcultar respuesta
Respuesta: B
La concatenación obtiene el texto del objeto mediante toString() y el objeto real de paquete refrigerado ejecuta su sobrescritura. Ese método obtiene «parcel» de super.toString() y añade « cold». Declarar la referencia como Parcel no suprime la sobrescritura.

Pregunta 7 de 10

Una clase final Label solo tiene public boolean equals(Label other), que compara su texto, y hereda equals(Object). Dos etiquetas distintas tienen el mismo texto. Ambas referencias se almacenan en variables declaradas como Object. ¿Qué devuelve a.equals(b)?
  1. false, porque el método seleccionado sigue comparando la identidad.
  2. true, porque el texto coincidente selecciona la sobrecarga de Label.
  3. true, porque Object compara cada campo automáticamente.
  4. La compilación falla porque Object no tiene un método de igualdad.
Mostrar respuestaOcultar respuesta
Respuesta: A
Con referencias Object, la llamada selecciona equals(Object), que Label no ha sobrescrito. La implementación heredada compara los dos objetos distintos por identidad y devuelve falso. El método del mismo nombre que recibe Label es una sobrecarga y no se selecciona a partir de la clase del argumento en tiempo de ejecución.

Pregunta 8 de 10

abstract class Sensor declara public abstract double reading();. ¿Qué debe proporcionar o heredar una clase concreta LightSensor extends Sensor?
  1. Un constructor público llamado Sensor.
  2. Una implementación estática de double reading().
  3. Una implementación privada de double reading().
  4. Una implementación pública de double reading().
Mostrar respuestaOcultar respuesta
Respuesta: D
Una subclase concreta debe completar la operación abstracta heredada con un método de instancia compatible. Su implementación debe mantener el acceso público prometido por Sensor. Un método estático no es una sobrescritura de instancia y un método privado reduciría el acceso requerido.

Pregunta 9 de 10

¿Por qué esta clase no compila?

interface Resettable { void reset(); }
class Counter implements Resettable {
    void reset() { }
}
  1. reset() debe ser estático en Counter.
  2. reset() debe ser público en Counter.
  3. Resettable necesita un cuerpo de constructor.
  4. Counter debe extender Resettable.
Mostrar respuestaOcultar respuesta
Respuesta: B
El método abstracto reset() de la interfaz es implícitamente público. La implementación tiene acceso de paquete, por lo que reduce el acceso prometido y hace fallar la compilación. Una clase implementa una interfaz; cambiar la relación a extends no corregiría la declaración del método.

Pregunta 10 de 10

¿Qué devuelve new TimedJob().cost()?

class Job {
    public int rate() { return 3; }
    public int cost() { return 4 * rate(); }
}
class TimedJob extends Job {
    @Override public int rate() { return 5; }
    @Override public int cost() { return super.cost() + 2; }
}
  1. 22
  2. 14
  3. 20
  4. 28
Mostrar respuestaOcultar respuesta
Respuesta: A
La sobrescritura entra primero en Job.cost() mediante super.cost(). Su llamada ordinaria a rate() sigue ejecutándose sobre el objeto de tarea temporizada, por lo que devuelve 5; el cálculo heredado da 20 y la sobrescritura externa añade 2. Utilizar 3 para la llamada interna desactivaría incorrectamente el enlace dinámico en todo el método de la superclase.
Set 2: Utiliza contratos y tipos de referencia generales10 Preguntas de Práctica

Guarda esta unidad para ver todas las respuestas y practicar cada set.

Pregunta 1 de 10

Una clase TextDocument y una clase Image no relacionada deben admitir ambas exportText(). Cada una ya extiende una clase diferente. ¿Qué tipo común expresa mejor esta capacidad?
  1. Una clase Exportable extendida por ambas como segunda superclase.
  2. Una superclase Image que también contiene documentos de texto.
  3. Una interfaz Exportable implementada por ambas.
  4. Una variable Object que expone directamente exportText().
Mostrar respuestaOcultar respuesta

Guarda esta unidad para ver esta respuesta.

Pregunta 2 de 10

¿Compila este código y por qué?

interface Named { String name(); }
class Entity {
    public String name() { return "entity"; }
}
class Asset extends Entity implements Named { }
  1. Sí; el método público heredado cumple el contrato de la interfaz.
  2. No; cada clase que implementa la interfaz debe redeclarar todos sus métodos.
  3. No; la superclase debe implementar explícitamente la misma interfaz.
  4. Sí; Java genera un cuerpo vacío para los métodos de interfaz que faltan.
Mostrar respuestaOcultar respuesta

Guarda esta unidad para ver esta respuesta.

Pregunta 3 de 10

¿Qué devuelve new Screen().label()?

interface Tagged {
    default String label() { return "tagged"; }
}
class Panel {
    public String label() { return "panel"; }
}
class Screen extends Panel implements Tagged { }
  1. «tagged»
  2. «paneltagged»
  3. Las declaraciones producen un error de compilación.
  4. «panel»
Mostrar respuestaOcultar respuesta

Guarda esta unidad para ver esta respuesta.

Pregunta 4 de 10

Fast y Safe son interfaces no relacionadas, cada una con default String mode(). Una clase concreta Service implementa ambas y no hereda ninguna implementación de clase de mode(). ¿Qué debe hacer Service?
  1. Usar la interfaz indicada en primer lugar después de implements.
  2. Sobrescribir mode() para resolver el conflicto heredado.
  3. Cambiar el nombre de ambos métodos de interfaz dentro del cuerpo de la clase.
  4. Llamar a Service.super.mode() desde su constructor.
Mostrar respuestaOcultar respuesta

Guarda esta unidad para ver esta respuesta.

Pregunta 5 de 10

Robot implementa Movable. En Movable unit = new Robot();, ¿cuál es el tipo declarado de unit?
  1. Robot
  2. Movable
  3. Object
  4. Tanto Movable como Robot
Mostrar respuestaOcultar respuesta

Guarda esta unidad para ver esta respuesta.

Pregunta 6 de 10

Vehicle tiene move() y solo Bicycle tiene ringBell(). Bicycle extends Vehicle. ¿Qué ocurre con v.ringBell() después de Vehicle v = new Bicycle();?
  1. No compila porque Vehicle no tiene un método ringBell().
  2. Compila y ejecuta la implementación de ringBell() de bicicleta.
  3. Compila, pero lanza una excepción porque el objeto se convirtió en Vehicle.
  4. Compila y ejecuta un método vacío heredado de Object.
Mostrar respuestaOcultar respuesta

Guarda esta unidad para ver esta respuesta.

Pregunta 7 de 10

interface Percent declara static int cap(int n) y Gauge implements Percent no declara ningún método cap. ¿Cómo debe el código llamar al método auxiliar de la interfaz con 120?
  1. Gauge.cap(120)
  2. new Gauge().cap(120)
  3. Percent.super.cap(120)
  4. Percent.cap(120)
Mostrar respuestaOcultar respuesta

Guarda esta unidad para ver esta respuesta.

Pregunta 8 de 10

Un método solo recorre figuras y llama a area(). Debe aceptar tanto ArrayList<Shape> como LinkedList<Shape>. ¿Qué tipo de parámetro expone las operaciones de lista necesarias?
  1. ArrayList<Shape>
  2. LinkedList<Shape>
  3. List<Shape>
  4. Object
Mostrar respuestaOcultar respuesta

Guarda esta unidad para ver esta respuesta.

Pregunta 9 de 10

¿Qué ocurre al compilar y ejecutar new Row().render()?

interface Renderable { String render(); }
abstract class Base implements Renderable {
    public String render() { return "[" + body() + "]"; }
    protected abstract String body();
}
class Row extends Base {
    @Override protected String body() { return "row"; }
}
  1. Falla porque Row debe redeclarar render().
  2. Compila y devuelve «[row]».
  3. Falla porque body() debe ser público.
  4. Compila y devuelve «[]».
Mostrar respuestaOcultar respuesta

Guarda esta unidad para ver esta respuesta.

Pregunta 10 de 10

Un entorno de informes tiene una clase abstracta Report con estado privado para el título, un constructor que inicializa ese estado y un método abstracto body(). Los informes y los objetos de gráficos no relacionados también deben poder imprimirse. ¿Qué estructura conserva ambos requisitos?
  1. Sustituir Report por una interfaz solo con métodos predeterminados que almacene cada título.
  2. Hacer que los gráficos extiendan Report aunque no sean objetos de informe.
  3. Mantener Report y añadir una interfaz Printable a las clases pertinentes.
  4. Hacer que las clases de informe extiendan Report y una Printable abstracta.
Mostrar respuestaOcultar respuesta

Guarda esta unidad para ver esta respuesta.

Set 3: Sigue llamadas, miembros ocultos y conversiones explícitas comprobadas10 Preguntas de Práctica

Guarda esta unidad para ver todas las respuestas y practicar cada set.

Pregunta 1 de 10

A Shape s = null; le sigue s instanceof Circle. Circle extends Shape. ¿Cuál es el resultado de la comprobación?
  1. false
  2. true
  3. Se lanza una ClassCastException.
  4. Se lanza una NullPointerException.
Mostrar respuestaOcultar respuesta

Guarda esta unidad para ver esta respuesta.

Pregunta 2 de 10

¿Qué ocurre con la última sentencia?

class Animal { }
class Fox extends Animal { }
Animal a = new Animal();
Fox f = (Fox) a;
  1. No compila porque las conversiones descendentes nunca están permitidas.
  2. Crea un Fox que contiene el estado original de Animal.
  3. Compila y después lanza una ClassCastException.
  4. Tiene éxito y da a f el mismo objeto Animal sin especializar.
Mostrar respuestaOcultar respuesta

Guarda esta unidad para ver esta respuesta.

Pregunta 3 de 10

¿Qué devuelve pick(value)?

static String pick(Object x) { return "object"; }
static String pick(CharSequence x) { return "sequence"; }
// Inside a method of the class containing these overloads:
Object value = "notes";
  1. «sequence»
  2. «notes»
  3. La llamada lanza una ClassCastException.
  4. «object»
Mostrar respuestaOcultar respuesta

Guarda esta unidad para ver esta respuesta.

Pregunta 4 de 10

¿Qué texto produce p.label + ":" + p.show()?

class Parent {
    String label = "P";
    String show() { return label; }
}
class Child extends Parent {
    String label = "C";
    @Override String show() { return label; }
}
Parent p = new Child();
  1. «P:P»
  2. «P:C»
  3. «C:C»
  4. «C:P»
Mostrar respuestaOcultar respuesta

Guarda esta unidad para ver esta respuesta.

Pregunta 5 de 10

Un método acepta List<Vehicle> y puede añadir un autobús. Bicycle extends Vehicle y Bus extends Vehicle. ¿Por qué no se le puede pasar una List<Bicycle>?
  1. Un objeto Bicycle no puede asignarse a ninguna referencia Vehicle.
  2. Las listas impiden la herencia cuando sus elementos son objetos mutables.
  3. El método podría insertar un Bus en una lista que debe contener bicicletas.
  4. El método convertiría cada objeto bicicleta en un Vehicle sin especializar.
Mostrar respuestaOcultar respuesta

Guarda esta unidad para ver esta respuesta.

Pregunta 6 de 10

¿Qué devuelve b.test()?

class Base {
    String use(Object x) { return "BO"; }
    String use(String x) { return "BS"; }
}
class Sub extends Base {
    @Override String use(Object x) { return "SO"; }
    @Override String use(String x) { return "SS"; }
    String test() {
        Object text = "x";
        return super.use(text) + ":" + use(text);
    }
}
Sub b = new Sub();
  1. «BO:SO»
  2. «BS:SS»
  3. «BO:SS»
  4. «SO:SO»
Mostrar respuestaOcultar respuesta

Guarda esta unidad para ver esta respuesta.

Pregunta 7 de 10

¿Qué imprime new Sub(); con Java 25 estándar?

class Base {
    Base() { System.out.print(value()); }
    int value() { return -1; }
}
class Sub extends Base {
    int n = 8;
    Sub() { super(); n = 11; }
    @Override int value() { return n; }
}
  1. -1
  2. 8
  3. 11
  4. 0
Mostrar respuestaOcultar respuesta

Guarda esta unidad para ver esta respuesta.

Pregunta 8 de 10

¿Qué imprime este código? Tool no declara ningún método.

class Tool { }
class Drill extends Tool {
    int speed() { return 6; }
}
Tool tool = new Drill();
Object copy = tool;
if (copy instanceof Drill d) {
    System.out.print(d.speed());
}
System.out.print(":" + (copy == tool));
  1. «6:false»
  2. «6:true»
  3. «0:true»
  4. La llamada a speed no compila.
Mostrar respuestaOcultar respuesta

Guarda esta unidad para ver esta respuesta.

Pregunta 9 de 10

Creator tiene public Object create(). TextCreator extends Creator lo sobrescribe. ¿Qué combinación de tipo de retorno y acceso es válida?
  1. protected String create()
  2. public String create()
  3. public int create()
  4. private Object create()
Mostrar respuestaOcultar respuesta

Guarda esta unidad para ver esta respuesta.

Pregunta 10 de 10

Un sistema de complementos tiene muchas subclases de Document. Su método de vista previa utiliza ramas instanceof para calcular el número de palabras de cada documento. Se añaden clases de documento nuevas con frecuencia. ¿Qué cambio permite que las nuevas clases admitan la vista previa sin modificar esa cadena de comprobaciones de tipo?
  1. Convertir cada Document a Object y mantener las mismas ramas de subtipo.
  2. Mover las ramas de subtipo a un método estático de la clase de vista previa.
  3. Dar a cada subtipo un campo del mismo nombre y leerlo mediante Document.
  4. Declarar wordCount() en Document e implementarlo en cada subtipo concreto.
Mostrar respuestaOcultar respuesta

Guarda esta unidad para ver esta respuesta.

Set 4: Modela valores, compáralos y ordénalos10 Preguntas de Práctica

Guarda esta unidad para ver todas las respuestas y practicar cada set.

Pregunta 1 de 10

Una aplicación utiliza enum Status { NEW, SENT, DONE }. ¿Qué hace Status.valueOf("sent")?
  1. Devuelve Status.SENT.
  2. Devuelve la cadena «sent».
  3. Devuelve Status.NEW.
  4. Lanza una IllegalArgumentException.
Mostrar respuestaOcultar respuesta

Guarda esta unidad para ver esta respuesta.

Pregunta 2 de 10

Para record Reading(int count, String unit) { }, ¿qué llamada generada lee el recuento de una variable r?
  1. r.getCount()
  2. r.count()
  3. r.count
  4. r.readCount()
Mostrar respuestaOcultar respuesta

Guarda esta unidad para ver esta respuesta.

Pregunta 3 de 10

enum Band { LOW(2), HIGH(9); ... } almacena el argumento del constructor en un campo inmutable code. Se inserta una constante nueva MID(5) entre LOW y HIGH. ¿Cómo cambian el ordinal() y el código almacenado de HIGH?
  1. Su posición ordinal pasa a 2; su código sigue siendo 9.
  2. Su posición ordinal sigue siendo 1; su código pasa a 2.
  3. Su posición ordinal pasa a 2; su código pasa a 2.
  4. Su posición ordinal sigue siendo 1; su código sigue siendo 9.
Mostrar respuestaOcultar respuesta

Guarda esta unidad para ver esta respuesta.

Pregunta 4 de 10

Dos objetos Token desiguales devuelven el mismo código hash. Sus implementaciones de equals(Object) comparan correctamente los campos que definen cada token. ¿El código hash compartido incumple el contrato de igualdad y de código hash?
  1. Sí; cada objeto debe tener un código hash único.
  2. Sí; compartir un código hash hace que los tokens sean iguales.
  3. No; los objetos desiguales pueden compartir un código hash.
  4. No; las colecciones basadas en hash ignoran la igualdad después de calcular el hash.
Mostrar respuestaOcultar respuesta

Guarda esta unidad para ver esta respuesta.

Pregunta 5 de 10

record Result(int seconds) implements Comparable<Result> devuelve Integer.compare(seconds, other.seconds). ¿Qué signo tiene new Result(418).compareTo(new Result(405))?
  1. Negativo.
  2. Positivo.
  3. Cero.
  4. No tiene resultado porque los registros no pueden implementar interfaces.
Mostrar respuestaOcultar respuesta

Guarda esta unidad para ver esta respuesta.

Pregunta 6 de 10

¿Qué imprime la última sentencia?

record Tag(String text) {
    Tag { text = text.trim(); }
}
Tag a = new Tag(" north ");
Tag b = new Tag("north");
System.out.println(a.equals(b));
  1. true
  2. false
  3. «north»
  4. La compilación falla porque los parámetros no pueden reasignarse.
Mostrar respuestaOcultar respuesta

Guarda esta unidad para ver esta respuesta.

Pregunta 7 de 10

Un Ticket.equals de la clase base acepta cualquier Ticket con el mismo código. PriorityTicket extends Ticket solo acepta objetos PriorityTicket y también compara la prioridad. Un ticket normal t y un ticket prioritario p tienen el mismo código. ¿Qué devuelven t.equals(p) y p.equals(t), y respeta eso la simetría?
  1. Ambos devuelven falso; el par respeta la simetría.
  2. Ambos devuelven verdadero; el par respeta la simetría.
  3. Devuelven verdadero y falso; el par incumple la simetría.
  4. Devuelven falso y verdadero; el par incumple la simetría.
Mostrar respuestaOcultar respuesta

Guarda esta unidad para ver esta respuesta.

Pregunta 8 de 10

¿Qué se imprime? List y ArrayList están importados.

record Batch(List<String> names) { }
var original = new ArrayList<String>(List.of("Rin"));
Batch first = new Batch(original);
Batch second = new Batch(new ArrayList<>(original));
original.add("Sol");
System.out.println(first.equals(second) + ":"
    + first.names().size() + ":" + second.names().size());
  1. «true:2:1»
  2. «false:1:1»
  3. «true:1:1»
  4. «false:2:1»
Mostrar respuestaOcultar respuesta

Guarda esta unidad para ver esta respuesta.

Pregunta 9 de 10

¿Qué ocurre al ejecutar Collections.sort(list)? Los tipos necesarios de java.util están importados.

record Rank(int score) implements Comparable<Rank> {
    public int compareTo(Rank other) {
        return Integer.compare(score, other.score);
    }
}
List<Rank> list = List.of(new Rank(8), new Rank(3));
  1. Ordena la lista con las puntuaciones 3 y después 8.
  2. No compila porque Rank no tiene un método de comparación.
  3. Lanza una UnsupportedOperationException.
  4. Deja las puntuaciones 8 y después 3 porque el orden de los registros es fijo.
Mostrar respuestaOcultar respuesta

Guarda esta unidad para ver esta respuesta.

Pregunta 10 de 10

¿Qué devuelve Rule.DOUBLE.apply(Rule.NEXT.apply(4))?

enum Rule {
    NEXT {
        int apply(int n) {
            return switch (n) { case 4 -> 6; default -> n + 1; };
        }
    },
    DOUBLE { int apply(int n) { return 2 * n; } };
    abstract int apply(int n);
}
  1. 12
  2. 10
  3. 9
  4. 16
Mostrar respuestaOcultar respuesta

Guarda esta unidad para ver esta respuesta.

Set 5: Combina reglas de Java y diagnostica fallos10 Preguntas de Práctica

Guarda esta unidad para ver todas las respuestas y practicar cada set.

Pregunta 1 de 10

¿Qué tipo de Java es adecuado para un conjunto fijo de opciones de exportación llamadas PDF, TEXT y HTML?
  1. Un registro con tres campos mutables para las opciones.
  2. Una enumeración que declare esas tres constantes.
  3. Un entero cuyos valores válidos no estén documentados.
  4. Una superclase con una subclase para cada archivo.
Mostrar respuestaOcultar respuesta

Guarda esta unidad para ver esta respuesta.

Pregunta 2 de 10

¿Qué devuelve new Terminal().name()?

interface Basic {
    default String name() { return "basic"; }
}
interface Detailed extends Basic {
    default String name() { return "detail"; }
}
class Terminal implements Basic, Detailed { }
  1. «basic»
  2. «basicdetail»
  3. «detail»
  4. La clase tiene un conflicto sin resolver entre métodos predeterminados.
Mostrar respuestaOcultar respuesta

Guarda esta unidad para ver esta respuesta.

Pregunta 3 de 10

Una List<Notifier> contiene objetos de varias clases no relacionadas que implementan Notifier.send(). Un bucle llama a n.send() en cada elemento. Una nueva clase PushNotifier implementa correctamente el mismo contrato. ¿Qué cambio necesita el bucle para admitir sus instancias?
  1. Ninguno; la llamada existente a la interfaz se despacha a cada implementación.
  2. Añadir una conversión explícita a PushNotifier antes de cada llamada a send().
  3. Sustituir la variable de interfaz por una variable Object para cada elemento.
  4. Añadir una rama para que todos los objetos anteriores llamen a la implementación nueva.
Mostrar respuestaOcultar respuesta

Guarda esta unidad para ver esta respuesta.

Pregunta 4 de 10

¿Qué se imprime con Java 25 estándar?

class Base {
    Base(int n) { System.out.print("B" + n); }
}
class Sub extends Base {
    Sub(int n) {
        if (n < 0) throw new IllegalArgumentException();
        super(n + 1);
        System.out.print("S" + n);
    }
}
new Sub(2);
  1. «S2B3»
  2. «B2S2»
  3. La compilación falla porque super no está en primer lugar.
  4. «B3S2»
Mostrar respuestaOcultar respuesta

Guarda esta unidad para ver esta respuesta.

Pregunta 5 de 10

Una clase mutable Code define la igualdad y el código hash utilizando su campo de texto. Un HashSet<Code> contiene c con el texto «red». El programa debe cambiar su texto a «blue», conservar el mismo objeto y mantener fiable la búsqueda en el conjunto. ¿En qué orden debe actualizar el objeto y el conjunto?
  1. Cambiar el texto, eliminar el objeto y volver a añadirlo.
  2. Volver a añadir el objeto, cambiar su texto y eliminarlo.
  3. Eliminar el objeto, volver a añadirlo y cambiar su texto.
  4. Eliminar el objeto, cambiar su texto y volver a añadirlo.
Mostrar respuestaOcultar respuesta

Guarda esta unidad para ver esta respuesta.

Pregunta 6 de 10

¿Qué produce x.call(arg) + ":" + x.call("x")?

class Alpha {
    String call(Object x) { return "AO"; }
    String call(String x) { return "AS"; }
}
class Beta extends Alpha {
    @Override String call(Object x) { return "BO"; }
    @Override String call(String x) { return "BS"; }
}
Alpha x = new Beta();
Object arg = "data";
  1. «AO:AS»
  2. «BO:BS»
  3. «BO:AS»
  4. «BS:BS»
Mostrar respuestaOcultar respuesta

Guarda esta unidad para ver esta respuesta.

Pregunta 7 de 10

¿Qué imprime la última sentencia? List y ArrayList están importados.

record Group(List<String> names) {
    Group { names = List.copyOf(names); }
}
var original = new ArrayList<String>();
original.add("Uma");
Group group = new Group(original);
original.add("Zed");
System.out.println(group.names().size() + ":" + original.size());
  1. «1:2»
  2. «2:2»
  3. «1:1»
  4. El constructor del registro no compila.
Mostrar respuestaOcultar respuesta

Guarda esta unidad para ver esta respuesta.

Pregunta 8 de 10

¿Qué ocurre al compilar este código?

interface Ready {
    default String state() { return "ready"; }
}
abstract class Base {
    public abstract String state();
}
class Console extends Base implements Ready { }
  1. La compilación tiene éxito; Console hereda la implementación predeterminada de Ready.
  2. La compilación tiene éxito; el método de Base devuelve null hasta que se sobrescribe.
  3. La compilación falla; Console debe implementar el método abstracto heredado.
  4. La compilación falla; las clases abstractas no pueden combinarse con interfaces.
Mostrar respuestaOcultar respuesta

Guarda esta unidad para ver esta respuesta.

Pregunta 9 de 10

Dos objetos record Player(String name, int points) tienen nombres diferentes, pero los mismos puntos. Su compareTo personalizado compara solo los puntos mediante Integer.compare. ¿Qué indican equals y compareTo para estos dos objetos?
  1. equals es verdadero; compareTo es cero.
  2. equals es falso; compareTo debe ser distinto de cero.
  3. equals es verdadero; compareTo debe ser distinto de cero.
  4. equals es falso; compareTo es cero.
Mostrar respuestaOcultar respuesta

Guarda esta unidad para ver esta respuesta.

Pregunta 10 de 10

Una biblioteca ya expone interface Storable { String saveText(); }. Muchas implementaciones nuevas comparten un campo privado de ruta, validación en el constructor y código auxiliar, mientras que algunas implementaciones existentes extienden otras clases. ¿Cómo puede la biblioteca ofrecer reutilización y conservar la interfaz pública común?
  1. Añadir una implementación abstracta de Storable que las clases puedan extender.
  2. Sustituir Storable por una clase que toda implementación deba extender.
  3. Mover el campo privado de instancia de ruta a la interfaz Storable.
  4. Añadir una segunda superclase a las implementaciones existentes para compartir almacenamiento.
Mostrar respuestaOcultar respuesta

Guarda esta unidad para ver esta respuesta.

¿Te atascas con una pregunta tuya? Ayuda con Tareas

Términos Clave

72 Términos Clave, cada uno con su definición

A

Acceso protegido
El acceso protegido permite el acceso dentro de un paquete y un acceso limitado desde subclases entre paquetes, sin hacer público un miembro.
El acceso protegido permite que una subclase utilice un método auxiliar de la superclase sin incluirlo en la interfaz pública.
Anotación @Override
La anotación @Override, escrita @Override, pide al compilador que verifique que un método sobrescribe o implementa un método heredado.
La anotación @Override detecta tostring() cuando quien programa pretendía sustituir toString().

C

Cadena de constructores
Una cadena de constructores es una secuencia de invocaciones de constructores de la misma clase y de la superclase que inicializa las partes de un objeto nuevo.
La cadena de constructores de una clase al final de la jerarquía llega a sus constructores base antes de que se ejecute el resto de su cuerpo.
Capacidad
Una capacidad es una operación o conjunto de operaciones que un objeto promete admitir, expresada a menudo mediante una interfaz entre clases no relacionadas.
Exportar es una capacidad que tanto una etiqueta de texto como un círculo pueden proporcionar conservando superclases diferentes.
Clase abstracta
Una clase abstracta es una clase que no puede instanciarse directamente y puede combinar campos y código compartidos con operaciones sin terminar.
Una clase abstracta Shape puede almacenar un nombre y exigir que los círculos y rectángulos concretos proporcionen un área.
Clase concreta
Una clase concreta es una clase no abstracta que proporciona o hereda implementaciones de todos los métodos abstractos obligatorios y puede tener instancias.
Rectangle es una clase concreta una vez que completa la operación de área exigida por su clase base abstracta de figura.
Clase en tiempo de ejecución
La clase en tiempo de ejecución es la clase real de un objeto, indicada por getClass() con independencia del tipo declarado de su referencia.
La clase en tiempo de ejecución de un estudiante sigue siendo Student incluso cuando un método lo recibe mediante un parámetro Person.
Código hash
Un código hash es un entero utilizado para localizar candidatos en una colección basada en hash; los objetos que se comparan como iguales deben devolver el mismo código hash.
El código hash de un libro puede construirse a partir de su código de catálogo y su edición cuando esos campos también determinan la igualdad.
Colisión de hash
Una colisión de hash ocurre cuando objetos desiguales devuelven el mismo código hash, algo permitido porque la igualdad puede distinguirlos.
Una colisión de hash entre dos tokens diferentes no exige que su método de igualdad devuelva verdadero.
Componente de registro
Un componente de registro es un valor declarado en la cabecera de un registro que determina un campo private final, un parámetro de constructor y un método de acceso público.
El componente de registro seconds puede leerse de un corredor mediante el método generado runner.seconds().
Composición
La composición modela una relación de «tiene un» almacenando referencias a otros objetos en campos, sin afirmar que el objeto sea uno de ellos.
La composición permite que un Customer contenga un Address sin que cada cliente pueda utilizarse como una dirección.
Conflicto entre métodos predeterminados
Un conflicto entre métodos predeterminados surge cuando interfaces no relacionadas proporcionan métodos predeterminados heredados que compiten y la clase que las implementa debe resolverlo.
Un conflicto entre métodos predeterminados exige que SmartLamp sobrescriba las descripciones coincidentes de Lamp y Speaker.
Constante de enumeración
Una constante de enumeración es un valor con nombre declarado en una enumeración, con una única instancia que puede contener datos y proporcionar comportamiento específico de la constante.
La constante de enumeración CupSize.LARGE puede almacenar una capacidad de 500 mL y compararse por identidad.
Constante de interfaz
Una constante de interfaz es un campo declarado implícitamente public, static y final, que pertenece a la interfaz y no a objetos individuales.
La constante de interfaz Chargeable.FULL puede contener 100 mientras cada dispositivo almacena su propio campo de batería cambiante.
Constructor canónico
Un constructor canónico es un constructor de registro cuyos parámetros coinciden con la lista de componentes e inicializan los campos de los componentes del registro.
El constructor canónico de Point(int x, int y) acepta la coordenada x seguida de la coordenada y.
Constructor compacto
Un constructor compacto es un cuerpo de constructor de registro escrito sin repetir sus parámetros, con asignación automática de componentes después.
Un constructor compacto puede eliminar los espacios de un nombre recibido antes de que ese valor normalizado se asigne al campo final del componente.
Constructor predeterminado
Un constructor predeterminado es un constructor sin argumentos que Java proporciona cuando una clase no declara ninguno, con una llamada a super() en su cuerpo.
Añadir Person(String name) elimina el constructor predeterminado del que podía depender una subclase.
Contrato de igualdad
El contrato de igualdad exige comparaciones reflexivas, simétricas, transitivas y consistentes, y que todo objeto no nulo sea desigual a null.
El contrato de igualdad descarta que un libro acepte un libro electrónico cuando este rechaza a su vez el mismo libro.
Conversión ascendente
La conversión ascendente trata una referencia como un tipo de superclase o de interfaz implementada, ampliando de forma segura su tipo de referencia sin cambiar el propio objeto.
Convertir de forma ascendente un teléfono a Chargeable conserva sus datos de batería, pero solo expone las operaciones disponibles mediante ese tipo.
Conversión descendente
La conversión descendente trata una referencia como un tipo más específico después de una comprobación de compatibilidad en tiempo de ejecución, sin convertir ni sustituir su objeto.
La conversión descendente de una referencia de persona a estudiante solo funciona cuando el objeto referenciado ya tiene el tipo estudiante.
Copia no modificable
Una copia no modificable conserva el contenido de una colección frente a modificaciones estructurales posteriores realizadas mediante la referencia a la colección original o a la devuelta.
Una copia no modificable de List.copyOf protege la lista de integrantes de un equipo frente a adiciones a la lista mutable de quien llama.
Cuerpo de constructor flexible
Un cuerpo de constructor flexible permite determinadas sentencias antes de una invocación explícita de constructor; es una función estándar en Java 25.
Un cuerpo de constructor flexible puede rechazar un parámetro negativo antes de pasarlo al constructor de la superclase.
Cuerpo específico de una constante
Un cuerpo específico de una constante es el cuerpo de clase propio de una constante de enumeración, utilizado para proporcionar comportamiento particular a ese valor con nombre.
Un cuerpo específico de una constante permite que MULTIPLY implemente una operación de forma diferente a la constante ADD de la enumeración.

D

Desbordamiento de enteros
El desbordamiento de enteros ocurre cuando un cálculo entero supera el rango representable y puede hacer que una resta indique un signo de ordenación incorrecto.
El desbordamiento de enteros puede hacer fallar una comparación basada en la resta; Integer.compare evita ese error de comparación.

E

Empate en la ordenación
Un empate en la ordenación ocurre cuando compareTo devuelve cero, lo que significa que dos objetos ocupan la misma posición de comparación según la regla de ordenación elegida.
Puede producirse un empate en la ordenación por tiempo de carrera para corredores con nombres diferentes, incluso cuando sus registros son desiguales.
Enlace dinámico
El enlace dinámico selecciona una implementación sobrescrita de un método de instancia en tiempo de ejecución utilizando la clase real del objeto receptor.
El enlace dinámico permite que un hello() heredado que llama a who() llegue a B.who() cuando el objeto receptor pertenece a la clase B.
Estado común
El estado común en una superclase son los datos comunes por objeto que las subclases relacionadas obtienen como parte de la porción de clase base de cada objeto.
El nombre y la posición de una figura son estado común cuando una superclase abstracta los define para todas las figuras.
Excepción ClassCastException
Una ClassCastException es un fallo en tiempo de ejecución causado por una conversión explícita de referencia cuyo objeto no nulo es incompatible con el tipo de destino solicitado.
Una ClassCastException ocurre cuando un objeto Animal sin especializar se convierte explícitamente a su subclase más específica Fox.
Expresión switch
Una expresión switch elige un resultado entre los casos coincidentes y debe cubrir las entradas permitidas; las constantes de enumeración permiten escribir casos concisos.
Una expresión switch que enumera las tres constantes de una enumeración de tamaños puede producir «S», «M» o «L» sin una rama predeterminada.

F

Firma de método
Una firma de método identifica un método por su nombre y sus tipos de parámetros para seleccionar sobrecargas y sobrescribir; el tipo de retorno por sí solo no basta.
Cambiar un parámetro de Object a String cambia la firma del método y crea una sobrecarga.

H

Herencia
La herencia es una relación en la que una subclase se basa en una superclase y admite las operaciones disponibles mediante esa superclase.
La herencia permite que Student reutilice Person.getName() y añada sus propios créditos y comportamiento de estudio.
Herencia simple
La herencia simple significa que una clase de Java tiene como máximo una superclase directa, aunque pueda pertenecer a una cadena más larga de subclases.
La herencia simple impide que una clase extienda tanto Student como Teacher como superclases directas.

I

Identidad
La identidad significa que las referencias apuntan al mismo objeto, con independencia de que objetos separados contengan valores iguales.
Dos libros construidos por separado pueden tener códigos iguales, mientras que su identidad distinta hace que a == b sea falso.
Igualdad por valor
La igualdad por valor considera iguales los objetos cuando coinciden sus datos seleccionados, incluso si las referencias apuntan a instancias de objeto distintas.
La igualdad por valor puede hacer que dos libros creados por separado sean iguales cuando coinciden su código de catálogo y su edición.
Implementación
Una implementación es código funcional que cumple el contrato de un método, proporcionado por una clase o heredado mediante una declaración de método adecuada.
Una implementación pública heredada de Entity puede cumplir Named.name() para una subclase Asset.
Implementación parcial
Una implementación parcial proporciona parte del comportamiento de una interfaz en una clase abstracta y deja que las subclases concretas completen las operaciones obligatorias.
Una implementación parcial de Storable puede compartir la validación de rutas mientras las clases concretas proporcionan el texto guardado.
Inicializador de campo
Un inicializador de campo es una expresión en la declaración de un campo que asigna su valor inicial cuando se ejecuta la etapa de inicialización de ese campo.
Un inicializador de campo de instancia de una subclase se ejecuta después de que retorne el constructor de la superclase de ese objeto.
Inmutabilidad superficial
La inmutabilidad superficial fija las referencias o valores de los propios campos de un objeto, pero permite que cambien los objetos mutables accesibles mediante esas referencias.
La inmutabilidad superficial permite que un registro conserve la misma referencia a una lista incluso cuando otra referencia añade un nombre a esa lista.
Interfaz
Una interfaz es un contrato de tipo con nombre que permite a las clases proporcionar una capacidad común sin compartir una superclase con estado.
La interfaz Chargeable permite que un teléfono y un patinete admitan las mismas llamadas mientras se cargan a velocidades diferentes.
Interfaz Comparable
Comparable es la interfaz de Java mediante la cual un tipo define el orden natural comparando una instancia con otra mediante compareTo.
Un corredor implementa Comparable<Runner> para que un método de ordenación pueda organizar corredores por tiempo de llegada.
Interfaz más específica
Una interfaz más específica extiende otra interfaz y tiene prioridad cuando ambas proporcionan un método predeterminado coincidente para una clase que las implementa.
Una interfaz más específica Detailed proporciona el name() elegido cuando extiende Basic y sustituye su método predeterminado.
Invocación del constructor de la superclase
Una invocación del constructor de la superclase utiliza super(...) para pasar argumentos a un constructor de la superclase directa para el objeto actual.
Una invocación del constructor de la superclase pasa el nombre de un estudiante a Person, que es responsable del campo privado del nombre.

M

Método abstracto
Un método abstracto declara una operación obligatoria sin cuerpo y deja que las subclases concretas proporcionen o hereden su implementación.
El método abstracto area() impide que una figura concreta incompleta utilice sin avisar un método provisional que devuelve un área cero.
Método concreto
Un método concreto tiene un cuerpo de implementación, por lo que quienes llaman pueden ejecutar su código sin depender de que una subclase complete una declaración.
El método concreto Shape.describe() puede reutilizar el nombre y llamar a la implementación de area() de cada subclase.
Método de acceso
Un método de acceso devuelve un valor almacenado; un método de acceso generado para un registro utiliza el nombre exacto de su componente sin un prefijo de método de acceso.
El método de acceso de un componente de registro count es count(), por lo que getCount() no está disponible salvo que se declare explícitamente.
Método estático de interfaz
Un método estático de interfaz es un método que pertenece a la propia interfaz, se llama mediante su nombre y no es heredado por las clases que la implementan.
El método estático de interfaz Chargeable.clamp(130) puede devolver 100 sin crear un objeto de dispositivo.
Método final
Un método final es un método cuya implementación no puede ser sobrescrita por una subclase, lo que conserva esa implementación en las clases descendientes.
Un método final que calcula un valor comprobado impide que una subclase sustituya ese cálculo.
Método predeterminado
Un método predeterminado es un método público de instancia de una interfaz con un cuerpo que las clases que la implementan heredan y pueden sustituir mediante una sobrescritura.
Un método predeterminado needsCharge() puede utilizar batteryPercent() sin conocer la disposición de los campos privados de ningún dispositivo.
Método privado de interfaz
Un método privado de interfaz es un método auxiliar con cuerpo visible solo para el código dentro de esa interfaz, que permite compartir código entre sus métodos funcionales.
Un método privado de interfaz puede eliminar comprobaciones duplicadas de dos métodos predeterminados sin formar parte del contrato público.

O

Object
Object es la clase raíz de la jerarquía de clases de Java y proporciona métodos comunes que las clases ordinarias heredan directa o indirectamente.
Una variable Object puede contener un estudiante o un arreglo, y ambos admiten los métodos que tiene todo objeto.
Objeto receptor
Un objeto receptor es el objeto sobre el que se invoca un método de instancia, cuya clase real guía el despacho dinámico de la firma de método seleccionada.
En shape.area(), el objeto receptor es el objeto figura, incluso cuando la variable tiene un tipo declarado abstracto.
Ocultación de campos
La ocultación de campos ocurre cuando una subclase declara un campo con el mismo nombre que un campo heredado, dejando campos separados que se seleccionan según el tipo declarado.
La ocultación de campos puede hacer que p.label lea el campo de la clase padre mientras p.show() ejecuta una sobrescritura de la clase hija que utiliza otra etiqueta.
Ocultación de métodos
La ocultación de métodos ocurre cuando una subclase declara un método estático con la misma firma que un método estático disponible de su superclase.
La ocultación de métodos hace que el tipo en tiempo de compilación seleccione un método estático incluso cuando una referencia contiene un objeto de subclase.
Operador instanceof
El operador instanceof comprueba si una referencia designa una instancia de un tipo o subtipo dado y devuelve falso cuando la referencia es null.
Una comprobación instanceof permite verificar si una persona tiene el tipo estudiante antes de utilizar un método exclusivo de estudiante.
Orden natural
Un orden natural es el orden predeterminado definido por el método de comparación de un tipo, utilizado por operaciones de ordenación que no solicitan un orden alternativo.
El orden natural de un corredor puede colocar tiempos de llegada menores antes que los mayores al ordenar una lista.

P

Polimorfismo
El polimorfismo permite que una referencia general trabaje con distintos subtipos mediante operaciones compartidas, con cada objeto proporcionando su propio comportamiento.
El polimorfismo permite que un bucle llame a area() en círculos, rectángulos y triángulos sin ramas de tipo separadas.
Posición ordinal
Una posición ordinal es la posición de declaración de una constante de enumeración contando desde cero, que puede cambiar cuando se insertan constantes antes de ese valor.
En una enumeración declarada como LOW, HIGH, la posición ordinal de HIGH cambia de 1 a 2 cuando se inserta MID antes.

R

Registro
Un registro es una clase concisa de Java para un grupo fijo de valores de componentes, con comportamiento generado de construcción, acceso, igualdad, cálculo de hash y texto.
Un registro Point(int x, int y) agrupa dos coordenadas y proporciona igualdad basada en componentes sin métodos escritos a mano.
Representación textual
Una representación textual es la cadena que toString() devuelve para un objeto y que se utiliza cuando el objeto se imprime o se une a otro texto.
La representación textual de un estudiante puede mostrar un nombre y créditos en lugar del texto predeterminado de clase y hash.

S

Simetría
La simetría en la igualdad exige que invertir dos objetos en una comparación de igualdad dé el mismo resultado en ambas direcciones.
La simetría falla si book.equals(ebook) es verdadero, pero ebook.equals(book) es falso para esos mismos objetos.
Sobrecarga
La sobrecarga declara métodos con el mismo nombre pero distintas listas de parámetros, lo que permite al compilador elegir una firma aplicable.
La sobrecarga puede añadir print(String text) junto a print(int number) sin sustituir ninguno de los métodos.
Sobrescritura
La sobrescritura proporciona una implementación de subclase de un método de instancia heredado con el mismo nombre y tipos de parámetros coincidentes.
Sobrescribir yearlyPay() permite que un gerente añada una bonificación mientras quienes llaman siguen utilizando la operación de empleado.
Subclase
Una subclase es una clase declarada para extender otra clase, que añade estado o comportamiento y sigue siendo utilizable como ese tipo más general.
Una subclase GraduateStudent puede basarse en Student y seguir utilizándose donde se espere un Person.
Superclase
Una superclase es una clase cuyos miembros accesibles hereda una subclase y que constituye el tipo más general en su relación de clases.
Person es la superclase de Student, por lo que un método que acepta una persona también puede recibir un estudiante.
Superinterfaz
Una superinterfaz es una interfaz extendida por otra interfaz, cuyas operaciones obligatorias pasan a formar parte del contrato de la interfaz que la extiende.
Chargeable es una superinterfaz de SmartDevice cuando esta última la extiende junto con Connectable.

T

Tipo de interfaz
Un tipo de interfaz es un tipo utilizado para referencias, parámetros o resultados que expone una capacidad y permite distintas clases que la implementen.
Utilizar List como tipo de interfaz permite que un método acepte tanto listas basadas en arreglos como listas enlazadas.
Tipo de retorno covariante
Un tipo de retorno covariante es un subtipo utilizado como tipo de retorno de referencia de una sobrescritura en lugar del tipo de retorno original más general.
Un tipo de retorno covariante permite que una sobrescritura devuelva String cuando el método de la superclase devuelve Object.
Tipo declarado
El tipo declarado es el tipo estático asignado a una variable en el código fuente, que determina qué operaciones permite el compilador mediante ella.
En Person p = new Student(...), el tipo declarado es Person, por lo que los métodos exclusivos de estudiante no están disponibles directamente.
Tipo enumerado
Un tipo enumerado es un tipo de Java que define un conjunto fijo de constantes con nombre e impide que quienes lo utilizan las sustituyan por cadenas o números arbitrarios.
Una enumeración de estados de entrega puede restringir un pedido a los valores con nombre NEW, SENT y DONE.
Tipo real
El tipo real es la clase del objeto al que apunta una referencia, fijada al crear ese objeto y utilizada para seleccionar sobrescrituras de instancia.
Una referencia Person puede apuntar a un estudiante cuyo tipo real sigue siendo Student después de la conversión ascendente.
Transitividad
La transitividad en la igualdad exige que, si un objeto es igual a un segundo y este es igual a un tercero, el primero también sea igual al tercero.
La transitividad hace que tres libros con el mismo código y edición que determinan la igualdad coincidan en las comparaciones por pares.

V

Variable de patrón
Una variable de patrón es una variable local tipada introducida por un patrón de tipo que ha coincidido, disponible donde se sabe que se cumple ese resultado.
La variable de patrón student en person instanceof Student student expone de forma segura el método de identificación del objeto coincidente.

Sobre esta unidad

Por StudeliInformar de un error

Crea tus propios Materiales de Estudio

Convierte tus notas, diapositivas y documentos en Guías de Estudio, Preguntas de Práctica, Flashcards y 6 herramientas más.

Comenzar