japan.internet.com The Internet & IT Network


RSSニュース検索
カテゴリ
> トップページ
> Webビジネス
> Eコマース
> Webファイナンス
> Webマーケティング
> パブリック
> Webテクノロジー
> 携帯・ワイヤレス
> Linux Today
> Linux Tutorial
> J.I.C.ブログ
キャリア
> 転職ならen
> 派遣ならen
> アルバイトならen
> IT求人情報
ヘッドライン
> 今日のヘッドライン
> 週間ヘッドライン
Special Link
> フォトコミュニティ
> ストックフォト
> クリップアート
> イラスト
> フェリカ
> Web2.0
> 写真
イベント&セミナー
> イベントカレンダー
> 書評「IT の耳」
> 出張・接待検索
> ニュースガジェット 注目
無料ニュースメール
> 新規登録
> 変更・解除
> オプトインメールの登録・変更・解除
インフォメーション
> パートナーサイト
転職ならエン
就職ならen
求人ならen
履歴書ならen
アルバイトならエン
CRM/SFAならオラクル
> 会社概要地図
> グループ会社
株式会社アエリア
(株)サンゼロミニッツ
株式会社エアネット
> お問い合わせ
> 広告掲載について
> リンクについて
> 著作権について
> その他お問い合わせ
> 利用規約
> 個人情報保護方針
コラム コラム一覧へ戻る

japan.internet.com 編集部 japan.internet.com 編集部
米国 Jupitermedia が運営する、プログラムコードに関する専門サイト。
多数の記事、多数のコードを掲載し、ソースコードをダウンロードすることもできる。


 メール  著者にメールする
 ホーム  http://www.codeguru.com/

最新コラム

Javaジェネリックを使ったコンパイル時の動的処理

著者: Sumith Puri プリンター用 記事を転送
2008年5月2日 10:00 付の記事
■海外internet.com発の記事

はじめに

 本稿では、ジェネリックを利用してコンパイル時の動的処理およびクライアント関連の型安全性(type-safety)を実現する方法について解説します。一般的に、サブクラス化を行う際の最も重要な側面は、クラス固有の機能を実現するために、いかにして同じメソッドパラメータを使ってオーバーライドを実現するかということです。場合によっては、クラス固有のパラメータが必要になることもあるかもしれません。さらに、オーバーライドメソッドが、こうしたクラス固有パラメータのスーパークラスであるパラメータを使用する場合も考えられます。このようなメソッドの例としては、パブリックAPIを通じて公開され、具象実装クラス内でオーバーライドされるメソッドが挙げられます。

シナリオ

 本稿で取り上げるのは非常によくあるシナリオで、多くの人が過去に直面したことがあり、同じような方法で解決してきたのではないかと思います。ここでは、単純なレンタカーシステムの例を使ってジェネリックの利用方法を説明します。

図1 オブジェクトのリレーション図
図1 オブジェクトのリレーション図
 今回の例では、メインのレンタルサービスクラスをRentVehicleManagerとしますが、このクラスはインターフェイスにしても抽象クラスにしてもかまいません。RentCarManagerとRentBikeManagerはRentVehicleManagerのサブクラスで、RentVehicleManagerのメソッドをそれぞれの機能に合わせてオーバーライドします。このシナリオの実用的な実装ソリューションとしては、次のようなものが考えられます。

  1. 一連のManagerクラスの作成にFactoryパターンを使用する
  2. パラメータの共通階層を作り、メソッド内で厳密なチェックを行う
  3. ジェネリックを利用した共通階層を使用する
 方法1は、関連クラスのインスタンス生成という点では優れたソリューションですが、オブジェクトパラメータの一般化は実現できません。

 方法2のソリューションは、たとえば図2のようになります。この場合、個々のオーバーライドメソッドは、そのクラスにふさわしい型の値が引数に渡されているかどうかをメソッドの冒頭で厳密にチェックしなければなりません。

図2 クラス図(方法2の場合)
図2 クラス図(方法2の場合)
 この場合のインターフェイスは、たとえば次のようになります。

package com.sumithp.codeguru.nongeneric.vehicle;

import com.sumithp.codeguru.vehicle.domain.Vehicle;

public interface RentVehicleMgr {

   public void rentOut(Vehicle vehicle);
   public void checkIn(Vehicle vehicle);
   public void diagnose(Vehicle vehicle);
   public void repair(Vehicle vehicle);
}
 方法2の場合、バイクレンタルに関する実装はたとえば次のようになります。

package com.sumithp.codeguru.nongeneric.vehicle;

