日々常々

ふつうのプログラマがあたりまえにしたいこと。

匿名クラスとかローカルクラスとか

Java入門では割愛としていたローカルクラスと、ついでに関連の強い匿名クラスについて書いておきます。ネストしたクラスとかインナークラス(内部クラス)とかはそこそこしっかり書いたつもりなので、読んでいただけると嬉しいです。

言語仕様とか読んでもいいと思いますし、この辺りも参考に見てみるといいと思います。

みなさまにはいつもおせわになってます。

宣言とか呼び方とか

それぞれどのようなコードで宣言するかと、呼び方について確認しておきます。 もともと書こうと思ったきっかけは呼び名が揺れまくって混乱してる人を見かけたからなので、整理がてら書いておきます。

匿名クラス

単純に作るとこんな感じになります。

Object obj = new Object() {};

これだけで匿名クラスのインスタンスができます。呼ばれ方は次のようにいろんなバリエーションある。

  • 匿名クラス
  • 無名クラス
  • 匿名内部クラス
  • 無名内部クラス
  • 匿名インナークラス
  • 無名インナークラス

正規表現だと (匿|無)名(内部|インナー)?クラス ですかね? どれでもだいたい同じものを指しているので、適当に読み替えてください。

ローカルクラス

これも単純に作るとこんな感じ。

class Hoge {
    {
        class Fuga { }
    }
}

これで Fuga がローカルクラスです。ブロックの中で宣言すればローカルクラス……と言おうと思ったけど、classも構文上はブロック(波かっこに囲まれているのをブロックと言うならば)ぽいので困った。とりあえず、メソッドやイニシャライザ、コンストラクタとかで宣言すればローカルクラスです。呼び名はこんな感じ。

  • ローカルクラス
  • ローカル内部クラス
  • ローカルインナークラス

内部つくかつかないかくらいですけれど、これもまー同じようなものです。適当に読み替えてください。

補足: 内部クラスと呼ぶか?

内部クラスとstaticなネストされたクラスが区別されることは意外と少ないのですが、意識して欲しいと思ってます。内部クラスは外側のクラスのインスタンス(エンクロージングインスタンス)への参照を持つものです。このことは、GC対象になるタイミングなどに影響があります。内部クラスのインスタンスがある限り、エンクロージングインスタンスへの参照があることになります。そのため、内部クラスと呼ばれるクラスのインスタンスは下手に公開してはいけないなど、取り扱いに注意が必要になります。

匿名クラスやローカルクラスがstaticでないメンバ(インスタンスメソッドなど、通常の用途です。)で宣言された場合、内部クラスとなる可能性があります。エンクロージングインスタンスのメンバにアクセスしなければコンパイル時にエンクロージングインスタンスの参照を持たないようにコンパイルされますが、一つでも使うと参照を持つ形になります。また、staticなメンバで宣言されたとしても、そのブロック内の変数を参照する実装がされているならば、その変数の指すオブジェクトへの参照を内部クラスは持つことになります。この場合もコンストラクタや引数で渡された以外の参照を持つことになりますので、注意が必要です。

例えば以下のようにすると、ローカルクラスFugaにはfieldを使用するためにHogeを、arg1を使用するためにObjectを受けるコンストラクタができることになります。興味があればjavac/javapしてみてください。

class Hoge {
  Object field;

  void method(Object arg1, Object arg2) {
    class Fuga {
      void m1() {
        System.out.println(field);
        System.out.println(arg1);
      }
    }
  }
}

定義する状況と定義内容により、コンストラクタの暗黙の引数経由で参照を暗黙的に持ち回すようになるかが変わります。構文的な差はなく、単に使用するか否かだけでうっかり参照を持つので、基本的に参照を持つ内部クラスとして扱った方が安全かなと思っています。

で、なんと呼ぶ?

短い方が楽なので短めの「匿名クラス」と「ローカルクラス」って呼んでます。

呼び方の揺れは目くじら立てずに、全部脳内でエイリアス貼っておけばいいんじゃないのとか思ってたり。いつも名前大事とかいうくせに。なのでこの記事ではこの呼び名で通してます。

