FC2ブログ
HOME   »  2012年12月
Archive | 2012年12月

スポンサーサイト

上記の広告は1ヶ月以上更新のないブログに表示されています。
新しい記事を書く事で広告が消せます。

[Java]配列の比較

2つの配列が等しいかどうかを比較する方法はいくつかある。
public class Main {
    public static void main(String[] args) {
        int[] array1 = new int[]{0, 1, 2, 3, 4};
        int[] array2 = new int[]{0, 1, 2, 3, 4};
        System.out.println("==:" + (array1 == array2));
        System.out.println("equals:" + (array1.equals(array2)));
        System.out.println("Arrays.equals:" + (Arrays.equals(array1, array2)));
    }
}
上記では内容が同じint配列array1とarray2を比較している。

上記の実行結果は以下の通り。


==:false
equals:false
Arrays.equals:true


配列は参照型なので、比較演算子==では同一性(同じインスタンスかどうか)の比較となり、結果はfalseとなる。
配列のequalsメソッドはスーパークラスであるObjectクラスのequalsメソッドがそのまま実行されるが、これは比較演算子==による比較と等価である為やはり結果はfalseとなる。
Arraysクラスのequalsメソッドは等価性(内容が同じであるかどうか)の比較となり、結果はtrueとなる。

では、下記の例ではどうなるか。
public class Main {
    public static void main(String[] args) {
        Object[] array1 = new Object[]{new int[]{0, 1, 2, 3, 4}, new int[]{5, 6, 7, 8, 9}};
        Object[] array2 = new Object[]{new int[]{0, 1, 2, 3, 4}, new int[]{5, 6, 7, 8, 9}};
        System.out.println("==:" + (array1 == array2));
        System.out.println("equals:" + (array1.equals(array2)));
        System.out.println("Arrays.equals:" + (Arrays.equals(array1, array2)));
        System.out.println("Arrays.deepEquals:" + (Arrays.deepEquals(array1, array2)));
    }
}
上記の実行結果は以下の通り。


==:false
equals:false
Arrays.equals:false
Arrays.deepEquals:true


比較演算子==とequalsメソッドの結果は先の例と同じ。
Arraysクラスのequalsメソッドの結果が異なるが、Arraysクラスのequalsメソッドでは要素の比較は要素のequalsメソッドで行われる為、上記の例では要素=配列のequalsメソッドによる比較(同一性比較)となり、結果がfalseとなっている。
入れ子配列の要素まで等価性比較を行いたい場合はArraysクラスのdeepEqualsメソッドを使用する。上記の結果でもtrueとなっている。


スポンサーサイト

[Java]cloneメソッドによる配列のコピー

cloneメソッドを使用した配列のコピーについて。

○要素が基本型の場合
public class Main {
    public static void main(String[] args) {
        int[] srcArray = {0, 1, 2, 3, 4};
        int[] dstArray = srcArray.clone();
        if (srcArray == dstArray) {
            System.out.println("srcArray == dstArray");
        } else {
            System.out.println("srcArray != dstArray");
        }
        if (Arrays.equals(srcArray, dstArray)) {
            System.out.println("srcArray equals dstArray");
        } else {
            System.out.println("srcArray not equals dstArray");
        }
        dstArray[0] = 9;
        for (int i = 0; i < srcArray.length; i++) {
            System.out.println("srcArray[" + i + "] = " + srcArray[i] + "   " + "dstArray[" + i + "] = " + dstArray[i]);
        }
    }
}
3行目でint配列srcArrayを生成し、4行目でcloneメソッドを使用してint配列dstArrayにコピー。
5行目でsrcArrayとdstArrayが同一インスタンスかどうかを判定し、10行目でsrcArrayとdstArrayの内容が同じかどうかを判定。
15行目でdstArrayの0番目の要素の値を変更。

上記の実行結果は以下の通り。


srcArray != dstArray
srcArray equals dstArray
srcArray[0] = 0 dstArray[0] = 9
srcArray[1] = 1 dstArray[1] = 1
srcArray[2] = 2 dstArray[2] = 2
srcArray[3] = 3 dstArray[3] = 3
srcArray[4] = 4 dstArray[4] = 4


