7

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!

Community
  • 1
  • 1
wehlutyk
  • 73
  • 4
  • 1
    What you're trying to do is casting a parent class obj to a child class obj now this should throw an unchecked warning, but you need to pass an argument that extends Food and cast it to a child class obj like Apple or Banana... If I were you I would `do void save() { getStorage().setFood(this);}` and override this method in each class extends food. yes more code but more solid design :/ – Muhammad Bekette May 22 '13 at 11:53
  • 1
    Related: [Is there a way to refer to the current type with a type variable?](http://stackoverflow.com/questions/7354740/is-there-a-way-to-refer-to-the-current-type-with-a-type-variable) – Paul Bellora May 22 '13 at 16:10
  • (Thanks for the edit Anders) – wehlutyk May 24 '13 at 10:05
  • +1 to @PaulBellora for the fact that leaf classes should be marked `final`. In essence the answer to that question is the same as meriton's. – wehlutyk May 24 '13 at 10:19

1 Answers1

6

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);
}
meriton
  • 68,356
  • 14
  • 108
  • 175
  • Brilliant! I was just referring to something like this but "this" is Brilliant – Muhammad Bekette May 22 '13 at 11:57
  • meh! I'd have said the same ;) – Seeta Somagani May 22 '13 at 13:41
  • Nice, and thank you @Muhammad. About the mutual dependency I'm not particularly attached to it, I just don't see any other way of implementing `apple.save()`. If a suggestion appears before a couple days without the mutual dependency I'll take it as an answer, other I'll validate this answer as the final one. – wehlutyk May 24 '13 at 10:08