広告

■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
                      2010年03月07日

    Java総合講座 - 初心者から達人へのパスポート
                  vol.194

                                セルゲイ・ランダウ
 バックナンバー: http://www.flsi.co.jp/Java_text/
■■■■■■■■■■■■■■■■■■■■■■■■■■■■■


-------------------------------------------------------
・現在、このメールマガジンは以下の2部構成になっています。
[1] 当初からのコース:毎週日曜の夜に発行
   これは現在、中級レベルになっています。
[2] 2009年11月開講コース:毎週水曜の夜に発行
   これは現在、初心者向けのレベルになっています。
・このメールマガジンは、画面を最大化して見てください。
小さな画面で見ていると、不適切な位置で行が切れてしまう
など、問題を起すことがあります。
・このメールマガジンに掲載されているソース・コード及び
文章は特に断らない限り、すべて筆者が著作権を所有してい
ます。また、これらのソース・コードは学習用のためだけに
提供しているものです。
-------------------------------------------------------


========================================================
◆ 01.Strutsのアプリケーション開発
========================================================

さて、このエラーはNullPointerExceptionですね。
Eclipseのコンソール内で探すとjava.lang.NullPointerExceptionのメッセージの下に

at jp.co.flsi.lecture.struts.db.DbManager.disconnect(DbManager.java:54)

というような行があるでしょう(数字(ソース・ファイル内の何行目かを示す)は
読者各自で多少のずれがあるかもしれません)。
このDbManager.java:54の部分に下線が引かれているはずなので、その部分を
クリックしてみて下さい。そうすると、DbManager.javaファイルの該当箇所
が表示されますね。

これで、disconnect()メソッドの中の

conn.close();

という行でNullPointerExceptionが発生していることがわかります。

なぜ、ここでNullPointerExceptionが発生したかというと、コンソール内の
さらに下の

at jp.co.flsi.lecture.struts.ItemSelectForm.reset(ItemSelectForm.java:42)

という行から、ItemSelectForm.javaファイル内の

dbManager.disconnect();

という行にたどりつけますから、ItemSelectFormでapplicationスコープから
categoryListが取り出されたために、
dbManager.connect();
が実行されなかったはずだ、ということが思い当たりますね。

つまり、connが空っぽ(null)だったはずなのに、
conn.close();
を実行しようとしたためにNullPointerExceptionが発生したと考えられます。

このエラーに対する一つの対処法は、このDbManager.javaファイルの

conn.close();

という行を、

if (conn != null && !conn.isClosed()) conn.close();

に書き換えることです。(connがnullのときやisClosedのときはclose
する必要がないですから。)

では、このように書き換えてファイルを保管したあと、再度同じテストを
行ってみて下さい。
これで、このエラーは出なくなりましたね。



◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆


以上で(U-3)の部分が完成しましたので、続いて(U-4)の部分を作成しましょう。


ショッピング・カート(買い物かご)に入れた商品の情報は、そのまま
注文(購入)の段階でも使用できますので、ここではアプリケーションを
簡潔にするために、注文のクラス(Orderという名前にする)をショッピング・
カートとしても使用することにし、ショッピング・カートか注文かの区別は、
注文が完了したかどうかの属性を持たせることによって示すことにします。

つまり、Orderオブジェクトの注文完了の属性が注文前を示していれば
商品がショッピング・カートにはいっている状態を意味し、注文完了を
示していればショッピング・カートから商品が取り出されてレジを通過し、
商品が購入済み(注文済み)の状態になったことを意味することとします。

というわけで、注文とショッピング・カートを表現するクラスとして
Orderを作りますが、その前に、そのOrderオブジェクトの中に含まれる
商品、つまり、ショッピング・カートにはいっている商品あるいは注文
が完了した商品を表現するクラスとして、OrderItemというクラスを用意
しましょう。
商品を表すフィールド(属性の変数)としては商品番号の変数(itemNumという
名前にする)を用意し、購入希望数を表すフィールドとしてはorderQuantityと
いう名前の変数を用意することにします。
また、注文書の中での商品項目の番号(何行目の項目であるかを表す番号)
をseqNumberという名前のフィールドで表すことにします。