比較演算子==による比較結果はfalse(同一インスタンスではない)、コピー先の要素変更がコピー元の要素に反映されていないので、完全なディープコピーとなっている。

○要素が参照型の場合
まず、以下の様なクラスを作成する。
public class Hoge {
    public int number;
	
    public Hoge(int value) {
        number = value;
    }
}
そして、上記のHogeクラスを下記の様に使用してみる。
public class Main {
    public static void main(String[] args) {
        Hoge[] srcArray = new Hoge[5];
        for (int i = 0; i < srcArray.length; i++) {
            srcArray[i] = new Hoge(i);
        }
        Hoge[] dstArray = srcArray.clone();
        if (srcArray == dstArray) {
            System.out.println("srcArray == dstArray");
        } else {
            System.out.println("srcArray != dstArray");
        }
        if (Arrays.equals(srcArray, dstArray)) {
            System.out.println("srcArray equals dstArray");
        } else {
            System.out.println("srcArray not equals dstArray");
        }
        dstArray[0].number = 9;
        for (int i = 0; i < srcArray.length; i++) {
            System.out.println("srcArray[" + i + "].number = " + srcArray[i].number + "   " + "dstArray[" + i + "].number = " + dstArray[i].number);
        }
    }
}
3行目から6行目でHogeクラスの配列srcArrayを生成し、7行目でcloneメソッドを使用してHogeクラスの配列dstArrayにコピー。
8行目でsrcArrayとdstArrayが同一インスタンスかどうかを判定し、13行目でsrcArrayとdstArrayの内容が同じかどうかを判定。
18行目でdstArrayの0番目の要素のフィールド変数の値を変更。

上記の実行結果は以下の通り。


srcArray != dstArray
srcArray equals dstArray
srcArray[0].number = 9 dstArray[0].number = 9
srcArray[1].number = 1 dstArray[1].number = 1
srcArray[2].number = 2 dstArray[2].number = 2
srcArray[3].number = 3 dstArray[3].number = 3
srcArray[4].number = 4 dstArray[4].number = 4


比較演算子==による比較結果はfalse(同一インスタンスではない)、コピー先の要素変更がコピー元の要素に反映されているので、配列そのものはディープコピーだが要素はシャローコピーとなっている。


[Java]メソッドの引数がStringの場合の挙動

以前の記事「値渡しと参照渡し」で書き切れなかった、メソッドの引数がStringの場合の挙動について。
public class Main {
    public static void main(String[] args) {
        String testString = "ABC";
        System.out.println("メソッドに渡す前の文字列:" + testString);
        method(testString);
        System.out.println("メソッドに渡した後の文字列:" + testString);
    }

    private static void method(String testString) {
        testString = testString + "DEF";
        System.out.println("メソッドで処理した後の文字列:" + testString);
    }
}
3行目で変数testStringに"ABC"を代入し、5行目でmethodメソッドに引数として渡している。
methodメソッドの中では、仮引数testStringに"DEF"を付加している。

上記の実行結果は以下の通り。


メソッドに渡す前の文字列:ABC
メソッドで処理した後の文字列:ABCDEF
メソッドに渡した後の文字列:ABC


Stringは参照型の為、引数として渡される(コピーされる)のは文字列そのものではなく参照情報である。
なので他の参照型と同様に、呼び出し先での処理が呼び出し元にも反映されそうに思うが、メソッドに渡す前と後で文字列が変化していない。

実はStringはイミュータブル(immutable)なオブジェクトで、生成した後に変化させること(文字列を追加で付加する等)ができない。
では、10行目の「testString = testString + "DEF";」はどういう処理になるのか
これは、右辺のtestString(処理前)の参照先文字列"ABC"に文字列"DEF"を付加した新たな文字列を生成し、その文字列に対する参照情報を左辺のtestString(処理後)に代入している。
従って、呼び出し元の実引数testStringの参照先文字列("ABC")と呼び出し先の仮引数testStringの参照先文字列("ABCDEF")は違う文字列になり、上記の実行結果の様になる。

