- Aprender
- Informática
- Programación
- Programación orientada a objetos con Java
- Herencia, interfaces y polimorfismo en Java
Herencia, interfaces y polimorfismo en Java
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
- 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 consuper.- Crea una subclase con extendsDeclara 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 superclasePasa 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.
- Crea una subclase con extends
- Cambia el comportamiento heredado mediante la sobrescritura de métodos, amplía la versión de la superclase con
supery dota a tus clases de métodostoString,equalsyhashCodedeObjectcon un significado útil.- Sobrescribe métodos heredadosSustituye o amplía un método heredado mediante una sobrescritura correcta, llama a la versión de la superclase con
supery distingue la sobrescritura de la sobrecarga. - Utiliza y sobrescribe los métodos de ObjectExplica por qué todos los objetos tienen
toString,equals,hashCodeygetClass, interpreta su comportamiento predeterminado y escribe untoStringútil. - Sobrescribe equals y hashCode correctamenteDefine cuándo dos objetos son iguales con un
equals(Object)correcto, implementahashCodede forma coherente y gestiona la igualdad en una jerarquía de clases.
- Sobrescribe métodos heredados
- 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 abstractosDeclara clases y métodos abstractos, complétalos en subclases concretas y utiliza una clase abstracta para compartir campos y código.
- Define e implementa interfacesEscribe 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 interfacesDota 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 interfazCompara las clases abstractas y las interfaces y elige la opción adecuada, o ambas juntas, para un diseño determinado.
- Escribe clases abstractas y métodos abstractos
- 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 interfazDistingue 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ámicoSigue 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 instanceofRealiza conversiones descendentes de forma segura con
instanceofy coincidencia de patrones, reconoceClassCastExceptione identifica cuándo una comprobación de tipo indica que falta un método sobrescrito. - Diseña código flexible con polimorfismoEscribe métodos y colecciones usando tipos generales para que las clases nuevas encajen sin cambios, y haz que las clases puedan ordenarse con
Comparable.
- Haz referencia a objetos mediante tipos de superclase y de interfaz
- 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 enumeracionesDeclara enumeraciones, utiliza sus métodos integrados y
switch, y dota a las constantes de enumeración de campos, constructores y comportamiento. - Modela datos con registrosDeclara registros para datos sencillos, utiliza el constructor generado, los métodos de acceso,
equals,hashCodeytoString, y valida datos en un constructor compacto.
- Representa conjuntos fijos de valores con enumeraciones
Guía de estudio de Herencia, interfaces y polimorfismo en Java
1Extiende 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 acceso | Dónde se puede acceder al miembro |
|---|---|
public | Cualquier clase con acceso al tipo que lo declara |
protected | El mismo paquete, además del acceso de subclases entre paquetes |
| Sin modificador | El mismo paquete |
private | La 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
finalno 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.
2Sobrescribe 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ón | Qué hace |
|---|---|
| Misma firma de método de instancia en una subclase | Sobrescribe el comportamiento heredado |
| Mismo nombre, distinta lista de parámetros | Añade una sobrecarga |
| Misma firma de método estático en una subclase | Oculta 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.
@Overridedetecta 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
Objectproporciona 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.equalscompara 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.
3Define 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 conflicto | Regla |
|---|---|
| Método público concreto heredado de la superclase y método predeterminado de interfaz | Gana el método de la clase |
| Método predeterminado de una interfaz y de la interfaz que la extiende | Gana la interfaz más específica |
| Métodos predeterminados de interfaces no relacionadas | La 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
| Pregunta | Clase abstracta | Interfaz |
|---|---|---|
| ¿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 superclase | Varias interfaces implementadas |
| ¿Qué niveles de acceso están disponibles? | Todos los niveles habituales | Mé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.
4Escribe 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 declarado | Llamadas disponibles mediante la referencia |
|---|---|
Object | Operaciones públicas proporcionadas por Object |
Person | Operaciones accesibles de Person, incluidas las heredadas |
Student | Operaciones 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 comoList<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 llamada | Qué lo selecciona |
|---|---|
| Método de instancia sobrescrito | Clase real del objeto receptor, después de seleccionar la firma |
| Método estático | Tipo utilizado para la llamada en tiempo de compilación |
| Campo oculto | Tipo declarado de la referencia |
| Método privado | Có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
superno 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
ClassCastExceptionen tiempo de ejecución. instanceofes falso para null y verdadero para instancias del tipo o de sus subtipos.- Un patrón de
instanceofintroduce 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.
compareToutiliza un resultado negativo, cero o positivo para expresar el orden.Integer.compareevita 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.
5Modela 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 yvalueOfbusca 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
newpara 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
1Construye paso a paso un dispositivo mediante herencia
12 pasos
- 1Introducció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.
- 2EjemploEmpieza con el estado que necesita todo dispositivo
class Device { private final String label; Device(String label) { this.label = label; } public String label() { return label; } }Devicecontrola su etiqueta privada. Otro código la lee mediantelabel(). 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. - 3EjemploAñ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; } }Sensorextiende una clase y añade un campo. Su invocaciónsuper(label)pasa la responsabilidad de la etiqueta aDevice; el constructor del sensor almacena él mismo la unidad. - 4Idea claveLee ambas partes a través del sensor
Después de
Sensor s = new Sensor("Room", "C");,s.label()devuelve «Room» ys.unit()devuelve «C». El primer método se hereda; el segundo pertenece aSensor.Ambas llamadas se refieren al mismo objeto. Extender
Deviceno 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. - 5DesafíoElige la relación para un laboratorio
Un
Labcontiene varios sensores. ¿DebeLabextenderSensoro 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
Sensorprometería que un laboratorio puede utilizarse donde se requiera un sensor, incluidas las operaciones que describen la unidad de un sensor. - 6ExplicaciónEntra en el constructor de la superclase
Llamar a
new Sensor("Room", "C")selecciona el constructor de sensor de dos argumentos. Susuper(label)invocaDevice(String)para ese mismo objeto.Devicellega primero a su propia superclase,Object, y luego asigna la etiqueta. CuandoDeviceretorna, 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. - 7ConsejoEncuentra el constructor sin argumentos que falta
Elimina
super(label)del constructor del sensor y Java insertasuper(). Esa llamada no tiene un constructor correspondiente:Devicesolo declaraDevice(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 desuper()implícito. Pasa la etiqueta al constructor de la superclase. - 8EjemploDelega 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 invocasuper(label). El objeto resultante tiene la etiqueta «Hall» y la unidad «unknown». No escribas tambiénsuper(...)en el constructor que delega: un constructor tiene como máximo una invocación explícita de constructor. - 9Conexió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.
- 10DesafíoPredice un mensaje de constructor
En otra versión,
Deviceimprime «device» después de su propia llamada a la superclase.Sensorimprime «sensor» después desuper(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.
- 11DesafíoCorrige una asignación a un campo heredado
Una subclase nueva intenta inicializar la etiqueta privada del dispositivo con
this.label = label;y omitesuper(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. Utilizasuper(label)para queDeviceasigne su propio estado privado y después asigna solo los campos declarados en la subclase. - 12RepasoLocaliza 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.
2Imprime y compara correctamente entradas de catálogo
14 pasos
Guarda esta unidad para seguir cada Lección Interactiva paso a paso.
- 1IntroducciónElige qué debe significar una entrada de catálogo
- 2EjemploAmplía una descripción básica
- 3ConsejoEvita que la sobrescritura se llame a sí misma
- 4DesafíoComprueba una sustitución aparente
- 5EjemploProporciona a la impresión un método exacto al que llamar
- 6Idea claveLee la clase real sin cambiarla
- 7DesafíoDiagnostica un texto impreso de aspecto predeterminado
- 8EjemploDeclara los datos que determinan la igualdad
- 9EjemploRechaza el objeto incorrecto antes de comparar campos
- 10EjemploCalcula el hash con los mismos campos de igualdad
- 11DesafíoDetecta la sobrecarga que una colección no utiliza
- 12Idea claveProtege las comparaciones en ambas direcciones
- 13DesafíoDistingue el texto igual de los valores iguales
- 14RepasoComprueba la interfaz pública de una clase de valor
3Construye un contrato y comparte su implementación
18 pasos
Guarda esta unidad para seguir cada Lección Interactiva paso a paso.
- 1IntroducciónDistingue los datos comunes de la capacidad común
- 2EjemploEmpieza una fuente abstracta de mensajes
- 3EjemploCompleta la operación que falta
- 4DesafíoDecide si una subclase puede ser concreta
- 5EjemploDefine la lectura como una capacidad
- 6ConexiónHaz que la base existente cumpla la interfaz
- 7EjemploEscribe código que llame mediante el contrato
- 8DesafíoCorrige una implementación con acceso más restringido
- 9EjemploAñade una operación predeterminada útil
- 10ExplicaciónMantén las funciones de los métodos auxiliares estáticos y privados
- 11EjemploCrea un conflicto real entre métodos predeterminados
- 12EjemploElige explícitamente un método predeterminado
- 13DesafíoDa prioridad a la implementación de la clase
- 14Idea claveOfrece una base sin exigir que todas las implementaciones la utilicen
- 15DesafíoColoca una etiqueta y un constructor en el tipo adecuado
- 16ConsejoComprueba los nuevos métodos predeterminados frente a los heredados
- 17DesafíoAñade un lector nuevo sin cambiar su superclase existente
- 18RepasoEvalúa un tipo común propuesto
4Sigue una llamada de informe mediante tipos generales
17 pasos
Guarda esta unidad para seguir cada Lección Interactiva paso a paso.
- 1IntroducciónSigue lo que promete la variable y lo que hace el objeto
- 2EjemploDa una referencia general a un informe especializado
- 3ExplicaciónComprueba lo que expone la referencia
- 4DesafíoElige tipos para una lista mixta de informes
- 5EjemploResuelve primero la llamada externa
- 6EjemploResuelve la llamada interna sobre el mismo objeto receptor
- 7EjemploElige una sobrecarga según el tipo declarado del argumento
- 8DesafíoDistingue dos decisiones en una llamada
- 9ConsejoMarca por separado los campos y las llamadas estáticas en un seguimiento
- 10EjemploSolicita de forma segura la operación exclusiva de ventas
- 11DesafíoPredice una conversión explícita cuando cambia el objeto
- 12EjemploObtén encabezados mediante la operación común
- 13ConexiónUtiliza la comparación como otra operación compartida
- 14DesafíoOrdena valores eligiendo una implementación
- 15Idea claveReserva las comprobaciones de subtipo para tareas específicas del subtipo
- 16DesafíoAdmite un encabezado de auditoría sin una rama nueva
- 17RepasoSigue en orden una llamada desconocida
5Construye una opción fija y un valor de datos protegido
15 pasos
Guarda esta unidad para seguir cada Lección Interactiva paso a paso.
- 1IntroducciónElige entre opciones con nombre y datos agrupados
- 2EjemploDa un tipo propio a las opciones de servicio
- 3EjemploAsocia números útiles a las opciones
- 4ConsejoMantén los datos de servicio independientes del orden de declaración
- 5DesafíoPredice una búsqueda y un campo útil
- 6ConexiónMantén el comportamiento junto a la opción fija
- 7EjemploDeclara los componentes de una solicitud de entrega
- 8EjemploNormaliza los datos recibidos antes de la asignación
- 9DesafíoCompara las solicitudes normalizadas
- 10EjemploEncuentra el objeto mutable dentro de un componente final
- 11EjemploAlmacena una copia no modificable
- 12ConsejoDevuelve un registro nuevo cuando deban cambiar los valores
- 13DesafíoElige la corrección adecuada para una ruta cambiante
- 14DesafíoModela nuevas opciones de servicio y solicitudes de clientes
- 15RepasoInspecciona el comportamiento generado antes de añadir código
Ejercicios de Herencia, interfaces y polimorfismo en Java
Set 1: Construye clases y sigue el comportamiento heredado10 Preguntas de Práctica
Pregunta 1 de 10
Library almacena varios objetos Shelf y cada estante almacena libros. ¿Cómo debe representar Library su relación con Shelf?Mostrar respuestaOcultar respuesta
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?Mostrar respuestaOcultar respuesta
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() { }
}
Mostrar respuestaOcultar respuesta
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); }
}
Mostrar respuestaOcultar respuesta
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?Mostrar respuestaOcultar respuesta
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);
Mostrar respuestaOcultar respuesta
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
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)?Mostrar respuestaOcultar respuesta
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?Mostrar respuestaOcultar respuesta
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() { }
}
Mostrar respuestaOcultar respuesta
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; }
}
Mostrar respuestaOcultar respuesta
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
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?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 { }
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 { }
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?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?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();?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?Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
Pregunta 8 de 10
area(). Debe aceptar tanto ArrayList<Shape> como LinkedList<Shape>. ¿Qué tipo de parámetro expone las operaciones de lista necesarias?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"; }
}
Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
Pregunta 10 de 10
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?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
Shape s = null; le sigue s instanceof Circle. Circle extends Shape. ¿Cuál es el resultado de la comprobación?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;
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";
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();
Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
Pregunta 5 de 10
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>?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();
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; }
}
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));
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?Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
Pregunta 10 de 10
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?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
enum Status { NEW, SENT, DONE }. ¿Qué hace Status.valueOf("sent")?Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
Pregunta 2 de 10
record Reading(int count, String unit) { }, ¿qué llamada generada lee el recuento de una variable r?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?Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
Pregunta 4 de 10
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?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))?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));
Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
Pregunta 7 de 10
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?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());
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));
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);
}
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
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 { }
Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
Pregunta 3 de 10
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?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);
Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
Pregunta 5 de 10
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?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";
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());
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 { }
Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
Pregunta 9 de 10
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?Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
Pregunta 10 de 10
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?Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
¿Te atascas con una pregunta tuya? Ayuda con Tareas
Términos Clave
A
Acceso protegido
Anotación @Override
@Override, pide al compilador que verifique que un método sobrescribe o implementa un método heredado.tostring() cuando quien programa pretendía sustituir toString().C
Cadena de constructores
Capacidad
Clase abstracta
Shape puede almacenar un nombre y exigir que los círculos y rectángulos concretos proporcionen un área.Clase concreta
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
getClass() con independencia del tipo declarado de su referencia.Student incluso cuando un método lo recibe mediante un parámetro Person.Código hash
Colisión de hash
Componente de registro
seconds puede leerse de un corredor mediante el método generado runner.seconds().Composición
Customer contenga un Address sin que cada cliente pueda utilizarse como una dirección.Conflicto entre métodos predeterminados
SmartLamp sobrescriba las descripciones coincidentes de Lamp y Speaker.Constante de enumeración
CupSize.LARGE puede almacenar una capacidad de 500 mL y compararse por identidad.Constante de interfaz
Chargeable.FULL puede contener 100 mientras cada dispositivo almacena su propio campo de batería cambiante.Constructor canónico
Point(int x, int y) acepta la coordenada x seguida de la coordenada y.Constructor compacto
Constructor predeterminado
super() en su cuerpo.Person(String name) elimina el constructor predeterminado del que podía depender una subclase.Contrato de igualdad
Conversión ascendente
Chargeable conserva sus datos de batería, pero solo expone las operaciones disponibles mediante ese tipo.Conversión descendente
Copia no modificable
List.copyOf protege la lista de integrantes de un equipo frente a adiciones a la lista mutable de quien llama.Cuerpo de constructor flexible
Cuerpo específico de una constante
MULTIPLY implemente una operación de forma diferente a la constante ADD de la enumeración.D
Desbordamiento de enteros
Integer.compare evita ese error de comparación.E
Empate en la ordenación
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.Enlace dinámico
hello() heredado que llama a who() llegue a B.who() cuando el objeto receptor pertenece a la clase B.Estado común
Excepción ClassCastException
Animal sin especializar se convierte explícitamente a su subclase más específica Fox.Expresión switch
F
Firma de método
Object a String cambia la firma del método y crea una sobrecarga.H
Herencia
Student reutilice Person.getName() y añada sus propios créditos y comportamiento de estudio.Herencia simple
Student como Teacher como superclases directas.I
Identidad
a == b sea falso.Igualdad por valor
Implementación
Entity puede cumplir Named.name() para una subclase Asset.Implementación parcial
Storable puede compartir la validación de rutas mientras las clases concretas proporcionan el texto guardado.Inicializador de campo
Inmutabilidad superficial
Interfaz
Chargeable permite que un teléfono y un patinete admitan las mismas llamadas mientras se cargan a velocidades diferentes.Interfaz Comparable
compareTo.Comparable<Runner> para que un método de ordenación pueda organizar corredores por tiempo de llegada.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
super(...) para pasar argumentos a un constructor de la superclase directa para el objeto actual.Person, que es responsable del campo privado del nombre.M
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
Shape.describe() puede reutilizar el nombre y llamar a la implementación de area() de cada subclase.Método de acceso
count es count(), por lo que getCount() no está disponible salvo que se declare explícitamente.Método estático de interfaz
Chargeable.clamp(130) puede devolver 100 sin crear un objeto de dispositivo.Método final
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
O
Object
Object puede contener un estudiante o un arreglo, y ambos admiten los métodos que tiene todo objeto.Objeto receptor
shape.area(), el objeto receptor es el objeto figura, incluso cuando la variable tiene un tipo declarado abstracto.Ocultación de campos
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
Operador instanceof
instanceof permite verificar si una persona tiene el tipo estudiante antes de utilizar un método exclusivo de estudiante.Orden natural
P
Polimorfismo
area() en círculos, rectángulos y triángulos sin ramas de tipo separadas.Posición ordinal
LOW, HIGH, la posición ordinal de HIGH cambia de 1 a 2 cuando se inserta MID antes.R
Registro
Point(int x, int y) agrupa dos coordenadas y proporciona igualdad basada en componentes sin métodos escritos a mano.Representación textual
toString() devuelve para un objeto y que se utiliza cuando el objeto se imprime o se une a otro texto.S
Simetría
book.equals(ebook) es verdadero, pero ebook.equals(book) es falso para esos mismos objetos.Sobrecarga
print(String text) junto a print(int number) sin sustituir ninguno de los métodos.Sobrescritura
yearlyPay() permite que un gerente añada una bonificación mientras quienes llaman siguen utilizando la operación de empleado.Subclase
GraduateStudent puede basarse en Student y seguir utilizándose donde se espere un Person.Superclase
Person es la superclase de Student, por lo que un método que acepta una persona también puede recibir un estudiante.Superinterfaz
Chargeable es una superinterfaz de SmartDevice cuando esta última la extiende junto con Connectable.T
Tipo de interfaz
List como tipo de interfaz permite que un método acepte tanto listas basadas en arreglos como listas enlazadas.Tipo de retorno covariante
String cuando el método de la superclase devuelve Object.Tipo declarado
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
NEW, SENT y DONE.Tipo real
Person puede apuntar a un estudiante cuyo tipo real sigue siendo Student después de la conversión ascendente.Transitividad
V
Variable de patrón
student en person instanceof Student student expone de forma segura el método de identificación del objeto coincidente.- 1Primeros programas en Java
- 2Condiciones y bucles en Java
- 3Métodos en Java
- 4Listas, arreglos y cadenas en Java
- 5Clases y objetos en Java
- 6Objetos y referencias en Java
- 7HashMap y colecciones en Java
- 8Herencia, interfaces y polimorfismo en Java
- 9Excepciones y archivos en Java
- 10Paquetes en Java
- 11Genéricos y expresiones lambda en Java
Sobre esta unidad
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.