FC2ブログ
HOME   »  Java
Category | Java

スポンサーサイト

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

[Java]リフレクションを使用したスーパークラスのメンバー情報の取得

以前の記事「リフレクションを使用したクラスメンバー情報の取得」で書いたgetFieldsメソッド、getMethodsメソッドではスーパークラスのメンバー情報を取得できるが、対象となるのはpublicメンバーのみである。

スーパークラスのpublicメンバー以外のメンバー情報を取得したい場合、対象クラスのスーパークラスが既知であればそのスーパークラスについてのClassクラスのインスタンスを直接取得し、そこからメンバー情報を取得できるが、対象クラスの情報がない場合(プログラム実行時に初めて対象クラスが指定される等)は、ClassクラスのgetSuperclassメソッドを使用する。

「リフレクションを使用したクラスメンバー情報の取得」で例として書いたSubクラスについて、以下の様にスーパークラスのメンバー情報を取得する。
public class Main {
    public static void main(String[] args) {
        Class<?> clazz;
        Class<?> superClazz;
        Field[] fields;
        Constructor<?>[] constructors;
        Method[] methods;
        try {
            clazz = Class.forName("reflectiontest.subpackage.Sub");
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
            return;
        }
        superClazz = clazz.getSuperclass();
        if (superClazz != null) {
            fields = superClazz.getDeclaredFields();
            System.out.println("getDeclaredFields()メソッドで取得したスーパークラスのフィールド");
            for (Field field : fields) {
                System.out.println(field.getName());
            }
            constructors =superClazz.getDeclaredConstructors();
            System.out.println("");
            System.out.println("getDeclaredConstructors()メソッドで取得したスーパークラスのコンストラクタ");
            for (Constructor<?> constructor : constructors) {
                System.out.println(constructor.getName());
            }
            methods = superClazz.getDeclaredMethods();
            System.out.println("");
            System.out.println("getDeclaredMethods()メソッドで取得したスーパークラスのメソッド");
            for (Method method : methods) {
                System.out.println(method.getName());
            }
        }
    }
}
まず9行目でSubクラスについてのClassクラスのインスタンスを取得し、14行目でgetSuperclassメソッドを使用してそのスーパークラス(今回の例ではSuperクラス)についてのClassクラスのインスタンスを取得している。
そして、以降でgetDeclaredFieldsメソッド、getDeclaredConstructorsメソッド、getDeclaredMethodsメソッドを使用してスーパークラスのメンバー情報を取得している。

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


getDeclaredFields()メソッドで取得したスーパークラスのフィールド
defaultSuperField
privateSuperField
protectedSuperField
publicSuperField

getDeclaredConstructors()メソッドで取得したスーパークラスのコンストラクタ
reflectiontest.superpackage.Super
reflectiontest.superpackage.Super
reflectiontest.superpackage.Super
reflectiontest.superpackage.Super
reflectiontest.superpackage.Super

getDeclaredMethods()メソッドで取得したスーパークラスのメソッド
defaultSuperMethod
privateSuperMethod
protectedSuperMethod
publicSuperMethod


スーパークラス(Superクラス)の全てのメンバー情報が取得できている。


スポンサーサイト

[Java]リフレクションを使用したクラスメンバー情報の取得

リフレクションを使用したクラスメンバー情報の取得について。

Classクラスで定義されているメソッドを使用して、任意のクラスのメンバー(フィールド、コンストラクタ、メソッド)の情報を取得することができる。

以下の様なクラス(Superクラスとそれを継承したSubクラス)がある。

○Superクラス
package reflectiontest.superpackage;

public class Super {
    int defaultSuperField;
    private int privateSuperField;
    protected int protectedSuperField;
    public int publicSuperField;

    //サブクラスからの暗黙的呼び出し用コンストラクタ
    protected Super() {
    }

    Super(int defaultValue) {
        defaultSuperField = defaultValue;
    }

    private Super(int defaultValue, int privateValue) {
        defaultSuperField = defaultValue;
        privateSuperField = privateValue;
    }

