Java admite la reutilización de clases a través de la herencia y la composición. Este tutorial de dos partes le enseña cómo usar la herencia en sus programas Java. En la Parte 1, aprenderá cómo usar la extends
palabra clave para derivar una clase secundaria de una clase principal, invocar constructores y métodos de clase principal y anular métodos. En la Parte 2, hará un recorrido java.lang.Object
, que es la superclase de Java de la que heredan todas las demás clases.
Para completar su aprendizaje sobre la herencia, asegúrese de consultar mi consejo de Java que explica cuándo usar la composición frente a la herencia. Aprenderá por qué la composición es un complemento importante de la herencia y cómo usarla para protegerse contra problemas de encapsulación en sus programas Java.
descargar Obtener el código Descargar el código fuente, por ejemplo, las aplicaciones de este tutorial. Creado por Jeff Friesen para JavaWorld.Herencia de Java: dos ejemplos
La herencia es una construcción de programación que los desarrolladores de software utilizan para establecer relaciones is-a entre categorías. La herencia nos permite derivar categorías más específicas de otras más genéricas. La categoría más específica es una especie de categoría más genérica. Por ejemplo, una cuenta corriente es un tipo de cuenta en la que puede realizar depósitos y retiros. Del mismo modo, un camión es un tipo de vehículo que se utiliza para transportar artículos grandes.
La herencia puede descender a través de múltiples niveles, dando lugar a categorías cada vez más específicas. Como ejemplo, la Figura 1 muestra un automóvil y un camión heredando del vehículo; camioneta heredada del automóvil; y camión de basura heredado de camión. Las flechas apuntan desde categorías "secundarias" más específicas (más abajo) a categorías "principales" menos específicas (más arriba).

Este ejemplo ilustra la herencia única en la que una categoría secundaria hereda el estado y los comportamientos de una categoría principal inmediata. Por el contrario, la herencia múltiple permite que una categoría secundaria herede el estado y los comportamientos de dos o más categorías principales inmediatas. La jerarquía de la Figura 2 ilustra la herencia múltiple.