なお、OrderItemはORDERITEMテーブルにマッピングしたクラス(エンティティー・
クラス)になり、OrderはORDERHEADERテーブルとORDERITEMテーブルを組み合わせ
た注文全体にマッピングしたクラスになります。


では、以下の手順でOrderItemクラスを作成しましょう。

(1) プロジェクト・エクスプローラー内でjp.co.flsi.lecture.struts.dbを右クリック
し、「新規」→「クラス」を選択します。

(2) 「名前」欄に

OrderItem

と入力し、「完了」ボタンをクリックします。

(3) OrderItem.javaのエディターが開いたら、下記のように編集しましょう。

--------------------------------------------------------
package jp.co.flsi.lecture.struts.db;

import java.io.Serializable;

public class OrderItem implements Serializable {
   private int seqNumber;
   private String itemNum;
   private String itemName;
   private int price;
   private String image;
   private String catName;
   private int orderQuantity;

   public OrderItem() {
      setSeqNumber(0);      // 0は未設定を意味するものとする。
      setPrice(0);
      setOrderQuantity(1);  // 注文個数のデフォルトは1とする。
   }
   public void setSeqNumber(int seqNumber) {
      this.seqNumber = seqNumber;
   }

   public int getSeqNumber() {
      return seqNumber;
   }

   public void setItemNum(String itemNum) {
      this.itemNum = itemNum;
   }

   public String getItemNum() {
      return itemNum;
   }

   public void setItemName(String itemName) {
      this.itemName = itemName;
   }

   public String getItemName() {
      return itemName;
   }

   public void setPrice(int price) {
      this.price = price;
   }

   public int getPrice() {
      return price;
   }

   public void setImage(String image) {
      this.image = image;
   }

   public String getImage() {
      return image;
   }

   public void setCatName(String catName) {
      this.catName = catName;
   }

   public String getCatName() {
      return catName;
   }

   public void setOrderQuantity(int orderQuantity) {
      this.orderQuantity = orderQuantity;
   }

   public int getOrderQuantity() {
      return orderQuantity;
   }

}
--------------------------------------------------------


続いて、下記の手順でOrderクラスを作成してください。

(1) プロジェクト・エクスプローラー内でjp.co.flsi.lecture.struts.dbを右クリック
し、「新規」→「クラス」を選択します。

(2) 「名前」欄に

Order

と入力し、「完了」ボタンをクリックします。

(3) Order.javaのエディターが開いたら、下記のように編集しましょう。

--------------------------------------------------------
package jp.co.flsi.lecture.struts.db;

import java.io.Serializable;
import java.util.Date;
import java.util.Vector;

public class Order implements Serializable {
   private int orderNumber;
   private Date orderDate;
   private String userType;
   private String userId;
   private int payMethod;
   private boolean payment;
   private short delivery;
   private Vector<OrderItem> orderItems;
   private boolean orderComplete;

   public Order() {
      setOrderNumber(0);        // 0は未設定を意味するものとする。
      setPayMethod(0);          // デフォルトはクレジット・カード払い
      setPayment(false);        // デフォルトは未払い。
      setDelivery((short)0);    // デフォルトは未配達。
      setOrderComplete(false);  // デフォルトは注文前
      orderItems = new Vector<OrderItem>();
   }

   public void setOrderNumber(int orderNumber) {
      this.orderNumber = orderNumber;
   }

   public int getOrderNumber() {
      return orderNumber;
   }

   public void setOrderDate(Date orderDate) {
      this.orderDate = orderDate;
   }

   public Date getOrderDate() {
      return orderDate;
   }

   public void setUserType(String userType) {
      this.userType = userType;
   }

   public String getUserType() {
      return userType;
   }

   public void setUserId(String userId) {
      this.userId = userId;
   }

   public String getUserId() {
      return userId;
   }

   public void setPayMethod(int payMethod) {
      this.payMethod = payMethod;
   }

   public int getPayMethod() {
      return payMethod;
   }

   public void setPayment(boolean payment) {
      this.payment = payment;
   }

