4

Is it possible to specify that an unknown generic type is self-referential?

A failed attempt:

import java.util.*;

class Generics {
   public enum A { A1, A2 }
   public enum B { B1, B2 }

   public static List<? extends Enum<?>> listFactory(String[] args) {
      if (args.length == 0) {
         return new ArrayList<A>(Arrays.asList(A.A1, A.A2));
      } else {
         return new ArrayList<B>(Arrays.asList(B.B1, B.B2));
      }
   }

   public static void main(String[] args) {
      List<? extends Enum<?>> lst = listFactory(args);
      dblList(lst);
      System.out.println(lst);
   }

   public static <EType extends Enum<EType>> void dblList(List<EType> lst) {
      int size = lst.size();
      for (int i = 0; i < size; i++) {
         lst.add(lst.get(i));
      }
   }
}

This results in a compilation error:

Generics.java:17: error: method dblList in class Generics cannot be applied to given types;
      dblList(lst);
      ^
  required: List<EType>
  found: List<CAP#1>
  reason: inferred type does not conform to declared bound(s)
    inferred: CAP#1
    bound(s): Enum<CAP#1>
  where EType is a type-variable:
    EType extends Enum<EType> declared in method <EType>dblList(List<EType>)
  where CAP#1 is a fresh type-variable:
    CAP#1 extends Enum<?> from capture of ? extends Enum<?>
1 error

Ideally, the return type of listFactory() would signal that the list contains self-referential generic types (whose exact type is unknown).

Is this possible? If so, what should the types of listFactory() and lst be?

Michaelll
  • 85
  • 7

2 Answers2

1

Effective Java Item 28 discourages using wildcards in return types:

Do not use wildcard types as return types. Rather than providing additional flexibility for your users, it would force them to use wildcard types in client code.

Properly used, wildcard types are nearly invisible to users of a class. They cause methods to accept the parameters they should accept and reject those they should reject. If the user of a class has to think about wildcard types, there is probably something wrong with the class's API.

This is a good example of exactly the issues EJ describes. listFactory() is really just returning a List<Enum<?>> but by declaring a wildcard return type you have to jump through hoops to accomplish seemingly simple tasks.

If you instead give listFactory() a signature like so:

public static List<Enum<?>> listFactory(String[] args)

You can clean up the signature of dblList() too:

public static <E> void dblList(List<E> lst)
dimo414
  • 47,227
  • 18
  • 148
  • 244
0

In Java, a type argument is either a concrete type, a wildcard type, or a type variable. A concrete type is not flexible enough for your use case, and wildcards can not be constrained to be self referential (because each occurrence of a wildcard can stand for a different type).

This leaves type variables, which can be constrained to be self referential, but are provided by the caller of the constructor or method, not the callee, so we can't just do:

<E extends Enum<E>> List<E> listFactory(String[] args);

because somebody might invoke this with an incorrect type argument.

One way to work around this is to decorate the return type:

interface EnumList<E extends Enum<E>> extends List<E> {

}

EnumList<?> listFactory(String[] args);

A caller can then do:

EnumList<?> x = listFactory(args);
dblList(x);

where dblList uses wildcard capture to manipulate the list:

<E extends Enum<E>> void dblList(List<E> list);

It is worth noting that this makes the method signatures quite a bit harder to write, so you should only do this if the method in question actually needs to know that the type is self referential. I mention this because your dblList method does not, and could simply be written as:

<E> void dblList(List<E> list);

instead.

meriton
  • 68,356
  • 14
  • 108
  • 175