Las categorías se describen por clases. Java admite la herencia única a través de la extensión de clase , en la que una clase hereda directamente los campos y métodos accesibles de otra clase al extender esa clase. Sin embargo, Java no admite la herencia múltiple mediante la extensión de clases.
Al ver una jerarquía de herencia, puede detectar fácilmente una herencia múltiple mediante la presencia de un patrón de diamante. La Figura 2 muestra este patrón en el contexto de vehículo, vehículo terrestre, vehículo acuático y aerodeslizador.
La palabra clave extiende
Java admite la extensión de clase a través de la extends
palabra clave. Cuando está presente, extends
especifica una relación padre-hijo entre dos clases. A continuación utilizo extends
para establecer una relación entre clases Vehicle
y Car
, y luego entre Account
y SavingsAccount
:
Listado 1. La extends
palabra clave especifica una relación padre-hijo
class Vehicle { // member declarations } class Car extends Vehicle { // inherit accessible members from Vehicle // provide own member declarations } class Account { // member declarations } class SavingsAccount extends Account { // inherit accessible members from Account // provide own member declarations }
La extends
palabra clave se especifica después del nombre de la clase y antes de otro nombre de clase. El nombre de la clase anterior extends
identifica al niño y el nombre de la clase posterior extends
identifica al padre. Es imposible especificar varios nombres de clases después extends
porque Java no admite la herencia múltiple basada en clases.
Estos ejemplos codifican relaciones is-a: Car
es un especializado Vehicle
y SavingsAccount
es un especializado Account
. Vehicle
y Account
se conocen como clases base , clases padre o superclases . Car
y SavingsAccount
se conocen como clases derivadas , clases secundarias o subclases .
Clases finales
Puede declarar una clase que no debería ampliarse; por ejemplo, por razones de seguridad. En Java, usamos la final
palabra clave para evitar que se extiendan algunas clases. Simplemente prefija un encabezado de clase con final
, como en final class Password
. Dada esta declaración, el compilador informará un error si alguien intenta extender Password
.
Las clases secundarias heredan los campos y métodos accesibles de sus clases principales y de otros ancestros. Sin embargo, nunca heredan constructores. En cambio, las clases secundarias declaran sus propios constructores. Además, pueden declarar sus propios campos y métodos para diferenciarlos de sus padres. Considere el Listado 2.
Listado 2. Una Account
clase principal
class Account { private String name; private long amount; Account(String name, long amount) { this.name = name; setAmount(amount); } void deposit(long amount) { this.amount += amount; } String getName() { return name; } long getAmount() { return amount; } void setAmount(long amount) { this.amount = amount; } }
El Listado 2 describe una clase de cuenta bancaria genérica que tiene un nombre y una cantidad inicial, que se establecen en el constructor. Además, permite a los usuarios realizar depósitos. (Puede hacer retiros depositando cantidades negativas de dinero, pero ignoraremos esta posibilidad). Tenga en cuenta que el nombre de la cuenta debe establecerse cuando se crea una cuenta.
Representando valores monetarios
cuenta de centavos. Es posible que prefiera usar a double
o a float
para almacenar valores monetarios, pero hacerlo puede generar inexactitudes. Para una mejor solución, considere BigDecimal
, que es parte de la biblioteca de clases estándar de Java.
El Listado 3 presenta una SavingsAccount
clase secundaria que amplía su Account
clase principal.
Listado 3. Una SavingsAccount
clase secundaria extiende su Account
clase principal
class SavingsAccount extends Account { SavingsAccount(long amount) { super("savings", amount); } }
La SavingsAccount
clase es trivial porque no necesita declarar campos o métodos adicionales. Sin embargo, declara un constructor que inicializa los campos en su Account
superclase. La inicialización ocurre cuando Account
se llama al constructor de mediante la super
palabra clave de Java , seguido de una lista de argumentos entre paréntesis.
Cuándo y dónde llamar a super ()
Así como this()
debe ser el primer elemento en un constructor que llama a otro constructor en la misma clase, super()
debe ser el primer elemento en un constructor que llama a un constructor en su superclase. Si rompe esta regla, el compilador informará un error. El compilador también informará de un error si detecta una super()
llamada en un método; solo llame super()
a un constructor.
El Listado 4 se extiende aún más Account
con una CheckingAccount
clase.
Listado 4. Una CheckingAccount
clase secundaria extiende su Account
clase principal
class CheckingAccount extends Account { CheckingAccount(long amount) { super("checking", amount); } void withdraw(long amount) { setAmount(getAmount() - amount); } }
CheckingAccount
is a little more substantial than SavingsAccount
because it declares a withdraw()
method. Notice this method's calls to setAmount()
and getAmount()
, which CheckingAccount
inherits from Account
. You cannot directly access the amount
field in Account
because this field is declared private
(see Listing 2).
super() and the no-argument constructor
If super()
is not specified in a subclass constructor, and if the superclass doesn't declare a no-argument
constructor, then the compiler will report an error. This is because the subclass constructor must call a no-argument
superclass constructor when super()
isn't present.
Class hierarchy example
I've created an AccountDemo
application class that lets you try out the Account
class hierarchy. First take a look at AccountDemo
's source code.
Listing 5. AccountDemo
demonstrates the account class hierarchy
class AccountDemo { public static void main(String[] args) { SavingsAccount sa = new SavingsAccount(10000); System.out.println("account name: " + sa.getName()); System.out.println("initial amount: " + sa.getAmount()); sa.deposit(5000); System.out.println("new amount after deposit: " + sa.getAmount()); CheckingAccount ca = new CheckingAccount(20000); System.out.println("account name: " + ca.getName()); System.out.println("initial amount: " + ca.getAmount()); ca.deposit(6000); System.out.println("new amount after deposit: " + ca.getAmount()); ca.withdraw(3000); System.out.println("new amount after withdrawal: " + ca.getAmount()); } }
The main()
method in Listing 5 first demonstrates SavingsAccount
, then CheckingAccount
. Assuming Account.java
, SavingsAccount.java
, CheckingAccount.java
, and AccountDemo.java
source files are in the same directory, execute either of the following commands to compile all of these source files:
javac AccountDemo.java javac *.java
Execute the following command to run the application:
java AccountDemo
You should observe the following output:
account name: savings initial amount: 10000 new amount after deposit: 15000 account name: checking initial amount: 20000 new amount after deposit: 26000 new amount after withdrawal: 23000
Method overriding (and method overloading)
A subclass can override (replace) an inherited method so that the subclass's version of the method is called instead. An overriding method must specify the same name, parameter list, and return type as the method being overridden. To demonstrate, I've declared a print()
method in the Vehicle
class below.
Listing 6. Declaring a print()
method to be overridden
class Vehicle { private String make; private String model; private int year; Vehicle(String make, String model, int year) { this.make = make; this.model = model; this.year = year; } String getMake() { return make; } String getModel() { return model; } int getYear() { return year; } void print() { System.out.println("Make: " + make + ", Model: " + model + ", Year: " + year); } }
Next, I override print()
in the Truck
class.
Listing 7. Overriding print()
in a Truck
subclass
class Truck extends Vehicle { private double tonnage; Truck(String make, String model, int year, double tonnage) { super(make, model, year); this.tonnage = tonnage; } double getTonnage() { return tonnage; } void print() { super.print(); System.out.println("Tonnage: " + tonnage); } }
Truck
's print()
method has the same name, return type, and parameter list as Vehicle
's print()
method. Note, too, that Truck
's print()
method first calls Vehicle
's print()
method by prefixing super.
to the method name. It's often a good idea to execute the superclass logic first and then execute the subclass logic.
Calling superclass methods from subclass methods
In order to call a superclass method from the overriding subclass method, prefix the method's name with the reserved word super
and the member access operator. Otherwise you will end up recursively calling the subclass's overriding method. In some cases a subclass will mask non-private
superclass fields by declaring same-named fields. You can use super
and the member access operator to access the non-private
superclass fields.
To complete this example, I've excerpted a VehicleDemo
class's main()
method:
Truck truck = new Truck("Ford", "F150", 2008, 0.5); System.out.println("Make = " + truck.getMake()); System.out.println("Model = " + truck.getModel()); System.out.println("Year = " + truck.getYear()); System.out.println("Tonnage = " + truck.getTonnage()); truck.print();
The final line, truck.print();
, calls truck
's print()
method. This method first calls Vehicle
's print()
to output the truck's make, model, and year; then it outputs the truck's tonnage. This portion of the output is shown below:
Make: Ford, Model: F150, Year: 2008 Tonnage: 0.5
Use final to block method overriding
Occasionally you might need to declare a method that should not be overridden, for security or another reason. You can use the final
keyword for this purpose. To prevent overriding, simply prefix a method header with final
, as in final String getMake()
. The compiler will then report an error if anyone attempts to override this method in a subclass.
Method overloading vs overriding
Suppose you replaced the print()
method in Listing 7 with the one below:
void print(String owner) { System.out.print("Owner: " + owner); super.print(); }
The modified Truck
class now has two print()
methods: the preceding explicitly-declared method and the method inherited from Vehicle
. The void print(String owner)
method doesn't override Vehicle
's print()
method. Instead, it overloads it.
Puede detectar un intento de sobrecarga en lugar de anular un método en el momento de la compilación al anteponer el encabezado del método de una subclase con la @Override
anotación:
@Override void print(String owner) { System.out.print("Owner: " + owner); super.print(); }
Especificar @Override
le dice al compilador que el método dado anula otro método. Si alguien intentara sobrecargar el método, el compilador reportaría un error. Sin esta anotación, el compilador no informaría de un error porque la sobrecarga de métodos es legal.
Cuándo usar @Override
Desarrolle el hábito de anteponer métodos primarios con @Override
. Este hábito le ayudará a detectar errores de sobrecarga mucho antes.