    protected Super(int defaultValue, int privateValue, int protectedValue) {
        defaultSuperField = defaultValue;
        privateSuperField = privateValue;
        protectedSuperField = protectedValue;
    }

    public Super(int defaultValue, int privateValue, int protectedValue, int publicValue) {
        defaultSuperField = defaultValue;
        privateSuperField = privateValue;
        protectedSuperField = protectedValue;
        publicSuperField = publicValue;
    }

    void defaultSuperMethod() {
        System.out.println("defaultSuperMethodが実行されました。");
    }

    private void privateSuperMethod() {
        System.out.println("privateSuperMethodが実行されました。");
    }

    protected void protectedSuperMethod() {
        System.out.println("protectedSuperMethodが実行されました。");
    }

    public void publicSuperMethod() {
        System.out.println("publicSuperMethodが実行されました。");
    }
}
○Subクラス
package reflectiontest.subpackage;

public class Sub extends reflectiontest.superpackage.Super {
    int defaultSubField;
    private int privateSubField;
    protected int protectedSubField;
    public int publicSubField;

    Sub(int defaultValue) {
        defaultSubField = defaultValue;
    }

    private Sub(int defaultValue, int privateValue) {
        defaultSubField = defaultValue;
        privateSubField = privateValue;
    }

    protected Sub(int defaultValue, int privateValue, int protectedValue) {
        defaultSubField = defaultValue;
        privateSubField = privateValue;
        protectedSubField = protectedValue;
    }

    public Sub(int defaultValue, int privateValue, int protectedValue, int publicValue) {
        defaultSubField = defaultValue;
        privateSubField = privateValue;
        protectedSubField = protectedValue;
        publicSubField = publicValue;
    }

    void defaultSubMethod() {
        System.out.println("defaultSubMethodが実行されました。");
    }

    private void privateSubMethod() {
        System.out.println("privateSubMethodが実行されました。");
    }

    protected void protectedSubMethod() {
        System.out.println("protectedSubMethodが実行されました。");
    }

    public void publicSubMethod() {
        System.out.println("publicSubMethodが実行されました。");
    }
}
そして、以下の様にリフレクションを使用して上記のSuperクラス、Subクラスのメンバー情報を取得する。
public class Main {
    public static void main(String[] args) {
        Class<?> clazz;
        Field[] fields;
        Constructor<?>[] constructors;
        Method[] methods;
        try {
            clazz = Class.forName("reflectiontest.superpackage.Super");
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
            return;
        }
        fields = clazz.getFields();
        System.out.println("getFields()メソッドで取得したSuperクラスのフィールド");
        for (Field field : fields) {
            System.out.println(field.getName());
        }
        fields = clazz.getDeclaredFields();
        System.out.println("");
        System.out.println("getDeclaredFields()メソッドで取得したSuperクラスのフィールド");
        for (Field field : fields) {
            System.out.println(field.getName());
        }
        constructors = clazz.getConstructors();
        System.out.println("");
        System.out.println("getConstructors()メソッドで取得したSuperクラスのコンストラクタ");
        for (Constructor<?> constructor : constructors) {
            System.out.println(constructor.getName());
        }
        constructors = clazz.getDeclaredConstructors();
        System.out.println("");
        System.out.println("getDeclaredConstructors()メソッドで取得したSuperクラスのコンストラクタ");
        for (Constructor<?> constructor : constructors) {
            System.out.println(constructor.getName());
        }
        methods = clazz.getMethods();
        System.out.println("");
        System.out.println("getMethods()メソッドで取得したSuperクラスのメソッド");
        for (Method method : methods) {
            System.out.println(method.getName());
        }
        methods = clazz.getDeclaredMethods();
        System.out.println("");
        System.out.println("getDeclaredMethods()メソッドで取得したSuperクラスのメソッド");
        for (Method method : methods) {
            System.out.println(method.getName());
        }
        try {
            clazz = Class.forName("reflectiontest.subpackage.Sub");
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
            return;
        }
        fields = clazz.getFields();
        System.out.println("");
        System.out.println("getFields()メソッドで取得したSubクラスのフィールド");
        for (Field field : fields) {
            System.out.println(field.getName());
        }
        fields = clazz.getDeclaredFields();
        System.out.println("");
        System.out.println("getDeclaredFields()メソッドで取得したSubクラスのフィールド");
        for (Field field : fields) {
            System.out.println(field.getName());
        }
        constructors = clazz.getConstructors();
        System.out.println("");
        System.out.println("getConstructors()メソッドで取得したSubクラスのコンストラクタ");
        for (Constructor<?> constructor : constructors) {
            System.out.println(constructor.getName());
        }
        constructors = clazz.getDeclaredConstructors();
        System.out.println("");
        System.out.println("getDeclaredConstructors()メソッドで取得したSubクラスのコンストラクタ");
        for (Constructor<?> constructor : constructors) {
            System.out.println(constructor.getName());
        }
        methods = clazz.getMethods();
        System.out.println("");
        System.out.println("getMethods()メソッドで取得したSubクラスのメソッド");
        for (Method method : methods) {
            System.out.println(method.getName());
        }
        methods = clazz.getDeclaredMethods();
        System.out.println("");
        System.out.println("getDeclaredMethods()メソッドで取得したSubクラスのメソッド");
        for (Method method : methods) {
            System.out.println(method.getName());
        }
    }
}
8行目でSuperクラスについてのClassクラスのインスタンスを取得して、13行目と18行目でフィールド情報(Superクラスの各フィールドについてのFieldクラスインスタンス)、24行目と30行目でコンストラクタ情報(Superクラスの各コンストラクタについてのConstructorクラスインスタンス)、36行目と42行目でメソッド情報(Superクラスの各メソッドについてのMethodクラスインスタンス)を取得している。

