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チェックを含める必要はなくなります。
これでクライアントコードは一層すっきりし、安全になります。一般的な実装は次のようになります。
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. * */ } }
このような設計にすると、保守やコーディングがしやすくなるだけでなく、実装についての理解もしやすくなります。この設計方法は、サービスや機能の実装側クラスだけでなく、サービスのコンシューマ側クラスにとってもメリットがあります。
まとめ
本稿で紹介したジェネリックの使い方は動的継承に役立ちますが、これはコンパイル時の動的処理を実現するだけです。前にも述べたとおり、実行時にはジェネリック変数はクラスから削除されます。

