いけむランド

はてダからやってきました

private についての疑問

private の挙動がちょっとひっかかったので、ここに記す。


PHP で親クラスの private なフィールドを子クラスから読み出すと NULL になる。*1

<?php
class ParentClass
{
  private $var;
}
class ChildClass extends ParentClass
{
  function func() { var_dump($this->var); }
}
$child = new ChildClass();
$child->func();
?>


そもそも親しかアクセスできないフィールドに子からアクセスするならば、NULL (実際は未定義) を返すのではなく、アクセス違反の例外を投げるのがデフォルトの動作であって欲しいと思ってつぶやいたら、「そもそも親がそのようなフィールドを private で持っていることがわかってしまったら、それはもう private じゃないよね。」*2 *3 *4 というご指摘をいただいた。

たしかに親の private なフィールドは子には関係ない。子が同じフィールドをあらためて宣言してしまえば、それにアクセスするだけだし、宣言しなければ、それは未定義であるのは冷静に考えれば当たり前だった。


何故、こんな勘違いをしてしまっていたかというと、たしか Java では親の private なフィールドにアクセスしようとすると、それを理由にエラーになるからだったからじゃないかと思い、確認してみた。

class Parent
{
    private int val;
}
class Child extends Parent
{
    static public void main(String[] args)
    {
	Child c = new Child();
	System.out.println(c.val);
    }
}
% javac Parent.java 
% javac Child.java 
Child.java:6: val は Parent で private アクセスされます。
	System.out.println(c.val);
                            ^
エラー 1 個


親に private なフィールドがあるということがばれてしまうメッセージが表示されている。子が親の private なメンバの存在の有無を知ることができないべきであるなら、以下のように宣言していない場合と同じメッセージを表示するべきであると思う。*5

% javac Child.java 
Child.java:6: シンボルを見つけられません。
シンボル: 変数 val
場所    : Parent の クラス
	System.out.println(c.val);
                            ^
エラー 1 個


ちなみに親のメンバを一旦 public にして、親と子をコンパイルした後に、親のメンバを private に戻して、親だけ再コンパイルをかけて、子が public だからと生成した getfield 命令で親の private なフィールドに子からアクセスさせると、普通にアクセスできてしまった。
リフレクションでゴニョゴニョすれば、実行時にアクセス制限を突破できるみたいだけど、それと同じことが起こっているのかな?

[追記] javap してみたところ getfield 命令の引数はコンスタントプールの Child.val というエントリであるが、Child クラス自体に val のフィールド定義はないみたいだ。どうなってるんだろ?

*1:後から調べてみたら __get() でフックできたため、アクセス不能なメンバ変数の読み出しと扱われることがわかる。

*2:http://twitter.com/shuyo/statuses/3162273517

*3:http://twitter.com/shuyo/statuses/3162410833

*4:http://twitter.com/shuyo/statuses/3162432248

*5:ただ、コンパイラとしてはエラーの理由を事細かに教えなければならないだろうから、上記のメッセージを出す方が良いとも思う。