引き続いて、49行目でSubクラスについてのClassクラスのインスタンスを取得して、54行目と60行目でフィールド情報(Subクラスの各フィールドについてのFieldクラスインスタンス)、66行目と72行目でコンストラクタ情報(Subクラスの各コンストラクタについてのConstructorクラスインスタンス)、78行目と84行目でメソッド情報(Subクラスの各メソッドについてのMethodクラスインスタンス)を取得している。

ClassクラスのgetFieldsメソッド、getMethodsメソッドは、それぞれ対象クラスとそのスーパークラスで定義されているpublicフィールド、publicメソッドの情報を取得することができる。
getDeclaredFieldsメソッド、getDeclaredMethodsメソッドは、それぞれ対象クラスで定義されている全フィールド、全メソッドの情報を取得することができる。
getConstructorsメソッドは対象クラスで定義されているpublicコンストラクタ、getDeclaredConstructorsメソッドは対象クラスで定義されている全コンストラクタの情報を取得することができる。

上記の実行結果をメソッド毎に見ていく。

○getFieldsメソッド

getFields()メソッドで取得したSuperクラスのフィールド
publicSuperField

Superクラスで定義されているpublicフィールドの情報が取得できている。
スーパークラスであるObjectクラスにはpublicフィールドがない為、情報はない。


getFields()メソッドで取得したSubクラスのフィールド
publicSubField
publicSuperField

SubクラスとそのスーパークラスであるSuperクラスで定義されているpublicフィールドの情報が取得できている。
Superクラスと同様に、Objectクラスの情報はない。

○getDeclaredFieldsメソッド

getDeclaredFields()メソッドで取得したSuperクラスのフィールド
defaultSuperField
privateSuperField
protectedSuperField
publicSuperField

Superクラスで定義されている全フィールドの情報が取得できている。


getDeclaredFields()メソッドで取得したSubクラスのフィールド
defaultSubField
privateSubField
protectedSubField
publicSubField

Subクラスで定義されている全フィールドの情報が取得できている。

○getConstructorsメソッド

getConstructors()メソッドで取得したSuperクラスのコンストラクタ
reflectiontest.superpackage.Super

Superクラスで定義されているpublicコンストラクタの情報が取得できている。


