getter/setterとはなんだったのか

Javaのgetter/setterのお話。
僕は当時を語るには若すぎるのだけど、過去を振り返って書いてみる。当時を知る人は誤りがあれば指摘してほしいし、情報があればコメントなりトラックバックなりして欲しい。前世紀の話というのは今となっては探すことがなかなか難しくなりつつある。

「privateな変数にpublicなアクセサを定義する」? - ネットの海の片隅で

getter/setterとは何か

Javaのオブジェクトにフィールドがあったとして、そのフィールドに値を設定するメソッドがsetter(せったー)、そのフィールドの値を取得するメソッドがgetter(げったー)と呼ばれる。慣習としてsetterはsetXXX(int value)といった様にsetから始まる名前をつけ、引数はひとつ。戻り値はvoid型。getterはgetXXX()といった様にgetから始まる名前をつけ、引数はなし。戻り値でフィールドの値を返す。

public class Point {
	private int x;
	private int y;

	public int getX() {
		return x;
	}
	public void setX(int x) {
		this.x = x;
	}
	public int getY() {
		return y;
	}
	public void setY(int y) {
		this.y = y;
	}
}

特記事項として、特にboolean型のフィールドについてはgetXXX()といった名称ではなくisXXX()といった名称が用いられる事がある。

内部のフィールドはprivateアクセスとして外から直接いじれないようにしつつ、操作をメソッド経由で行う。

歴史的経緯

話は前世紀、Java 1.0の頃に遡る。1.0の頃の標準APIでは命名規約が定まっておらず、標準APIでも「導入されたバージョン」が1.0のクラスについてはところどころ(現代からすれば)違和感のあるメソッド・フィールド名をしている。

1997年2月にJava 1.1がリリースされる。この時、ようやく今のおなじみの命名規約が定まった。ここで英語の動詞+名詞といったメソッド名が推奨されるようになる。getter/setterの命名規約もここに端を発していると思う。(が、僕もこのぐらいの時代のことははっきり分からないので事情を把握しておられる方や資料にあたられた方は情報を頂けると有難いです)

getter/setterの慣習についてはJavaBeans仕様の影響があるという説が強い。JavaBeansについては97年後半に登場したという情報もあるが*1、Java1.1のjavadocにはすでにjava.beansパッケージが存在しており、前後関係がよく分からない。当時の雑誌にでも当たったほうが良さそうである。

JavaBeansはGUIなどで再利用可能なコンポーネントを提供する際の規格のようなもので(僕もあまり詳しくない)2000年ぐらいにGUIのコンポーネントを作るときに意識したような、どうでもよかったような、イマイチ恩恵が実感できなかった代物だった(当時の僕がヘボいだけ説もある:-)。

何を解決したかったのか

Java 1.0時代のオブジェクトとしてjava.awt.Pointに登場してもらおう。このPointクラスはx,yの座標を持つクラスで、awtという初期のJavaのGUIコンポーネントで多用されていた。

当時の標準APIのソースコードが手元にないのでイメージで恐縮だが、だいたい以下の様な作りになっている。

public class Point {
	public int x;
	public int y;
}

そう、publicフィールドを使っているのである。なお、Java 1.2からはgetX()とgetY()メソッドが追加になっている:-P

private フィールド + getter/setterという作りでやりたいことのひとつは、読み取り専用のオブジェクトを作ることだった。public フィールドの場合、読み書きが自由であるため、protected フィールド + getterのみという構成で読み取り専用なクラスを作ろうとした。そして、それを継承して書き込みも可能なクラスを作るのである。

public class ReadOnlyPoint {
	protected int x;
	protected int y;

	public ReadOnlyPoint(int x, int y) {
		this.x = x;
		this.y = y;
	}

	public int getX() {
		return x;
	}
	public int getY() {
		return y;
	}
}

public class ReadWritePoint extends ReadOnlyPoint {
	public ReadWritePoint(int x, int y) {
		super(x, y);
	}
	
	public void setX(int x) {
		this.x = x;
	}
	public void setY(int y) {
		this.y = y;
	}
}

ReadOnlyPoint を継承して ReadWritePoint を作るということは奇妙に感じるかもしれない(奇妙に感じないのであればJavaのオブジェクト指向の世界に順応している:-)。しかし、Javaのオブジェクト指向に置ける継承は、継承した機能をすべて子クラス側でも提供しなければならない。読み書きができる ReadWritePoint を親にして 読み取り専用の ReadOnlyPoint を作った場合、setXとsetYの機能を子供側で潰す必要が出てくる。これはJavaのオブジェクト指向のルールに反する。

