I have the following two interfaces:
/**
* A marker interface to denote that an object implements a view on some other object.
*
* @param <T> The type of object that is viewed
*/
public interface View<T extends Viewable<View<T>>> {
}
/**
* An interface for objects that are viewable via a view.
*
* @param <T> The type of viewable object
*/
public interface Viewable<T extends View<?>> {
public void addViewCallback(final T view);
public void removeViewCallback(final T view);
}
I want to enforce the following:
View
(called (a)), should be a Viewable
that views upon that view (a).Viewable
(called (b)), should be a View
, which is viewable via that same viewable (b).I think I got the bounds done for View
, but how am I going to make them work for Viewable
? What I got now compiles, but does not offer enough protection.
I cannot, as of now, formulate something that gets accepted which I do not want, I can however formulate what I do want, if that helps:
public class Hand implements Viewable<HandView>
public interface HandView extends View<Hand>
There may be times when you want to restrict the types that can be used as type arguments in a parameterized type. For example, a method that operates on numbers might only want to accept instances of Number or its subclasses. This is what bounded type parameters are for.
A recursive type is one that includes a function that uses that type itself as a type for some argument or its return value. In our example, compareTo(T other) is the function of the recursive type that takes the same recursive type as an argument.
A bound is a constraint on the type of a type parameter. Bounds use the extends keyword and some new syntax to limit the parameter types that may be applied to a generic type. In the case of a generic class, the bounds simply limit the type that may be supplied to instantiate it.
Multiple Bounds Bounded type parameters can be used with methods as well as classes and interfaces. Java Generics supports multiple bounds also, i.e., In this case, A can be an interface or class. If A is class, then B and C should be interfaces. We can't have more than one class in multiple bounds.
As your View
is a marker interface, and the fact that you as of now haven't formulated anything that gets accepted which you don't want, I question the use of the View
interface entirely. You're putting an unnecessary restriction on yourself that leads to nothing but trouble.
/**
* An interface for objects that are viewable via a view.
*
* @param <T> The type of viewable object
*/
public interface Viewable<T> {
public void addViewCallback(final T view);
public void removeViewCallback(final T view);
}
As discussed with @SimonAndréForsberg, programming too defensively is often an unnecessary waste of time, and we should trust our own declarations.
However, for the fun of it, here is a solution to enforce all the restrictions you want on class/interfaces declarations at compile time.
View
interface:public interface View<T extends Viewable<T, ?>> {}
Here, we just want to ensure that the type parameter is a Viewable
.
The second type argument does not need restriction, because the declaration of Viewable
would not allow a Viewable<T,?>
to exist if the ?
were not itself a View<T>
.
Viewable
interface:public interface Viewable<T extends Viewable<T,?>, V extends View<T>> {
public void addViewCallback(final V view);
public void removeViewCallback(final V view);
}
T
needs to be declared as a Viewable
because we use it as type parameter for View<T>
at the end of the line.?
because we say right after that the second type parameter needs to be a View<T>
if the first type parameter is a T
.public class Hand implements Viewable<Hand, HandView> {
@Override
public void addViewCallback(HandView view) {}
@Override
public void removeViewCallback(HandView view) {}
}
public interface HandView extends View<Hand> {
}
I managed to get it working, while taking all other answers here into consideration.
It seems like @Joffrey's answer got me started quite a bit, but then I realised that it is simply not possible this way, as method arguments cannot be covariant.
I also agree that it might be too much work, but that's why it is some experimental stuff, at work with time pressure I might indeed think twice before wasting time on this.
One of the possible solutions is:
/**
* A marker interface to denote that an object implements a view on some other object.
*
* @param <T> The type of object that is viewed
*/
public interface View<T extends Viewable<T, ? extends View<T>>> {
}
/**
* An interface for objects that are viewable via a view.
*
* @param <T> The type of viewable object
* @param <V> The concrete view on the viewable object
*/
public interface Viewable<T extends Viewable<T, V>, V extends View<T>> {
public void addViewCallback(final V view);
public void removeViewCallback(final V view);
}
Example implementation:
public interface HandView extends View<Hand> {
public void onCardAdded(final Card card);
public void onCardPlayed(final int cardIndex);
public void onCardsSwapped(final int cardIndexOne, final int cardIndexTwo);
}
public class Hand extends AbstractCollection<Card> implements Collection<Card>, Viewable<Hand, HandView> {
private final List<HandView> views = new ArrayList<>();
//...
@Override
public void addViewCallback(final HandView view) {
views.add(Objects.requireNonNull(view));
}
@Override
public void removeViewCallback(final HandView view) {
views.remove(Objects.requireNonNull(view));
}
@Override
public boolean add(final Card card) {
Objects.requireNonNull(card);
States.requireFalse(isFull(), "hand is full");
list.add(card);
views.forEach(view -> view.onCardAdded(card));
return true;
}
//...
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With