究極のスーパークラス、パート1
経験豊富なJava開発者は、多くの場合、初心者が混乱していると感じるJava機能を当然のことと思っています。たとえば、初心者はObject
クラスについて混乱するかもしれません。この投稿では、3部構成のシリーズを開始します。このシリーズでは、Object
とその方法に関する質問を提示して回答します。
キングオブジェクト
Q:Object
クラスとは何ですか?
A:Object
に格納されたクラス、java.lang
パッケージは、(を除くすべてのJavaクラスの究極のスーパークラスですObject
)。また、配列はObject
。を拡張します。ただし、インターフェースは拡張Object
されません。これは、Java言語仕様のセクション9.6.3.4で指摘されています。...インターフェースにはObject
スーパータイプがないことを考慮してください。
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()
できません。
Q:Object
クラスを明示的に拡張できますか?
A:はい、明示的に拡張できますObject
。たとえば、リスト1を確認してください。
リスト1.明示的に拡張する Object
import java.lang.Object; 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(javac Employee.java
)をコンパイルして、結果のEmployee.class
ファイル()を実行java Employee
するJohn Doe
と、出力として観察できます。
コンパイラーはjava.lang
パッケージから型を自動的にインポートするため、import java.lang.Object;
ステートメントは不要です。また、Javaは明示的にを拡張することを強制しませんObject
。もしそうならObject
、Javaがクラス拡張を単一のクラスに制限しているという理由以外に、クラスを拡張することはできません。したがって、Object
リスト2に示すように、通常は暗黙的に拡張します。
リスト2.暗黙的に拡張する Object
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のEmployee
クラスはObject
そのメソッドを拡張および継承します。
オブジェクトのクローン作成
Q:このclone()
方法は何を達成しますか?
A:このclone()
メソッドは、このメソッドが呼び出されたオブジェクトのコピーを作成して返します。
Q:この方法はどのように機能しclone()
ますか?
A:ネイティブメソッドとしてObject
実装さclone()
れます。つまり、そのコードはネイティブライブラリに保存されます。このコードが実行されると、呼び出し元オブジェクトのクラス(またはスーパークラス)がチェックされ、java.lang.Cloneable
インターフェイスが実装されているかどうかが確認Object
されCloneable
ます。実装されていません。このインターフェイスが実装されていない場合、clone()
throwsjava.lang.CloneNotSupportedException
はチェックされた例外です(clone()
呼び出されたメソッドのヘッダーにthrows句を追加して、メソッド呼び出しスタックで処理または渡す必要があります)。このインターフェイスが実装されている場合clone()
、新しいオブジェクトを割り当て、呼び出し元オブジェクトのフィールド値を新しいオブジェクトの同等のフィールドにコピーして、新しいオブジェクトへの参照を返します。
Q:clone()
オブジェクトのクローンを作成するメソッドを呼び出すにはどうすればよいですか?
A:オブジェクト参照が与えられた場合、clone()
この参照を呼び出して、返されたオブジェクトをObject
複製されるオブジェクトのタイプにキャストします。リスト3に例を示します。
リスト3.オブジェクトのクローン作成
public class CloneDemo implements Cloneable { int x; public static void main(String[] args) throws CloneNotSupportedException { CloneDemo cd = new CloneDemo(); cd.x = 5; System.out.printf("cd.x = %d%n", cd.x); CloneDemo cd2 = (CloneDemo) cd.clone(); System.out.printf("cd2.x = %d%n", cd2.x); } }
リスト3CloneDemo
は、Cloneable
インターフェースを実装するクラスを宣言しています。このインターフェイスを実装する必要があります。そうしないと、Object
のclone()
メソッドを呼び出すとCloneNotSupportedException
インスタンスがスローされます。
CloneDemo
int
名前付きの単一ベースのインスタンスフィールドx
と、main()
このクラスを実行するメソッドを宣言します。メソッド呼び出しスタックmain()
を渡すthrows句で宣言されCloneNotSupportedException
ます。
main()
first instantiates CloneDemo
and initializes the resulting instance's copy of x
to 5
. It then outputs the instance's x
value and invokes clone()
on this instance, casting the returned object to CloneDemo
before storing its reference. Finally, it outputs the clone's x
field value.
Compile Listing 3 (javac CloneDemo.java
) and run the application (java CloneDemo
). You should observe the following output:
cd.x = 5 cd2.x = 5
Q: Why would I need to override the clone()
method?
A: The previous example didn't need to override the clone()
method because the code that invokes clone()
is located in the class being cloned (i.e., the CloneDemo
class). However, if the clone()
invocation is located in a different class, you will need to override clone()
. Otherwise, you will receive a "clone has protected access in Object
" message because clone()
is declared protected
. Listing 4 presents a refactored Listing 3 to demonstrate overriding clone()
.
Listing 4. Cloning an object from another class
class Data implements Cloneable { int x; @Override public Object clone() throws CloneNotSupportedException { return super.clone(); } } public class CloneDemo { public static void main(String[] args) throws CloneNotSupportedException { Data data = new Data(); data.x = 5; System.out.printf("data.x = %d%n", data.x); Data data2 = (Data) data.clone(); System.out.printf("data2.x = %d%n", data2.x); } }
Listing 4 declares a Data
class whose instances are to be cloned. This class implements the Cloneable
interface to prevent CloneNotSupportedException
from being thrown when the clone()
method is called, declares int
-based instance field x
, and overrides the clone()
method. This method executes super.clone()
to invoke its superclass's (Object
's, in this example) 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 this instance's instance field, clones the Data
instance, and outputs this instance's 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
Q: What is shallow cloning?
A:Shallow cloning (also known as shallow copying) is the duplication of an object's fields without duplicating any objects that are referenced from the object's reference fields (if it has any). Listings 3 and 4 demonstrate 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 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 presents a demonstration.
Listing 5. Demonstrating 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; } } public class CloneDemo { public static void main(String[] args) throws CloneNotSupportedException { Employee e = new Employee("John Doe", 49, new Address("Denver")); System.out.printf("%s: %d: %s%n", e.getName(), e.getAge(), e.getAddress().getCity()); Employee e2 = (Employee) e.clone(); System.out.printf("%s: %d: %s%n", e2.getName(), e2.getAge(), e2.getAddress().getCity()); e.getAddress().setCity("Chicago"); System.out.printf("%s: %d: %s%n", e.getName(), e.getAge(), e.getAddress().getCity()); System.out.printf("%s: %d: %s%n", 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
Q: What is deep cloning?
A:Deep cloning (also known as deep copying) is the duplication of an object's fields such that any referenced objects are duplicated. Furthermore, their referenced objects are duplicated -- and so on. For example, Listing 6 refactors Listing 5 to leverage deep cloning. It also demonstrates covariant return types and a more flexible way of cloning.
Listing 6. Deeply 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 Employee clone() throws CloneNotSupportedException { Employee e = (Employee) super.clone(); e.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 Address clone() { return new Address(new String(city)); } String getCity() { return city; } void setCity(String city) { this.city = city; } } public class CloneDemo { public static void main(String[] args) throws CloneNotSupportedException { Employee e = new Employee("John Doe", 49, new Address("Denver")); System.out.printf("%s: %d: %s%n", e.getName(), e.getAge(), e.getAddress().getCity()); Employee e2 = (Employee) e.clone(); System.out.printf("%s: %d: %s%n", e2.getName(), e2.getAge(), e2.getAddress().getCity()); e.getAddress().setCity("Chicago"); System.out.printf("%s: %d: %s%n", e.getName(), e.getAge(), e.getAddress().getCity()); System.out.printf("%s: %d: %s%n", e2.getName(), e2.getAge(), e2.getAddress().getCity()); } }
Listing 6 leverages Java's support for covariant return types to change the return type of Employee
's overriding clone()
method from Object
to Employee
. The advantage is that code external to Employee
can clone an Employee
object without having to cast this object to the Employee
type.
Employee
's clone()
method first invokes super.clone()
, which shallowly copies the name
, age
, and address
fields. It then invokes clone()
on the address
field to make a duplicate of the referenced Address
object.
The Address
class 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つだけです。
Address
オブジェクトのクローンを作成するには、新しいAddress
オブジェクトを作成し、city
フィールドから参照されているオブジェクトの複製に初期化するだけで十分です。Address
その後、新しいオブジェクトが返されます。