java.lang.Stringクラスのequalsメソッドのソースコードを読んでみた
ソースコード
バージョンは1.6。ソースコードを読むのは初めてなので、間違いは多分いっぱいある。
public boolean equals(Object anObject) {
// (1)
if (this == anObject) {
return true;
}
// (2)
if (anObject instanceof String) {
String anotherString = (String)anObject;
// (3)
int n = count;
// (4)
if (n == anotherString.count) {
// (5)
char v1[] = value;
char v2[] = anotherString.value;
// (6)
int i = offset;
int j = anotherString.offset;
// (7)
while (n-- != 0) {
if (v1[i++] != v2[j++]) {
return false;
}
}
return true;
}
}
return false;
}
解説メモ
(1) this == anObject
thisは該当クラスのオブジェクトを参照する参照型変数である。 コンストラクタ内、初期化ブロック内、フィールド宣言の初期化子内のthis変数は生成中のオブジェクトを参照する。 メソッド内のthis変数は、メソッド呼び出し対象のオブジェクト(レシーバ)を参照する。
つまり、equals()メソッドのレシーバと引数のanObjectが等しい場合にtrueを返すことになる
String string = new String("string");
if (string.equals(string)) {
System.out.println("レシーバと引数は同一である。よって必ず同値になる");
} else {
System.out.println("not equals");
}
(2) instanceof (パーフェクトJava P240参照)
instanceof演算子は、オブジェクトの型を判定する2項演算子。左オペランドがオブジェクト参照で、右オペランドが参照型の型名になる。 評価値の型はboolean。左オペランド式が、右オペランドの型に代入可能な参照型の時、式の評価値が真になる。代入可能でない場合は偽になる。 別名「型比較演算子」。
・instanceof演算子の目的 instanceof演算子はダウンキャストと密接に関連する。instanceof演算子を使う目的はダウンキャストを安全に行えるかを事前にチェックするためである。
void doit(Object obj) {
// 変数objがString型に代入可能であればobjのダウンキャスト(Object型→String型)をする
if (obj instanceof String) {
String s = (String) obj; // ダウンキャスト
System.out.println(s);
}
}
※ 型変換・キャスト
- キャストとは、明示的に型を変換すること。javaの場合は、カッコの中に型名を書き、つづけて変数を書くことで変数の型を変換することができる
- 拡大変換:型をより大きなサイズの型に変換すること(例:intをlongにする)。拡大変換はキャスト無しで変換することができる。
- 縮小変換:型をより小さなサイズの型に変換すること(例:longをshortにする)。縮小変換はキャストが必要になる。
- アップキャスト:あるクラスのスーパークラスへと型を変換すること
- ダウンキャスト:あるクラスのサブクラスへと型を変換すること
- アップキャストは安全だが、ダウンキャストは必ずしも安全とは言えない。むしろ避けるべき動作である。
- 互換性が無い型同士でキャストを行うと、「java.lang.ClassCastException」例外が発生する
(3) int n = count
countは、Stringクラスのインスタンスフィールド String.java 102行目:"private final int count;" コメントには「The count is the number of characters in the String.」と書かれているので、「文字列内の文字数」という解釈で合っているだろう。
またここで気がついたことだが、String.javaはコンストラクタが多数オーバーロードされている。標準クラスは大抵こんなものなのかな? これに関しては リファレンス を参照するとよくわかる
(4) n == anotherString.count
ここではレシーバと引数、それぞれのオブジェクトのフィールド「count」を用いて、文字数の比較をしている。 ここで一致しなければfalseを返して終了
(5) char v1[] = value
レシーバのStringオブジェクトの文字列値をchar型のv1配列に格納 パラメータのStringオブジェクトの文字列値をchar型のv2配列に格納 例えば、
String s = new String("abc");
のように宣言された変数sであれば、 {"a","b","c"}
のようになっているのだと思われる
(6) int i = offset
整数型変数iにレシーバのStringオブジェクトのoffset値を、同じく整数型変数jにequals()メソッドの引数に指定されたStringオブジェクトのoffset値を代入する offsetは、いまいちよくわからなかったが、調べた結果、v1[]とv2[]に格納した文字列の開始位置のことらしい。
(7) while (n-- != 0) ..
配列に格納した文字のセットを、1つずつ比較する。 1つでも異なった場合はfalseを返す。全て合致した場合はtrueを返す。 whileの条件文にデクリメントを使っているのは、新鮮だった。
その他
オーバーロード
- メソッドのオーバーロード : 同一クラス内でメソッド名が等しく、かつ引数の型・数・並び順が異なるメソッドを複数定義すること
- コンストラクタのオーバーロード : 引数の並びが異なるコンストラクタを複数定義すること
コンストラクタのオーバーロードにおけるthis呼び出しとsuper呼び出し
- this()メソッドを用いることで、同じクラス内のコンストラクタを呼び出すことができる
- これにより、それぞれのコンストラクタの共通部分をくくり出すことができる
class Book {
String title;
int price;
String author;
Date published;
Book(String title, int price, String author, Date published) {
this.title = title;
this.price = price;
this.author = author;
this.published = published;
}
Book(String title, int price, String author) {
// this()メソッドで上述のコンストラクタを呼び出す
this(title, price, author, new Date());
}
}
同様に、super()メソッドを用いることで、クラスを拡張継承したときに、継承したクラスから継承元のコンストラクタを呼び出すことができる。
class ProgrammingBook extends Book {
String language;
// 継承したProgrammingBookと継承元のBookではlanguageが増えただけなので、
// 下記のように書くことでコードの重複をなくすことができる
ProgrammingBook(String title, int price, String author, Date published, String language) {
super(title, price, author, published);
this.language = language;
}
equalsメソッドの基本的な情報(APIリファレンスから)
- オーバーライド : クラスObject内のequalsメソッド
- パラメータ : anObject - このStringと比較されるオブジェクト
- 戻り値 : boolean。指定されたオブジェクトがこの文字列に等しいStringを表す場合はtrue、そうでない場合はfalse
equalsメソッドと「==」演算子について
equalsメソッドと==演算子は明確な違いがあり、これは同一性と同値性という概念として区別しておくと良い。
- 同一性 : 2つのオブジェクト参照が同一のオブジェクトを参照している時に真になる比較
- 同値性 : 同値性の比較基準はオブジェクト毎に異なるが、例えば文字列であれば2つの文字列の内容が一致していれば真になる。数値であれば2つの数値が一致していれば真になる。
同一性が真であれば、かならず同値性も真になる。しかし逆は必ずしも成り立たない。 同一性の比較には、等値演算子である「==」または「!=」を用いる。 同値性の比較には、文字列の場合は、equalsメソッドを用いる。