「無名クラス」の方が匿名クラスよりも短いのですが、そこは後述の「補足:クラスの名前」で書いているように一応名前あるので「無」を避けてる、くらいです。でもたまに無名クラスっていったりもしてます。

匿名クラスとローカルクラスの違い

わかりやすい違いは、名前がつくかどうかです。名前がつけばnew演算子インスタンスが作れます。つまり、匿名クラスのインスタンスは通常一つだけれど、ローカルクラスのインスタンスは普通に複数作れます。そのことが役に立ったことは今までありませんが。(匿名クラスのインスタンスを複数作成できます。これは補足で。)

次によく挙げられるのが、匿名クラスはコンストラクタを持てない点。イニシャライザで代替できるという話は、この記事書き始めてから知りました。なるほどと思いました。 確かにコンストラクタでしかできないことを代替することはできるけれど、少々無理矢理感ある。無理矢理やってるのだから正しいけれど。

もう一つの違いは拡張についてです。匿名クラスは1つのクラスを明示的に拡張するか、1つのインタフェースを実装することにより暗黙でObjectクラスを拡張するかしかできません。一方でローカルクラスは、通常のクラスと同じように1個のクラス(なにも書かなければObjectクラス)を継承できて、0個以上のインタフェースを実装できます。

一番大きな違いは3つ目じゃないかなぁと思ってたりします。でもここで挙げた3つのことが実務で役に立ったことは今までありません。というか、ローカルクラスを書いた記憶はないです。

補足: クラスの名前

class Hoge {
  {
    class Fuga {}
    new Object(){};
  }
}

これコンパイルしたら Hoge$1.class Hoge$1Fuga.class Hoge.class の3ファイルできます。 それぞれのインスタンスに対して instance.getClass().getName() をしたらそのまま Hoge$1 とか Hoge$1Fuga とかになります。 匿名クラスとは言え、一応名前はあることはある。で、一応コンパイルした後なら new Hoge$1() とか new Hoge$1Fuga() とかできたりする。一応。やらないけど。

補足: 匿名クラスのインスタンス

匿名クラスはnew演算子に続けて作成するため、複数インスタンスを作成することはできなさそうに思えます。ですが、一つ前の補足にも書いているように、作成されたインスタンスgetClassメソッドを呼び出すことでClassオブジェクトを取得することが可能です。 Classオブジェクトが取得できれば、例えばnewInstanceメソッドを使用してインスタンスを作成することもできなくはありません。つまり、次のようなコードが動作します。

class Hoge {
  public static void main(String... args) throws Exception {
    Object o1 = new Object(){};
    Object o2 = o1.getClass().newInstance();

    System.out.println(o1.getClass().getName());
    System.out.println(o2.getClass().getName());
  }
}

この匿名クラスは引数なしのコンストラクタを持っていますが、インスタンスメソッドなどで宣言した場合は「そんなコンストラクタないよ」と怒られたりします。

この辺りは昔書いたのを参考にしてください。

意識してほしいこと

知識が十分でないならば、内部クラス系(staticでないネストしたクラス、ローカルクラス)の乱用は避けましょう。いちいちクラスファイル作るのが面倒だとかクラス作るのにエクセルの申請書がいるとか言ってないで、普通にクラス作ったほうが安全ですし、変に巧妙なコードよりもわかりやすくてまともなコードになると思います。 また、内部クラスを作ったならば、そのインスタンスは下手に公開しないように注意しましょう。

匿名クラスは使い所です。一部はラムダ式で置き換えられるでしょうが、全てがそういうわけではありません。少なくとも2抽象メソッド持つインタフェースはラムダ式に出来ませんし。また、本当に匿名クラスで実装するのが正しいかは一考の余地があるはずです。匿名クラスでの実装を想定するAPIのライブラリやフレームワークは数ありますが、「サンプルコードでそうなっていたから」と言って、常にそうするのが正しいわけではありません。

まとめ

匿名クラスはともかく、ローカルクラスは本当に使いません。使い所もわかりません。無理に使わないでいいと思います。 ……有効に使っている例があったらこっそり教えてください!

