2011年5月22日日曜日

C/C++プログラマのためのJava入門覚書(2/2)

続き


■Object

Java上で使うクラスは自作だろうとなんだろうと、すべて「Object」というクラスを暗黙のうちに継承している。いくつかの処理ではこのObjectクラスのメソッドが勝手に呼び出されており、僕らはその恩恵をうけている。お世話になっている(なる)のはだいたいこのあたり。

  • clone(): オブジェクトのコピーを作成する。ただし、Cloneableインターフェースを実装していないと(継承していないと)例外が飛ばされる
  • - toString (): オブジェクトの文字列表現を生成してString型で返す。Override可能。System.out.printlnに文字以外を突っ込んだ時に、よしなに表示してくれるのはこいつのおかげ。
  • - hashCode (): オブジェクトのハッシュコードを返す。ハッシュテーブルとかのデータ構造にオブジェクトをぶち込むとき、キーを作成してくれる。Overrideできるが非推奨。
  • - equals(): 同じオブジェクトであるかどうかを検査する。Overrideできるが、同値関係(反射性、対称性、推移性、整合性)をすべて満たす必要があり、意外と厄介。また、hashCodeが「等価なオブジェクトは同じハッシュコードを返す」という性質との整合性もとらないといけないので、基本非推奨。


■クラス変数の比較

クラス変数XとYがあった場合、JavaにおいてX == Yは参照先の比較(すなわちポインタの比較)をしているに過ぎない。各オブジェクト同士が等しいかどうかを検証するために、equals()というメソッドが全てのクラスの親であるObjectクラスに用意されており、こいつが適宜Overrideされている。ただし、OverrideをするときにはhashCode()メソッドとの整合性をとったり、同値関係が成り立つことを確認したりと結構面倒なので注意。したがって、クラス変数の比較に == を使うことは、ほとんどない(んじゃないかな)。ちなみにJavaは演算子のoverrideは許されていない。


一方、Stringにはちょっとした罠が用意されている。
String a = new String ("hoge");
String b = new String ("hoge");
とやって、(a == b)とすると、falseになる。これは参照しているオブジェクトが違うから当然だ。ところがどっこい
String a = "hoge";
String b = "hoge";
とやって、(a == b)とすると、trueと評価される。なぜか。


これは、"hoge"という同じ文字列定数がコードに出現した場合、javacが同じオブジェクトとして勝手に最適化するため、このようなややこしいことが起きる。ちなみにコードが最適化されると、
String tmp = new String ("hoge")
String a = tmp;
String b = tmp;
のように置換されているそうだ。さらに、
a += "mage";
などと、文字列を変更しようとした瞬間に、さらに別のオブジェクトに置き換えられているらしい。実にややこしい。


[参考] http://education.yachinco.net/tips/java/06/1.html

■配列の記述

配列の括弧はどちらでもOK
int [] a;
int a[];
は等価らしい。

[参考] http://msugai.fc2web.com/java/array2.html


ちなみに、Javaは new Hoge[10]; とやっても、nullの入った配列が生成されるだけで
各要素(中身)は個別に生成する必要がある。


■数値型あれこれ

  • 値あふれが起きると、C言語などと同様に値がひっくり返る(例:最大値 → 最小値)
  • 各プリミティブ型に対応するラップクラス (int v.s. Integer, long v.s. Longなどなど)がデフォルトで用意されている。文字列からの変換や最大値、最小値の定数提供などをしてくれるが、一文字間違えただけで大惨事になる可能性もある。
  • 精度が低い型から高い型への代入は可能。逆はコンパイルエラー。
int a = 1;
short b = 2;
a = b; // OK
b = a; // Compile error

C/C++プログラマのためのJava入門覚書(1/2)

会社の研修でJava研修があったのですが、正直これまでC/C++/LLだけで生活してきて、「えー、Javaかよ」みたいな偏見を持っていた人間なので、研修1週間ぐらえ前にコソコソJavaを勉強してました。文法は概ねC++と変わらないんですが、当然ながら流儀や細かい部分はだいぶ違うので、覚書をメモっておきます。


■変数

Javaの変数はプリミティブ型(基本型)、クラス型(オブジェクト型、参照型)の2つに分類できる。プリミティブ型はboolean, char, byte, short, int, long, float, doubleのように、Javaがあらかじめ用意している数値用の変数。これらは=(イコール)や関数での引数指定では、常に値渡しになる。一方、クラス型は常に参照渡しになる。

[参考] http://www.makino-style.org/education/jed03.html


■クラス内の初期化の順番

クラス内での初期化に関連する要素は以下のとおり。

  • コンストラクタ(Class名と同じ返り値無しのメソッド)
  • インスタンスイニシャライザ(クラス内かつメソッド外にある {} で囲まれているブロック)
  • スタティックイニシャライザ(クラス内かつメソッド外にあるstatic {} となっているブロック)
  • スタティック変数(static宣言つき変数)
  • インスタンス変数(staticなし変数)

具体的な順序としては

  1. [プログラム起動]
  2. スタティックイニシャライザとスタティックなクラス変数 (newで領域確保されてるやつ)が書いてあるとおりに上から実行&初期化されていく。これは1度きり。
  3. [途中でインスタンスが生成される]
  4. インスタンスイニシャライザとインスタンス変数が、これまた上から順番に実行&初期化されていく
  5. 最後に自身のコンストラクタが実行される

[参考] http://d.hatena.ne.jp/Yoshiori/20090116/1232093347


■デストラクタは使わない

Javaのクラス変数はすべて参照型になっているので、あるインスタンスがどの変数からも参照されなくなった時点で破棄される。と、言いたいところだが、実際はGCのキューに入るだけなので、いつ本当にメモリが開放されるか(デストラクタが呼び出されるか)は分からない。そして、多くの場合、それはかなり時間がかかるらしい。

■文字列

Javaにおける基本的な文字(列)型はchar型とstring型。おおよそ想像がつくとおり、charは一文字だけ、Stringは文字列を扱う。Javaにおいても「文字」の表現は 'X' (シングルクオーテーション)、「文字列」の表現は "XXX" (ダブルクオーテーション)になる。ただし、この場合の文字は1バイトである必要はなく、マルチバイト文字も '欝' で文字型となる。よって、ひらがなや漢字でswitch文を記述することも可能。

stringからcharを取り出す時には atChar (int index) メソッドを使う。

■継承

Javaの継承には通常のクラス継承とinterfaceを使う方法の2種類がある。


  • - クラス継承
    • 多重継承はできない。子供は1つの親しかもつことはできない。ただし繰り返しの継承は可能。
    • 変数も継承できるし、子クラスで同じ名前の変数を宣言することができる (thisとsuperで区別する)
    • C++などでお馴染みのクラス継承なので共通のメソッドを作れるし、必要箇所だけoverrideもできる。
  • - interface
    • 多重継承ができる。
    • メソッドは全てabstract(C++の純粋仮想関数)として扱われる。
    • 変数は宣言できるが、static + final扱い(つまり定数)になる
    • クラス、インターフェースも宣言できるが、static扱いになる。


聞くところによると、interfaceは通常の継承として使うというよりは、コールバック用のメソッドを定義するためのフレームワークとして使うのがよい使い方らしい。ただし、使う人の宗教によって違うかも知れないので注意。


長いんで続きます。→ (2/2)
勉強したてで若干怪しい部分もあるので、ご指摘など大歓迎です。