19

Is there a syntax or workaround to constrain a generic type parameter to any of a range of types?

I am aware that you can constrain a type to be all of a range of types (ie AND logic):

public class MyClass<T extends Comparable<T> & Serializable> { } // legal syntax

Is there an OR logic version, ie something like this:

public class MyClass<T extends Comparable<T> | Serializable> { } // illegal syntax

If there isn't a syntax that supports this (I don't think there is), is there a workaround or approach that is a good pattern?

For some context, one example use case might be:

/** @return true if the obj is either has the same id, or if obj is the same as id */
public <T extends MyClass | String> boolean sameAs(T obj) {
    if (obj instanceof String) return this.id.equals(obj);
    if (obj instanceof MyClass) return this.id.equals(((MyClass)obj).id);
    return false;
}

People seem to be getting hung up on the exact semantic of my method example above. Let's try this instead:

public class MyWrapper<T extends A | B> {
    // my class can wrap A or B (unrelated classes). Yes I will probably use instanceof
}

EDITED:
I won't know at compile time which I might get (coming from external code), so I want to avoid having concrete classes for each type. Also, I have to give my class to a foreign system who invokes my class.method, but the other system can give me instances of a variety of classes, but a narrowly defined and known variety.

Some people have commented on instanceof being "impure". Well, one workaround is to use a factory method to pick my concrete class based on the class of the incoming object, but that factory method would have to use instanceof, so you're just moving the instanceof to another place - you still need the instanceof.

Or is this idea just not ever a good one?

Bohemian
  • 412,405
  • 93
  • 575
  • 722
  • 1
    Would it not be better to provide two overloaded sameAs implementations? – 01es Jul 06 '11 at 07:24
  • 2
    The OR syntax does not make sense. When you specify a generic constraint, it's a contract and it cannot be either or type. In your example, `obj` might as well not be generic if you are going to use `instanceof`; just make it `Object obj`. – Bala R Jul 06 '11 at 07:29
  • Just use an `extends `. Nothing in the following code would be any different between this situation and the language feature you are imagining, because Java does not support "duck typing" or interface synthesis. – BadZen Aug 15 '22 at 16:07
  • @BadZen the most specific common superclass of A and B is `Object`, which allows anything and so puts no limit on the type. Even if there were a subclass of `Object` that is a common superclass, it still wouldn't limit the type to only A or B. – Bohemian Aug 15 '22 at 20:18
  • Again, there is no code you could write that would demonstrate a difference between those situations without an explicit cast or run-type-type check. Consider that if you did have this language feature, the compiler would simply use the most specific common superclass (ie. the upper bound of the generic type expression, as per usual) after type erasure. It's literally no different after compile-time. If that is `Object`, you could not access any methods other than those defined in `Object` via parametric polymorphism - they would not be members of the type. – BadZen Aug 17 '22 at 15:16

5 Answers5

8
public class MyWrapper<T extends A | B> {}

You can't do this for interfaces that you don't have control over, but for your own stuff you could use an empty marker interface:

interface AOrB {
}

interface A extends AOrB {
    someMethodHere();
}

interface B extends AOrB {
    someOtherMethodHere();
}

public class MyClass<T extends AOrB> {}

Regardless of what purists say, using instanceof is perfectly fine when you need it.

Joonas Pulakka
  • 36,252
  • 29
  • 106
  • 169
7

No. It wouldn't make any sense unless all the types had a non-empty union type, e.g. an interface they all implemented, or a base class they all extended, in which case you just specify the union type.

user207421
  • 305,947
  • 44
  • 307
  • 483
  • After some thought (2 months :)) I agree - it doesn't make sense. If you can't deal with the types homogeneously, don't try to merge the types. – Bohemian Sep 13 '11 at 21:38
4

While Java has limited support for "intersection types" like T1 & T2, support for "union types" is scanty.

Generic types with wildcard are actually union types: G<? extends X> is the union of all G<S> where S is a subtype of X.

No support for union of arbitrary two types.

Java 7's multi-catch syntax looks like it supports union of arbitrary exception types

catch (IOException|SQLException ex){ .. }

but not really, the type of ex is a fixed super class, not a union of the two classes.

S.R.I
  • 1,860
  • 14
  • 31
irreputable
  • 44,725
  • 9
  • 65
  • 93
1

Using of instanceof is considered as not very good style of programming, and allowing you to use OR in generics implies you will use one.

Sergey Aslanov
  • 2,397
  • 15
  • 16
  • 1
    It does not imply it, actually. For example, think of a generic static helper function which calls one of two concrete functions of the same name with a different parameter type. – Chris Betti Jan 25 '17 at 19:08
  • @ChrisBetti Actually, you couldn't just call a specific method like this with just the name consider two unrelated classes passed to the same method taking `Object` typed parameters - you have to perform a cast befor calling `someMethod()` even if they both implement it. However, "does not imply it" actually holds - it is still true that you could employ purely parametric polymorphism here by only invoking methods of a common superclass of the two types. In this case, however, there's no need for the disjunctive composition suggested by OP - you would just use the common superclass' type. – BadZen Aug 15 '22 at 16:04
  • If I recall correctly, I was attempting to paint a picture of how this could work if Java did provide generic unions. In that hypothetical situation, I would imagine that the compiler could determine that the syntax is compatible without use of instanceof. It was a nitpick of a hypothetical design choice :) – Chris Betti Aug 15 '22 at 18:56
0

The following code would do the same thing as in the provided example, but without runtime type checking and typecasts.

public boolean sameAs(MyClass obj) {
    return this.id.equals(obj.id);
}
public boolean sameAs(String obj) {
    return this.id.equals(obj);
}

NPE checking might be a good idea.

01es
  • 5,362
  • 1
  • 31
  • 40