JAX-RS ClientでBasic認証してみる

背景

REST APIにアクセスする必要がある。BASIC認証がかかっている。JAX-RSのClientAPIを使ってアクセスしたい。

とりあえずHTTPヘッダに直接AuthorizationBase64エンコードしたのを突っ込んでやってたんだけど、なんかイマイチな感じがした。で、うらがみさんに「どうしたら良いんだろ?」って言ったら「あー」とか言いながら書いてくれました。ちなみに楽しそうに書いてたのはクライアントの方じゃなくてContainerRequestFilterの方です。

で、その流れでこの辺のドキュメント読んだりしたのでメモメモ。

やりかた

  • Jersey - 2.17
  • RESTEasy - 3.0.9.Final

WebTargetBasic認証をやってくれる子を登録する。

// Jersey
ClientBuilder.newClient()
        .target(TARGET_URI)
        .register(HttpAuthenticationFeature.basic("user", "pass"))
        .request()
        .get();

// RESTEasy
ClientBuilder.newClient()
        .target(TARGET_URI)
        .register(new BasicAuthentication("user", "pass"))
        .request()
        .get();

Jerseyでは HttpAuthenticationFeature と言う javax.ws.rs.core.Feature の実装クラスを使う。RESTEasyだと BasicAuthentication と言う javax.ws.rs.client.ClientRequestFilter の実装クラスを使う。

どっちも使い方は同じようなものだけど、各実装ライブラリに依存したクラスが出てきちゃうので、実装ライブラリの切り替えはし辛くなりそうな感じです。実装ライブラリの切り替えってそんな頻繁にあるものじゃないだろうけど。

Basic認証はヘッダにBase64エンコードしたユーザ名/パスワードを付ければなんとかなりますので、力技で書くことも可能ではあります。 だからと言って、自分で書くべきとは思いません。あるもの使わないのは勿体ないですし。

ところで言えJavaEEアプリケーションサーバーを使うときは javax:javaee-api とかをprovidedで依存させるような気がする。それだとこれらの実装クラスって使えないね……どうしよう。ある程度実装依存のを使った方が良いんだろうか。むーん。

カテゴリ「Java入門+α」を作ってみた

ブログにカテゴリ「Java入門+α」を作ってみた。

カテゴリ名の通り「Javaエンジニア養成読本のJava入門を読んだ人」に対して、+αになりそうなものを分類しております。

Javaエンジニア養成読本 [現場で役立つ最新知識、満載!] (Software Design plus)

Javaエンジニア養成読本 [現場で役立つ最新知識、満載!] (Software Design plus)

ブログは適当に書いてることもあり、たまに「昔書いたなー」と思っても賞味期限切れてたり、触れ方が軽すぎたりしてイマイチになってるものも多いのが実情。そんな中で「過去エントリ参照」と言ってもなかなか掘り返してもらうのも厳しいです。それでも見てもらいたいものもあるものでして。

ということで、その手の一覧を作ろうと思いました。で、せっかくカテゴリの機能があるんだから、それを使おうかなと。

このカテゴリは更新していくつもりです。つまり、内容が古くなっていったり誤っていたりしたら外すし、続きを書く必要があると思ったら「続きを書く」カテゴリに放り込んでいこうと思ってます。あとは、気が向き次第に続きを書きます。

いまのところはJava入門を書いた時よりも前に書いたものばかりなので、書いた時の私は知っててそれを要約したものになってる感じだと思います。なので、本と突き合わせれば「あーこの部分はこのブログの内容を書いてるのかー」とか、そんな感じで見ることもできるのではないでしょうか。

しかし昔に書いたものとは言え、突っ込みどころが結構ある……。それも仕方ないことだし、遠慮なく指摘してください。誤ったものをそのままにしておく趣味はありませんので。

きたいしてる

成果はこちら。

bufferings.hatenablog.com

記事のタイトルはともかく、自分で読み返しても勉強になる(結構忘れてる)ものがあってびっくりした。ばふぁさんさすがだなーと思いました。