Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Casting a generic superclass to a subclass

This is my first SO question, I hope it's useful enough, both for readers and myself! I've googled and ducked the world around with this for the past two days.

I have abstract model and storage classes from which concrete model and storage classes are derived:

abstract class Food {}

abstract class FoodStorage<T extends Food> {
    abstract void setFood(T food);
}

class Apple extends Food {}

class Basket extends FoodStorage<Apple> {
    @Override
    void setFood(Apple apple) {
        // Save that apple to the basket
    }
}

No problem. Now, I would like to be able to call a save() directly on an Apple instance, persisting it to its Basket (without having to bother about the basket), and have that implemented in the abstract classes. The best I've found yet is this:

abstract class Food<T extends Food<T,S>,
        S extends FoodStorage<T,S>> {

    abstract S getStorage();

    void save() {
        getStorage().setFood((T)this); // <---- Unchecked cast warning
    }

}

abstract class FoodStorage<T extends Food<T,S>, S extends FoodStorage<T,S>> {
    abstract void setFood(T food);
}

class Apple extends Food<Apple,Basket> {
    Basket basket = new Basket(); // or Basket.getInstance();

    @Override
    Basket getStorage() {
        return basket;
    }
}

class Basket extends FoodStorage<Apple,Basket> {
    @Override
    void setFood(Apple apple) {
        // Save that apple to the basket
    }
}

Which works, but IntelliJ gives me a warning about the unchecked cast in save(). Indeed, I'm casting from Food<T,S> to T.

Question: how can I implement this apple.save() in a typesafe way?

I don't want any wildcards appearing in the client code, so changing abstract void setFood(T food); to abstract <Z extends Food<T,S>> void setFood(Z food); is not the solution. (Obviously I'm avoiding SupressWarnings("unchecked") also).

I'm aware of Java Generics, how to avoid unchecked assignment warning when using class hierarchy? , of Generics cast issue , and of The get-put principle, but I still can't get my head around it.

Thanks in advance!

like image 925
wehlutyk Avatar asked May 22 '13 10:05

wehlutyk


People also ask

Can you cast a superclass to a subclass?

You can try to convert the super class variable to the sub class type by simply using the cast operator. But, first of all you need to create the super class reference using the sub class object and then, convert this (super) reference type to sub class type using the cast operator.

Does the assignment of a superclass to a subclass require a cast?

Java doesn't allow assigning a superclass object to a subclass one. To do so would require explicit casting, or known as downcasting.

Can you cast a subclass?

As it was said before, you can't cast from superclass to subclass unless your object was instantiated from the subclass in the first place.

Can you cast a subclass to another subclass?

This isn't possible. If you could do this, you could always cast any reference to any other class, since all classes are subclasses of Object . You will have to create a new instance of the class you want, and transfer the information manually.


1 Answers

I seems rather questionable design to have a mutual dependency between the food and its storage. A unidirectional depedency would simplify the generics greatly:

class Food { ... }
class FoodStorage<F extends Food> {
    void setFood(F f);
}

But if you insist on the mutual dependency, you can do it without a cast as follows:

abstract class Food<F extends Food<F, S>, S extends FoodStorage<F, S>> {
    abstract F getThis();
    abstract S getStorage();

    void save() {
        getStorage().setFood(getThis());
    }
}
abstract class FoodStorage<F extends Food<F, S>, S extends FoodStorage<F, S>> {
    abstract void setFood(F food);
}
like image 98
meriton Avatar answered Oct 06 '22 00:10

meriton