getConstructors()メソッドで取得したSubクラスのコンストラクタ
reflectiontest.subpackage.Sub

Subクラスで定義されているpublicコンストラクタの情報が取得できている。

○getDeclaredConstructorsメソッド

getDeclaredConstructors()メソッドで取得したSuperクラスのコンストラクタ
reflectiontest.superpackage.Super
reflectiontest.superpackage.Super
reflectiontest.superpackage.Super
reflectiontest.superpackage.Super
reflectiontest.superpackage.Super

Superクラスで定義されている全コンストラクタ(シグネチャ違いの5つ)の情報が取得できている。


getDeclaredConstructors()メソッドで取得したSubクラスのコンストラクタ
reflectiontest.subpackage.Sub
reflectiontest.subpackage.Sub
reflectiontest.subpackage.Sub
reflectiontest.subpackage.Sub

Subクラスで定義されている全コンストラクタ(シグネチャ違いの4つ)の情報が取得できている。

○getMethodsメソッド

getMethods()メソッドで取得したSuperクラスのメソッド
publicSuperMethod
wait
wait
wait
equals
toString
hashCode
getClass
notify
notifyAll

SuperクラスとそのスーパークラスであるObjectクラスで定義されているpublicメソッドの情報が取得できている。


getMethods()メソッドで取得したSubクラスのメソッド
publicSubMethod
publicSuperMethod
wait
wait
wait
equals
toString
hashCode
getClass
notify
notifyAll

SubクラスとそのスーパークラスであるSuperクラス、Objectクラスで定義されているpublicメソッドの情報が取得できている。

○getDeclaredMethodsメソッド

getDeclaredMethods()メソッドで取得したSuperクラスのメソッド
defaultSuperMethod
privateSuperMethod
protectedSuperMethod
publicSuperMethod

Superクラスで定義されている全メソッドの情報が取得できている。


getDeclaredMethods()メソッドで取得したSubクラスのメソッド
defaultSubMethod
privateSubMethod
protectedSubMethod
publicSubMethod

Subクラスで定義されている全メソッドの情報が取得できている。