呼び出し元で生成した文字列に対して呼び出し先で処理を行いたい場合も当然あり得るが、その場合はStringBuilderクラスやStringBufferクラスを使用する。
StringBuilderクラスやStringBufferクラスはミュータブル(mutable)なので、生成後に変化させることができる。
public class Main {
    public static void main(String[] args) {
        StringBuilder stringBuilder = new StringBuilder("ABC");
        System.out.println("メソッドに渡す前の文字列:" + stringBuilder.toString());
        method(stringBuilder);
        System.out.println("メソッドに渡した後の文字列:" + stringBuilder.toString());
    }

    private static void method(StringBuilder stringBuilder) {
        stringBuilder.append("DEF");
        System.out.println("メソッドで処理した後の文字列:" + stringBuilder.toString());
    }
}
3行目で文字列"ABC"からStringBuilderクラスのインスタンスを生成し、5行目でmethodメソッドに引数として渡している。
methodメソッドの中では、仮引数stringBuilderに"DEF"を付加している。

上記の実行結果は以下の通り。


メソッドに渡す前の文字列:ABC
メソッドで処理した後の文字列:ABCDEF
メソッドに渡した後の文字列:ABCDEF


10行目で呼び出しているStringBuilderクラスのappendメソッドは文字通り文字列を付加する処理である為、仮引数stringBuilderの参照先は変わらない。
従って、呼び出し先での仮引数への処理=呼び出し元の実引数への処理となり、methodメソッドに渡す前と後で呼び出し元の文字列が変化している。


[Java]値渡しと参照渡し

引数の値渡しと参照渡しについて。

メソッドに引数を渡す際、booleanやint等の基本型(プリミティブ型)は値渡し、クラスインスタンス等の参照型(オブジェクト型)は参照渡しとなる。

○引数が基本型の場合
public class Main {
    public static void main(String[] args) {
        int i = 0;
        System.out.println("メソッドに渡す前の値:i = " + i);
        method(i);
        System.out.println("メソッドに渡した後の値:i = " + i);
    }

    private static void method(int i) {
        i = i + 1;
        System.out.println("メソッドで処理した後の値:i = " + i);
    }
}
3行目で変数iに0を代入し、5行目でmethodメソッドに引数として渡している。
methodメソッドの中では、仮引数iの値に+1している。

上記の実行結果は以下の通り。


メソッドに渡す前の値:i = 0
メソッドで処理した後の値:i = 1
メソッドに渡した後の値:i = 0


methodメソッドで処理した後のmethodメソッドの仮引数iの値は、渡された時の値0に+1しているので1となっている。
一方、methodメソッド呼び出し元の変数iの値は、methodメソッドに渡す前と後で変わらず0のままである。

呼び出し元の変数iとmethodメソッドの仮引数iは独立した別々の変数であり、それぞれ個別に値を持っている。
引数として渡す際には呼び出し元の変数iの値がmethodメソッドの仮引数iに代入され(値がコピーされ)、methodメソッド内での処理は仮引数iの値に対してのみの処理となる為、呼び出し元の変数iの値には影響を及ぼさない。

これが値渡しの場合の動作となる。

○引数が参照型の場合
まず、以下の様なクラスを作成する。
public class Hoge {
    public int i;

    public Hoge() {
        i = 0;
    }
}
そして、上記のHogeクラスを下記の様に使用してみる。
public class Main {
    public static void main(String[] args) {
        Hoge hoge = new Hoge();
        System.out.println("メソッドに渡す前の値:hoge.i = " + hoge.i);
        method(hoge);
        System.out.println("メソッドに渡した後の値:hoge.i = " + hoge.i);
    }

    private static void method(Hoge hoge) {
        hoge.i = hoge.i + 1;
        System.out.println("メソッドで処理した後の値:hoge.i = " + hoge.i);
    }
}
3行目でHogeクラスのインスタンスを生成し、5行目でmethodメソッドに引数として渡している。
methodメソッドの中では、引数として渡されたHogeクラスのインスタンスのフィールド変数iに+1している。

上記の実行結果は以下の通り。


