Java constrain parameter to common superclass

Viewed 251

Motivation

I have an Either<L, R> class, which represents a value of one of two types, or semantically different states. In some cases, it is valuable to operate on it no matter which alternative the value is.

Problem

I want a (non-static) method that takes a Consumer<T>, where T is a supertype of both L and R, where L and R are type parameters for the class.
Currently, java lets me do this: (static implementation)

public static <T, L extends T, R extends T> void collapse(Either<L,R> e, Consumer<T> op)

But of course, with a non-static implementation, I can't impose constraints on L and R, because they're already defined for the instance in question. I need those constraints imposed on T instead, but java won't let me write the following because it only allows one class in a supertype or subtype constraint at once. This is especially frustrating given all classes share at least Object as a common supertype, so these constraints are always satisfiable.

public void collapse(Consumer<? super L & R> op)

Is there any other way to define this constraint, any hint of it being allowed in a later version of java, or any explanation of why it would be a breaking feature?

2 Answers

In your static version, since you need the consumer to be able to accept either the "L" or the "R" type, you don't actually need those type variables: Either<? extends T, ? extends T> e.

But beyond this, I would say that your static version is really the best you can do. Java just doesn't have a particularly expressive type system.

I don't think that Eran's answer is a particularly good solution (with respect), because:

  1. it bakes in a bound on the Consumer: you have to use as general a bound as you ever expect, and unless you just use Object (which is annoyingly broad), you'll always find a case where you wish it were just a tiny bit more permissive;
  2. it introduces an extra type parameter everywhere you use an Either type, even where you don't need to use the consumer, because you have to always provide 3 type parameters (even if they are ?). A third type parameter just feels like cruft.

The only real downside I see to the static version is the slightly awkward calling convention: Either.collapse(anEither, aConsumer) as opposed to anEither.collapse(aConsumer). Sure, the former is marginally more verbose... but it does what you want, so you may just have to accept the awkwardness.

Perhaps you should add T as a third type parameter of your class:

class Either<T, L extends T, R extends T>
{
    public void collapse(Consumer<T> op) {

    }
}
Related