Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Java Generics Call Constructor

Let's assume I have four classes: Car, Convertible, PickupTruck and CarManufacturer.

Car is the abstract class that Convertible and PickupTruck inherit from:

public abstract class Car {
    private String name;
    private String colour;

    //Constructor
}

Convertible and PickupTruck both have parameterless constructors:

public class Convertible extends Car {
    private boolean roofUnfolded;

    public Convertible() {
        super("Convertible", "Red");
        this.roofUnfolded = false;
    }
}

public class PickupTruck extends Car {
    private double capacity;

    public PickupTruck() {
        super("Pickup Truck", "Black");
        this.capacity = 100;
    }
}

CarManufacturer stores a List of either Convertibles or PickupTrucks.

public class CarManufacturer <T extends Car>{
    private List<T> carsProduced = new LinkedList<>();
}

How can I implement a function produceCar() that calls the parameterless constructor and adds the object to the list? I tried:

public void produceCar(){
    this.carsProduced.add(new T());
}

Returning the error: Type parameter 'T' cannot be instantiated directly

like image 606
Jan-Benedikt Jagusch Avatar asked Jul 19 '17 08:07

Jan-Benedikt Jagusch


2 Answers

The same issues was solved here: https://stackoverflow.com/a/36315051/7380270

With regards to the problem, this works:

public class CarManufacturer <T extends Car> {
    private Supplier<T> carType;
    private List<T> carsProduced = new LinkedList<>();

    public CarManufacturer(Supplier<T> carType) {
        this.carType = carType;
    }

    public void produceCar() {
        this.carsProduced.add(carType.get());
    }

}

public class Main {
    public static void main(String[] args) {
        CarManufacturer<Convertible> convertibleCarManufacturer = new CarManufacturer<>(Convertible::new);
        convertibleCarManufacturer.produceCar();
    }
}
like image 184
Jan-Benedikt Jagusch Avatar answered Oct 13 '22 21:10

Jan-Benedikt Jagusch


You can add Class<T> to the CarsManufacturer, which will preserve the meta-information about the type-parameter at Runtime. This could allow you to instantiate T, by using the Class#newInstance() method:

public class CarManufacturer<T extends Car> {

    private List<T> carsProduced = new LinkedList<>();

    private Class<T> clazz;

    public CarManufacturer(Class<T> clazz) {
        this.clazz = clazz;
    }

    public void produceCar() throws IllegalAccessException, InstantiationException {
        this.carsProduced.add(clazz.newInstance());
    }

}

You can then use it like this:

CarManufacturer<Convertible> carManufacturer = new CarManufacturer<>(Convertible.class);
carManufacturer.produceCar();

Even though this should work, keep in mind that there are few notes that are worth mentioning:

  • I wouldn't use the Class<T> member, just to get access to the type-parameter replacement at Runtime. I would rather add a (T instance) parameter to the produceCar method signature and directly add this instance to the list. Since you instantiate the CarManufactured by explicitly specifying the type-parameter, then there's no need to keep that Class<T>, because you already have the awareness of what the parameter is.
  • I would rename the produceCar method to something more related to what the method does - for example, saveCar() or addCar().
like image 39
Konstantin Yovkov Avatar answered Oct 13 '22 20:10

Konstantin Yovkov