Javaでの継承、パート1:extendsキーワード
Javaは、継承と構成を通じてクラスの再利用をサポートします。この2部構成のチュートリアルでは、Javaプログラムで継承を使用する方法を説明します。パート1では、extends
キーワードを使用して親クラスから子クラスを派生させ、親クラスのコンストラクターとメソッドを呼び出し、メソッドをオーバーライドする方法を学習します。パート2java.lang.Object
では、他のすべてのクラスが継承するJavaのスーパークラスであるツアーを行います。
継承についての学習を完了するには、コンポジションと継承をいつ使用するかを説明しているJavaのヒントを確認してください。コンポジションが継承を補完する重要な理由と、それを使用してJavaプログラムでのカプセル化の問題を防ぐ方法を学習します。
ダウンロードコードを取得するこのチュートリアルのサンプルアプリケーションのソースコードをダウンロードします。JavaWorld用にJeffFriesenによって作成されました。Javaの継承:2つの例
継承は、ソフトウェア開発者がis-aを確立するために使用するプログラミング構造です-カテゴリ間の関係。継承により、より一般的なカテゴリからより具体的なカテゴリを導き出すことができます。より具体的なカテゴリは、より一般的なカテゴリの一種です。たとえば、当座預金口座は、預金と引き出しを行うことができる一種の口座です。同様に、トラックは大きな物を運ぶために使用される一種の車両です。
継承は複数のレベルを経て下降する可能性があり、これまで以上に具体的なカテゴリにつながります。例として、図1は、車両から継承した車とトラックを示しています。車から受け継いだステーションワゴン。とトラックから継承するゴミトラック。矢印は、より具体的な「子」カテゴリ(下が下)からあまり具体的でない「親」カテゴリ(上が上)を指しています。

この例は、子カテゴリが1つの直接の親カテゴリから状態と動作を継承する単一継承を示しています。対照的に、多重継承により、子カテゴリは2つ以上の直接の親カテゴリから状態と動作を継承できます。図2の階層は、多重継承を示しています。

カテゴリはクラスごとに記述されます。Javaは、クラス拡張による単一継承をサポートします。このクラスでは、あるクラスがそのクラスを拡張することにより、別のクラスからアクセス可能なフィールドとメソッドを直接継承します。ただし、Javaはクラス拡張による多重継承をサポートしていません。
継承階層を表示すると、ひし形のパターンの存在によって多重継承を簡単に検出できます。図2は、車両、陸上車両、水上車両、およびホバークラフトのコンテキストでのこのパターンを示しています。
extendsキーワード
Javaは、extends
キーワードを介したクラス拡張をサポートしています。存在する場合、extends
2つのクラス間の親子関係を指定します。以下では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はクラスベースの多重継承をサポートしていないため、後で複数のクラス名を指定することはできません。
これらの例は、次のことを体系化しています-関係: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は、名前と初期金額が両方ともコンストラクターで設定されている一般的な銀行口座クラスを示しています。また、ユーザーは預金をすることができます。(マイナスの金額を入金することで引き出しができますが、この可能性は無視します。)アカウントの作成時にアカウント名を設定する必要があることに注意してください。
通貨値を表す
1セント硬貨の数。金銭的価値を保存するためにdouble
またはを使用することfloat
をお勧めしますが、そうすると不正確になる可能性があります。より良い解決策BigDecimal
として、Javaの標準クラスライブラリの一部であるを検討してください。
リスト3は、親クラスSavingsAccount
を拡張する子クラスを示していAccount
ます。
リスト3.SavingsAccount
子クラスはそのAccount
親クラスを拡張します
class SavingsAccount extends Account { SavingsAccount(long amount) { super("savings", amount); } }
SavingsAccount
追加のフィールドやメソッドを宣言する必要がないため、このクラスは簡単です。ただし、Account
スーパークラスのフィールドを初期化するコンストラクターを宣言します。初期化は、Account
のコンストラクタがJavaのsuper
キーワードを介して呼び出され、その後に括弧で囲まれた引数リストが続く場合に発生します。
super()をいつどこで呼び出すか
同じようにthis()
、同じクラスの別のコンストラクタを呼び出すコンストラクタの最初の要素でなければならない、super()
そのスーパークラスでコンストラクタを呼び出すコンストラクタの最初の要素でなければなりません。このルールに違反すると、コンパイラはエラーを報告します。コンパイラはsuper()
、メソッドの呼び出しを検出した場合にもエラーを報告します。super()
コンストラクターを呼び出すだけです。
リスト4Account
は、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
。を付ける習慣を身に付けます。この習慣は、過負荷の間違いをより早く検出するのに役立ちます。