Java開発Tips

Javaに関する作業Tipsを紹介します。 数が増えてきたら、別に分けるかもしれません。

クローンの生成

2005年01月08日 記述

Javaでオブジェクトをコピーする場合、シャローコピーとディープコピーの2種類があることを理解しなくてはいけません。 シャローコピーはオブジェクトの参照のみを複製してオブジェクト自体は同一のものを指すのに対し、 ディープコピーはオブジェクト自体を複製します。


Objectクラスのclone()メソッドは、シャローコピーを行ないます。 そのため、参照型のオブジェクト変数(String,一般的なClass型など)は、参照先のオブジェクトの複製は行なわれません。 一方、組み込み型のオブジェクト変数(short,int,doubleなど)は、参照先は存在しないので、 自然とディープコピーとなります。 そのため、オブジェクトの参照先も含めた完全な複製を行ないたい場合は、 Objectクラスのclone()メソッドをオーバライドして、参照先のオブジェクトの複製を、 プログラマが行なう必要があります。 従って、clone()メソッドを利用する場合は、publicなclone()メソッドをオーバライドする必要があります。 (Objectクラスのclone()メソッドはprotectedです)

参照先のクラスにもclone()メソッドのオーバライドを行い、更に参照先がある場合はそのクラスにも同様の内容を行ないます。 これは、参照先のオブジェクトに参照型の変数がなくなるまで、繰り返します。 場合によっては、参照先のオブジェクトが、既に辿ってきたクラスのオブジェクトを参照することもあります。 参照が循環しないように、適切な処理を記述しましょう。もし、以後値が変わらないようなオブジェクトがあれば、 それは複製する必要はありません。

参考例


class Charactor implements Cloneable{
    private int id;       // 基本型
    private String name;  // String型
	public Skill skill;   // クラス型

    public Charactor(int id, String name) {
        this.id = id;
        this.name = name;
        skill = new Skill();
    }

    public Object clone() {
        try {
            Charactor chara = (Charactor)super.clone();
    //        chara.name = new String(name);
    //        chara.skill = (Skill)this.skill.clone();
            return chara;
        } catch(CloneNotSupportedException e) {
            throw new InternalError();
        }
    }

    public int getId() {
        return id;
    }
    public void setId(int id) {
        this.id = id;
    }
	public String getName() {
		return name;
    }
	public void setName(String name) {
		this.name = name;
    }

    public static void main(String[] args) {
        Charactor ch01 = new Charactor(1, "taro");
        Charactor ch02 = (Charactor)ch01.clone();

        ch01.skill.setName("notDefault");
        ch01.setName("jiro");
        ch01.setId(2);

        System.out.println("ch01-skillName = "+ch01.skill.getName());
        System.out.println("ch01-name = "+ch01.getName());
        System.out.println("ch01-id = "+ch01.getId());
        System.out.println("--------------------------------------");
        System.out.println("ch02-skillName = "+ch02.skill.getName());
        System.out.println("ch02-name = "+ch02.getName());
        System.out.println("ch02-id = "+ch02.getId());
    }
}

class Skill implements Cloneable{
    private String name;

    public Skill() {
        name = "defaultSkill";
    }

    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }

    public Object clone() {
        try {
            //最終的にObjectクラスのclone()を呼ぶ
            Skill skill = (Skill)super.clone();
            skill.name = new String(name);
            return skill;
        } catch(CloneNotSupportedException e) {
            throw new InternalError();
        }
    }
}

mainメソッドでは、Charactorオブジェクトを作成して、その参照をch01に代入します。 そしてそのオブジェクトの複製の参照をch02に代入します。 次にch01の参照先オブジェクトの値を変更し、最後に両オブジェクトの値を出力します。

Charactorクラスのclone()メソッド内でSkillクラスの複製処理を省くと、 Skillオブジェクトのnameの値がch01とch02で同じになります。 オブジェクト生成後、別の値を代入したのに同じ値が出力されるのは、参照しているオブジェクトが同一であるためです。 一方、Charactorクラスのidは、きちんと違う値を返しています。 それは、idは参照型ではなく実体型なので、オブジェクトごとに別の格納先が用意されているからです。