import com.sumithp.codeguru.vehicle.domain.Vehicle;

public class RentBikeMgrImpl implements RentVehicleMgr {

   // If we don't use Vehicle as the parameter here, the clients
   // will not be able to use a generalized interface to call our
   // methods.

   public void rentOut(Vehicle vehicle) {
      // if (vehicle instanceof bike)
         // Renting Out Related DB Operations
   }

   public void checkIn(Vehicle vehicle) {
      // if (vehicle instanceof bike)
         // Vehicle Check In Related DB Operations
   }

   public void diagnose(Vehicle vehicle) {
      // if (vehicle instanceof bike)
         // Self Diagnose functionality of a vehicle
         // Print diagnosis
   }

   public void repair(Vehicle vehicle) {
      // if (vehicle instanceof bike)
         // Perform pre-defined repair
         // Print repair details
   }
}
 方法2の場合、コンパイル時のクライアント側の使い方は次のようになるでしょう。

package com.sumithp.codeguru.nongeneric.vehicle.client;

import com.sumithp.codeguru.nongeneric.vehicle.RentBikeMgrImpl;
import com.sumithp.codeguru.nongeneric.vehicle.RentCarMgrImpl;
import com.sumithp.codeguru.nongeneric.vehicle.RentVehicleMgr;
import com.sumithp.codeguru.vehicle.domain.Bike;
import com.sumithp.codeguru.vehicle.domain.Car;
import com.sumithp.codeguru.vehicle.domain.Vehicle;

public class RentNonGenericVehicleClient {

   public void rentBike() {

      // You want only one interface to handle all rentals
      RentVehicleMgr rentVehicleMgr;

      rentVehicleMgr = new RentBikeMgrImpl();

      Vehicle vehicle = new Bike(104,"TWO",true,150);
      rentVehicleMgr.rentOut(vehicle);

      /*
       * Client can as well do this
       *
       * Vehicle vehicle = new Car(104,"FOUR",true,"PETROL");
       * rentVehicleMgr.rentOut(vehicle);
       *
       * If there are no instanceof checks, this bombs!
       *
       */
   }

   public void rentCar() {

      // You want only one interface to handle all rentals
      RentVehicleMgr rentVehicleMgr;

      rentVehicleMgr = new RentCarMgrImpl();

      Vehicle vehicle = new Car(104,"FOUR",true,"PETROL");
      rentVehicleMgr.rentOut(vehicle);

      /*
       * Client can do the same as shown for rentBike()
       *
       * Vehicle vehicle = new Bike(104,"TWO",true,150);
       * rentVehicleMgr.rentOut(vehicle);
       *
       * If there are no instanceof checks, this bombs too!
       *
       */
   }

}
 方法3は、最も完成度が高く、有効なソリューションです。必要なメソッド群は1つのクラスで公開され、各メソッドはジェネリック変数を通じてそれぞれの実装を提供するため、一貫性に優れています。それでは、このソリューションについて詳しく見ていきましょう。

Javaジェネリックを使った共通階層

 ジェネリックの大きな長所は、その「型消去(Type Erasure)」という性質です。これはつまり、コンパイル時チェックのみが行われ、その後はジェネリック変数が消去され、実行時の検証は行われないということを意味します。一方、これには短所もあり、サードパーティ製のコードと併用するときに安全性が保証されないなどの問題があります(内部コードでも、ジェネリックを使用しないコードであれば同様の問題が起こります)。

 このソリューションを実現するには、方法2に簡単な変更を加え、階層内の各クラスに新しいジェネリック変数を追加します。トップレベルでは、RentVehicleManagerの宣言を、Vehicleを拡張(extends)する型のジェネリック変数を使用するように書き換えます。次に例を示します。

package com.sumithp.codeguru.generic.vehicle;

import com.sumithp.codeguru.vehicle.domain.Vehicle;

public interface RentVehicleMgr< T extends Vehicle > {

   public void rentOut(T vehicle);
   public void checkIn(T vehicle);
   public void diagnose(T vehicle);
   public void repair(T vehicle);
}
 さらに、RentVehicleManagerを継承するクラス(つまりRentBikeManagerとRentCarManager)の宣言を、それぞれにふさわしい型のジェネリック変数を使用するように書き換えます。たとえばカーレンタルに関する実装は次のようになります。

package com.sumithp.codeguru.generic.vehicle;

import com.sumithp.codeguru.vehicle.domain.Car;

public class RentCarMgrImpl implements RentVehicleMgr< Car > {

   // Can use Car as parameter here, as well as allow
   // clients to have a generalized interface

