Javaにおける参照型
一通りJavaプログラミングを経験した方であれば、プリミティブ型と参照型についてはご存じでしょう。Javaは強い型付け言語であることをうたっており、全ての変数や式は、何らかの型を持つことになっています。そしてJavaにおける型は、プリミティブ型と参照型の2種類に分類されます(※null型というのもありますが、ここでは省略します)。
- プリミティブ型:boolean型、char型、数値型(byte、short、int、long、float、double)
- 参照型:配列型、クラス型、インタフェース型
プリミティブ型のデータはビット幅やその取り得る値の範囲が具体的に規定されています。では、参照型はどのように規定されているでしょうか。『Java言語仕様』の第4章「型、値、変数」から引用します。
参照型の値は, オブジェクトへの参照となる。
オブジェクトとは, クラス型から動的に生成されるインスタンス、あるいは動的に作成される配列のことである。
CやC++を知っている方なら「ああポインタのことね」と思うことでしょう。実際、別のところには次のように書かれています。
参照値(しばしば単に参照(reference)とも呼ばれる)は、こういったオブジェクトへのポインタ(pointer)や, どのオブジェクトも参照しない特殊なnull参照となる。
ガベージコレクタの仕組みを考えると、 Javaの参照型はC/C++のポインタが2段に組み合わさったものと考えるのがよさそうです。それはともかく、プリミティブ型のデータと参照型のデータは扱いが異なるという点を理解しておくことが重要です。
プリミティブ型変数には、プリミティブ型の値自身が収められており、代入などの操作を行うと、値がコピーされていきます。これに対して、参照型変数に収められているのは参照なので、代入操作を行うとオブジェクト自身がコピーされるわけではなく、参照(ポインタ)がコピーされます。
Javaのコードでは、プリミティブ型も参照型も同じように記述できるため、ついついプリミティブ型データと同じようにオブジェクト自身がコピーされると思いがちですが、そうではありません。『Java言語仕様』でもわざわざ次のように記述されています。
2つの変数が同じオブジェクトへの参照を保持している場合、どちらか一方の変数に設定されたオブジェクト参照を用いてオブジェクトの状態を変更することができ、変更された状態はもう一方の変数に設定された参照を用いて取得することができる。
同じ箇所に掲載されているサンプルコード(を簡略化したもの)を以下に示します。
class Value { int val; } class Test { public static void main(String[] args){ Value v1 = new Value(); v1.val = 5; Value v2 = v1; v2.val = 6; System.out.print("v1.val==" + v1.val); System.out.println(" and v2.val==" + v2.val); } }
v1とv2はどちらも、new Value()
で生成されたValueクラスのインスタンスを指しています。そのため、v1が指しているインスタンスのvalフィールドの値をv2を使って変更することができるわけです。
この参照型の特徴に注意してコーディングしないと、クラス定義などによるデータのカプセル化やプログラムのモジュール化が台無しになってしまう危険があります。
参照型データを使う
具体的に次のサンプルコードで見てみましょう。
class i_Container { private int i; i_Container(int arg){ i = arg; } public int get_i(){ return i; } public void set_i(int iarg){ i = iarg; } } class User { private i_Container ic; public User(i_Container arg){ ic = arg; } public i_Container get_ic(){ return ic; } // その他のメソッド }
クラスUserのフィールドicはprivate宣言されているので、Userのインスタンスを扱うコードで、直接フィールドicにアクセスすることはできません。しかし、コンストラクタUser()では、引数として受け取った参照をそのままicに代入しているため、User()コンストラクタに渡した参照を使えば、icが指しているi_Containerインスタンスにアクセスすることが可能です。
// ...... 攻撃者が実行するコード ...... i_Container iarg = new i_Container(10); User u = new User(iarg); // u.ic.set_i(100); とするとコンパイルエラーになる iarg.set_i(100); // iarg を使えば u.ic.i を 10 から 100 に変更できる
Userクラスの他のメソッドがicの値に応じた処理を行っている場合、外部からicを操作されることによって、意図せぬ動作をさせられる危険があります。Userクラスでは、攻撃者がこのようなコードを用意して実行する可能性を考慮しておかなければなりません。