   public boolean isPayment() {
      return payment;
   }

   public void setDelivery(short delivery) {
      this.delivery = delivery;
   }

   public short getDelivery() {
      return delivery;
   }

   public void setOrderItems(Vector<OrderItem> orderItems) {
      this.orderItems = orderItems;
   }

   public Vector<OrderItem> getOrderItems() {
      return orderItems;
   }

   public void addOrderItem(OrderItem anOrderItem) {
      this.orderItems.add(anOrderItem);
   }

   public void removeOrderItem(OrderItem anOrderItem) {
      for (int i = 0; i < this.orderItems.size(); i++) {
         if (this.orderItems.get(i).getItemNum().equals(anOrderItem.getItemNum())) {
            this.orderItems.remove(i);
         }
      }
   }

   public void setOrderComplete(boolean orderComplete) {
      this.orderComplete = orderComplete;
   }

   public boolean isOrderComplete() {
      return orderComplete;
   }

}
--------------------------------------------------------


ここで、OrderクラスやOrderItemクラスではSerializableの実装(implements Serializable
という記述)がされていることに注意して下さい。
これは、これらのクラスをあとでsessionスコープに保管するようにプログラミング
するためです。

sessionスコープに保管されたオブジェクトは、サーバー(Webコンテナー)によっては
ハードディスクに保存される場合があります。たとえば、サーバーの停止時にハードディ
スクにセッション・オブジェクトを保存しておき、次回のサーバーの起動時に復元して
使用するなどが行われます。あるいは、Tomcatでは特に複数のサーバーに負荷を分散させ
る機能もあり、この機能を使用する場合はセッションのレプリケーション(Replication
 = 複製)が行われて他のサーバーにもセッションが送付されるので、そのためには
Serializableの実装が必須になります。
このためにSerializableを実装(implements)しておく必要があるのです。

したがって、sessionスコープに保管するオブジェクトは原則として
implements Serializable
を指定するようにします。
(Serializableの詳細は、下記「Java(文法等)解説」のコーナーを参照して下さい。)

この点は、applicationスコープに保管するオブジェクトについても同様です
が、ItemSelectFormクラスでapplicationスコープに保管しているオブジェクト
はVectorであり、VectorはSerializableを実装(implements)済みなので、
問題ありません。



◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆


では、続いてCartActionクラスをコーディングしましょう。

CartAction.javaのエディターを開いて下さい。(Eclipseのプロジェクト・
エクスプローラー内のStrutsShop配下の「Javaリソース:src」配下の
jp.co.flsi.lecture.struts配下のCartAction.javaをダブルクリックする。)

CartAtion.javaを下記のように編集しましょう。

--------------------------------------------------------
package jp.co.flsi.lecture.struts;

import java.util.Vector;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

import jp.co.flsi.lecture.reflect.ValueLogStringMaker;
import jp.co.flsi.lecture.struts.db.Item;
import jp.co.flsi.lecture.struts.db.ItemDbManager;
import jp.co.flsi.lecture.struts.db.Order;
import jp.co.flsi.lecture.struts.db.OrderItem;
import jp.co.flsi.lecture.struts.db.StruShopDbException;

import org.apache.log4j.Logger;
import org.apache.struts.action.Action;
import org.apache.struts.action.ActionForm;
import org.apache.struts.action.ActionForward;
import org.apache.struts.action.ActionMapping;

public class CartAction extends Action {
   private static Logger logger = Logger.getLogger(CartAction.class);

