詳細は、Code Conventions for the Java Programming
Languageを見てください。
というより一部は意図的に変えている部分もあり、JDKの実装などは上記に従っています。
独自クラスのパッケージ名は一般的にドメイン名の逆順が良いとされています.
現在パッケージ名は全て小文字で表記する事が推奨されているようです.
例:jp.co.java_conf.shin
クラス名は単語の先頭を大文字にし、単語同士は繋げて表記します.
単語を省略形で表記するのはよしましょう(あまりにも長くなりすぎない限り).
例:TextField、ApplicationFrame、ExtendedTextAreaBeanInfo←InformationをInfoと略すのは許されるようです.
×:CDlg、TxtFld
※上記のBeanInfoというのはJava言語仕様で決められた名前付け規約の為省略表記になっていますが、
やはりなるべく省略しない方が良いとされています.
先頭の単語を小文字で表記し、それ以降の単語に関してはクラス名と同様に表記します.
例:getFontSize()、addActionListener
メソッドと同様の表記法を使います.
私の場合は特にローカル変数と区別がつくように最後に"_"を付加しています.
人によってはクラス変数/インスタンス変数を区別する為に
クラス変数には"__"(2個のアンダーバー)を付加したりします.
(この辺は人それぞれですが)
例:count_、actionListener_、fontSize_
基本的にクラスフィールド/インスタンスフィールドと同様の表記に沿っていれば単語を省略形で示したり
単純な名前を用いてもかまわないと思います.
例:count、cnt、chr、i、x、y
String等の有名な(?)クラスでない限り、フルネームで表記しましょう。
フルネームとはパッケージ名を含めた表記です。(FQCNと呼びます)
コアAPI(javaパッケージ配下)のパッケージ名は省略される事が多いですが、
マイナーなものの場合はフルネームで書いた方が良いです。
例:java.io.Serializable、sun.security.provider.SystemIdentity
一般にクラス名#メソッド名()と表記します。
#を使った表記法はjavadocでの@seeタグの表記法と一致します。
クラスメソッド(staticメソッド)の場合はクラス名.メソッド名()でも構わないと思ってましたが、より一般的にはやはり#のようです。
例:Component#requestFocus()、Toolkit.getDefaultToolkit()
Modelはコンポーネントの「状態」を管理します.VIEWは表現、Controllerは入力応答を示しますが、
この二つは比較的近い関係にあります.
設計によってはViewオブジェクトの入れ替えだけでコンポーネントの外観を変えるような作りにもできますが、より拡張性を持たせる為にSwingではView/Controllerを一纏めにした
「UIオブジェクト」を入れ替える事で一つのModelに対して複数の外観+入力形態を実現しています.
また、swingの場合、UIオブジェクトの内部でViewとして独立した「Look
& Feel」オブジェクトが存在し、操作性(Controller)を変えずに見た目だけを切り替えることができます.
ただ、Look & Feelは個々のコンポーネントが管理するわけではなく、swing全体で一つであるため、特定のコンポーネントのLook
& Feelだけを切り替えるということは基本的にできないようです.
Modelは「状態」という事でデータクラスを想像しますが、実際にはこれはinterfaceとして提供します.
これは、Modelの実装方法を決め打ちにしたくないためです.
これはフレームワークの特徴でもあるのですが、interface内の抽象メソッドは殆どの場合、
それをimplementsしたオブジェクト以外から呼び出されます.
即ちその抽象メソッドを実装するとき、それを呼び出すのが自分の作ったコードだけとは限らないという事です.
フレームワークの場合、いわゆるメイン処理をフレームワーク側に持っている事が多く、
プログラマーはフレームワークのドキュメントに従って、フレームワークから呼び出されるinterfaceの実装を記述して行きます.
これは「制御の反転」の典型例です.
getCodeBase()ではそのアプレットのクラスファイルのあるパスのURLを返します.
getDocumentBase()ではアプレットをロードしたHTMLファイルを示すURLを返します.
以前はこれらで得たURL+相対パスを元にgetImage等でリソースのロードを行ってきました.
しかし、jarからクラスファイルやリソースをLOADする手段は特別なClassLoaderを
用いる事になっており、上記のメソッド+getImage等ではjarファイルからの リソースのLOADが行えません.
具体的には以下のようなコードで読み込む事になります.
public static Image loadAppletImage(java.applet.Applet o,String fileName) {
try {
//ClassクラスのgetResource:oのクラスローダを用いてURLを生成
URL url = o.getClass().getResource(fileName);
//アプレットのツールキット
java.awt.Toolkit tk = o.getToolkit();
return tk.createImage((java.awt.image.ImageProducer) url.getContent());
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
注意:getResource()メソッドに渡すリソースファイル名はそのクラスの存在する
ディレクトリからの相対パスとなります.
リソースファイル名の先頭に[/]を付加するとCODEBASEからの相対となります.
(DocumentBaseからjarをたどる事はできません?)
↑98/09/08今は怪しいです.
以下にappletをHTMLに配置する際のタグ例を示します.
<APPLET
↓HTMLと別の位置にjar(またはクラスファイル群)がある場合
CODEBASE = "/java" 省略するとHTMLのあるurl
CODE = idea.shin.testapplet.Applet1.class 必須
NAME = "テスト アプレット"
WIDTH = "400" 必須
HEIGHT = "300" 必須
HSPACE = "0"
VSPACE = "0"
ARCHIVE = "app.jar" ←CODEBASE上に配置
>
<PARAM NAME = "SPEED" VALUE = "100">
<PARAM NAME = "Strings" VALUE = "文字列">
</APPLET>
LightWeightComponentを持っているコンテナのpaint()をオーバーライド する場合はsuper.paint(g);を記述しておく必要があります.そうしないとLightWeightPeerを持つコンポーネント達のpaint()が呼ばれません.
イベントリスナ登録モデル(DelegationEventModel)の場合はadd???Listener()内で勝手にやってくれます.
process???Event()は使用する必要が無いので、意識する必要はないと思われます.
キーボードフォーカスを*完全に*得るにはisFocusTraversable()をtrueを返すようにオーバーライドします.こうすることにより、Tabキーでもフォーカスがくるようになります.
Component#getPreferredSize()で得られるサイズ(=validate()/pack()時のサイズ)は、 peerのサイズです.
「LightWeightComponentでは現在のサイズがpreferredSizeになります」
と言う風に記述されており、ソースを見てもgetPreferredSize()は現在のサイズを返しているはずなのに、
実はLightWeightPeerと言うpeerが設定されており、このgetPreferredSize()が返す値は(1,1)になってしまいます.
即ち、レイアウトマネージャを起動した時に、サイズが極小になってしまいます.
コンテナの場合は内部のコンポーネントのサイズに応じて適切なPreferredSizeを返しますが、
コンポーネントの場合は、getPreferredSize()及びgetMinimumSize()を以下のようにオーバーライドしないと
指定したサイズになりません.
public Dimension getPreferredSize() {
return(getMinimumSize());
}
public Dimension getMinimumSize() {
return(getSize()); //或いは適当なサイズ
}
レイアウトによってはこの実装ではまずい(getSize()が0,0を返す場合もあるので)ので、推奨サイズを独自に保持するようなやり方の方がいいかも知れません.
いずれにしても時と場合によりけりってことですね.
コンポーネントの外側に境界線を付加します.
Windowsでいうグループボックスをレイアウトマネージャの枠組みで利用できます.
// タイトル付きのBorder(Windows風)
TitleBorder titleBorder = new TitledBorder(
null, "タイトル", TitledBorder.LEFT, TitledBorder.TOP);
EmptyBorder emptyBorder = new EmptyBorder(5,5,5,5);
CompoundBorder compoundBorder = new CompoundBorder(titleBorder, emptyBorder);
EmptyBorderはInsetsのようなもの.
複数のBorderを重ねる(内/外)ためにCompoundBorderを用います.
3種類以上でも問題なく重ねていけます.
ちょっと今暇が無いのでここまでね
Containerのサブクラス達の中にはLayoutManagerがデフォルトで設定されるものとされないものがあります.
しかも、設定されるものでも、サブクラス毎にまちまちだったりします.
LayoutManagerは常にプログラマが設定するほうが安全です(というより「設定しましょう」).
★痛い目に会った人より
Thread#interrupt()メソッドを呼び出すと、対象スレッドにInterruptedExceptionが投げられるのですが、
当たり前の事ですが、これは対象スレッドが、wait/sleep等によってブロック中の時だけです.
JDK1.2からThread#stop()/suspend()/resume()等がdeprecatedになっており外部からスレッドを操作できなくなります.
これに対処する為に、スレッド側で外部要求に応じて終了可能な設計にしておく必要が出るので、
InterruptedExceptionが使えないかなと思ったのですが浅はかでした.
共有フラグの操作と一緒に行わなければならなさそうです.(待ち状態以外の時は随時終了フラグの状態を監視).
☆通常のループするタイプのスレッドの場合はinterrupt()で問題ないんですが.
☆不明;SeverSocket#accept()やInputStream#read()関係のように入力待ちでブロックしていた場合はどうするのでしょう?
→やはり無理矢理外からclose()してIOExceptionをcatchするというのが常套手段のようです.
つまり外部のクラスがServerSocketの参照を持つかclose()を呼び出すメソッドを公開しておくことになります.
ただし、標準入力をclose()する場合、復活できないのでアプリケーション終了時のみ用いることにしましょう(未確認).
→最新情報:上記の場合もThread#interrupt()でInterruptedIOExceptionが発生するのが正しい仕様とのことです.
Windowsのみバグにより正しく割り込みが発生してくれないということです.
Thread#isInterrupted()メソッドによって割り込みが発生したか否かを調べることができるので、以下のように書けます.
public void run() {
try {
while (!Thread.currenThread().isInterrupted()) {
// 処理
}
} catch(InterruptedException e) {
}
}
ただし、この方法だとThread#interrupt()メソッドをThreadの終了の合図としてしか使えないことになってしまいます.
interruptを本来のように割り込みの為に使いたい場合は別の「終了状態」を管理する変数が必要になります.
これらのメソッドはそのオブジェクトがロックされている時しか呼び出せません.
しかし、wait()メソッドで待ち状態に入る時にそのオブジェクトに対するロックは一時的に解除されます.
ちなみにsleep()メソッドの場合オブジェクトをロックしたまま待ちに入ってしまうので、
synchronized節でsleep()を使う時は注意する必要があります.
ObjectInputStream/ObjectOutputStreamを用いればオブジェクト単位でファイルや ネットワークに入出力が可能です.
特定のオブジェクトの出力ストリームへの書き出しtry {
ObjectOutputStream so = new ObjectOutputStream(
new FileOutputStream("temp.ser"));
so.writeObject(this);
} catch(IOException e) {
e.printStackTrace();
} catch(ClassNotFoundException e) {
e.printStackTrace();
}
オブジェクトの入力ストリームからの読み込み
try {
ObjectInputStream si = new ObjectInputStream(
new FileInputStream("temp.ser"));
Object o = si.readObject();
} catch(ClassNotFoundException e) {
e.printStackTrace();
} catch(StreamCorruptedException e) {
e.printStackTrace();
} catch(FileNotFoundException e) {
e.printStackTrace();
} catch(IOException e) {
e.printStackTrace();
}
PixelGrabberクラスで、Imageオブジェクトからintの配列にイメージデータを抽出します.
MemoryImageSourceクラスで、intまたはbyteの配列に入ったイメー>ジデータから、Imageオブジェクトを作成します.
this#writeObject()/readObject()の内部でそれぞれの変換を行う事でSerializeが実現できます.
これは、自分でImageを生成していたりしてロードするだけで復元できる状況じゃない場合に利用します.
登録されたイベントリスナはSerializableではない可能性があるので、リストはtransient宣言を行って自分で(可能なものを)直列化します.
this#writeObject()の内部では、リストからSerializableなリスナのみをObjectOutputStream#writeObject()
で書き出し、最後にnullを書き出しておきます.
this#readObject()ではリスナのリストの新規生成及び、
nullがくるまでリスナオブジェクトの読み込み+add???Listener()での登録を繰り返します.
尚、BeansのPropertyChangeSupport等Beansのイベントサポータでは以下のメソッド
PropertyChangeSupport#writeObject(ObjectOutputStream)
PropertyChangeSupport#readObject(ObjectInputStream)
にて下記コードのような実装を行っている為、transientを指定する必要はありません.
逆に、AWTEventMulticasterではイベントリスナのSerializeが行えません.
★不明:save()がprotectedなので呼べないんですけど・・呼べれば可能になります.
つまり、AWTEventMulticasterはサブクラスを生成して使用する事を前提としており(clone()と同じ理由ですね)、
元々独自イベントをAWTEventMulticasterで処理させようとした場合はそのサブクラスを作る事になるので、
そこからsave()を呼び出して直列化すれば良いのですが、AWTEvent自体についてはだめ?
(未稿:実験するとちゃんと保存されているようです)
private void writeObject(ObjectOutputStream s)
throws java.lang.ClassNotFoundException,
IOException {
s.defaultWriteObject(); //プロパティの保存
//イベントリスナの保存
Vector l_ = (Vector)listener.clone();
if (l_ != null) {
for (int i = 0; i < l_.size(); i++) {
if (l_.elementAt(i) instanceof Serializable) {
s.writeObject(l_.elementAt(i));
}
}
}
s.writeObject(null); //リストの終端
}
private void readObject(ObjectInputStream s)
throws java.lang.ClassNotFoundException,
IOException {
Object o;
s.defaultReadObject(); //プロパティの復元
listener = new Vector(); //transient部分の再生成
//nullがくるまでリスナを読み込む
while((o = s.readObject()) != null) {
add????Listener((????Listener)o);
}
//ThreadやImageの生成などを行います.
}
setXORModeでそれ以降の描画操作をすべて排他的論理和で行うはずなのですが、 JBuilderのJVMでは何も起こりません.(実装が無いようです)
なぜか二重のforループの両方で終了条件にiを使用してる.
おかげでメソッドを正しく認識させられません.
ついでにBeanInfo.snippetsにもバグがある為(すぐ見つけられます)、BeanExpressのBeanInfo
はあまり使用しないほうがよいのでは?(メソッドデスクリプタを使用しないなら使えるけど)
配布ウィザードではクラス間の依存関係をチェックして必要なクラスを全てjar化してくれますが、
クラスから参照しているリソース(gif等)及び、BeanInfoは探してきてくれませんので、
プロジェクトに追加して配布ウィザードを実行しなければなりません.
関係ないけど、JBCL/JGLパッケージを使用するときはその中のクラスもjarにいれましょう.
Wrapperコンテナで斜線を表示していますが、これがsuper.paint()を呼んでいない為、
コンポーネントのpaint()が呼び出されません.即ち、コンポーネントが表示されません.
BeanBoxのソースを変更すれば対応できます(私はやっていませんが).
他のBeanへの参照を受け取るような事はできません.
但し、コーディングによりオーナーコンテナ上に貼り付いたコンポーネントを検索する事はできる(未確認)
為、instanceofを駆使して特定のBeanの参照を取得する事は可能と思われます.
(上記はコンテナに貼り付けるだけで他のBeanとの関連付けを行おうとした時に
思いついたのですが、BeanBox意外では通用しませんね)
# 理由は他のツールは動的で無いからと言うことで...
基本書式は以下のようなものが一般的です.
@????はjavadoc専用HTMLタグです.
/**
* クラスの概要.(見出しになります.「.」を付けましょう)
* <p> ←見出しと詳細の区切り
* 詳しい説明(HTMLタグを使用できます)
* <p>段落開始
* <br>改行
* <dl>用語説明など
* </p>
* <pre>そのままの書式で表示(プログラム例など)</pre>
* @author Shin ←作成者
* @version 1.0 1998.xx.xx ←バージョン
* @see Object#clone() ←他のクラスなどへの参照がある場合
*/
public class xxxxx {
/**
* フィールドの説明.
* @see
*/
static final int AAA = 1263732;
/**
* フィールドの説明.
* @see
*/
int x = 123456;
// privateフィールド/メソッドは
// あえてjavadoc用コメントでなくても良い
private int y;
/**
* メソッドの概要.
* <p>
* 詳しい説明.
* @param para パラメータ
* @return リターン値
* @exception メソッドがthrowする例外
* @see
*/
public int method(int para) throws ???Exception {
}
}
基本的にpublic/protectedなフィールド/メソッドのみにjavadoc用コメントを付加すれば良いのですが、 package/privateスコープのものについても、javadocコマンドのオプションにより ドキュメントを生成できます.
javadoc -auther -version -d doc packagename
-dオプションでドキュメント生成位置を指定します.
基本的にパッケージ名で指定するので、そのパッケージのディレクトリ
(".java"ファイルの存在するディレクトリでなく)で実行します.
パッケージ一覧などのリンクを正しく張らせる為には、全てのパッケージを一度のjavadocで指定しなければならないのですが、JDK1.2のjavadocでは広大なメモリが無いと結構すぐにoverflowを起こしました.
今はどうなってるのかよく解りませんが(使って無いので)、その頃は-J-mx64mを付加して回避していました.
今なら-J-Xmx64mといった感じで確保するメモリを指定します.
詳しくはjavaコマンドとjavadocコマンドのチュートリアルを参照してください.
メソッドヘッダに@deprecatedタグを付加すると、そのメソッドを「推奨されない」メソッドにする事ができます.
これによりコンパイル時に警告を発生させる事ができるようになりますが、
代替メソッド(@deprecatedの後ろに記述)については警告メッセージには表示されません.
コンパイラがそこまでやればかなり親切ですが遅くなるんでしょうね.
javadocでのドキュメントには代替メソッドの記述も表示されます.
primitive型のラッパクラスを生成してtoString()という方法をとる方もいると思うのですが、余計なオブジェクトを作る必要はありませんし、toString()は運用コードでは用いない方がよいです。
final変数は定数とよく呼ばれますが、実際の定義は「それ以上の変更を許さない」です。
つまり、コンストラクタ内で一回は代入が行えます。
同じクラスの異なるオブジェクトで、異なるfinal値を持つ事ももちろんできます。
メソッド内で定義したfinal変数はそのメソッドに入るたびに値を初期化できる為、
メソッドのパラメタで初期化させる事ができます。
そんな事ができても何の意味があるのかと思ってしまいますが、
そのfinal変数は、メソッド内で宣言したInnerClassからも参照できるのです。
この性質を使えば、無名クラスからローカル変数が参照できないといって
その変数をクラスフィールドにする必要はないわけです。
値に別名を付けてソースコード中で参照する為にpublic
static finalな定数をよく使用します。
これを定義する場所としては、その値を格納するフィールドが定義されているクラスが最も適切なのですが、
値をパラメタに取るメソッドがある場合、クラス自体が外部に公開されていなければなりません。
つまり、以下のような場合にちょっと困ります。
class A {
public static final int AAA=1;
public static final int BBB=2;
private int field_;
public void methodA(int param) {
if (param == AAA) {
:
:
}
field_ = param;
}
}
public class B {
private A aclass_;
public void methodA(int param) {
aclass_.methodA(param);
}
}
これで別パッケージからクラスBのmethodA(クラスAを呼び出す)を呼び出す時にクラスAで定義されている定数 AAA/BBBを指定する事ができないケースが出来上がります.
定数をクラスBに宣言するなど他にも手段があるかもしれませんが、私は以下のようにします。
public interface AValue {//←或いはparameters等
int AAA = 1;
int BBB = 2;
}
class A implements AValue {
private int field_;
public void methodA(int param) {
if (param == AAA) {
:
:
}
field_ = param;
}
}
public class B {
private A aclass_;
public void methodA(int param) {
aclass_.methodA(param);
}
}
この場合AValue.AAAやAValue.BBBとして外部パッケージからもパラメタとして定数を指定可能になります.
呼び出し側がimplements AValueすると、どこで定義された定数か解らなくなる元です.
final変数をコンストラクタで初期化する場合、以下のようにしたくなります.
class Foo {
private final int a_;
Foo() {
this(1);
}
Foo(int a) {
a_ = a;
}
:
:
}
しかし、上記のコードはJDK1.1.6でコンパイルエラーになってしまいます.
ちゃんと全てのコンストラクタで初期化されるはずなのに・・
これはコンパイラのバグで、JDK1.2β4以降では正しくコンパイルされます.
ちなみにJBuirder1.1(だっけ)でも正常にコンパイルされました.
privateメンバは「そのクラスからしかアクセスを許さない」ですね.
私などはそのオブジェクトからしかアクセスを許さないものと勘違いしておりましたので、
以下のコードは駄目と思っていました.
class Foo {
private int i;
Foo(){
i = 1;
}
public void add(Foo b) {
i += b.i; //←1
}
public static void main(String[] arg) {
Foo a=new Foo();
Foo b=new Foo();
a.add(b);
System.out.println(a.i); //←2
}
}
1や2では実行位置から見てその属しているオブジェクトとは異なるオブジェクトのprivateメンバを参照していますが、
自分のクラスのメンバである為問題ないのでした.
じょーしきですか?私は結構勘違いしてましたが・・
これが出来るならコピーコンストラクタもちゃんと出来るんやん.(後述)
「識別子nullはオブジェクトである.なぜなら、nullオブジェクトに対するメソッド呼び出しが可能だからである.」
普通いわゆるnullポインタ(nullが代入された/または初期化されていないオブジェクト変数)に対して、
メソッド呼び出しを行おうとするとNullPointerExceptionがthrowされます.
しかし、staticメソッドについては対象がnullであっても呼び出すことが可能です.
以下の呼び出しはすべてOKです.
class A {
static void staticMethod() {
}
}
:
:
A.staticMethod();
A a;
a.staticMethod(); ←null pointer
((A)null).staticMethod();
また、非staticなメソッドについても、以下の呼び出し文でコンパイルが通ってしまうことを考えると、
やはりnullオブジェクトの存在を認めないわけにはいかないといったところです.
(まあ、コンパイル時のチェックは型情報しか見ないせいでもありますが)
((A)null).method();
一般的には自分に来たAWTイベントを他のコンポーネントに転送する時に使用しますが、
このメソッドを使う事でユーザの操作を模倣させる事もできます.
マクロ記録→実行なんかにも使えます.
→dispatchEvent()よりToolkit#getSystemEventQueue().postEvent()の方がよりよいです.
postEvent()はメッセージキューにpostするのでその呼び出しはすぐに戻ってきます.
ドラッグ対象コンポーネントをドラッグ中ラバーバンド(Window)上にaddして表示させようとしたら、
ドラッグイベントが継続してきてくれませんでした.
(再度ラバーバンドに対してドラッグしなければならない)ちゃんと動くときもあったんですが?
通常、特定のオブジェクトが特定のクラスのインスタンスか否かを調べるにはinstanceof演算子を用います.
しかし、instanseof演算子は型名をハードコーディングしなければならない為、調べる対象の型を動的に変化させられません.
ClassクラスのisInstance()メソッドを使用すれば実行時に調査する型を変更できます.
例:指定オブジェクトがパラメタに指定したクラスの型か否かを調べる.
boolean check(object o,Class clazz) {
return(clazz.isInstanse(o));
}
Objectクラスのclone()メソッドはオブジェクトコピーを行う為の補助メソッドです.
このメソッドはprotectedである為外部のクラスから使用できません.
また、Objectクラスのclone()はシャローコピー(参照の解決をしない浅いコピー)で実装されています.
つまり、複製されたオブジェクトが内部で参照するオブジェクトが同じになると言う事です.
なぜなら、そのオブジェクトの参照オブジェクトまでコピーしようとすると循環参照(お互いに参照)していた場合に、
無限ループになってしまうため、どこまでコピーすべきかはプログラマが判断すべきである為です.
(もっと単純にデフォルトでディープコピーをサポートすると
シャローコピーができなくなる事と、性能/効率面からと言ったほうがいい?)
従って、オブジェクトが参照している他のオブジェクトのコピーはプログラマが実装する必要があります.
そのような理由から、clone()を利用するには、そのクラスでpublicなclone()を再定義する事が義務づけられています.
(これは別にclone()と言う名前でなくてもいいんですが、この名前にした方が良いでしょう.
でも、違う名前にすれば復帰値の型を自分の型にできるので便利ですが)
実際には再定義と言うよりは新規にコピーロジックを実装するといったほうがいいんですが、
オブジェクトのコピーを行う為にはスーパークラスのprivateメンバもコピーしなければならないので、
プログラマが自力でコピーを実装するのは大変です.
コピーを実装する時は、一般的に下記のようにします.
//Hogehogeはこのメソッドの存在するクラスです
public Object clone() {
try {
//最終的にObjectクラスのclone()を呼ぶ
Hogehoge t = (Hogehoge)super.clone();
t.抽象フィールド = (抽象フィールドの型)this.抽象フィールド.clone();
:
:
return(t);
} catch(CloneNotSupportedException e) {
throw new InternalError();//クローン失敗はエラーとして再送出
}
}
例外について補足すれば、super.clone()はCloneNotSupportedExceptionを投げます.
これが投げられるのはそのクラスがCloneableインターフェイスを実装していなかった時です.
CloneableはObjectクラスのclone()を呼び出す事を明示する為の印ですので、super.clone()を実装する
クラスは必ずこのインターフェイスを実装します.
(逆に言えばsuper.clone()を呼ばずに複製を生成するならcloneableは不要ですね)
上記のような実装の場合、clone()を呼び出す事でコピーを生成すると言う事でしたが、ちょっと見苦しい
書き方でもあります(一応これが標準なんですが).
C++にあったようなコピーコンストラクタを作成しておくと、コピーの実行処理が以下のように記述できます.
Foo a = new Foo("特定のデータ");
Foo b = new Foo(a);
これでaとbはデータを共有しない、データの中身が等しい別のオブジェクトとなります.
ちなみにclone()の場合は以下のようになります.
Foo a = new Foo("特定のデータ");
Foo b = (Foo)a.clone();
どちらが見やすいかと言う視点ではなんとも言えないところですが、「複製オブジェクトを生成する」
と言うイメージではコピーコンストラクタの方が自然でしょう.
コピーコンストラクタは以下のように実装します.
public Foo(Foo f){
フィールド=f.フィールド;
//フィールドに対しコピーコンストラクタの呼び出し
抽象フィールド=new 抽象フィールド(f.抽象フィールド);
}
しかしこの方法ではFooに対してFooのサブクラスが渡されると正常に機能しないという問題点があります.
解決する方法としては、classをfinalにしてしまう方法もあります.
このようにクラスの種類を選ぶ事になりますが、利用する側にとっては自然な記述ができると言う意味で価値があるのではないでしょうか?
以下のメソッドで外部プログラムの起動が行えます.
ただし、これはコードをプラットフォーム依存にします.
Process p = Runtime.getRuntime().exec(command);
p.waitFor(); //外部コマンドの終了を待ちます
System.out.println("return code: "+ p.exitValue());
実行したアプリケーションの標準出力を奪い取るには、Process#getInputStream()を使います.
javacはエラーを標準エラー出力に書き込みます.従って
NT 上では次のように使用します.
javac myfile.java 2> errors.txt
Win95/98上では次のように記述します.
javac -J-Djavac.pipe.output=true myfile.java > errors.txt
ちなみにWin95/98で実行時例外をファイルにリダイレクト出来るようにするには、System.setErr(System.out);をmain先頭に記述しておきます。
でももっと根本的な解決があります。cygwin32をインストールしましょう。bashなどの高機能シェルが使えます。
ファイルを選択させる部分を自分で実装する場合などにファイル一覧の取得は必須です.
以下にその方法を示します.
String[] files=new File("c:/").list(); または
File[] files=new File("c:/").listFiles();
Fileクラスは「ディレクトリパス名」または「ファイルパス名」を管理するクラスです.
File#list()メソッドはFileオブジェクトにディレクトリ名が設定されている時のみ機能します.
尚、File#list()メソッドはパラメタとしてFileNameFilterを渡す事で、フィルタリングを行えますが、
その場合、自分でフィルタオブジェクトを実装する事になります.
ディレクトリ内のファイル名毎にFileNameFilter#accept()が呼び出されるので、
そのファイルを一覧に追加するか否かを返すように実装します.
また、(私はWindows環境なのですが)ルートのドライブ名の一覧を取得するのは以下です.
File[] roots = File.listRoots();
置き場所についてはアプリケーションでは好きな場所でかまいません(フルパスでも指定できるので)が、
コード中ではそのオブジェクトのクラスが存在するパスからの相対で指定するのが常套でしょう.
★先頭に/を付けた場合はクラスパスまたはCODEBASEからの相対となります.
IE4でjarにする場合はうまく読んでくれないみたいです.
Imageの生成はToolkit.getDefaultToolkit.createImage(ImageProducer)で行えます.
ImageProducerとしてはMemoryImageSourceを渡せばオフスクリーンイメージを生成できます.
MemoryImageSourceにはアニメーション操作メソッドなんかも用意されています.
MemoryImageSourceに描画するには、これの生成元になるint配列に対して色コード(TRGB4Byte)
を設定してからImage#flushを行えば配列→MemoryImageSource→Imageと反映されます.
MediaTrackerというクラスがあります.これにリソースを登録しておき、
waitForAll()/checkID()と呼び出すと、登録した全てのリソースのロードを行い、
ロードの終了までブロックします.
これを使えば、drawImageのタイミングでloadしに行ってアニメーションの動きが
不自然になる、などを回避できます.
int id = 0;
MediaTracker mt = new MediaTracker(this);
Image image= getImage("URL");
mt.addImage(image,id);
try {
mt.waitForID(id);
} catch(InterruptedException e) {
}
//mt.checkID(id);
複数の関連性の無いクラスで同一のオブジェクトを参照したい場合、 擬似Singletonパターンを用いれば手軽に実現できます.
public class Singleton {
private static Singleton singleton_;
private Singleton() {
:
:
}
public static Singleton getSingleton() {
if (singleton_ == null) {
singleton_ = new Singleton();
}
return singleton_;
}
}
超基本の実装例ですが、これでどこからでもSingleton.getSingleton()を呼び出す
事で同じクラスへの参照を得る事ができます.
元々インスタンスの数を制限するという意味のデザインパターンですが、どちらかというと
同じオブジェクトを共有させる手段としての方が使い道がある気がします.
尚、Javaでは全てのメンバをstaticとする事で同様の効果を得られますが、
それでは継承ができなくなるので発展的にはこちらの方が良いでしょう。(コンストラクタprivateとしてしまうとそれもできないのですが)
Windowsではjarファイルをダブルクリックするだけで特定のクラスのmainメソッドを実行することができます.
jarファイルに含めるManifest.mfファイルに次のように記述しておけばOKです.
Manifest-Version: 1.0 Main-Class: 起動したいクラス名
jarコマンドに-mオプションで明示的にManifestファイルを指定する必要があります.
指定しないと自動的にManifestファイルが生成されますが、この場合は最低限の記述しかされていません.
通常同名で同一のパラメタ郡を持つメソッドの戻り値のみを変更してオーバーライドしようとするとコンパイルエラーとなりますが、interfaceの実装時に関しては例外的に許されています.
→うっ嘘だった...
interface Foo {
public int method();
}
class Bar implements Foo {
public void method() {
return 0;
}
}
なぜこのようなことが可能なのかの本当の理由は知らないのですが、とりあえずBeanにおけるイベントリスナのメソッドとしてvoid以外の戻り値を持つメソッドを宣言していても、BDKによって実際に生成されるアダプタクラス(実装クラス)ではvoidに変更されています.
ちなみにBeanのイベントで戻り値を返させることができないのは、Bean同士を実際に繋ぎ合わせるときに、接続先のBeanのメソッドの復帰値の型が、リスナの復帰値の型と同一とは限らないためです.
上記のようにBeanBoxでは実際に実装クラスの戻り値の型をvoidに変更するようなことが行われているのですが、JDK1.2で上のコードをコンパイルするとコンパイルエラーとなってしまった...
BDKが例外的に行っていることらしいです.しかしどうやっているんだろう.また気になるのに調べる時間がないようなことが増えた...
自分のクラスのインスタンス同士の同値関係を定義したい時、equals()は次のような実装をすることが多いようです.
class A {
int a_;
public boolean equals(Object other) {
if (!(other instanceof A)) return false;
return a_ == ((A)other).a_;
}
}
しかし、これではそのサブクラスで追加されたメンバも比較したくて、さらにequals()をオーバーライドした場合に、以下のような不都合が出てしまいます.
class B {
int b_;
public boolean equals(Object other) {
if (!(other instanceof B)) return false;
if (b_ != ((B)other).b_) return false;
return super.equals(other);
}
}
a.equals(b) = true
b.equals(a) = false
そこで、オーバーライドされても対称律/推移律を壊さない実装方法を以下に挙げます.
class AA {
int a_;
public final boolean equals(Object other) {
if (!(other instanceof AA)) return false;
if (getClass() != other.getClass() &&
getClass().isInstance(other)) {
return ((AA)other).equalsFields(this);
}
}
return equalsFields(other);
}
protected boolean equalsFields(Object other) {
if (a_ != ((AA)other).a_) return false;
return true;
}
}
class BB extends AA {
int b_;
// b_は一致判定対象外
}
class CC extends BB {
int c_;
protected boolean equalsFields(Object other) {
// オーバーライドするときのみ必要
if (!(other instanceof CC)) return false;
// この場合にb_も比較対照とするかは物によると思いますが.
if (b_ != ((CC)other).b_) return false;
if (c_ != ((CC)other).c_) return false;
return super.equalsFields(other);
}
}
さて、この方法により一本のクラスツリー上のオブジェクト同士では完全に対称律/推移律が保たれるわけですが、これでもまだだめなケースがありました.
上記例では、クラスAAのサブクラスDDを定義した場合に、d.equals(c)→true、c.equals(d)→falseとなってしまうのです.
原因は上記の条件式では「自分のオブジェクトの直属のサブクラスか否か」しかチェックされないからです.
仕方がないので取り敢えず、完全な方法を以下に挙げておきます.
しかし、この方法ではequals()の同値関係を変更したい人はprotectedフィールドequalsTypeをコンストラクタで書き換える必要があります.
ここまでしなければサブクラスでの同値関係の変更は行えないということから、Sunは「一度定義した同値関係をサブクラスで変更するような時は別のメソッドを用意し、equals()のオーバーライドは行ってはならない」という方針のようです.
実は(equals(other) && other.equals(this))の条件をうまく実装すれば簡単にできました.
しかし、この場合、同じ条件を二回比較することになるのでどうなんでしょう.
実はClass#isAssignableFrom()等を呼ぶ方が遅いと思われるので、こっちのほうが手軽でいいですね。
// 完全版
class A {
// 同値関係を定義しているクラス
protected Class equalsType_;
// *************************
int a_;
public A(int a) {
a_ = a;
equalsType_ = A.class; // ********************
}
public final boolean equals(Object other) {
if (!(other instanceof A)) return false;
if (!((A)other).equalsType_.isAssignableFrom(equalsType_)) {
return ((A)other).equalsFields(this);
}
return equalsFields(other);
}
protected boolean equalsFields(Object other) {
if (a_ != ((A)other).a_) return false;
return true;
}
}
class B extends A {
// Aで定義した同値関係に従属
// スーパークラスのフィールドのみ比較される
int b_;
public B(int a, int b) {
super(a);
b_ = b;
}
}
class C extends B {
// 新たな同値関係の定義
int c_;
public C(int a, int b, int c) {
super(a, b);
c_ = c;
equalsType_ = C.class; // ********************
}
protected boolean equalsFields(Object other) {
// オーバーライドするときのみ必要
if (!(other instanceof C)) return false;
// この場合にb_も比較対照とするかは物によると思いますが.
if (b_ != ((C)other).b_) return false;
if (c_ != ((C)other).c_) return false;
return super.equalsFields(other);
}
}
class D extends C {
// Cで定義した同値関係に従属
int d_;
public D(int a, int b, int c, int d) {
super(a, b, c);
d_ = d;
}
}
class E extends A {
int e_;
public E(int a, int e) {
super(a);
e_ = e;
}
}
JavaおよびHotJavaは米国Sun Microsystems社の商標または登録商標です.