JavaBeansとは?Beansを支える3つのルールと考え方
JavaBeansは、データを納める倉庫のような役割のクラスです。ごく普通のクラスなのですが、以下のルールを守ったクラスを指すのが一般的です。
- プロパティへのgetter/setterメソッドを持つ
- 引数のないコンストラクタを持つ
- java.io.Serializableを実装している
JavaBeansのルールは、現在のJavaが持つ豊富なフレームワークを語る上では欠かせないものです。このルールが作られ、プログラマ達がこのルールを守ってきたからこそ、Javaがここまで広く普及したと言っても過言ではありません。
この記事ではそんなJavaBeansについて「JavaBeansという言葉は聞いたことがあるけれど、よくわからないなぁ」という方向けに、勘所や背景を含めてしっかりと解説します。
この記事が、Javaを使う上では欠かせない「JavaBeansという考え方」を理解するための一助となることを願っています。
※この記事のサンプルは、Java 10の環境で動作確認しています。
目次
1.JavaBeansはデータの倉庫
JavaBeansは、データを自由に出し入れできる、倉庫のようなものです。そんな一つ一つの倉庫のことをbeanと呼ぶのですが、beanがたくさん集まって大きなプログラムになるから、Java + Beans = JavaBeansなのですね。
JavaBeansを使うと、Javaプログラマの皆が知っているいくつかのルールを守れば、簡単に、しかも分かりやすくbeanからのデータの出し入れができるのです!
JavaBeansは、実は…ただのどこにでもあるようなクラスです。ですから、JavaBeansを使うには、まずはそのクラスのインスタンスを作ります。でも、ただ単にnewするだけです。
そしてJavaBeansでモノを出し入れするには、出し入れしたいモノの名前を使って「これをしまっておいて」とか「これが欲しいんだけれど」と、beanに決まった形で伝えてあげればいいだけです。
つまり、Javaのプログラムでいうと、以下のような感じになります。これだけ見ると「あれ、これって普通のクラスの使い方じゃないの?」と思われたでしょう。
Depot depot = new Depot(); depot.setSomething("しまいたいモノ"); String something = depot.getSomething();
JavaBeansのいいところは、まさにそこ!! 普通のクラスなのだけれども、いくつかのルールを守るだけでいろいろと便利なことができて、世の中にあるたくさんのフレームワークをガンガン活用できるのです。
2.JavaBeansの3つのルール
JavaBeansのルールとは以下の3つです。ちょっと難しかったり、聴き慣れないかもしれない言葉が並んでいますが、心配ご無用です。この章ではこれらのルールを分かりやすくお伝えします。
ルール1:プロパティへのgetter/setterメソッドを持つ
ルール2:引数のないコンストラクタを持つ
ルール3:java.io.Serializableを実装している
以下のクラスは前述のJavaBeansのルールに従っています。この章ではこのクラスを例にしていきます。
public class JavaBeansSample implements java.io.Serializable { private String name; private int amount; public JavaBeansSample() { } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAmount() { return amount; } public void setAmount(int amount) { this.amount = amount; } }
2-1.ルール1:プロパティへのgetter/setterメソッドを持つ
JavaBeansではこれが一番重要なルールですので、しっかりと覚えましょう。
2-1-1.プロパティとはインスタンスが持つ情報のこと
プロパティ(property)は「財産、資産、所有物、(ものの)特質、特性」(weblio英和辞典)を意味します。Javaの文脈では、クラスのインスタンスが持っている情報や値のことを意味します。
Javaのプロパティは名称と型でワンセットです。名称は英単語が原則です。英語は嫌だなぁという方もいらっしゃると思いますが、プログラムは英語が自然ですし、ちょうどいい名前を付けてあげるのもプログラマの大切な仕事です。
サンプルのJavaBeansSampleだと、nameとamountの二つのプロパティを持たせています。名前としてStringのname、金額としてintのamountです。皆さんもbeanに持たせたいプロパティを考えてみてください。
せっかくですから「XにはYというプロパティがある」とか「XはプロパティYを持つ」という言い方にも慣れておきましょう。Javaに限らず、プログラマがよく使う言い方です。
2-1-2.プロパティとgetter/setterは名前で結び付く
JavaBeansのルールでは、プロパティを読み書きするメソッドを作る必要があります。それがgetter/setter(ゲッター、セッター)と呼ばれるメソッドです。
プロパティとgetter/setterを結びつけるのはそのメソッド名です。ここでメソッド名に付くプロパティ名は、先頭が大文字になるのがルールです。
getter:「get + プロパティ名」、プロパティの値を取得する
setter:「set + プロパティ名」、プロパティの値を設定する
サンプルのJavaBeansSampleだと、プロパティnameにはgetName/setName、amountにはgetAmount/setAmountというgetter/setterがあります。ルール通りですね。
ですので「JavaBeansSampleにはStringのプロパティnameがある」と言われたら、Javaプログラマは以下のようにプログラミング出来ると分かります。これだけでも、プログラマ間での意思疎通には便利ですね。
JavaBeansSample bean = ....; // 何らかの形で生成したJavaBeansSampleでは、プロパティnameに対して bean.setName("何かの名称"); // setName(String)と、 String name = bean.getName(); // String getNameが使える
JavaBeansの仕様では、getter/setterにはもう少し細かいルールがあります。例えば、booleanの場合はgetterが「is + プロパティ名」になったり、値が配列型の場合にインデックスでアクセスするメソッドについてなどです。
2-1-3.プロパティ名のフィールドはJavaBeansでは必須ではない
その他に頭の片隅に入れておいていただきたいのは、クラスにプロパティ名のフィールド(インスタンス変数)を持つことは、プロパティの考え方では必要ない、ということです。
「プロパティ名に紐づいたgetter/setterを持つこと」だけがプログラマに求められていることです。つまり、JavaBeansSampleではString nameと、int amountのフィールドそのものは必須ではなく、実はどんな名前のフィールドでもいいのです。
フィールド名とメソッド名を合わせてあるのは、ソースコードを分かりやすく、かつ書きやすくするための単なる慣習です。メソッド名が最重要…これだけはよく覚えておきましょう。
2-2.ルール2:引数のないコンストラクタを持つ
JavaBeansのルールでは、クラスは「引数のないコンストラクタ」を持つことが必要です。ですので、JavaBeansSampleにも、引数のないコンストラクタがありますね(public JavaBeansSample()のことです)。
コンストラクタをオーバーライドして、引数のあるコンストラクタを持っていても構わないのですが、それでも引数のないコンストラクタが必ずなければならないのです。
このルールは、JavaBeansならば引数なしでのnewが必ずできるということです。つまり、決まった方法でクラスからインスタンスを生成できることを意味します。
でも、普通にnewできるのがそんなに大事なことなのか?…と疑問に思われるでしょう。これは後述するように、汎用的なフレームワークを作ったり、クラスがSerializableであったりする上では大事なルールなのです。
2-3.ルール3:java.io.Serializableを実装している
java.io.Serializableは、何かのクラスのインスタンスを、バイト列…すなわちファイルに書き込んだりネットワーク経由でやり取りできる形式に変換したり、そこから元通りのインスタンスに戻したりできるということを、Javaへ教えるためのインターフェイスです。
Serializableには抽象メソッドはありませんので、implementsすればそれで終わりです。
でも、Serializableであることにはまたしてもルールがあります。クラスのフィールドも全てSerializableでなければならないのです。つまり、クラス全体がSeriaizableなモノで出来ていなければならない、ということです。
これは、JavaBeansを色々な場所で使うためのルールです。beanはJava仮想マシン間やサーバ間でネットワークなどを通じてやり取りされますし、ファイルやデータベース上に保存され、また再びメモリ上に読み込まれたりします。それができると保証してあげることがプログラマの役割です。
サンプルのJavaBeansSampleはというと、まずは implements java.io.Serializable しています。フィールドのnameはStringで、Javadocを見ればわかりますが、StringはSerializableなクラスです。amountのintも条件を満たします。プリミティブ型は全てSerializable扱いだからです。
3.JavaBeansはフレームワークを使う人と作る人の間のルール
3-1.フレームワークには使う人と作る人がいる
JavaBeansはクラスを使う人と作る人の間で守るべきルールの一つです。特にフレームワークでは重要です。このルールがあるからこそ、今現在のJavaの豊富かつ充実したフレームワークが作り上げられたといってもいいでしょう。
この章での「使う人」とはフレームワークのユーザです。「作る人」とはフレームワークのプロバイダです。それぞれの視点から、JavaBeansのようなルールがあると便利ですし、嬉しいのです。
3-2.ルールがあると使う人と作る人の両方が嬉しい
このJavaBeansの3つのルールがあると嬉しいのは、主にフレームワークを作る人です。もちろん、使う側も嬉しいのですが、作る側にとってのメリットが大きいのです。
フレームワークで用意されているインターフェイスやクラス、それを使う側で実装・継承してプログラムを作る手法はよく見かけました。この方法のフレームワークは、有名なものでは例えばStruts 1で、他にもいろいろあります。
しかし、それはフレームワークの枠内に使う人を縛り付けることになります。自分で作ったクラスを継承させたくても、既にフレームワークのクラスを継承していると、クラスの多重継承ができないJavaではどうしようもありません。実際に、これはStruts 1への批判の一つです。
フレームワークとはそういうものだという見方もあります。でも、使う人からは柔軟なフレームワークを求める声が大きいのです。言い換えれば、使う人はできる限り普通のクラスでフレームワークの機能を使いたい、作る人はなるべくフレームワークの枠内のルールを守ってもらいたい…という、それぞれの立場からのせめぎ合いが起きます。
その対応方法の一つとして、JavaBeansのようなルールがあります。ルールがあることで双方が実現したいことへの妥協線が見えてきます。お互いに決まりごとに合意できていれば、十分にフレームワークを使えるし、作れるのです。
3-2-1.ルール1の利点:クラスを知らなくてもプロパティが使える
フレームワークを作る上では、何かのクラスのインスタンスに値を設定したり、逆にインスタンスから値を読み出すことは日常茶飯事です。
でも、使う側が用いる具体的なクラスはフレームワーク側では分からないので、何かしらの手を打つ必要があります。そこで使えるのがプロパティの考え方です。
プロパティのルールがあるおかげで、実体が何かわからないクラスのインスタンスでも、以下のようにプロパティへのgetter/setterを呼べるのです。これも「get/set + プロパティ名」というルールがあればこそです。
この例では、JavaBeansSampleのgetName/setNameの呼び出しを直接記述していないところに注目しましょう。つまり、プログラム上でsetName/getNameを書いていないのにもかかわらず、値の読み書きができるのです。
String property = "name"; // アクセスするプロパティ名は name とする Object bean = new JavaBeansSample(); // 型がObjectなので、変数の型からは実体が何かは分からない Class<?> clazz = bean.getClass(); // bean.setName("TEST")を、具体的なクラスを知らないままに呼び出す String setterName = "set" + property.substring(0, 1).toUpperCase() + property.substring(1); Method setter = clazz.getDeclaredMethod(setterName, String.class); setter.invoke(bean, "TEST"); // bean.getName()を、具体的なクラスを知らないままに呼び出す String getterName = "get" + property.substring(0, 1).toUpperCase() + property.substring(1); Method getter = clazz.getDeclaredMethod(getterName); String ret = (String) getter.invoke(bean); // bean.getName()を呼び出している System.out.println(ret); // → TEST
3-2-2.ルール2の利点:インスタンスの生成を確実に行える
何かのクラスのインスタンスを生成するには、当然ながら必ずクラスのコンストラクタを呼ぶ必要があります。その際、引数のないコンストラクタがあると決められていれば、それをプログラム上から決め打ちで呼べるのです。
フレームワークでは、具体的なクラス名が分からない状態で、クラスのインスタンスを生成しなければならないことが頻繁にあります。でも、Javaのプログラミングでは、newでは具体的なクラス名を書かなければなりませんよね。
その際には、JavaBeansには引数のないコンストラクタがある、というルールが突破口になります。
例えば、以下のようにするとnewを使わずにクラスのインスタンスを生成できます。つまり、クラスあるいはクラスの名称さえ何らかの手順で分かれば、プログラム上からは自由にインスタンスを生成できるのです。
Class<?> clazz = Class.forName("JavaBeansSample"); // クラスを名称から取得して… Object obj = clazz.getDeclaredConstructor().newInstance(); // クラスから直接インスタンスを取得する(newしていない!!) JavaBeansSample bean = (JavaBeansSample) obj; bean.setName("TEST"); System.out.println(bean.getName()); // → TEST
引数のあるコンストラクタのみだと、こうは簡単にはいきません。それぞれの引数に何を設定すればいいのかはわからないので、結局のところインスタンスを作れないのと同じことだからです。
3-2-3.ルール3の利点:インスタンスをバイナリデータとして扱える
先述したSerializableの特性により、例えば以下のようにインスタンスをバイト列に変換してファイルへ出力したり、そのファイルを読み込んでインスタンスを復元できるのです。しかもとても簡単にです。
これはデータを保持するクラスとしては好ましい性質です。いちいちインスタンスの値を調べて、ファイルへ保存したり、ファイルを読み込んで解釈する処理を自分で作らなくてもいいのですから。
インスタンスをバイト列として扱えるということは、データを保持しているインスタンスを保管方法を問わずに保存したり、ネットワーク経由でのやりとりすらできることを意味します。これはフレームワークとしても是非とも活用したい機能です。
// インスタンスを生成してプロパティを設定して… JavaBeansSample bean1 = new JavaBeansSample(); bean1.setName("TEST"); bean1.setAmount(1000); // インスタンスを「直列化」してファイルに書き出して… try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("object.ser"))) { oos.writeObject(bean1); } // ファイルから読み込んでインスタンスを復元する!! JavaBeansSample bean2; try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream("object.ser"))) { bean2 = (JavaBeansSample) ois.readObject(); } System.out.println(bean2.getName()); // → "TEST"が出力される!! System.out.println(bean2.getAmount()); // → 1000が出力される!!
3-3.JavaBeansのルールを活用している例
例えば、JSTLを使ったJSP(Servlet)では以下のようにプログラムをします。Spring Frameworkでも以下のように設定をすると、クラスのインスタンスをフレームワークから取得できます。例示はしませんでしたが、MyBatisやHibernate、JPAなどのデータベース向けフレームワークでも、プロパティのルールを守ることが大前提です。
これらのフレームワークの機能を使うには、特定のクラスやインターフェイスを継承したり実装する必要はありません。クラスのプロパティへアクセスするためのgetter/setterがありさえすればいいのです。
これはJavaBeansのルールを使う側も作る側も守るという約束があるからこそ、実現できることなのです。
<% JavaBeansSample bean = new JavaBeansSample(); request.setAttribute("bean", bean); %> <c:set target="bean" property="name" value="TEST" /> <c:set target="bean" property="amount" value="1000" /> <%-- HTMLにはTESTと1000が表示される --%> <c:out value="${bean.name}"/> <c:out value="${bean.amount}"/>
// Spring Frameworkでのbean定義(XML)の一部 <bean id="javaBeansSample" class="JavaBeansSample"> <property name="name" value="TEST" /> <property name="amount" value="1000" /> </bean> // Springからbeanを取得すると、あらかじめプロパティに値が設定されている!! ApplicationContext context = ...; // ApplicationContextを何らかの手段で初期化 JavaBeansSample bean = (JavaBeansSample)context.getBean("javaBeansSample"); // idがjavaBeansSampleのbeanを取得 System.out.println(bean.getName()); // → TEST System.out.println(bean.getAmount()); // → 1000
4.【参考】JavaBeansは汎用的なコンポーネント技術
狭義のJavaBeansは、JavaBeans API仕様に記述された仕様に従ったクラス、およびそれを使うためのユーティリティクラスを含めた環境のことです。JavaBeans API仕様は1996年にSun Microsystemsにより初版が作成され、1997年に現時点での最新版である1.01に改版されています。もう20年以上も前のことです。
JavaBeans Spec
https://www.oracle.com/technetwork/articles/javaee/spec-136004.html
Java SE Desktop Technologies – Java Beans
https://www.oracle.com/technetwork/java/javase/tech/index-jsp-138795.html
JavaBeans API仕様は、元々はJavaによる汎用的なソフトウェアコンポーネントを実現するために作られました。先述したルールの他にも、プロパティ管理用のクラス、イベントハンドリング、永続化、GUIコンポーネントエディタ向けのインターフェイスなど、定義されている仕様は多岐に渡ります。
この意味でのJavaBeansは残念ながら普及しませんでした。Sunは、JavaBeansで実装されたソフトウェアコンポーネントを扱うマーケットが立ち上がると想定していたようですが、そう上手くはいかなかったようです。
普及しなかった理由は、ちょうどその頃のエンドユーザコンピューティングの方向性が、クライアントOS上で動くネイティブアプリから、WEBブラウザを用いたWEBアプリに移り変わったから…とも考えられるでしょう。
ですが、この仕様で決められたクラス(java.beansにある、BeanInfoやPropertyDescripter、PropertyEditor等)は、実は今でも用いられています。フレームワークを使う人はまず意識はしませんが、JavaBeans的な動作をサポートするフレームワークでは内部でこれらのクラスを使っています。それに、標準APIとしてのメンテナンスも、ずっと続けられています。
ですから、当初目指していた姿とはずいぶんと違いますが、JavaBeansの考え方自体は今もしっかりとJavaの中に生き続けているのです。
5.【参考】Enterprise JavaBeansはJava EEの基幹技術
Enterprise JavaBeans(EJB)は、JavaBeansの考え方を応用して、データの管理を統合的に行おうというフレームワークです。EJBはServletに並ぶJava EE(現Jakarta EE)の基幹技術の一つです。狭義のJavaBeansは広く普及はしませんでしたが、EJBは企業システムなどのサーバサイドJavaにおいて現役の技術です。
EJBの仕様には、サービス間のデータのやり取りの仕組みであったり、データの永続化やトランザクション管理などが含まれています。考え方としては、やはりデータを保持するクラス(=bean)の存在が根底にあり、それをどういう仕組みで生成し、やりとりするかが仕様で詳細に決められています。
以前は、EJBを使うのは少々敷居が高いものでした。EJBの仕様を満たしたクラスを作る他に、大量の設定用XMLを作らねばならなかったのも原因の一つでしょう。これは、EJBの設定XMLは、人間はXMLを直接編集せず、専用のXML作成支援ソフトウェアの存在を前提としていたからです。
ですが、最近のEJBはずっと気軽に使えます。Javaの注釈(アノテーション)を活用して、XMLが不要なように仕様が強化されたからです。もし、昔のEJBを使った経験があり設定の複雑さに辟易していたなら、最新の仕様を少し確認してみると、もう一度使ってみようかと言う気になるかもしれませんね。
6.まとめ
JavaBeansには3つの大事なルールがあります。最も重要なのはプロパティを読み書きするためのgetter/setterで、これが現在のJavaの豊富なフレームワークにとって欠かせない役割を果たしています。
狭義のJavaBeansは広く使われるには至りませんでしたが、その考え方のエッセンスは、Javaプログラマの間にしっかりと根付きました。Enterprise JavaBeansは現役の技術で、最近は扱いやすいように仕様変更されています。こちらもサーバサイドJavaのエンジニアであれば最新情報は要チェックです。
ちなみに、beanとはコーヒー豆のことです。Javaのシンボルマークはコーヒーカップに注がれたコーヒーですし、アーカイブ形式のjarは、いわゆるジャーポットのジャーです。
初期のJavaではコーヒーに関連した言葉やイメージを借りた、しゃれた名前付けが多かったものです。この記事を書きながら、そんなことをふと思い出してしまいました。
コメント