普通のSI会社では評価されにくいのだけど、多くのシステムは研究熱心な技術者の小さな発見と工夫の積み重ねによって支えられているのでは?

昨晩遅く、id:backpaper0さんの以下のツイートが目にとまりました。

staticでない本物の内部クラスは暗黙で外側のインスタンス(エンクロージングインスタンス)のthisにアクセスできる仕様なので、newを使った普通のインスタンス化の方法では決して外部のインスタンスが存在しない状態でnewできないようになっています。つまり、内部クラスのインスタンス化にはエンクロージングインスタンスの存在が前提となるというのが私が長年Javaのコードを書いてきた中での常識でした。
実際、エンクロージングインスタンスの外側で内部クラスのインスタンスを作成する場合、以下のようにnew演算子の前にエンクロージングインスタンスを書かなくてなならない仕様となっているため、これがnullでは決して内部クラスが生成できないように構文上工夫されています。

public class Outer {

    private String outer = "outer";

    public class Inner {
        public void hello() {
            System.out.println("Hello World " + outer);
        }
    }

    public static void main(String[] args) throws Exception {
        Outer outer = new Outer();
        Inner inner = outer.new Inner(); // この行を実行するにはouterがnullであってはならない。
        inner.hello();
    }
}

なお、ちょっとした違いですが、Innerがstaticであれば、厳密には内部クラスでなくトップレベルのクラスになるため、以下のようにOuterのインスタンスがなくても、普通にインスタンス化できます。

public class Outer {

    public static class Inner {
        public void hello() {
            System.out.println("Hello World ");
        }
    }

    public static void main(String[] args) throws Exception {
        Inner inner = new Outer.Inner(); 
        inner.hello();
    }
}

ここまでは、きちんとJava言語を勉強したことのある人であれば、常識として理解している内容であると思います。だから、最初は何かの勘違いなのではと思ったのですが、その後、

というつぶやきがありました。なるほど、内部クラスのインスタンス化にはリフレクションを使う手があるのを忘れていました。内部クラスをリクレクションで生成するには通常のClassクラスのnewInstance()メソッドは使えません。なぜなら、インスタンス化にはエンクロージングインスタンスをパラメーターとして渡す必要があるからです。したがって、代わりにConstructorクラスのnewInstance(Object...)を使う必要があります。

import java.lang.reflect.*;

public class Outer {

    private String outer = "outer";

    public class Inner {
        public void hello() {
            System.out.println("Hello World " + outer);
        }
    }

    public static void main(String[] args) throws Exception {
        Outer outer = new Outer();
        Constructor<Inner> c = Inner.class.getConstructor(Outer.class);

        Inner inner = c.newInstance(outer); // インスタンス化の際にエンクロージングインスタンスを渡す
        inner.hello();
    }
}

このように内部クラスをインスタンス化する場合、ConstructorクラスのJava DOCにも、第一引数にエンクロージングインスタンスを渡す必要があると書かれています。

コンストラクタの宣言クラスが非 static コンテキスト内の内部クラスである場合、コンストラクタへの最初の引数は囲むインスタンスである必要があります。『Java 言語仕様』のセクション 15.9.3 を参照してください。

しかし、実際に確認してみると以下のようにnullを渡しても例外とならず、内部クラスのインスタンスがエンクロージングインスタンスなしで生成されてしまいます。

import java.lang.reflect.*;

public class Outer {

    private String outer = "outer";

    public class Inner {
        public void hello() {
            System.out.println("Hello World " + outer); // ここでNullPointerExceptionが発生する
        }
    }

    public static void main(String[] args) throws Exception {
        Outer outer = new Outer();
        Constructor<Inner> c = Inner.class.getConstructor(Outer.class);

        Inner inner = c.newInstance((Object)null); // エンクロージングインスタンスがnullでも例外とならない
        inner.hello();
    }
}

ちょっと、インターネットで検索してみた限りでは、それらしいバグレポートが見つからないようですが、動作的には限りなくバグに近いのではないかと考えています。実際、決してエンクロージングインスタンスなしで内部クラスを生成できないnew演算子の仕様と整合していませんし、仮にインスタンスが生成できてもまったくうれしくありませんから。
ちなみに、newInstance()を呼び出している行のパラメーターを(Object)のように明示的にキャストしていることにも注意してください。可変長引数をとるメソッドに対してnullを渡す場合、曖昧さを回避するために、明示的なキャストをしないと警告となるのです。

  • (Object)でキャストすると単一のnull値を要素とする長さ1の配列が渡される
  • (Object[ ])でキャストすると配列そのものがnull値として渡される
  • キャストしないと警告となるが配列そのものがnull値として渡される
  • パラメーターそのものを渡さなかった場合長さ0の配列が渡される

という仕様のようです。これも、情けないことにこの話を知るまで曖昧にしか理解していませんでしたね。
Free Web Hosting - Your Website need to be migrated
Java可変長引数メモ(Hishidama's Java VarArgs Memo)
もともと、この話はid:backpaper0さんがGroovyで内部クラスの動作を調査している中で発見されたみたいです。
http://d.hatena.ne.jp/backpaper0/20110429/1304096170
JavaとInnerClassとGroovy - Togetter
ちょっとした疑問を掘り下げていろいろ調査し、新たな発見につなげていくというあたりはプログラマーとしてすばらしいことだと思いました。世の中には研究熱心なプログラマーがいるものだなと感心させられました。
こういったプログラマー(SEなども含まれるし、営業やマネジメントも広い意味で含まれる)の日常の探求や工夫に対する努力というのは、残念ながら忙しい業務開発の中では実践する時間がないということがありますし、また、多くの場合、会社からはまったく評価されないのが現実です。プログラマーの創意工夫と言えば、新しい技術を使ったビジネスサービスの構築など誰の目に見ても華やかな発明や発見の方に注目がいきがちです。でも、技術開発の経験の乏しい人には理解できない傾向があるのですが、そうしたブレークスルーは一朝一夕に簡単にひらめいて得られるものではなく、日々のちょっとした工夫や探求の積み重ねによって得られるものであるということも忘れてはならないと思います。
確かに、重箱の隅をつつくような言語仕様やちょっとしたツールの使い方の工夫などは、99%はどうでもよいことだったりするのかもしれませんが、そうした重ねが大きな発見や改善につながるというのも事実なのではないかと思います。
今回の件も、別に答えを知っていたからといって直ちに収入が上がるなど、目に見える報酬に結びつくものではないかもしれません。しかし、日々ちょっとした疑問に対しても問題意識をもって取り組み、改善に取り組むという姿勢はエンジニアとしては大切ですし、また、多くの場合目立たないけれどもそうした技術者の日々の小さな努力の積み重ねによって支えられているという事実を忘れてはならないのではないかとあらためて感じました。