Java поддържа повторно използване на класа чрез наследяване и композиране. Този урок от две части ви учи как да използвате наследяването във вашите Java програми. В част 1 ще научите как да използвате extends
ключовата дума за извличане на дъщерен клас от родителски клас, извикване на конструктори и методи на родителски клас и замяна на методи. В част 2 ще обиколите java.lang.Object
, което е суперкласът на Java, от който наследява всеки друг клас.
За да завършите обучението си за наследяване, не забравяйте да разгледате моя съвет за Java, обясняващ кога да използвате композиция срещу наследяване. Ще научите защо композицията е важно допълнение към наследяването и как да я използвате, за да се предпазите от проблеми с капсулирането във вашите Java програми.
изтегляне Вземете кода Изтеглете изходния код, например приложения в този урок. Създадено от Jeff Friesen за JavaWorld.Наследяване на Java: Два примера
Наследяването е програмна конструкция, която разработчиците на софтуер използват, за да установят връзките между категориите. Наследството ни позволява да извличаме по-специфични категории от по-общи. По-специфичната категория е вид по-общата категория. Например текущата сметка е вид сметка, в която можете да правите депозити и тегления. По същия начин камионът е вид превозно средство, използвано за извозване на големи предмети.
Наследството може да се спуска през множество нива, което води до все по-специфични категории. Като пример, Фигура 1 показва автомобил и камион, наследени от превозно средство; комби, наследено от автомобил; и камион за боклук, наследен от камион. Стрелките сочат от по-специфични категории „деца“ (отдолу надолу) към по-малко специфични категории „родители“ (по-горе).

Този пример илюстрира единично наследяване, при което детска категория наследява състояние и поведение от една непосредствена родителска категория. За разлика от тях, множественото наследяване позволява на детска категория да наследява състояние и поведение от две или повече непосредствени родителски категории. Йерархията на фигура 2 илюстрира множествено наследяване.

Категориите са описани по класове. Java поддържа единично наследяване чрез разширение на класа , при което един клас директно наследява достъпни полета и методи от друг клас, като разширява този клас. Java обаче не поддържа множествено наследяване чрез разширение на класа.
Когато разглеждате йерархия на наследяване, можете лесно да откриете множествено наследяване чрез наличието на диамантен шаблон. Фигура 2 показва този модел в контекста на превозно средство, сухопътно превозно средство, водно превозно средство и кораб на въздушна възглавница.
Ключовата дума extends
Java поддържа разширение на клас чрез extends
ключовата дума. Когато присъства, extends
указва връзката родител-дете между два класа. По-долу използвам extends
за установяване на връзка между класове Vehicle
и Car
, а след това между Account
и SavingsAccount
:
Листинг 1. extends
Ключовата дума указва връзката родител-дете
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 }
В extends
ключовата дума е уточнен след името на класа и пред друг името на класа. Името на класа преди extends
идентифицира детето и името на класа след extends
идентифицира родителя. Невъзможно е да се посочат множество имена на класове след това, extends
защото Java не поддържа базирано на класове множествено наследяване.
Тези примери кодифицират отношенията is-a: Car
е специализирано Vehicle
и SavingsAccount
е специализирано Account
. Vehicle
и Account
са известни като базови класове , родителски класове или суперкласове . Car
и SavingsAccount
са известни като производни класове , детски класове или подкласове .
Заключителни класове
Можете да декларирате клас, който не трябва да се разширява; например от съображения за сигурност. В Java използваме final
ключовата дума, за да предотвратим разширяването на някои класове. Просто добавете заглавката на клас final
, като в final class Password
. Като се има предвид тази декларация, компилаторът ще докладва за грешка, ако някой се опита да разшири Password
.
Детските класове наследяват достъпни полета и методи от своите родителски класове и други предци. Те обаче никога не наследяват конструктори. Вместо това детските класове декларират свои собствени конструктори. Освен това те могат да декларират свои собствени полета и методи, за да ги различават от родителите си. Помислете за списък 2.
Листинг 2. Account
Родителски клас
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; } }
Листинг 2 описва общ клас на банкова сметка, който има име и начална сума, които са зададени в конструктора. Освен това позволява на потребителите да правят депозити. (Можете да правите тегления, като депозирате отрицателни суми пари, но ние ще пренебрегнем тази възможност.) Обърнете внимание, че името на акаунта трябва да бъде зададено при създаването на акаунт.
Представяне на валутни стойности
брой стотинки. Може да предпочетете да използвате a double
или a float
за съхраняване на парични стойности, но това може да доведе до неточности. За по-добро решение помислете BigDecimal
, което е част от стандартната библиотека на Java.
Листинг 3 представя SavingsAccount
подчинен клас, който разширява своя Account
родителски клас.
Листинг 3. SavingsAccount
Детски клас разширява своя Account
родителски клас
class SavingsAccount extends Account { SavingsAccount(long amount) { super("savings", amount); } }
Най- SavingsAccount
класа е тривиално, тъй като не е необходимо да декларират допълнителни полета или методи. Той обаче декларира конструктор, който инициализира полетата в своя Account
суперклас. Инициализацията се случва, когато Account
конструкторът на 'е извикан чрез super
ключовата дума на Java , последван от списък с аргументи в скоби.
Кога и къде да се обадя на супер ()
Точно както this()
трябва да е първият елемент в конструктор, който извиква друг конструктор от същия клас, super()
трябва да е и първият елемент в конструктор, който извиква конструктор в неговия суперклас. Ако нарушите това правило, компилаторът ще докладва за грешка. Компилаторът също ще докладва за грешка, ако открие super()
повикване в метод; извиква super()
само конструктор.
Листинг 4 допълнително се разширява Account
с CheckingAccount
клас.
Листинг 4. CheckingAccount
Детски клас разширява своя Account
родителски клас
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.
Можете да откриете опит за претоварване, вместо да замените метод по време на компилация, като добавите заглавката на метода на подклас с @Override
анотацията:
@Override void print(String owner) { System.out.print("Owner: " + owner); super.print(); }
Посочването @Override
казва на компилатора, че даденият метод заменя друг метод. Ако някой се опита да претовари метода вместо това, компилаторът ще докладва за грешка. Без тази анотация компилаторът не би съобщил за грешка, тъй като претоварването на метода е законно.
Кога да използвате @Override
Развийте навика да добавяте префиксиращи методи с @Override
. Този навик ще ви помогне да откриете претоварващи грешки много по-рано.