ReadWritePoint extends ReadOnlyPointであるから、読み書きできるReadWritePointのインスタンスを読み込み専用のReadOnlyPoint型の変数に代入することができる。そして読み込み専用として扱うことができる。しかし、逆は成り立たない。(この下りは正確さに欠けるが型システムが発達する前の時代の空気感だと思ってもらうぐらいで読んでいただきたい)

現代ではimmutable(いみゅーたぶる)という思想が広まっているが、前世紀のころ、Java界隈でimmutableという言葉を聞くことはあまりなかったように思う。getter/setterでやりたいことのひとつはこのimmutableだった。

当時の貧弱な開発環境ではメソッドやフィールドの参照元を探すということは困難であった。更新されるべきではないフィールドが更新されていたとして参照元を探してバグを潰すというのは容易ではなかったのだ。フィールド名が複雑で名前が重複しなければ単純なテキスト検索でもある程度候補は絞り込めただろう。それでも代入と参照を分けて検索することは難しいのだが。後から調べることができないからこそ防衛的にgetter/setterをコントロールしたかった。そんな風に記憶している。

なお、余談だが、例に挙げたPointクラスはフィールドがプリミティブのint型なので問題にならないが、これがListといったオブジェクトだと完全にimmutableにするのは難しい。完全なimmutableを目指すとオブジェクトの生成コストが高くついたりするのでほどほどのところで妥協することが多い。(この話題だけでblogがひとつ書けてしまう)

オーバーライドへの布石

getter/setterを使った理由は他にもあった。それは、後の改変への備え、あるいはオーバーライド、あるいはアスペクトのウィービングであった。

あるオブジェクトの状態が更新される箇所にSystem.out.println()を仕込んで変化をログに出してデバッグすることを試みるとしよう。publicフィールドへのアクセスには途中に処理をはさむ余地がないが、getter/setterになっていればフィールドへの値の読み書きに際して何らかの処理を差し挟む余地が生まれる。

え?そんなのはIDEのデバッガで監視すればいいじゃないかって?まったくその通りである。ただし、前世紀にそのような高機能なIDEとそれを動かすパワフルな開発機があれば、の話だが :-P

JDK付属ツールのjdbでがんばれと言う話かもしれないが、あまりprintfデバッグ(Javaでは当時printfメソッドが存在しないが:-)をいじめないであげてほしい。

当時の開発環境は貧弱だった。テキストエディタでJavaのコードを書き、コマンドプロンプトでjavacコマンドを叩いて開発したりしていたのだ。現代ではメソッドやフィールドの参照元の検索というのは簡単に行うことができるし、リファクタリング機能で名称の変更も簡単にかつ安全に行えるが、当時はそうはいかなかったのである。

そんな中で、何かあった際に処理を書き込める余地を作っておくという「備え」には十分な価値があった。

惰性的に量産されるgetter/setter

GUIのパーツなどを設計するような場合、あんまり安直なgetter/setterを作ることは少ないかもしれない。フィールドをpublicにすることもあまりしない。適当に値をいじられると矛盾した状態に陥りやすいからだ。異常値はsetter時点で弾きたいし、どこかの値の更新されれば連鎖的に変更することが出てきたりする。例えば横幅が変われば描画する各コンポーネントの横幅もそれぞれ変更しないといけないとか(一般にはそういうところはLayoutManagerがやってくれるけどね:-)

しかし、Java Servlet が流行った2000年前後には printfデバッグ(C言語以来の古式ゆかしき名称で敢えて呼ぶ:-)の布石として、さして意味のないPOJO(Plain Old Java Object.ここでは単にデータを格納するだけのフィールドが宣言されただけのクラス)にもとりあえずgetter/setterを作るような風潮ではあった。僕も仕様書のExcelからgetter/setterを生成するマクロを書いたっけ。

当時のServletの開発では、画面からServletにHTTPリクエストが飛ぶ際にその内容を格納するためのPOJOを作り、ServletからJSPにデータを受け渡しするためのPOJOを作り、DBを参照しながらデータの詰め替えをせっせと行うアクション・クラスを実装したものだった。HTMLとの間のインターフェース仕様書、DBの仕様書、JSPとの間のインターフェース仕様書、そんなのを眺めながらデータを右から左に詰め替えるコードをみんなでせっせと書いていた。マクロとかでばーっと自動生成しても手作業と同じだけのお金が貰えたのである意味ではよい時代だったのかもしれない :-P

このころにgetter/setterは書くものだ、という価値観が固定化されていったように思う。フレームワークもまたgetter/setterのあるオブジェクトを想定した作りとなっていた。