   public void rentOut(Car car) {
      // Renting Out Related DB Operations
   }

   public void checkIn(Car car) {
      // Vehicle Check In Related DB Operations
   }

   public void diagnose(Car car) {
      // Self Diagnose functionality of a vehicle
      // Print diagnosis
   }

   public void repair(Car car) {
      // Perform pre-defined repair
      // Print repair details
   }
}
 これはつまり、クライアント側から特定の種類のManagerクラスのメソッドを呼び出すときは、目的クラスに対応する型のオブジェクトを渡さなければならないということです。これにより、Manager実装クラスはメソッド内で厳密なチェックを行わずに済むようになります。また、こうしたサブクラスのメソッドを修正するときに、instanceofチェックを含める必要はなくなります。

図3 クラス図(方法3の場合)
図3 クラス図(方法3の場合)
 これでクライアントコードは一層すっきりし、安全になります。一般的な実装は次のようになります。

package com.sumithp.codeguru.generic.vehicle.client;

import com.sumithp.codeguru.generic.vehicle.RentBikeMgrImpl;
import com.sumithp.codeguru.generic.vehicle.RentCarMgrImpl;
import com.sumithp.codeguru.generic.vehicle.RentVehicleMgr;
import com.sumithp.codeguru.vehicle.domain.Bike;
import com.sumithp.codeguru.vehicle.domain.Car;

public class RentGenericVehicleClient {

   public void rentBike() {

      // You want only one interface to handle all rentals
      RentVehicleMgr< Bike > rentVehicleMgr;

      rentVehicleMgr = new RentBikeMgrImpl();

      Bike bike = new Bike(104,"TWO",true,150);
      rentVehicleMgr.rentOut(bike);

      /*
       * Client cannot do this:
       *
       * Vehicle vehicle = new Car(104,"FOUR",true,"PETROL");
       * rentVehicleMgr.rentOut(vehicle);
       *
       * Even if there are no instanceof checks, all is well!
       * Client is absolutely clear on what he needs to do.
       *
       */
   }

   public void rentCar() {

      // You want only one interface to handle all rentals
      RentVehicleMgr< Car > rentVehicleMgr;

      rentVehicleMgr = new RentCarMgrImpl();

      Car car = new Car(104,"FOUR",true,"PETROL");
      rentVehicleMgr.rentOut(car);

      /*
       * Client cannot do the following as shown for rentBike():
       *
       * Vehicle vehicle = new Bike(104,"TWO",true,150);
       * rentVehicleMgr.rentOut(vehicle);
       *
       * Even if there are no instanceof checks, all is well!
       * Client is absolutely clear on what he needs to do.
       *
       */
   }
}
 このような設計にすると、保守やコーディングがしやすくなるだけでなく、実装についての理解もしやすくなります。この設計方法は、サービスや機能の実装側クラスだけでなく、サービスのコンシューマ側クラスにとってもメリットがあります。

まとめ

 本稿で紹介したジェネリックの使い方は動的継承に役立ちますが、これはコンパイル時の動的処理を実現するだけです。前にも述べたとおり、実行時にはジェネリック変数はクラスから削除されます。

著者紹介

Sumith Puri(Sumith Puri)
4年以上にわたりJava/J2EEテクノロジを使ったアプリケーションの設計/開発に携わる。現在はSymantec India(インド、プネー)の開発者として勤務。SRSIT(インド、バンガロール)にて情報工学の学士号を取得。Sun Certified Java Programmerの資格を持つ。

過去コラム集
Visual Basicのソートアルゴリズム
VBにおけるタイマーコントロールの基本
Win32で動作するMySQL 5.1用ストレージエンジンプラグイン
Silverlightで作るVistaガジェット「ギターチューナー」
テンプレートによるメタプログラミングと数論
JavaScriptでDOMレンジを扱う
Ajax対応コントロール/コンポーネントの開発:Periodic Refreshパターン
ASP.NETでグリッドビューをネストする
ビューとストアドプロシージャの強力な代替手段「ユーザー定義関数」
軽量で高速なハイパーリンクコントロールを実現するC++クラス
海外のインターネットコム アメリカ韓国ドイツトルコ
関連企業のサイト:ストックフォト イラスト ネットストリート ホテル予約サイト タウン情報 出張 事業継承 シミュレーション トランクルーム 優待映画チケット 田舎暮らしガイド オリジナルTシャツ ニタコエ
Copyright 2008 Jupitermedia Corporation All Rights Reserved. http://www.internet.com/
space.gif space.gif