メソッドに渡す前の値:hoge.i = 0
メソッドで処理した後の値:hoge.i = 1
メソッドに渡した後の値:hoge.i = 1


methodメソッドで処理した後のフィールド変数iの値は、渡された時の値0に+1しているので1となっている。
一方、methodメソッド呼び出し元のフィールド変数iの値は、methodメソッドに渡す前は0だが、渡した後は1となっている。

呼び出し元の変数hogeとmethodメソッドの仮引数hogeは独立した別々の変数であり、それぞれ個別に値を持っている(基本型と同じ)。
引数として渡す際には呼び出し元の変数hogeの値がmethodメソッドの仮引数hogeに代入される(ここも基本型と同じ)。
基本型と参照型で異なるのは、基本型が代入された値そのものを保持しているのに対して参照型は代入されたインスタンスの参照情報(どのインスタンスを指しているか)を保持している、という点である。

引数として渡す際に呼び出し元の変数から呼び出し先の仮引数に代入される(コピーされる)値はこのインスタンスの参照情報であり、インスタンスそのものではない。従って、呼び出し元の変数hogeが参照しているHogeクラスのインスタンスと呼び出し先の仮引数hogeが参照しているHogeクラスのインスタンスは同一である。
その為、methodメソッドでのフィールド変数iの処理=呼び出し元のフィールド変数iの処理となり、methodメソッドに渡す前と後で呼び出し元のフィールド変数iの値が変化することになる。

では、下記ではどうなるか。
public class Main {
    public static void main(String[] args) {
        Hoge hoge = new Hoge();
        System.out.println("メソッドに渡す前の値:hoge.i = " + hoge.i);
        method(hoge);
        System.out.println("メソッドに渡した後の値:hoge.i = " + hoge.i);
    }

    private static void method(Hoge hoge) {
        hoge = new Hoge();
        hoge.i = hoge.i + 1;
        System.out.println("メソッドで処理した後の値:hoge.i = " + hoge.i);
    }
}
上記の実行結果は以下の通り。


メソッドに渡す前の値:hoge.i = 0
メソッドで処理した後の値:hoge.i = 1
メソッドに渡した後の値:hoge.i = 0


上記の場合は、10行目でmethodメソッドの仮引数hogeに新たに生成したHogeクラスのインスタンスの参照情報を代入しており、この時点で呼び出し元の変数hogeと呼び出し先の仮引数hogeが参照しているHogeクラスのインスタンスが違うものになる
その為、methodメソッドでのフィールド変数iの処理は呼び出し元のフィールド変数iの値には影響を及ぼさない。

以上、基本型と参照型での動作の違いを書いたが、参照型でもStringではちょっと違った動作となる。
が、長文になってしまったのでそれは別の記事で。

ところで、ここまで散々「参照渡し」と書いてきたが実はJavaにあるのは「値渡し」だけで、「参照渡し」はない。
上に書いた通り参照型引数で渡されるのは「参照情報」であり、渡し方も実引数から仮引数への「参照情報のコピー」なので、正確にはこれも「値渡し」である。
参照型引数の動作を理解して明示的に「呼び出し元で生成したインスタンスと同一のインスタンスを呼び出し先で使用する」のを目的とするのであれば、便宜的に「参照渡し」と言っても大きな問題はないと思うが、「Javaには値渡ししかない」という点から参照型引数での「インスタンスのコピー」を期待すると想定外の動作となってしまうことがあるので注意が必要。


プロフィール

まさお

Author:まさお
プログラミングは趣味レベルなので、お手柔らかに。

ブログランキング
ブログランキング参加中。是非クリックお願いします。


にほんブログ村 IT技術ブログ Androidアプリ開発へ

人気ブログランキングへ

ブログランキング



ブログ王

ブログランキング【ブログの惑星】

プログラム人気ブログランキング
最新記事
最新コメント
最新トラックバック
月別アーカイブ
カテゴリ
検索フォーム
RSSリンクの表示
リンク
ブロとも申請フォーム
QRコード
QR
上記広告は1ヶ月以上更新のないブログに表示されています。新しい記事を書くことで広告を消せます。