   public ActionForward execute(ActionMapping mapping, ActionForm form,
         HttpServletRequest request, HttpServletResponse response) {
      logger.info("Start ...............");
      ValueLogStringMaker stringMaker = new ValueLogStringMaker();
      logger.info("Method parameter: <<<<<" + stringMaker.getValues("mapping", mapping));
      logger.info(">>>>>");
      logger.info("Method parameter: <<<<<" + stringMaker.getValues("form", form));
      logger.info(">>>>>");
      logger.info("Method parameter: <<<<<" + stringMaker.getValues("request", request));
      logger.info(">>>>>");
      logger.info("Method parameter: <<<<<" + stringMaker.getValues("response", response));
      logger.info(">>>>>");
      ItemListForm itemListForm = (ItemListForm) form;
      HttpSession session = request.getSession();
      Order anOrder = (Order)session.getAttribute("ORDER");
      if (anOrder == null) anOrder = new Order();
      ItemDbManager itemDbManager = new ItemDbManager();
      try {
         itemDbManager.connect();
         Vector<Item> itemList = null;
         itemList = itemDbManager.getDataByItemNameAndCatName("", "");  // 手抜きに注意
         String[] checkedItems = itemListForm.getCheckedItems();
         if (checkedItems != null && checkedItems.length > 0) {
            for (String itemNum : checkedItems) {
               OrderItem anOrderItem = new OrderItem();
               for (Item anItem : itemList) {
                  if(itemNum.equals(anItem.getNum())) {
                     anOrderItem.setItemNum(anItem.getNum());
                     anOrderItem.setItemName(anItem.getName());
                     anOrderItem.setPrice(anItem.getPrice());
                     anOrderItem.setImage(anItem.getImage());
                     anOrderItem.setCatName(anItem.getCatName());
                  }
               }
               anOrder.addOrderItem(anOrderItem);
            }
         }
         session.setAttribute("ORDER",anOrder);

      } catch (StruShopDbException e) {
         logger.error(e, e);
      }
      catch (Throwable e) {
         logger.error(e, e);
      }
      finally {
         try {
            itemDbManager.disconnect();
         }
         catch (Exception e) {
            logger.info("Disconnect failed or there is no connection.");
         }
         logger.info("End ...............");
      }
      logger.info("Method return: <<<<<" + stringMaker.getValues("mapping", mapping));
      logger.info(">>>>>");
      return mapping.findForward("success");
   }

}
--------------------------------------------------------

なお、ソース・コード上でもコメントで書いてあるように、当記事では話を簡単に
するために、効率を無視して手抜きでコーディングを行っています。
したがって、当記事のソース・コードをそのまま現場のアプリケーションに使ったり
しないように注意してください。(ちなみに、著作権がありますので、無断コピーは
違法行為になります。)



◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆


では、続いてcart.jspを編集しましょう。

Eclipseのプロジェクト・エクスプローラー内のStrutsShop配下のWebContent配下にある
cart.jspを右クリックし、「アプリケーションから開く」→「Amateras JSPエディター」
を選択して下さい。

--------------------------------------------------------
<%@ page contentType="text/html; charset=UTF-8" %>
<%@ taglib uri="/tags/struts-bean" prefix="bean" %>
<%@ taglib uri="/tags/struts-logic" prefix="logic" %>
<%@ taglib uri="/tags/struts-html" prefix="html" %>
<%@ taglib uri="/tags/struts-nested" prefix="nested" %>

<html:html>
   <head>
      <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
      <title>ショッピング・カート</title>
   </head>
   <body bgcolor="#77ffff" text="#fa5a00">
      <h1>ショッピング・カートの中のリスト</h1>

      <table border="1" width="100%">
        <tbody>
          <tr>
            <th>商品番号</th>
            <th>商品名</th>
            <th>価格</th>
            <th>カテゴリー</th>
            <th>イメージ</th>
            <th>購入個数</th>
          </tr>
          <%@ page import="java.util.Vector" %>
          <%@ page import="jp.co.flsi.lecture.struts.db.OrderItem" %>
          <% String homebase = request.getContextPath(); %>
          <bean:define id="orderItemList" name="ORDER" property="orderItems" type="Vector<OrderItem>" scope="session" />
          <logic:iterate id="item" name="orderItemList" scope="page">
            <tr>
               <td><bean:write name="item" property="itemNum" /></td>
               <td><bean:write name="item" property="itemName" /></td>
               <td><bean:write name="item" property="price" />円</td>
               <td><bean:write name="item" property="catName" /></td>
               <td><img src="<%= homebase %><bean:write name="item" property="image" />"></td>
               <td><bean:write name="item" property="orderQuantity" /></td>
             </tr>
         </logic:iterate>
        </tbody>
      </table>
      <br>
      <br>
      <br>
      <html:link page="/itemSelect.jsp">トップ・ページ(商品検索のページ)に戻る</html:link>
   </body>
