Javaでの継承、パート2:オブジェクトとそのメソッド
Javaは、数千のクラスとその他の参照型で構成される標準のクラスライブラリを提供します。機能の違いにもかかわらず、これらのタイプは、Object
クラスを直接的または間接的に拡張することにより、1つの大規模な継承階層を形成します。これは、作成するすべてのクラスやその他の参照型にも当てはまります。
Java継承に関するこのチュートリアルの前半では、継承の基本、特にJavaextends
とsuper
キーワードを使用 して親クラスから子クラスを派生させる方法、親クラスのコンストラクターとメソッドを呼び出す方法、メソッドをオーバーライドする方法などを説明しました。ここで、Javaクラスの継承階層の母性に焦点を当てますjava.lang.Object
。
学習Object
とその方法は、継承とそれがJavaプログラムでどのように機能するかをより機能的に理解するのに役立ちます。これらのメソッドに精通していると、一般的にJavaプログラムをよりよく理解するのに役立ちます。
オブジェクト:Javaのスーパークラス
Object
は、他のすべてのJavaクラスのルートクラスまたは究極のスーパークラスです。java.lang
パッケージに格納され、Object
他のすべてのクラスが継承する次のメソッドを宣言します。
protected Object clone()
boolean equals(Object obj)
protected void finalize()
Class getClass()
int hashCode()
void notify()
void notifyAll()
String toString()
void wait()
void wait(long timeout)
void wait(long timeout, int nanos)
Javaクラスはこれらのメソッドを継承し、宣言されていないメソッドをオーバーライドできますfinal
。たとえば、非final
toString()
メソッドはオーバーライドできますが、メソッドはオーバーライドfinal
wait()
できません。
これらの各メソッドと、Javaクラスのコンテキストで特別なタスクを実行できるようにする方法について説明します。まず、Object
継承の基本的なルールとメカニズムについて考えてみましょう。
ジェネリック型
上記のリストでは、あなたは気づいているかもしれませんgetClass()
その、Class
戻り値の型の一例である一般的なタイプ。ジェネリック型については、今後の記事で説明します。
オブジェクトの拡張:例
Object
リスト1に示すように、クラスは明示的に拡張できます。
リスト1.オブジェクトを明示的に拡張する
public class Employee extends Object { private String name; public Employee(String name) { this.name = name; } public String getName() { return name; } public static void main(String[] args) { Employee emp = new Employee("John Doe"); System.out.println(emp.getName()); } }
最大で1つの他のクラスを拡張できるため(Javaはクラスベースの多重継承をサポートしていないことをパート1から思い出してください)、明示的に拡張する必要はありませんObject
。そうしないと、他のクラスを拡張できません。したがって、Object
リスト2に示すように、暗黙的に拡張します。
リスト2.暗黙的に拡張するオブジェクト
public class Employee { private String name; public Employee(String name) { this.name = name; } public String getName() { return name; } public static void main(String[] args) { Employee emp = new Employee("John Doe"); System.out.println(emp.getName()); } }
リスト1またはリスト2を次のようにコンパイルします。
javac Employee.java
結果のアプリケーションを実行します。
java Employee
次の出力を確認する必要があります。
John Doe
クラスについて調べる:getClass()
このgetClass()
メソッドは、呼び出されたオブジェクトのランタイムクラスを返します。ランタイムクラスが表されClass
で発見されたオブジェクト、java.lang
パッケージ。Class
は、Java Reflection APIへのエントリポイントです。これについては、Javaプログラミングのより高度なトピックに入るときに学習します。今のところ、JavaアプリケーションClass
がJava Reflection APIの残りの部分を使用して、独自の構造について学習していることを知っておいてください。
クラスオブジェクトと静的同期メソッド
返されるClass
オブジェクトはstatic synchronized
、表されたクラスのメソッドによってロックされているオブジェクトです。たとえば、static synchronized void foo() {}
。(今後のチュートリアルでJava同期を紹介します。)
オブジェクトの複製:clone()
このclone()
メソッドは、呼び出されたオブジェクトのコピーを作成して返します。clone()
の戻り値の型はObject
であるため、clone()
返すオブジェクト参照は、オブジェクトの型の変数にその参照を割り当てる前に、オブジェクトの実際の型にキャストする必要があります。リスト3は、クローン作成を示すアプリケーションを示しています。
リスト3.オブジェクトのクローン作成
class CloneDemo implements Cloneable { int x; public static void main(String[] args) throws CloneNotSupportedException { CloneDemo cd = new CloneDemo(); cd.x = 5; System.out.println("cd.x = " + cd.x); CloneDemo cd2 = (CloneDemo) cd.clone(); System.out.println("cd2.x = " + cd2.x); } }
リスト3のCloneDemo
クラスCloneable
は、java.lang
パッケージに含まれているインターフェースを実装しています。Cloneable
はクラスによって(implements
キーワードを介して)実装され、Object
のclone()
メソッドがCloneNotSupportedException
クラスのインスタンスをスローしないようにします(にもありますjava.lang
)。
CloneDemo
int
名前付きの単一ベースのインスタンスフィールドx
と、main()
このクラスを実行するメソッドを宣言します。メソッド呼び出しスタックを渡す句でmain()
宣言されます。throws
CloneNotSupportedException
main()
最初のインスタンス化CloneDemo
と初期化の結果として、インスタンスのコピーx
へ5
。次に、インスタンスのx
値を出力し、clone()
このインスタンスを呼び出してCloneDemo
、参照を格納する前に返されたオブジェクトをキャストします。最後に、クローンのx
フィールド値を出力します。
リスト3(javac CloneDemo.java
)をコンパイルし、アプリケーション(java CloneDemo
)を実行します。次の出力を確認する必要があります。
cd.x = 5 cd2.x = 5
clone()のオーバーライド
前の例ではclone()
、呼び出すコードclone()
が複製されるクラスにあるため、オーバーライドする必要はありませんでした(CloneDemo
)。clone()
ただし、への呼び出しが別のクラスにある場合は、をオーバーライドする必要がありますclone()
。clone()
が宣言されているため、クラスをコンパイルする前にオーバーライドしなかった場合protected
、「クローンはオブジェクト内のアクセスが保護されています」というメッセージが表示されます。リスト4は、オーバーライドを示すリファクタリングされたリスト3を示していますclone()
。
リスト4.別のクラスからのオブジェクトのクローン作成
class Data implements Cloneable { int x; @Override public Object clone() throws CloneNotSupportedException { return super.clone(); } } class CloneDemo { public static void main(String[] args) throws CloneNotSupportedException { Data data = new Data(); data.x = 5; System.out.println("data.x = " + data.x); Data data2 = (Data) data.clone(); System.out.println("data2.x = " + data2.x); } }
Listing 4 declares a Data
class whose instances are to be cloned. Data
implements the Cloneable
interface to prevent a CloneNotSupportedException
from being thrown when the clone()
method is called. It then declares int
-based instance field x
, and overrides the clone()
method. The clone()
method executes super.clone()
to call its superclass's (that is, Object
's) clone()
method. The overriding clone()
method identifies CloneNotSupportedException
in its throws
clause.
Listing 4 also declares a CloneDemo
class that: instantiates Data
, initializes its instance field, outputs the value of the instance field, clones the Data
object, and outputs its instance field value.
Compile Listing 4 (javac CloneDemo.java
) and run the application (java CloneDemo
). You should observe the following output:
data.x = 5 data2.x = 5
Shallow cloning
Shallow cloning (also known as shallow copying) refers to duplicating an object's fields without duplicating any objects that are referenced from that object's reference fields (if there are any reference fields). Listings 3 and 4 actually demonstrated shallow cloning. Each of the cd
-, cd2
-, data
-, and data2
-referenced fields identifies an object that has its own copy of the int
-based x
field.
Shallow cloning works well when all fields are of the primitive type and (in many cases) when any reference fields refer to immutable (unchangeable) objects. However, if any referenced objects are mutable, changes made to any one of these objects can be seen by the original object and its clone(s). Listing 5 demonstrates.
Listing 5. The problem with shallow cloning in a reference field context
class Employee implements Cloneable { private String name; private int age; private Address address; Employee(String name, int age, Address address) { this.name = name; this.age = age; this.address = address; } @Override public Object clone() throws CloneNotSupportedException { return super.clone(); } Address getAddress() { return address; } String getName() { return name; } int getAge() { return age; } } class Address { private String city; Address(String city) { this.city = city; } String getCity() { return city; } void setCity(String city) { this.city = city; } } class CloneDemo { public static void main(String[] args) throws CloneNotSupportedException { Employee e = new Employee("John Doe", 49, new Address("Denver")); System.out.println(e.getName() + ": " + e.getAge() + ": " + e.getAddress().getCity()); Employee e2 = (Employee) e.clone(); System.out.println(e2.getName() + ": " + e2.getAge() + ": " + e2.getAddress().getCity()); e.getAddress().setCity("Chicago"); System.out.println(e.getName() + ": " + e.getAge() + ": " + e.getAddress().getCity()); System.out.println(e2.getName() + ": " + e2.getAge() + ": " + e2.getAddress().getCity()); } }
Listing 5 presents Employee
, Address
, and CloneDemo
classes. Employee
declares name
, age
, and address
fields; and is cloneable. Address
declares an address consisting of a city and its instances are mutable. CloneDemo
drives the application.
CloneDemo
's main()
method creates an Employee
object and clones this object. It then changes the city's name in the original Employee
object's address
field. Because both Employee
objects reference the same Address
object, the changed city is seen by both objects.
Compile Listing 5 (javac CloneDemo.java
) and run this application (java CloneDemo
). You should observe the following output:
John Doe: 49: Denver John Doe: 49: Denver John Doe: 49: Chicago John Doe: 49: Chicago
Deep cloning
Deep cloning (also known as deep copying) refers to duplicating an object's fields such that any referenced objects are duplicated. Furthermore, the referenced objects of referenced objects are duplicated, and so forth. Listing 6 refactors Listing 5 to demonstrate deep cloning.
Listing 6. Deep cloning the address field
class Employee implements Cloneable { private String name; private int age; private Address address; Employee(String name, int age, Address address) { this.name = name; this.age = age; this.address = address; } @Override public Object clone() throws CloneNotSupportedException { Employee e = (Employee) super.clone(); e.address = (Address) address.clone(); return e; } Address getAddress() { return address; } String getName() { return name; } int getAge() { return age; } } class Address { private String city; Address(String city) { this.city = city; } @Override public Object clone() { return new Address(new String(city)); } String getCity() { return city; } void setCity(String city) { this.city = city; } } class CloneDemo { public static void main(String[] args) throws CloneNotSupportedException { Employee e = new Employee("John Doe", 49, new Address("Denver")); System.out.println(e.getName() + ": " + e.getAge() + ": " + e.getAddress().getCity()); Employee e2 = (Employee) e.clone(); System.out.println(e2.getName() + ": " + e2.getAge() + ": " + e2.getAddress().getCity()); e.getAddress().setCity("Chicago"); System.out.println(e.getName() + ": " + e.getAge() + ": " + e.getAddress().getCity()); System.out.println(e2.getName() + ": " + e2.getAge() + ": " + e2.getAddress().getCity()); } }
Listing 6 shows that Employee
's clone()
method first calls super.clone()
, which shallowly copies the name
, age
, and address
fields. It then calls clone()
on the address
field to make a duplicate of the referenced Address
object. Address
overrides the clone()
method and reveals a few differences from previous classes that override this method:
Address
doesn't implementCloneable
. It's not necessary because onlyObject
'sclone()
method requires that a class implement this interface, and thisclone()
method isn't being called.- オーバーライドする
clone()
メソッドはスローしませんCloneNotSupportedException
。この例外は、呼び出されないObject
のclone()
メソッドからのみスローされます。したがって、例外を処理したり、throws句を介してメソッド呼び出しスタックに渡したりする必要はありません。 Object
クラスに浅いコピーが必要ないため、のclone()
メソッドは呼び出されません(super.clone()
呼び出しはありません)Address
。コピーするフィールドは1つだけです。