Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Recursive type parameters for an almost-cyclic type bound

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:

  • The type parameter of the View (called (a)), should be a Viewable that views upon that view (a).
  • The type parameter of 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>
like image 972
skiwi Avatar asked Apr 29 '14 12:04

skiwi


People also ask

What are bounded type parameters?

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.

What is a recursive type in Java?

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.

What is type bound in Java?

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.

Is it possible to declare a multiple bounded type parameter?

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.


3 Answers

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.

Simplify!

/**
 * 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);
}
like image 76
Simon Forsberg Avatar answered Oct 20 '22 19:10

Simon Forsberg


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.

Declarations

The 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>.

The 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);
}
  • Here, T needs to be declared as a Viewable because we use it as type parameter for View<T> at the end of the line.
  • Same as before, no restriction needed on ? because we say right after that the second type parameter needs to be a View<T> if the first type parameter is a T.

Usage

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> {
}
like image 42
Joffrey Avatar answered Oct 20 '22 20:10

Joffrey


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;
    }

    //...
}
like image 3
skiwi Avatar answered Oct 20 '22 18:10

skiwi