</html:html>
--------------------------------------------------------


以前のitemList.jspのときと同じく未完成(FORMなどを組み込んでいない)である
ことに注意して下さい。これは、完成させると、他の関連ファイル(例えばFORMに
対応したActionFormのサブクラス)も作らなければエラーになってしまい、確認テスト
ができないからです。


では、ここで再度このWebアプリケーション(Strutsのアプリケーション)を
実行して動作確認してみましょう。



◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆ ◆


(次回に続く)


では、今日はここまでにします。



========================================================
◆ 02.Java(文法等)解説 [Serializable]
========================================================


Javaのプログラム(Javaだけでなく通常のプログラム)は、実行が終わると
メモリー上から消えます。
したがって、プログラムの実行時にメモリー上に存在していたオブジェクト
もメモリー上から消えます。

これに対して、実行時のオブジェクトの状態をそのままハードディスクなどに
保管しておいて、次回のプログラムの実行時に復元して再使用したいという場合が
ありますが、そのためにJavaでは直列化(serialization)という機能が提供され
ています。
この直列化の機能を利用するためにはSerializableというインターフェース(または
そのサブインターフェース)を実装(implements)する必要があります。

この直列化(serialization)の機能は、元々JavaBeans技術の開発と同時期に
作られた機能であり、JavaBeansと深い関係があります。
たとえば、ビルダー・ツールでBeanをビジュアルに貼り付けていって、各Beanの
プロパティーを画面上で設定したときに、そのBeanの状態を保管し、次回のビルダー・
ツールの起動時にBeanの状態を再現するためなどに使われていました。
(したがって、AWTやSwingのGUI部品などのビジュアルなクラス群は予めSerializable
が実装されています。)

したがって、BeanはSerializableを実装することが推奨されています。
(よく誤解している人がいるようなので特記しましたが、Serializableを
実装することはBeanにとって必須ではなく、あくまで推奨です。
とはいっても、直列化の機能を利用したい場合は、Serializable(またはその
サブインターフェース)の実装は必須になります。)

現在ではビルダー・ツールはほとんど使われていないので、上記の話は重要ではないの
ですが、しかしながら直列化(serialization)の機能は重宝で今でも(今後も)よく
使われています。


ところで、実装するという言い方をしましたが、Serializableは他の普通のインター
フェースと異なり、実装すべきメソッドはありません。フィールドもありません。
Serializableは、たんに「直列化(serialization)が可能である」とか「直列化して
いいよ」という意味を表すだけの特殊なインターフェースです。
したがって、たんにクラスの定義に
implements Serializable
と書けばいいだけなので使い方はとても簡単です。


さて、この直列化(serialization)の機能を使うと、オブジェクト(インスタンス)を
そのままファイルに保管することができます(あるいはネットワークを通して他のコン
ピューターに送信することもできます)。
ただし、そのままと言ってもメソッドのロジックまでも保存されるわけではなく、その
時点のインスタンスの状態(つまり各属性(フィールド)の値)だけが保存されます。

この機能がどうして直列化(serialization)と呼ばれるのか疑問に思う人もいるでしょう。

直列化という翻訳はおそらく電子回路の素子や電池などの「直列接続(serial connection)」
という言葉を参考にして作られたものと思われます。(ちなみに「並列接続」は英語で
parallel connectionと言う。)
しかし、この翻訳では、どういう意味だかさっぱりわからないでしょう。

そこで、このserializationという用語の成り立ちからお話しましょう。


ことの始めは、順次アクセス(serial access)という言葉からスタートします。

コンピューターの内部(主にメモリー)に書かれたデータは、いつでも好きな場所
(アドレス)に直接読み書きができます。
ところが、コンピューターの外部へデータを保管しようと思ったら、昔は磁気テープの
ような順次アクセス(serial access)をする媒体を使うしかなかったのです。

