Java魂3章

前回、前々回に引き続き、Java魂の3章、「不変な型」のおかしな部分の紹介。

まずは3.1.1 不変な型の作成から、3.1.2 不完全な不変型。以下のコードは、不変ではない例として紹介されている。

package oreilly.hcj.immutable;
public class ImmutablePerson {
  private String firstName;
  private String lastName;
  private int age;
  public ImmutablePerson(final String firstName, final String lastName,
                         final int age) {
  if (firstName == null)
    throw new NullPointerException("firstName");
  if (lastName == null)
    throw new NullPointerException("lastName");
  this.age = age;
  this.firstName = firstName;
  this.lastName = lastName;
  }
  public int getAge() {
    return age;
  }
  public String getFirstName() {
    return firstName;
  }
  public String getLastName() {
    return lastName;
  }
}
例3-1 不変な「個人」

まず言いたいことは、コンストラクタでageのチェックがない点。文字列に対してはnullチェックを行っているが、まぁ、実用上はこれで十分だろう。しかし、ageに負の数が渡されることが無いかというと、そんな保証はどこにもない。

これは今回はあまり関係のない問題なので、置いておくとして、問題は「このクラスが不変かどうか」である。

  • finalなクラスではないから継承は可能
  • フィールドにfinalはないが、全てprivate
  • フィールドは全てイミュータブル
  • セッターはなく、コンストラクタとゲッターのみ

クラスと全てのフィールドに対してfinalを付けたいところだが、このクラスはイミュータブルに見える。しかし、著者が言うには、

ImmutablePersonクラスの属性は、外部のクラスに関してのみ不変です。これでは、よい仕事をして認められたい若手の開発者がfirstNameをnullに変更するメソッドをクラスに書き込んで、すべてのGUIコードをNullPointerExceptionになるように壊してしまうかもしれません。結局のところ、不変と思っていた型が完全には不変ではないことがわかるまでには、何時間もかけてコードをデバッグすることになるでしょう。
幸い、このような心得違いの開発者の行いを簡単に阻止できる方法があります。以前学んだfinalを私用して、以下のコードに示すように不変オブジェクトを書き直せばいいのです。

〜以下略*1

3.1.2 不完全な不変性より

コードを書き直す、か。その発想はなかったな。そんなことを言い始めたらソースが編集可能である限り、イミュータブルなクラスなんて存在しないことになる*2。だから、「フィールドにfinalをつければ完全だ!」はおかしい。そうすることでフィールドが変更されてもコンパイルエラーになるが、著者が想定している状況、「よい仕事をして認められたい若手の開発者」なら、コンパイルエラーが出た時点で、諸悪の根源であるfinalを削除してしまうかもしれない。

イミュータブルにしたいクラスのフィールドをfinalにすべきなのは同意するが、だからといって上のクラスがイミュータブルでないかというと、立派にイミュータブルだと思う。

次に、3.2.1 Stringの罠だが、

多くの中級レベルの開発者は、Stringが不変であることを知りません。

3.2.1 Stringの罠より

とある。Stringが不変であることを知らない開発者は、まだ初級レベルだと思いたい。まぁ、これは感じ方の違いなのであまり深くは言わないが。

問題なのは、次の記述である。

割り当てを最適化するために、必要に応じてバッファのインスタンスを調整することもできます。動的SQLのようなあまり長くない連結では、バッファサイズは500から1000で大丈夫でしょう。非常に長い連結の場合は、以下のように初期サイズと増分を大きく取ることができます。

final StringBuffer moreSpace = new StringBuffer(2500);
3.1.2 Stringの罠より

最大の長さがわかるならその長さ分だけバッファを確保すべきであり、このように適当に決めるのは間違いである。また、ここでは示していないが、一文字をバッファに追加するのに、一文字の文字列をappendで追加しているが、これも文字型のappendを使ったほうがいいと思う*3

3章は章自体が短いのでこれで終了。何度も言うが、これで3600円・・・ネタ本への出資としては痛いなぁ。

*1:フィールドにfinalをつけただけ

*2:さらに、バイトコードを弄れば、イミュータブルなクラスなんて(ry

*3:が、これは好みの問題だと思う