最後に難しいのはStringオブジェクトです。本来clone()メソッド内で、複製処理を追記しないと複製されないのですが、 ch01.setName("jiro")で"jiro"という新しいStringオブジェクトが生成され、 その参照先がCharactorクラスのオブジェクト変数nameに代入されるため、 結果として複製されているのです。 実際、Stringオブジェクトの文字列値が変更される場合、Stringオブジェクト自身が新規に生成されるので、 Stringオブジェクトを明示的に複製する必要がないように思えます。 ただ、当然トラブルもあるかと思いますので、きちんと複製(Stringオブジェクトの新規作成)しておきましょう。

ここからは、イメージ的な視点から見ようと思います。 上にある図は、上記のソースコードで記述されたオブジェクト関連図です。 このように、Charactorオブジェクト(赤枠)は、Stringオブジェクト(橙枠)、 SKillオブジェクト(緑枠)、Skillオブジェクト内で参照されるStringオブジェクト(青枠)を 参照先に持っています。そのため、Charactorオブジェクトを完全に複製するためには、 これら全てのオブジェクトを複製する必要があります。 しかし、標準のclone()メソッドはそのオブジェクト自身しか複製できません。 Charactorクラスのclone()メソッドなら、赤枠のオブジェクトのみ複製され、 他の色枠のオブジェクトは複製されません。 そのため、標準のclone()メソッド内に、先に述べたような複製のための追加処理を加える必要があるのです。

最後に、オブジェクトの複製処理を書くにあたり、重要なポイントをまとめてみました。


Javaプラグインの利用(Firefox)

2005年01月10日 記述

以下のリンク先から、JavaのPluginをダウンロードして下さい(JREが相当します)。 Firefoxの場合は、一度ファイルをダウンロードしてから実行する、オフラインタイプを選択します。 ダウンロードマネージャを利用している場合は、ダウンロード完了後ダウンロード履歴を見ると、 ファイル名の右側に「開く」というメニューがありますので、 そこを押下すると自動的にインストール処理が始まります。 後はメニューに従い、進んでいけば良いです。


Javaアプレットの利用

2005年01月10日 記述

Javaアプレットの利用には、複数のタグ利用方法があり、 各ブラウザごとに対応状況が異なるという、非常に嫌な環境があります。

伝統てきなappletタグは現在非推奨であり、代わりにobjectタグの利用が推奨されています。 embedタグもよく利用されていますが、このタグはIE,NNが独自に拡張したタグであり、 W3Cでは定義されていません。

どの方法を利用するか、各人の嗜好もあるでしょうが、 うちは、解釈の違いこそあるもののW3Cにて正統なObjectタグによる処理を選択しました。 Objectタグの違いは、IEとその他のブラウザにほぼ分けることが出来ます(IEの解釈が正しくないのではありません)。 そこで、javascriptを用いてブラウザを判別して、各々対応するObjectタグを出力しています。

MSIE向けのObjectタグ

ブラウザ 確認したバージョン
MSIE 6.0

<object 
    classid="clsid:8AD9C840-044E-11D1-B3E9-00805F499D93"
    width="550"
    height="550"
    codebase="http://java.sun.com/products/plugin/autodl/jinstall-1_4_0_01-win.cab#Version=1,4,0,10">
  <param name="code" value="com.omoshiro.nyanja.client.SampleApplet">
  <param name="archive" value="nyanja_clt_local.jar">
  表示できません
</object>

Mozilla(NN,Firefox)/Opera向けのObjectタグ

ブラウザ 確認したバージョン
Firefox 1.0
NN 7.0
Opera 7.03

<object 
    codetype="application/x-java-applet"
    archive="nyanja_clt_local.jar"
    codebase="./"
    classid="java:com.omoshiro.nyanja.client.SampleApplet"
    width="550"
    height="550">
  表示できません
</object>

トラックバック