順次アクセス(serial access)というのは、前から順番に読み書きしていかなけれ
ばならないアクセスの仕方をいい、たとえ途中に必要なデータがあったとしても
直接そこに読み書きに行くことはできず、必ず前から順番に読み取って行ってそこに
到達しなければなりません。

┌補足─────────────────────────┐
serialとは「連続的な」とか「順次の」というような意味を表す
形容詞で、何かが前から順番に続いているようなイメージを持つ
言葉です。
たとえば、serial numberというと、通し番号とか連番というよう
な意味になります。
└───────────────────────────┘

したがって、メモリー上のデータを外部記憶装置に保管したいと思ったら、データ構造
を順次アクセス用のデータ構造に変換してやる必要があったのですが、このような変換は
serializeと呼ぶようになりました。
英語には、「・・化する」という意味を持つ"ize"という接尾辞があるので、serial化する
と言いたいときは、自然とserializeという動詞が使われるようになったのです。
(serializeという動詞は以前から存在していましたが、以前は「連載する」という
ような意味で使われており、コンピューターとは関係ありませんでした。
したがって、serializeという言葉がコンピューター用に別の意味に転用されたことに
なります。)

さらには、順次化というような意味の名詞を使いたいときには、serializeのうしろに
名詞化の接尾辞"ation"をつけて、serializationという言葉にして使うようになりま
した。

また、「serializeが可能である」という状態を表す形容詞を使いたいときには、
serializeのうしろに可能の意味を持つ形容詞化の接尾辞"able"をつけて、serializable
という言葉にして使うようになりました。

したがって、この直列化(serialization)は、元々は、オブジェクト(インスタンス)
を順次アクセスのデータ構造に変換することを意味しているのです。

インスタンスの状態を保管したいときは、順次アクセスのデータ構造にしておけば、
ハードディスクであろうと、磁気テープであろうとどんな記憶媒体にも保管できます。
あるいは、ネットワークを通して他のコンピューターに送付することもできます。
(ネットワークを通して送付するときは時系列的に順次に送るしかないので必然的に
順次アクセスになる。)

というわけで、インスタンスの状態をファイルに保管したり、ネットワークを通して
送付するための機能をserializationと呼ぶようになっています。



ところで、serializationは、あくまでインスタンスの状態を外部に書き出す機能です。
状態を書き出すとは、具体的にはインスタンスの属性の値、つまりフィールドの値を
書き出すものであり、メソッドは書き出されません。
また、staticやtransient(後述)が指定されているフィールドの値は書き出されませ
ん。これは、staticはインスタンスには属さないもの(いわゆるクラス変数)であり、
transientは一時的なものだからです。
直列化(serialization)はインスタンスのフィールドでかつ永続的なもの(一時的では
ないもの)しか保管しません。



直列化(serialization)によるインスタンスの状態の書き出し/読み込みは直列化の
メソッド(ObjectOutputStreamのwriteObject()メソッド/readObject()メソッド)を
呼び出すことにより実行することができます。
このメソッドが具体的に何をどんな順番でファイルに書き出すのかについては、直列化
(serialization)の仕様書に書かれていますが、プログラミングのために細かい仕様
を知る必要はありません。

特定のフィールドの値を保管しないなど、インスタンスの状態の書き出し/読み込みに
独自の制御を行いたい場合は、Serializableの代わりにそのサブインターフェースであ
るExternalizableというインターフェースを実装することによって、独自にプログラミ
ングを行うことができます。



ところで、直列化(serialization)で保管したインスタンスを次回読み込んで使おう
としたら、その時点ではそのクラスに改良が加えられていて、保管しておいたインス
タンスとちぐはぐが生じていた、なんてことになると困りますね。

インスタンスの状態が正しく読み込めなくてエラーになるのはまだいいほうで、一番
困るのは読み込みは正常に行えてしまうが、プログラムが誤動作を起し、しかもその
誤動作に気がつかずにそのままプログラムを続行させてしまう場合です。

