Objectクラス 〜toString( );とequals( );〜
☆ Javaルール
親なしのクラスは定義出来ない
…では、extends(継承)で親クラスを指定していないクラスは、
いったいどのクラスを継承して定義されているのか?
実は、あるクラスを定義する時、extendsで親クラスを指定しなければ、java.lang.Objectを親クラスとして継承したとみなされる。
ex. public class Empty (extends Object) ←この部分が隠されているだけ
Object 親
↑ ↑
親 Date 子 extends指定なしで
新しく作ったクラス
↑
子 (extends)
Dateクラスを継承して新しく作ったクラス
….どのクラスも元を辿れば、Objectクラスを継承している
☆ Javaルール
親クラスに定義されているメソッドは、子クラスで再定義なしで使用できる
→どのクラスもObjectクラスを継承しているので、
Objectクラスに定義されているメソッドはどのクラスにおいても使用可能
〈Objectクラス内で定義されているメソッド〉
・toString( )…自分自身の内容の文字列表現を返す
・equals( )….あるインスタンスと自分自身とが同じかを調べる
Q. なぜ「全ての先祖Objectクラス」を作ったの?
理由1 多態性を利用できるようになるから
「あらゆるクラスはザックリ見るとどれもObject」として同一視できる
↓
よって、Object型の変数には、どんなインスタンスも代入できる
ex) Object a=new Empty( );
Object b=new Hero( );
Object c="こんにちは";
また、多態性を利用できるということは、
引数(渡す情報)としてObject型を用いることで、
「何型でもいいからインスタンスを渡せる」メソッドを作ることができる
ex) public void printAnything(Object o){
//何型でもいいから、引数を一つ受け取り、画面に表示させるメソッドを定義する
System.out.println(o.toString( )); //toString( );→自分自身の内容の文字列を表示
☆ Object型の変数は、あらゆる参照型のインスタンスを格納できる
× 基本データ型(intやlongなど)
理由2 Objectクラスにメソッドを定義しておけば、全てのクラスが最低限備えるべきメソッドを定義したことになるから
先に書いたtoString( )やequals( );など
☆ toStringメソッド...オブジェクトの中身の情報を文字列にしてくれる
→ただし、Objectクラスに定義されているtoString( )メソッドの情報は、
継承した側でオーバーライド(上書き)しなければ、
全て「型名@英数字」で出力される….これだと情報としてよく分からない
そこで、toString( );メソッドは継承した側でどのような形式で情報を出力したいか必ずオーバーライドする
ex. System.out.println(h.toString( )); と指定した時に、
「名前: /HP 」と表示したい
↓
public class Hero{
public String toString( ){
return "名前:"+this.name+"/HP:"+this.hp;
//表示したい形式を戻り値として記述
}
}
例外)Dateクラスにおいては、Formatの書式文字列("yyy/MM/dd…")でtoString( );をオーバーライドしている
なので、表示形式をtoString( );メソッドで指定する必要はない
☆ equals( )メソッド….ふたつのインスタンスが「同じ内容であるか」を判断する
ex) Hero h1=new Hero( ); //Hero1人目
h1.name="太郎";
h1.hp=100;
Hero h2=new Hero( ); //Hero2人目
h2.name="太郎";
h2.hp=100;
if(h1.equals(h2)==true){
System.out.println("同じ内容です");
}else{
System.out.println("違う内容です");
}
Q.if(h1==h2)ではダメ?
☆ ==(等値判定)とは、
指しているものが「完全に同一の存在であること」
→つまり、同じアドレスを指している事
下記のような記述であれば、等値判定でOK
ex. Hero h1=new Hero("太郎");
Hero h2=h1;
インスタンスを新たに生成したわけではないので、
変数h1もh2も"太郎"という名のインスタンスが入った同一の番地を指している
2132番地 インスタンスA "太郎"
↑ ↑
h1 h2
(2132番地を指す) (2132番地を指す)
この時、h1==h2
☆ equals(等価判定)とは、
指している2つのものが「同じ内容」であること
(同じアドレスを指していなくても良い)
ex. Hero h1=new Hero("太郎");
Hero h2=new Hero("太郎");
太郎 太郎
↑ ↑
h1 h2
(2132番地を指す) (9142番地を指す)
この時h1とh2は違う番地を指しているため、h1!=h2(h1とh2は完全に一致しない)だが、
インスタンスの名前は一緒なので、h1.equals(h2)である
実は、Objectクラス内のequals( )の中身はほとんど記載されていない
なぜなら、「何を持って意味的に同じと見なすか」はクラスによって異なり、一律には決められないから
ex. 名前 たろう(ひらがな)
名前 タロウ(カタカナ)
名前 太朗(漢字)
↑音として捉えると、全部同じ名前だし、文字の種類で捉えると全部異なる名前。何を基準に判断するかは、人によって異なる。
よって、そのクラスのインスタンスがequals( );で「何を持って同じもの」と満たすかをequals( );メソッドをオーバーライドして定義し直す必要がある
ex. Heroクラスでは、「名前が同じであれば、同じ内容のインスタンスと満たす」ことにする
public class Hero{
String name;
int hp;
:
//ここから下がequals( ):メソッドの再定義
public boolean equals(Object o){
if(this==o){ //this→Hero
//渡されてきた引数であるインスタンスoがHeroインスタンスそのものだった場合(Hero==o 完全に一致)
return true;
}
if(o instance of Hero){
//oインスタンスの型がHero型だった場合(中身は完全に一致していない)
Hero h=(Hero)o;
// Object型はHero型であるので、強制型変換(キャスト)を行い、変数hにoを代入する
if(this.name equals (h.name){
//Heroクラスの名前(this.name)と渡されてきたインスタンスの名前が同じだった場合
return true;
}
else return false; //上記のどちらにも当てはまらない場合はfalseを返す
}
}
※ 新しくクラスを開発したら、必ずtoString( );とequals( );をオーバーライドする必要がないか確認すること