隣の青い芝

Javaがリリースされた1996年当時、MicrosoftはJava陣営だった。Microsoftもまた次世代の開発言語としてのJavaを迎合していた時代である。しかし、MSはちょっとやんちゃが過ぎた。MSにとっての次世代の開発言語としての利便性を求めた魔改造をしたり(J++/J#)、Windows版Javaだけで動くような細工をしたり。Javaの開発元でありライセンス元であったSun microsystems(当時。現在はOracle社となっている)の怒りを買い訴訟に発展していた。

そんな世紀末の2000年にC#/.NETが生まれたのだった。詳しい内情は知らないが傍からニュースを見ていた僕らの目には、Javaはライセンスがどーのとかうるせーから独自にやることにしたわ!という風に見えた。D言語のようなネイティブコンパイルする言語になるのかと思いきや、JavaのようなVM方式の.NET Frameworkだったのは僕には意外に思えた。

C#は当初からプロパティ構文を持っていた。getter/setterのジェネレーターに工夫を凝らしていた当時、プロパティ構文は青い芝のように見えたものである。Javaのリリース当時、すでにVBなどは存在していたわけだがJavaはプロパティ構文を盛り込まなかった。言語をシンプルにしたかったのかもしれない。

プロパティ構文については今でも欲する人は多いのではなかろうか。C#にとっては初期バージョンからの導入だがJavaにとっては途中からの導入ということになる。クラスにメソッドとフィールドのほかに要素が追加されるとなると、リフレクション周りでの互換性に大きな危惧があるのも事実。先発であったJavaはプロパティ構文を導入するタイミングがないのかもしれない。

現代の評価

IDEの発達した現代、getter/setterはフィールド宣言から簡単に生成できるようになった。しかしまた逆に、IDEが発達したからこそgetter/setterを必要としなくなりつつもある。

対外向けのAPIライブラリであれば、そのインターフェースを修正した場合の影響はユーザ数に応じて大きなものになるから、そう簡単に修正することはできない。必要になってから必要な形にリファクタリングすればよいのだよ、といったスタンスではやれず、事前に想定して外界とのインターフェースを定める必要がある。

しかし、内製のソフトウェアについて言えば、必要になってから直せばよいと言えてしまえる時代である。デバッグはもちろんのこと、フィールドやメソッドの参照元も容易に検索することができるし、リファクタリングツールも充実した。修正のコストは少なくリスクも低い。「いざというときに備えて」getter/setterを用意しておくという行為にさしたる意義を見出せなくなりつつある。

immutableの実現に関してはgetter/setterに対するプロパティ構文をどうするかという議論より、関数型言語における不変データ型をサポートしてくれ的な議論が主流だろう。

結局のところ、現代においてgetter/setterを用意する理由というのが「フレームワークがそうした形状のオブジェクトを求めるから」になってしまっているように思える。もちろん、publicフィールドでもうまくマッピングしてくれるようなフレームワークもあるが、getter/setterである必要があるフレームワークもある。業務システムでは多くのフレームワークを用いる。XMLやJSONといったデータとのマッピングやHTTPリクエストとのマッピング、O/RマッパーのようにDBとのやりとりでもマッピングが発生する。

かつてはこうしたマッピングを手作業やマクロでの生成に頼って記述していたわけだが、こうしたプログラミングにおける単純作業はほとんどがフレームワークによってなされる時代になった。getter/setterの記述もIDEによって容易に行えるが、そもそも必要ないんじゃないかと思える時代でもある。

さあ、現代を前提にして getter/setterメソッドを使わなければならない10の理由 を眺めてみよう。そこに列挙されている理由それぞれが、現代を前提とした場合に弱くなっていることが感じ取られるだろう。逆に古代を前提としたならばそれが十分な動機付けになっただろうことも感じ取れるのではないだろうか。

getter/setterは古いプログラミングスタイルのひとつの象徴かもしれない。しかし、それを捨て去るには、そもそもなぜそうしたのかを考える必要がある。なぜフェンスが立っているのかを知ることで初めてフェンスを撤去する決断が下せる。根拠もなくフェンスの中に飛び込むのは蛮勇でしかない。

おまけ

事情が許すならLombokを試してみるのも良いかもしれない。
あるいはGroovyのようなJavaVM上で動く別の言語を用いるのも良いかもしれない。

もちろん、多くの場合、事情が許さないことも分かっている。

10/13追記

JavaBeansについて情報をいただいたので記載しておく。

Javaのgetter,setterメソッドは(特に)GUI部品のための規格だった話