简体   繁体   中英

Static polymorphism with generics in Java

I was hoping to create some clean code that can recursively dig into a collection and print the first Integer it finds. Coming from a C++ background that approach looked like this:

class Test {
static void print(Integer i) {
  System.out.println(i);
}

static <T> void print(ArrayList<T> arr) {
  T entry= arr.at(0);
  Test.print (entry);
}

static void Test() {
    ArrayList<ArrayList<Integer>> arrayOfArrayOfInt= create();
    print( arrayOfArrayOfInt );
}
}

Unfortunately this doesn't work.

One alternative is to give up on static polymorphism and create a function print(Object o) and then do a number of instanceof checks to branch to the right behavior. Is that the only way it can be done due to Java's type erasure or is there a more elegant approach?

You're right in assuming that you can't treat java generics as you would a c++ template.

So, without instanceof , this will not guess that entry is an Integer (neither in runtime nor in compile time):

static <T> void print(ArrayList<T> arr) {
  T entry= arr.get(0);
  Test.print (entry);
}

So to "to create some clean code that can recursively dig into a collection and print the first Integer it find" (or rather the first non list element at index 0) in java:

static void print(Object obj) {
    System.out.println(obj);
}

static <T> void print(List<T> arr) {
    T entry = arr.get(0);
    if (entry instanceof List)
        print((List<?>) entry);
    else 
        print(entry);
}

static void test() {
    List<List<Integer>> arrayOfArrayOfInt = create();
    print(arrayOfArrayOfInt);
}

Not the elegant solution you'd like but the feasible solution the generic type system requires.

The following method will recursively dig into an Object and return an Optional containing the first Integer it finds, or Optional.empty() if it can't find one.

static Optional<Integer> firstInt(Object o) {
    if (o instanceof Integer)
        return Optional.of((Integer) o);
    if (o instanceof int[]) {
        int[] array = (int[]) o;
        return array.length > 0 ? Optional.of(array[0]) : Optional.empty();
    }
    if (o instanceof Object[])
        return firstInt(Arrays.asList((Object[]) o));
    if (o instanceof Iterable) {
        for (Object o2 : (Iterable<?>) o) {
            Optional<Integer> result = firstInt(o2);
            if (result.isPresent())
                return result;
        }
    }
    return Optional.empty();
}

You can do this using polymorphism, but you would have to create an interface Searchable like this

interface Searchable {
    Optional<Integer> search();
}

and then create wrapper classes for all the concrete types you want to be able to search. For example:

public final class SearchableList<T extends Searchable> extends AbstractList<T> implements Searchable {

    private final List<T> list;

    SearchableList(List<T> list) {
        this.list = list;
    }

    // rest omitted
}

However, this would be such a convoluted mess, and I would avoid it, preferring the instanceof checks instead.

Short answer: Yep, give up on static polymorphism and use instanceof . Or just write a local loop to do your work.

Java's generics are much weaker than C++'s templates. In particular, the compiler will not produce new specializations for you. All you can do with them is do more restrictive static type checking on classes and methods that are already defined through normal means (eg you wrote them out by hand or generated them with a tool). As you suspected, type erasure is relevant.

With Java generics, this code:

static <T> void print(ArrayList<T> arr) {
  T entry= arr.at(0);
  Test.print (entry);
}

always effectively turns in to this, due to type erasure:

static Object void print(ArrayList<Object> arr) {
  Object entry= arr.at(0);
  Test.print (entry);
}

It will not produce separate specializations as C++ templates would.

Your particular piece of code isn't working because it's missing a couple particular methods, which you'd have to supply by hand. But I think I can see where you're trying to go. (I assume you want to end up with code that's going to work on a broader range of types than just List<List<Integer>> .) And you're not going to get a satisfactory solution for that using Java generics. (You can make it work, but you'll have to write a whole lot of code by hand, which templates won't help you with. And there would be some grody edge cases.) Switch to dynamic type checking and instanceof ; that's the Java way to do this stuff.

First of all, the function to get an element is "get" not "at." However, your main problem is that you are trying to call the function "print" which takes an integer, but passing it a value of type "T."

If you are going to call print and pass it a value of unknown type, the function must either take an argument of type Object or take a generic argument, such as

public <T> void print(T item)

or

public void print (Object item)

Java will only match function calls to the most specific overload at compile time, if you're trying to find the correct overload at runtime you will have to use instanceof. Alternatively, you could build a HashMap that points from Class objects to Consumer objects.

HashMap<Class<?>, Consumer<?>> prints = new HashMap<>();
void setup() {
    prints.put(ArrayList.class, (list) -> {
        for (Object o : list) {
            print(o);
        }
    }

    prints.put(Integer.class, (integer) -> System.out.println(integer));
}

void print(Object o) {
    Consumer<?> func = prints.get(o.getClass());
    func.accept(o);
}

If you want to keep this code the way it is, you'll have to add another print-method for objects.

public static void print(Object o){}

But there are better approaches for your example anyways.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM