I am trying to create a method that will return a generic type parameter.
I have a class VehicleOrder that extends the abstract class Order. In class Order, I created an abstract method receiveHiredObject. This method will not receive any parameter and will return a generic.
public abstract class Order implements Orderable {
private Date date;
private Customer customer;
public abstract <T> T receiveHiredObject();
I implemented this method in the class VehicleOrder and I set it up to return the class parameter vehicle.
public class VehicleOrder extends Order {
private Vehicle vehicle;
public VehicleOrder(final Date date, final Customer customer, final Vehicle vehicle) {
super(date, customer);
this.vehicle = vehicle;
}
@SuppressWarnings("unchecked")
@Override
public Vehicle receiveHiredObject() {
return vehicle;
}
The problem is that when I'm instantiating a new VehilceOrder of the type Order and, am using the method receiveHiredObject, this method is returning me an Object and I have available only the method toString.
Order a = new VehicleOrder();
a.receiveHiredObject();
I want that in the future to use the same method for other types of objects like HotelOrder.
What I'm doing wrong, why does the method receiveHiredObject is returning an Object instead of a VehicleOrder? How can I improve the method to return the right type?
Thank you,
CodePudding user response:
Note that receiveHiredObject
does "return the right type", if you tell it what type to return.
Order order = new VehicleOrder();
Vehicle v = order.receiveHiredObject();
It returns Object
because without you specifying a type, the type parameter T
gets inferred to be Object
, because T
is only bounded by Object
.
However, your method is not safe at all! You can do silly things like:
Order order = new VehicleOrder();
Hotel h = order.receiveHiredObject();
And this will compile, but crash when you run it.
This is because you declared receiveHiredObject
to return a T
- the generic parameter. This means that whatever T
that the caller specifies - whether it is Vehicle
, Hotel
, or Foo
, receiveHiredObject
is declared to return that type of thing. Clearly, it doesn't do that. What it returns depends on the subclass used. The caller doesn't get to decide what it returns! When you implement receiveHiredObject
in VehicleOrder
, you received a warning because returning a Vehicle
might not be the T
that the caller expects and the runtime cannot check this for you, but you suppressed the warning.
So receiveHiredObject
should not be generic at all. You can instead make Order
generic:
// might have to change Orderable too, if receiveHiredObject comes from there
public abstract class Order<T> implements Orderable {
private Date date;
private Customer customer;
public abstract T receiveHiredObject();
}
public class VehicleOrder extends Order<Vehicle> {
// same code as before...
}
Now the caller can do:
Order<Vehicle> order = new VehicleOrder();
order.receiveHiredObject().someVehicleSpecificThing();
Now it is much safer - you won't be able to get hotels from a vehicle order.
Note that it is absolutely necessary to provide the type information of Vehicle
. This is because what order.receiveHiredObject
returns is determined by the runtime type of order
, and the compiler has no way of knowing what that is. Think in the extreme case:
Order order = new Random().nextBoolean() ? new VehicleOrder() : new HotelOrder();
// what type should order.receiveHiredObject return here?
CodePudding user response:
You could move the generic type declaration to the class level for example:
public abstract class Order<T> implements Orderable {
private Date date;
private Customer customer;
public abstract T receiveHiredObject();
}
public class VehicleOrder extends Order<Vehicle> {
private Vehicle vehicle;
public VehicleOrder(final Date date, final Customer customer, final Vehicle vehicle) {
super(date, customer);
this.vehicle = vehicle;
}
@SuppressWarnings("unchecked")
@Override
public Vehicle receiveHiredObject() {
return vehicle;
}
}
(Think syntax is correct)