morrow
morrow

Reputation: 316

Covariance on a generic class with generic methods

I'm unable to correctly define a generic method when using covariance on a generic class, if this is at all possible the way I intend to. I'll best explain the issue at hand by example.

Say we have the following setup of interfaces for cars

interface Car { ... }
interface SportsCar extends Car { ... }

and such generic interfaces for car vendors returning a Sale object

interface CarVendor<C extends Car> {    
  Sale<C> sell(C car);
}

interface SportsCarVendor extends CarVendor<SportsCar> {    
  @Override
  Sale<SportsCar> sell(SportsCar car);
}

Let's now suppose we want our cars to be generic, e.g. regarding fuel type:

interface Car<F extends FuelType> { ... }
interface SportsCar<F extends FuelType> extends Car<F> { ... }
class PetrolSportsCar extends SportsCar<Petrol> { ... }
class DieselSportsCar extends SportsCar<Diesel> { ... }

We run into problems when redefining our vendor interfaces if we wan't them to be able to sell cars for any kind of fuel. A generic method seem to be the answer, however I'm unable to correctly define it since the generic Car<?> is defined on the class but the generic FuelType should be defined on the method. To get the idea:

interface CarVendor<C extends Car<?>> {
  <F extends FuelType> Sale<Car<F>> sell(Car<F> param);
}

interface SportsCarVendor extends CarVendor<SportsCar<?>> {
  @Override 
  <F extends FuelType> Sale<SportsCar<F>> sell(SportsCar<F> param);
}

SportsCarVendor obviously doesn't compile since the signature sell(SportsCar<F>) doesn't match with the expected type SportsCar<?>.

Can anybody offer a viable solution for this problem?

Upvotes: 4

Views: 129

Answers (2)

smac89
smac89

Reputation: 43186

If you really have to handle fuel type separately for each car vendor (maybe tax reasons?), here is how you define your car vendor to allow this:

interface CarVendor<F extends FuelType, C extends Car<F>> {
    Sale<C> sell(C param);
}

interface SportsCarVendor <F extends FuelType> extends CarVendor<F, SportsCar<F>> { }

Now for the concrete implementations:

class SportsCarVendorDiesel implements SportsCarVendor<DieselFuel> {
    @Override
    public Sale<SportsCar<DieselFuel>> sell(SportsCar<DieselFuel> param) {
        return null;
    }
}

class SportsCarVendorGas implements SportsCarVendor<GasolineFuel> {
    @Override
    public Sale<SportsCar<GasolineFuel>> sell(SportsCar<GasolineFuel> param) {
        return null;
    }
}

The rest of the classes I used are as so:

interface FuelType {
    double burnRate();
}

class DieselFuel implements FuelType {

    @Override
    public double burnRate() {
        return 0;
    }
}

class GasolineFuel implements FuelType {

    @Override
    public double burnRate() {
        return 0;
    }
}

interface Car<F extends FuelType> { }
interface SportsCar<F extends FuelType> extends Car<F> { }

class Sale<C> { }

Upvotes: 0

rgettman
rgettman

Reputation: 178293

From what you have provided so far, I don't see any reason why most of your interfaces and classes should be generic, and I see reasons why most should not be generic.

FuelType sounds like it should be an attribute, not a type parameter, of a Car. Perhaps it could be declared as an enum, depending on your exact requirements.

enum FuelType {
    PETROL,
    DIESEL;
}

public class Car {
    private FuelType fuelType;
    // rest of implementation
}

Similarly, Car should be an attribute, not a type parameter, of a Sale.

public class Sale {
    private Car sold;
    // rest of implementation
}

You may still need SportsCarVendor to be generic so that you can narrow the type of car the implementation class can sell, but the Sale the sell method returns still doesn't need to be generic.

interface CarVendor<C extends Car> {    
    Sale sell(C car);
}

interface SportsCarVendor extends CarVendor<SportsCar> {    
    @Override
    Sale sell(SportsCar car);
}

Additionally, if you happen to need a specific subclass of Sale, e.g. SportsCarSale, then you can use return-type covariance, which is the ability for a subclass to narrow the return type without generics:

interface SportsCarVendor extends CarVendor<SportsCar> {    
    @Override
    SportsCarSale sell(SportsCar car);
}

Upvotes: 3

Related Questions