このような問題に対処するために、直列化(serialization)にはクラスのバージョン
をチェックする機能が備えられています。
この機能により、インスタンスの状態を保管(ファイルに書き出し)したときには、
そのクラスのバージョンID(バージョン番号)がいっしょにファイルに書き込まれます。
そして、そのファイルからインスタンスを復元(インスタンスの状態を読み出し)する
ときに、読み出しを行うプログラムの中の該当するクラスのバージョンIDとファイルの中
のバージョンIDが照合されます。
そして、それらのバージョンIDに食い違いがあるときにはエラー(InvalidClassException)
を返すようになっています。

このバージョンIDは、クラスにserialVersionUIDというフィールド、具体的には

   private static final long serialVersionUID = 1L;

というような指定(privateの代わりにpublicでも何でもかまわないが通常はprivate
にする)でlong型のフィールドを定義することによって設定できます。

このserialVersionUIDは明示的に指定しなくてもコンパイラーが自動的に生成して
くれますが、明示的に指定されることが推奨されます。

そのため、Serializableを実装している(あるいはスーパークラスがSerializableを実装
している)クラスにおいてserialVersionUIDを明示的に指定していないと、コンパイラー
が警告を返します。
Eclipseのエディターではこのとき警告の橙色のマークがつきます。そこで、橙色の下線
が引かれているクラス名にカーソルを入れてCtrl+1キーを押す(Ctrlキーと数字の1のキー
を同時に押す)と、対処法のリストが出ますので、その中から「デフォルト・シリアル
バージョンIDの追加」を選択すると
   private static final long serialVersionUID = 1L;
という行が自動的に挿入されます。この機能を使うと、serialVersionUIDの指定は
簡単に行うことができますが、実際の業務で使用するプログラムではこの1Lという
番号は、クラスのバージョンに合わせて変える(バージョンIDをきちんと取り決める)
べきです。)



直列化について、さらに詳しく知りたい人は、

http://java.sun.com/javase/ja/6/docs/ja/platform/serialization/spec/serial-arch.html

などを参照して下さい。



では、Eclipseで直列化の機能を使ったプログラムを書いてみましょう。

まず、外部ファイルに保管するオブジェクトのクラスを以下の2種類作成しましょう。

--------------------------------------------------------
package jp.co.flsi.test.serialization;

import java.io.Serializable;

public class Student implements Serializable {
   private static final long serialVersionUID = 1L;

   private String name;
   private int age;
   private transient boolean truant;    // 授業をさぼっているかどうか
   private static int numberOfStudents = 0;

   public Student() {
      setName("無名");
      setAge(0);
      setTruant(false);
   }

   public void setName(String name) {
      this.name = name;
   }

   public String getName() {
      return name;
   }

   public void setAge(int age) {
      this.age = age;
   }

   public int getAge() {
      return age;
   }

   public void setTruant(boolean truant) {
      this.truant = truant;
   }

   public boolean isTruant() {
      return truant;
   }

   public static void setNumberOfStudents(int numberOfStudents) {
      Student.numberOfStudents = numberOfStudents;
   }

   public static int getNumberOfStudents() {
      return numberOfStudents;
   }

}
--------------------------------------------------------



--------------------------------------------------------
package jp.co.flsi.test.serialization;

import java.io.Serializable;

public class Teacher implements Serializable {
   private static final long serialVersionUID = 1L;

   private String name;
   private int age;
   private String subject;

   public Teacher() {
      setName("無名");
      setAge(0);
      setSubject("適当な科目");
   }

   public void setName(String name) {
      this.name = name;
   }

   public String getName() {
      return name;
   }

   public void setAge(int age) {
      this.age = age;
   }

   public int getAge() {
      return age;
   }

   public void setSubject(String subject) {
      this.subject = subject;
   }

   public String getSubject() {
      return subject;
   }

}
--------------------------------------------------------


これらのクラスのインスタンスを生成し、それを外部のファイル(ここではファイル名を
"SerialData.test"とする)に保管するプログラムを下記のように作成しましょう。

--------------------------------------------------------
package jp.co.flsi.test.serialization;

import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;

public class SerialWriter {

