![]() ![]() ![]() ![]() Javaジェネリックを使ったコンパイル時の動的処理この記事のURLhttp://japan.internet.com/developer/20080502/26.html
著者:Sumith Puri
海外internet.com発の記事
はじめに本稿では、ジェネリックを利用してコンパイル時の動的処理およびクライアント関連の型安全性(type-safety)を実現する方法について解説します。一般的に、サブクラス化を行う際の最も重要な側面は、クラス固有の機能を実現するために、いかにして同じメソッドパラメータを使ってオーバーライドを実現するかということです。場合によっては、クラス固有のパラメータが必要になることもあるかもしれません。さらに、オーバーライドメソッドが、こうしたクラス固有パラメータのスーパークラスであるパラメータを使用する場合も考えられます。このようなメソッドの例としては、パブリックAPIを通じて公開され、具象実装クラス内でオーバーライドされるメソッドが挙げられます。シナリオ本稿で取り上げるのは非常によくあるシナリオで、多くの人が過去に直面したことがあり、同じような方法で解決してきたのではないかと思います。ここでは、単純なレンタカーシステムの例を使ってジェネリックの利用方法を説明します。今回の例では、メインのレンタルサービスクラスをRentVehicleManagerとしますが、このクラスはインターフェイスにしても抽象クラスにしてもかまいません。RentCarManagerとRentBikeManagerはRentVehicleManagerのサブクラスで、RentVehicleManagerのメソッドをそれぞれの機能に合わせてオーバーライドします。このシナリオの実用的な実装ソリューションとしては、次のようなものが考えられます。
方法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); } 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 } } 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! * */ } } 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); } 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 } } これでクライアントコードは一層すっきりし、安全になります。一般的な実装は次のようになります。 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の資格を持つ。
|