[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には値渡ししかない」という点から参照型引数での「インスタンスのコピー」を期待すると想定外の動作となってしまうことがあるので注意が必要。


[Java]ASCIIコードを文字列に変換する

以前の記事「文字列をASCIIコードに変換する」とは逆に、ASCIIコードを文字列に変換する場合は、Stringクラスのコンストラクタ(String(byte[] bytes, String charsetName)シグネチャ)が使用できる。
public class Main {
    public static void main(String[] args) {
        byte[] asciiCodes = new byte[]{65, 66, 67};
        String resultString;
        try {
            resultString = new String(asciiCodes, "US-ASCII");
        } catch (Exception e) {
            e.printStackTrace();
            return;
        }
        System.out.println("変換後の文字列:" + resultString);
    }
}
上記では、文字列に変換したいASCIIコードを格納したbyte配列(変数asciiCodes)をStringクラスのコンストラクタの第1引数に設定し、文字セットとしてASCIIコードを表す文字列"US-ASCII"を第2引数に指定することで、変換後の文字列を取得している(変数resultString)。

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


変換後の文字列:ABC



[Java]文字列をASCIIコードに変換する

文字列をASCIIコードに変換するには、StringクラスのgetBytesメソッドが使用できる。

但し、getBytes()シグネチャではデフォルトの文字セット(環境に依存するのでASCII以外の文字セットもあり得る)が使用される為、getBytes(String charsetName)シグネチャを使用して明示的に文字セットを指定する必要がある。
public class Main {
    public static void main(String[] args) {
        byte[] asciiCodes;
        String sampleString = "ABC";
        try {
            asciiCodes = sampleString.getBytes("US-ASCII");
        } catch (Exception e) {
            e.printStackTrace();
            return;
        }
        for (int i = 0; i < asciiCodes.length; i++) {
            System.out.println("asciiCodes[" + i + "]:" + asciiCodes[i]);
        }
    }
}
上記では、getBytesメソッドを使用して文字列"ABC"(変数sampleString)の各文字のASCIIコードを取得し、byte配列型変数asciiCodesに格納している。
ここでgetBytesメソッドの引数として文字列"US-ASCII"を設定することにより、文字セットとしてASCIIを使用することを明示的に指定している。

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


asciiCodes[0]:65
asciiCodes[1]:66
asciiCodes[2]:67



[Java]スレッドの終了

マルチスレッド動作時に自スレッド以外のスレッドを終了させたい場合は、ThreadクラスのInterruptメソッドが使用できる。
public class Main {
    public static boolean interrupted;
    public static long startTime;
    public static long interruptedTime;

    public static void main(String[] args) {
        Thread thread = new Thread() {
            @Override
            public void run() {
                long referenceTime = startTime;
                long currentTime;
                while (!isInterrupted()) {
                    currentTime = System.currentTimeMillis();
                    if ((currentTime - referenceTime) >= 1000) {
                        referenceTime = currentTime;
                        System.out.println("別スレッド動作中:" + currentTime);
                    }
                }
                interruptedTime = System.currentTimeMillis();
                System.out.println("割り込み:" + interruptedTime);
                interrupted = true;
            }
        };
        interrupted = false;
        startTime = System.currentTimeMillis();
        System.out.println("開始:" + startTime);
        thread.start();
        for (int i = 0; i < 5; i++) {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
            }
            System.out.println("メインスレッド動作中:" + System.currentTimeMillis());
        }
        thread.interrupt();
        while (!interrupted) {
        }
        System.out.println("経過時間:" + (interruptedTime - startTime));
    }
}
上記では、メインスレッド、別スレッド双方で開始から1秒周期で「動作中」メッセージを表示し、メインスレッド側で5秒経過(5回目の「動作中」メッセージを表示)したタイミングで別スレッド側のInterruptメソッドを実行している。
別スレッド側では、isInterruptedメソッドで割り込み発生状態(Interruptメソッド実行状態)をチェックし、割り込みが発生していたら(Interruptメソッドが実行され、その結果isInterruptedメソッドの戻り値がtrueとなったら)「割り込み」メッセージ表示後runメソッドを終了する。

実行結果は以下の通りで、開始から約5秒後(約5000ミリ秒後)に割り込みが発生し、そこで別スレッドの動作が終了している。


開始:1330828609421
別スレッド動作中:1330828610421
メインスレッド動作中:1330828610453
別スレッド動作中:1330828611421
メインスレッド動作中:1330828611484
別スレッド動作中:1330828612421
メインスレッド動作中:1330828612484
別スレッド動作中:1330828613421
メインスレッド動作中:1330828613484
別スレッド動作中:1330828614421
メインスレッド動作中:1330828614484
割り込み:1330828614500
経過時間:5079


但し、Interruptメソッドはそれ自体にスレッドを終了させる機能はなく、あくまでもトリガとして機能するだけである(上記でも、Interruptメソッド実行を契機としてwhileループから抜ける様にしている)。

スレッドがsleepメソッド、joinメソッド、waitメソッドを実行中にInterruptメソッドが実行された場合はInterruptedExceptionがスローされる為、これをキャッチすることにより割り込み発生を認識することができる。
public class Main {
    public static boolean interrupted;
    public static long startTime;
    public static long interruptedTime;

    public static void main(String[] args) {
        Thread thread = new Thread() {
            @Override
            public void run() {
                while (!isInterrupted()) {
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        interruptedTime = System.currentTimeMillis();
                        System.out.println("割り込み(InterruptedException):" + interruptedTime);
                        interrupted = true;
                        return;
                    }
                    System.out.println("別スレッド動作中:" + System.currentTimeMillis());
                }
                interruptedTime = System.currentTimeMillis();
                System.out.println("割り込み(isInterrupted):" + interruptedTime);
                interrupted = true;
            }
        };
        interrupted = false;
        startTime = System.currentTimeMillis();
        System.out.println("開始:" + startTime);
        thread.start();
        for (int i = 0; i < 5; i++) {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
            }
            System.out.println("メインスレッド動作中:" + System.currentTimeMillis());
        }
        thread.interrupt();
        while (!interrupted) {
        }
        System.out.println("経過時間:" + (interruptedTime - startTime));
    }
}
上記の実行結果は以下の通り(上記もそうだが、runメソッド内にsleepメソッド、joinメソッド、waitメソッド以外の処理内容がある場合は、割り込み発生タイミングによってはInterruptedExceptionがスローされない可能性があることを考慮して、InterruptedExceptionのキャッチと併せてisInterruptedメソッドによる割り込み発生確認も行うべき)。


