Java Language
オブジェクトクラスのメソッドとコンストラクタ
サーチ…
前書き
このドキュメンテーション・ページは、Javaクラス・コンストラクタに関する例と、新しく作成されたクラスのスーパークラス・Object
から自動的に継承されるオブジェクト・クラス・メソッドに関する詳細を表示するためのページです。
構文
- public final native Class <?> getClass()
- public final native void notify()
- public final native void notifyAll()
- public final native void wait(long timeout)はInterruptedExceptionをスローします。
- public final void wait()throws InterruptedException
- public final void wait(long timeout、int nanos)は、InterruptedExceptionをスローします。
- public native int hashCode()
- public boolean equals(Object obj)
- public String toString()
- protected native Object clone()はCloneNotSupportedExceptionをスローします。
- protected void finalize()Throwableをスローします。
toString()メソッド
toString()
メソッドは、オブジェクトのコンテンツを使用してオブジェクトのString
表現を作成するために使用されます。このメソッドは、クラスを作成するときにオーバーライドする必要があります。 toString()
は、オブジェクトが"hello " + anObject
ように文字列に連結されたときに暗黙的に"hello " + anObject
ます。
次の点を考慮してください。
public class User {
private String firstName;
private String lastName;
public User(String firstName, String lastName) {
this.firstName = firstName;
this.lastName = lastName;
}
@Override
public String toString() {
return firstName + " " + lastName;
}
public static void main(String[] args) {
User user = new User("John", "Doe");
System.out.println(user.toString()); // Prints "John Doe"
}
}
ここで、 Object
クラスのtoString()
は、オブジェクトを印刷する際にオブジェクトに関する意味のあるデータを提供するために、 User
クラスでオーバーライドされていUser
。
println()
を使用すると、オブジェクトのtoString()
メソッドが暗黙的に呼び出されます。したがって、これらのステートメントは同じことを行います。
System.out.println(user); // toString() is implicitly called on `user`
System.out.println(user.toString());
上記のUser
クラスでtoString()
がオーバーライドされていない場合、 System.out.println(user)
はUser@659e0bfd
またはクラス名以外の有用な情報がほとんどない同様のStringを返すことがありUser@659e0bfd
。これは、呼び出しがUser
クラスの構造やビジネスルールについて何も知らない基本Java Object
クラスのtoString()
実装を使用するためです。クラスでこの機能を変更したい場合は、単にメソッドをオーバーライドしてください。
equals()メソッド
TL; DR
==
参照の等価性のテスト(それらが同じオブジェクトであるかどうか)
.equals()
は値の等しいかどうかをテストします( 論理的に "等しい"かどうか)。
equals()
は、2つのオブジェクトが等しいかどうかを比較するために使用されるメソッドです。 Object
クラスのequals()
メソッドのデフォルトの実装は、両方の参照が同じインスタンスを指している場合にのみtrue
返します。したがって、それは==
による比較と同じように動作します。
public class Foo {
int field1, field2;
String field3;
public Foo(int i, int j, String k) {
field1 = i;
field2 = j;
field3 = k;
}
public static void main(String[] args) {
Foo foo1 = new Foo(0, 0, "bar");
Foo foo2 = new Foo(0, 0, "bar");
System.out.println(foo1.equals(foo2)); // prints false
}
}
foo1
とfoo2
は同じフィールドで作成されていても、メモリ内の2つの異なるオブジェクトを指しています。したがって、デフォルトのequals()
実装はfalse
評価されfalse
。
オブジェクトの内容をequals()
か比較するには、 equals()
をオーバーライドする必要があります。
public class Foo {
int field1, field2;
String field3;
public Foo(int i, int j, String k) {
field1 = i;
field2 = j;
field3 = k;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null || getClass() != obj.getClass()) {
return false;
}
Foo f = (Foo) obj;
return field1 == f.field1 &&
field2 == f.field2 &&
(field3 == null ? f.field3 == null : field3.equals(f.field3));
}
@Override
public int hashCode() {
int hash = 1;
hash = 31 * hash + this.field1;
hash = 31 * hash + this.field2;
hash = 31 * hash + (field3 == null ? 0 : field3.hashCode());
return hash;
}
public static void main(String[] args) {
Foo foo1 = new Foo(0, 0, "bar");
Foo foo2 = new Foo(0, 0, "bar");
System.out.println(foo1.equals(foo2)); // prints true
}
}
ここでオーバーライドされたequals()
メソッドは、フィールドが同じ場合にオブジェクトが等しいと判断します。
hashCode()
メソッドも上書きされています。このメソッドの契約では、2つのオブジェクトが等しい場合、そのハッシュ値も同じでなければならないことが示されています。そのため、 hashCode()
とequals()
一緒にオーバーライドする必要があります。
equals
メソッドの引数の型に特に注意してください。 Foo obj
ではなくObject obj
Foo obj
です。後者をメソッドに入れた場合、それはequals
メソッドのオーバーライドではありません。
独自のクラスを書くときequals()
とhashCode()
オーバーライドするときに同様のロジックを記述する必要があります。ほとんどのIDEは自動的にこれを生成します。
equals()
実装の例は、コアJava APIの一部であるString
クラスにあります。むしろポインタを比較するよりも、 String
クラスは、コンテンツの比較String
。
Java 1.7ではjava.util.Objects
クラスが導入されました。このクラスは、2つの潜在的なnull
参照を比較する便利なメソッドequals
を提供するため、 equals
メソッドの実装を簡略化するために使用できます。
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null || getClass() != obj.getClass()) {
return false;
}
Foo f = (Foo) obj;
return field1 == f.field1 && field2 == f.field2 && Objects.equals(field3, f.field3);
}
クラスの比較
equalsメソッドはどのオブジェクトに対しても実行できるので、メソッドがしばしば( null
チェックした後に)最初に行うことの1つは、比較されるオブジェクトのクラスが現在のクラスと一致するかどうかをチェックすることです。
@Override
public boolean equals(Object obj) {
//...check for null
if (getClass() != obj.getClass()) {
return false;
}
//...compare fields
}
これは、通常、クラスオブジェクトを比較することによって上記のように行われます。しかし、それは明らかでないかもしれないいくつかの特殊なケースでは失敗する可能性があります。たとえば、いくつかのフレームワークはクラスの動的プロキシを生成し、これらの動的プロキシは実際には異なるクラスです。 JPAを使用した例を次に示します。
Foo detachedInstance = ...
Foo mergedInstance = entityManager.merge(detachedInstance);
if (mergedInstance.equals(detachedInstance)) {
//Can never get here if equality is tested with getClass()
//as mergedInstance is a proxy (subclass) of Foo
}
その制限を回避するための1つのメカニズムは、 instanceof
を使用してクラスを比較することです
@Override
public final boolean equals(Object obj) {
if (!(obj instanceof Foo)) {
return false;
}
//...compare fields
}
しかし、 instanceof
を使用するinstanceof
は避けなければならない落とし穴がいくつかありinstanceof
。 Fooのは、潜在的に他のサブクラスを持つことができ、それらのサブクラスは、変更される可能性がありますのでequals()
使用すると、ケースに入る可能性Foo
に等しいFooSubclass
が、 FooSubclass
等しくないFoo
。
Foo foo = new Foo(7);
FooSubclass fooSubclass = new FooSubclass(7, false);
foo.equals(fooSubclass) //true
fooSubclass.equals(foo) //false
これは、対称性と推移性の特性に違反するため、 equals()
メソッドの無効な実装です。結果として、 instanceof
を使用するときequals()
上記の例のようにequals()
メソッドをfinal
にすることをおequals()
ます。これにより、サブクラスがequals()
をオーバーライドしたり、主要な前提条件に違反しないようにequals()
ます。
hashCode()メソッド
Javaクラスがequals
メソッドをオーバーライドするequals
、 hashCode
メソッドもオーバーライドする必要があります。 メソッドの契約で定義されているとおり:
- Javaアプリケーションの実行中に同じオブジェクトに対して複数回呼び出されると、オブジェクトのequals比較で使用される情報が変更されない限り、
hashCode
メソッドは常に同じ整数を返す必要があります。この整数は、アプリケーションの1回の実行から同じアプリケーションの別の実行まで一貫している必要はありません。equals(Object)
メソッドに従って2つのオブジェクトが等しい場合、2つのオブジェクトのそれぞれでhashCode
メソッドを呼び出すと、同じ整数の結果が生成されなければなりません。equals(Object)
メソッドによって2つのオブジェクトが等しくない場合、2つのオブジェクトのそれぞれでhashCode
メソッドを呼び出すと、別々の整数結果が生成される必要はありません。しかし、プログラマは、不等なオブジェクトに対して別個の整数結果を生成すると、ハッシュテーブルのパフォーマンスが向上する可能性があることに注意する必要があります。
ハッシュコードは、 HashMap
、 HashTable
、 HashSet
などのハッシュ実装で使用されます。 hashCode
関数の結果は、オブジェクトが配置されるバケットを決定します。これらのハッシュ実装は、提供されたhashCode
実装が良好であれば、より効率的です。良いhashCode
実装の重要な特性は、 hashCode
値の分布が一様であることです。言い換えれば、多数のインスタンスが同じバケットに格納される可能性は低いです。
ハッシュコード値を計算するアルゴリズムは、次のようになります。
public class Foo {
private int field1, field2;
private String field3;
public Foo(int field1, int field2, String field3) {
this.field1 = field1;
this.field2 = field2;
this.field3 = field3;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null || getClass() != obj.getClass()) {
return false;
}
Foo f = (Foo) obj;
return field1 == f.field1 &&
field2 == f.field2 &&
(field3 == null ? f.field3 == null : field3.equals(f.field3);
}
@Override
public int hashCode() {
int hash = 1;
hash = 31 * hash + field1;
hash = 31 * hash + field2;
hash = 31 * hash + (field3 == null ? 0 : field3.hashCode());
return hash;
}
}
Arrays.hashCode()をショートカットとして使用する
Java 1.2以降では、ハッシュコードを計算するアルゴリズムを開発する代わりに、フィールド値を含むObjectまたはプリミティブ配列を提供することで、 java.util.Arrays#hashCode
を使用して生成することができます。
@Override
public int hashCode() {
return Arrays.hashCode(new Object[] {field1, field2, field3});
}
Java 1.7ではjava.util.Objects
クラスが導入されましたjava.util.Objects
クラスは、提供されるオブジェクトの値に基づいてハッシュコードを計算する便利なメソッドhash(Object... objects)
提供します。このメソッドは、 java.util.Arrays#hashCode
同じように機能します。
@Override
public int hashCode() {
return Objects.hash(field1, field2, field3);
}
注意:この方法は非効率的で、カスタムhashCode()
メソッドが呼び出されるたびにガベージオブジェクトが生成されます。
- 一時
Object[]
が作成されます。 (Objects.hash()
バージョンでは、配列は "varargs"メカニズムによって作成されます)。 - いずれかのフィールドがプリミティブ型である場合、それらはボックス化されなければならず、より一時的なオブジェクトが作成される可能性があります。
- 配列に値を設定する必要があります。
- 配列によって繰り返さなければなら
Arrays.hashCode
又はObjects.hash
方法。 -
Arrays.hashCode
またはObjects.hash
が(おそらく)作成しなければならないObject.hashCode()
の呼び出しはインライン化できません。
ハッシュコードの内部キャッシュ
オブジェクトのハッシュコードの計算は高価になる可能性があるため、最初に計算されたときにオブジェクト内にハッシュコード値をキャッシュすることは魅力的です。例えば
public final class ImmutableArray {
private int[] array;
private volatile int hash = 0;
public ImmutableArray(int[] initial) {
array = initial.clone();
}
// Other methods
@Override
public boolean equals(Object obj) {
// ...
}
@Override
public int hashCode() {
int h = hash;
if (h == 0) {
h = Arrays.hashCode(array);
hash = h;
}
return h;
}
}
このアプローチは、ハッシュコードをキャッシュする余分なフィールドのオーバーヘッドに対してハッシュコードを(繰り返し)計算するコストを排除します。これがパフォーマンスの最適化として得られるかどうかは、特定のオブジェクトがハッシュされる頻度(ルックアップ)やその他の要因によって決まります。
また、 ImmutableArray
真のハッシュコードがゼロ(2 32で 1度)であれば、キャッシュは無効であることに気づくでしょう。
最後に、このアプローチは、ハッシュしているオブジェクトが変更可能な場合、正しく実装するのがはるかに難しくなります。しかし、ハッシュコードが変更されると大きな懸念があります。上記の契約を参照してください。
wait()メソッドとnotify()メソッド
wait()
とnotify()
のスレッドの呼び出しが場合-タンデムで働くwait()
別のスレッドの呼び出しがオブジェクトまでに、そのスレッドがブロックされますnotify()
またはnotifyAll()
が同じオブジェクトに。
(関連項目: wait()/ notify() )
package com.example.examples.object;
import java.util.concurrent.atomic.AtomicBoolean;
public class WaitAndNotify {
public static void main(String[] args) throws InterruptedException {
final Object obj = new Object();
AtomicBoolean aHasFinishedWaiting = new AtomicBoolean(false);
Thread threadA = new Thread("Thread A") {
public void run() {
System.out.println("A1: Could print before or after B1");
System.out.println("A2: Thread A is about to start waiting...");
try {
synchronized (obj) { // wait() must be in a synchronized block
// execution of thread A stops until obj.notify() is called
obj.wait();
}
System.out.println("A3: Thread A has finished waiting. "
+ "Guaranteed to happen after B3");
} catch (InterruptedException e) {
System.out.println("Thread A was interrupted while waiting");
} finally {
aHasFinishedWaiting.set(true);
}
}
};
Thread threadB = new Thread("Thread B") {
public void run() {
System.out.println("B1: Could print before or after A1");
System.out.println("B2: Thread B is about to wait for 10 seconds");
for (int i = 0; i < 10; i++) {
try {
Thread.sleep(1000); // sleep for 1 second
} catch (InterruptedException e) {
System.err.println("Thread B was interrupted from waiting");
}
}
System.out.println("B3: Will ALWAYS print before A3 since "
+ "A3 can only happen after obj.notify() is called.");
while (!aHasFinishedWaiting.get()) {
synchronized (obj) {
// notify ONE thread which has called obj.wait()
obj.notify();
}
}
}
};
threadA.start();
threadB.start();
threadA.join();
threadB.join();
System.out.println("Finished!");
}
}
いくつかの出力例:
A1: Could print before or after B1
B1: Could print before or after A1
A2: Thread A is about to start waiting...
B2: Thread B is about to wait for 10 seconds
B3: Will ALWAYS print before A3 since A3 can only happen after obj.notify() is called.
A3: Thread A has finished waiting. Guaranteed to happen after B3
Finished!
B1: Could print before or after A1
B2: Thread B is about to wait for 10 seconds
A1: Could print before or after B1
A2: Thread A is about to start waiting...
B3: Will ALWAYS print before A3 since A3 can only happen after obj.notify() is called.
A3: Thread A has finished waiting. Guaranteed to happen after B3
Finished!
A1: Could print before or after B1
A2: Thread A is about to start waiting...
B1: Could print before or after A1
B2: Thread B is about to wait for 10 seconds
B3: Will ALWAYS print before A3 since A3 can only happen after obj.notify() is called.
A3: Thread A has finished waiting. Guaranteed to happen after B3
Finished!
getClass()メソッド
getClass()
メソッドを使用して、オブジェクトのランタイムクラスタイプを見つけることができます。以下の例を参照してください。
public class User {
private long userID;
private String name;
public User(long userID, String name) {
this.userID = userID;
this.name = name;
}
}
public class SpecificUser extends User {
private String specificUserID;
public SpecificUser(String specificUserID, long userID, String name) {
super(userID, name);
this.specificUserID = specificUserID;
}
}
public static void main(String[] args){
User user = new User(879745, "John");
SpecificUser specificUser = new SpecificUser("1AAAA", 877777, "Jim");
User anotherSpecificUser = new SpecificUser("1BBBB", 812345, "Jenny");
System.out.println(user.getClass()); //Prints "class User"
System.out.println(specificUser.getClass()); //Prints "class SpecificUser"
System.out.println(anotherSpecificUser.getClass()); //Prints "class SpecificUser"
}
getClass()
メソッドは最も具体的なクラス型を返します。これは、 getClass()
が呼び出されたanotherSpecificUser
に、戻り値がUser
class SpecificUser
よりも継承ツリーの方が低いため、 class SpecificUser
です。
注目すべき点は、 getClass
メソッドは次のように宣言されています。
public final native Class<?> getClass();
getClass
呼び出しによって返される実際の静的型はClass<? extends T>
ここで、 T
はgetClass
が呼び出されるオブジェクトの静的型です。
すなわち、以下のものがコンパイルされます:
Class<? extends String> cls = "".getClass();
clone()メソッド
clone()
メソッドは、オブジェクトのコピーを作成して返すために使用されます。このメソッドは、問題があり、コピーコンストラクタやその他のコピー方法をclone()
ために使用する必要があるため、避けるべきです。
メソッドを呼び出すすべてのクラスは、 Cloneable
インタフェースを実装する必要があります。
Cloneable
インターフェイス自体は、呼び出すオブジェクトクラスがCloneable
実装しているかどうかをチェックするnative
clone()
メソッドの動作を変更するために使用される単なるタグインターフェイスです。呼び出し元がこのインタフェースを実装していない場合、 CloneNotSupportedException
がスローされます。
Object
クラス自体はこのインタフェースを実装していないため、呼び出し元オブジェクトがクラスObject
場合はCloneNotSupportedException
がスローされます。
クローンが正しいようにするには、クローン元のオブジェクトとは独立している必要があります。したがって、オブジェクトが返される前にオブジェクトを変更する必要があります。つまり、複製されるオブジェクトの内部構造を構成する変更可能なオブジェクトのいずれかをコピーすることによって、基本的に「ディープコピー」を作成することを意味します。これが正しく実装されていない場合、複製されたオブジェクトは独立していないし、クローン元のオブジェクトと同じ変更可能なオブジェクトへの参照を持ちます。これは、一方の変更が他方の変更に影響を及ぼすため、不一致の動作につながります。
class Foo implements Cloneable {
int w;
String x;
float[] y;
Date z;
public Foo clone() {
try {
Foo result = new Foo();
// copy primitives by value
result.w = this.w;
// immutable objects like String can be copied by reference
result.x = this.x;
// The fields y and z refer to a mutable objects; clone them recursively.
if (this.y != null) {
result.y = this.y.clone();
}
if (this.z != null) {
result.z = this.z.clone();
}
// Done, return the new object
return result;
} catch (CloneNotSupportedException e) {
// in case any of the cloned mutable fields do not implement Cloneable
throw new AssertionError(e);
}
}
}
finalize()メソッド
これは、 Object
クラスの保護された 非静的メソッドです。このメソッドは、オブジェクトがメモリから削除される前にオブジェクトの最終操作またはクリーンアップ操作を実行するために使用されます。
ドキュメントによると、このメソッドは、ガベージコレクションがオブジェクトへの参照がなくなったと判断したときに、オブジェクトのガベージコレクタによって呼び出されます。
しかし、オブジェクトがまだ到達可能であるか、オブジェクトが適格になったときにガベージコレクタが実行されない場合、 finalize()
メソッドが呼び出されるという保証はありません。それで、この方法に頼らない方がいい理由です。
Javaのコアライブラリでは、 FileInputStream.java
使用例がいくつか見つかりFileInputStream.java
。
protected void finalize() throws IOException {
if ((fd != null) && (fd != FileDescriptor.in)) {
/* if fd is shared, the references in FileDescriptor
* will ensure that finalizer is only called when
* safe to do so. All references using the fd have
* become unreachable. We can call close()
*/
close();
}
}
この場合、そのリソースが以前に閉じられていない場合、リソースを閉じる最後のチャンスです。
一般に、あらゆる種類のアプリケーションでfinalize()
メソッドを使用するのは悪い習慣と考えられ、避けるべきです。
ファイナライザは、リソースを解放するためのものではありません (ファイルのクローズなど)。ガベージコレクタは、ヒープスペース上でシステムの実行が不十分な場合に呼び出されます。ファイルハンドルが不足しているときや、その他の理由で呼び出されることに依存することはできません。
ファイナライザの意図されたユースケースは、差し迫った運命について他のオブジェクトに通知するために再生しようとしているオブジェクトに対するものです。この目的のために、 java.lang.ref.WeakReference<T>
クラスのより良いメカニズムが現在存在していjava.lang.ref.WeakReference<T>
。 finalize()
メソッドを記述する必要があると思われる場合は、代わりにWeakReference
を使用して同じ問題を解決できるかどうかを調べる必要があります。これで問題が解決しない場合は、デザインをより深く考え直す必要があります。
さらに読むには、Joshua Bloch著「Effective Java」ブックのfinalize()
メソッドに関する項目があります。
オブジェクトコンストラクタ
Javaのすべてのコンストラクタは、 Object
コンストラクタを呼び出す必要があります。これは、 super()
の呼び出しで行われます。これはコンストラクタの最初の行でなければなりません。この理由は、追加の初期化を実行する前にオブジェクトを実際にヒープ上に作成できるようにするためです。
コンストラクタでsuper()
呼び出しを指定しないと、コンパイラはそれをあなたのために入れます。
したがって、これらの3つの例はすべて機能的に同一です
super()
コンストラクタへの明示的呼び出し
public class MyClass {
public MyClass() {
super();
}
}
super()
コンストラクタへの暗黙の呼び出しで
public class MyClass {
public MyClass() {
// empty
}
}
暗黙のコンストラクタで
public class MyClass {
}
コンストラクタチェーンはどうですか?
他のコンストラクタをコンストラクタの最初の命令として呼び出すことは可能です。スーパーコンストラクタへの明示的呼び出しと別のコンストラクタへの呼び出しは両方とも最初の命令でなければならないため、相互に排他的です。
public class MyClass {
public MyClass(int size) {
doSomethingWith(size);
}
public MyClass(Collection<?> initialValues) {
this(initialValues.size());
addInitialValues(initialValues);
}
}
新しいMyClass(Arrays.asList("a", "b", "c"))
を呼び出すとList-Argumentを持つ2番目のコンストラクタが呼び出され、最初のコンストラクタに委譲されます(これは暗黙的にsuper()
)、次にaddInitialValues(int size)
をリストの2番目のサイズで呼び出します。これは、複数のコンストラクターが同じ作業を行う必要があるコードの重複を減らすために使用されます。
どのように特定のコンストラクタを呼び出すのですか?
上記の例では、 new MyClass("argument")
またはnew MyClass("argument", 0)
呼び出すことができます。つまり、 メソッドのオーバーロードと同じように、選択したコンストラクタに必要なパラメータでコンストラクタを呼び出します。
Objectクラスのコンストラクタではどうなりますか?
デフォルトの空のコンストラクタ( super()
呼び出しを除く)を持つサブクラスでは何も起こりません。
既定の空のコンストラクターは明示的に定義できますが、そうでない場合は、他のコンストラクターが既に定義されていない限り、コンパイラーはそれを入れます。
どのようにオブジェクトは、オブジェクトのコンストラクタから作成されますか?
オブジェクトの実際の作成はJVMに任されています。 Javaのすべてのコンストラクタは、インスタンスの初期化を担当する<init>
という特別なメソッドとして表示されます。この<init>
メソッドはコンパイラによって提供され、 <init>
はJavaの有効な識別子ではないため、言語で直接使用することはできません。
JVMはこの
<init>
メソッドをどのように呼び出すのですか?
JVMはinvokespecial
命令を使用して<init>
メソッドを呼び出し、初期化されていないクラスインスタンスに対してのみ呼び出すことができます。
詳細は、JVM仕様とJava言語仕様を参照してください。
- 特殊メソッド(JVM) - JVMS - 2.9
- コンストラクタ - JLS - 8.8