   public static void main(String[] args) {
      Student tarou = new Student();
      tarou.setName("太郎");
      tarou.setAge(16);
      tarou.setTruant(true);
      tarou.setNumberOfStudents(tarou.getNumberOfStudents() + 1);
      Student hanako = new Student();
      hanako.setName("花子");
      hanako.setAge(17);
      hanako.setNumberOfStudents(hanako.getNumberOfStudents() + 1);
      Teacher hanawa = new Teacher();
      hanawa.setName("塙");
      hanawa.setAge(35);
      hanawa.setSubject("数学");
      try {
         ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("SerialData.test"));
         out.writeObject(tarou);
         out.writeObject(hanako);
         out.writeObject(hanawa);
         out.close();
      } catch (FileNotFoundException e) {
         e.printStackTrace();
      } catch (IOException e) {
         e.printStackTrace();
      }
      System.out.println(tarou.getName());
      System.out.println(tarou.getAge());
      System.out.println(tarou.isTruant());
      System.out.println(tarou.getNumberOfStudents());
      System.out.println(hanako.getName());
      System.out.println(hanako.getAge());
      System.out.println(hanako.isTruant());
      System.out.println(hanako.getNumberOfStudents());
      System.out.println(hanawa.getName());
      System.out.println(hanawa.getAge());
      System.out.println(hanawa.getSubject());
   }

}
--------------------------------------------------------


続いて、"SerialData.test"ファイルから保管されたオブジェクトを読み出して
インスタンスを復元するプログラムを以下のように作成しましょう。

--------------------------------------------------------
package jp.co.flsi.test.serialization;

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.ObjectInputStream;

public class SerialReader {

   public static void main(String[] args) {
      Student tarou = new Student();
      Student hanako = new Student();
      Teacher hanawa = new Teacher();
      try {
         ObjectInputStream in = new ObjectInputStream(new FileInputStream("SerialData.test"));
         tarou = (Student)in.readObject();
         hanako = (Student)in.readObject();
         hanawa = (Teacher)in.readObject();
      } catch (FileNotFoundException e) {
         e.printStackTrace();
      } catch (IOException e) {
         e.printStackTrace();
      } catch (ClassNotFoundException e) {
         e.printStackTrace();
      }
      System.out.println(tarou.getName());
      System.out.println(tarou.getAge());
      System.out.println(tarou.isTruant());
      System.out.println(tarou.getNumberOfStudents());
      System.out.println(hanako.getName());
      System.out.println(hanako.getAge());
      System.out.println(hanako.isTruant());
      System.out.println(hanako.getNumberOfStudents());
      System.out.println(hanawa.getName());
      System.out.println(hanawa.getAge());
      System.out.println(hanawa.getSubject());
   }

}
--------------------------------------------------------

各オブジェクトを保管した順番でreadObject()していることに注意して下さい。
このようにちゃんと順次に処理していく必要があります。


完成した各ソース・コードを保管したら、まずSerialWriterを実行してみて
下さい。
各インスタンスがファイルに保管されるとともにそのインスタンスの
各フィールドの値がコンソールに出力されますね。

次にSerialReaderを実行してみて下さい。
各インスタンスがファイルから復元されるとともにそのインスタンスの
各フィールドの値がコンソールに出力されますね。

このときtransientを指定したフィールドに注目してください。
SerialWriterの実行時にはtrueに設定しておいたはずのフィールドも、
SerialReaderを実行してみると、初期値のfalseに変わってしまって
いますね。

staticフィールドも同様に初期値の0に変わってしまっていますね。



┏━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
★ホームページ:
      http://www.flsi.co.jp/Java_text/
★このメールマガジンは
     「まぐまぐ(http://www.mag2.com)」
 を利用して発行しています。
★バックナンバーは
      http://www.flsi.co.jp/Java_text/
 にあります。
★このメールマガジンの登録/解除は下記Webページでできます。
      http://www.mag2.com/m/0000193915.html
★このメールマガジンへの質問は下記Webページにて受け付けて
 います。わからない所がありましたら、どしどしと質問をお寄
 せください。
      http://www.flsi.co.jp/Java_text/
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━┛

Copyright (C) 2010 Future Lifestyle Inc. 不許無断複製