開始:1330828060578
別スレッド動作中:1330828061609
メインスレッド動作中:1330828061609
別スレッド動作中:1330828062609
メインスレッド動作中:1330828062609
別スレッド動作中:1330828063609
メインスレッド動作中:1330828063609
別スレッド動作中:1330828064609
メインスレッド動作中:1330828064609
別スレッド動作中:1330828065609
メインスレッド動作中:1330828065609
割り込み(InterruptedException):1330828065609
経過時間:5031



[Java]InputStreamの読み出しデータを格納した配列型変数の状態 その2

以前の記事「InputStreamの読み出しデータを格納した配列型変数の状態」で書いたreadメソッドは、read(byte[] b)というシグネチャについてだったが、readメソッドにはもう1つread(byte[] b, int off, int len)というシグネチャが存在する(更にもう1つread()というシグネチャも存在するが、これは配列型引数がないので割愛する)。

read(byte[] b, int off, int len)は、入力ストリームから最大lenバイトのデータを読み出し、b[off]からb[off+k-1](kはメソッドの戻り値で実際に読み出したバイト数)に格納する、という動作になる。
従って、read(byte[] b)シグネチャはread(byte[] b, int off, int len)シグネチャにてoff=0、len=b.lengthとした場合と等価である。

英大文字の"A"、"B"、"C"、"D"、"E"、"F"、"G"、"H"、"I"の9文字を記したテキストファイル(test.txt)を作成し、以下の様にFileInputStreamクラスのreadメソッド(read(byte[] b, int off, int len)シグネチャ)を使用してこのファイルからデータを読み出す。
public class Main {
    public static void main(String[] args) {
        File file = new File("/*****/test.txt");
        FileInputStream fileInputStream;
        int readDataLength;
        byte[] buffer = new byte[9];
        int index = 0;
        try {
            fileInputStream = new FileInputStream(file);
            for (int i = 0; i < 3; i++) {
                readDataLength = fileInputStream.read(buffer, index, 3);
                index = index + readDataLength;
                System.out.println(i + 1 + "回目");
                System.out.println("読み出したデータ数:" + readDataLength);
                for (int j = 0; j < buffer.length; j++) {
                    System.out.println("buffer[" + j + "]:" + buffer[j]);
                }
                System.out.println("");
            }
            fileInputStream.close();
        } catch (Exception e) {
        }
    }
}
上記の実行結果は以下の通り。


1回目
読み出したデータ数:3
buffer[0]:65
buffer[1]:66
buffer[2]:67
buffer[3]:0
buffer[4]:0
buffer[5]:0
buffer[6]:0
buffer[7]:0
buffer[8]:0

2回目
読み出したデータ数:3
buffer[0]:65
buffer[1]:66
buffer[2]:67
buffer[3]:68
buffer[4]:69
buffer[5]:70
buffer[6]:0
buffer[7]:0
buffer[8]:0

3回目
読み出したデータ数:3
buffer[0]:65
buffer[1]:66
buffer[2]:67
buffer[3]:68
buffer[4]:69
buffer[5]:70
buffer[6]:71
buffer[7]:72
buffer[8]:73


次回読み出し時の格納先先頭インデックス(変数index)を、12行目で直前に読み出したバイト数分ずらしている為、読み出す度にデータが上書きではなく追加される。


プロフィール

まさお

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

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


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

人気ブログランキングへ

ブログランキング



ブログ王

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

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