Search code examples
javagenericsgeneric-type-argument

In Java, can I reuse the generics types from an interface parameter to create a different class which also requires generic types? And if so, how?


Here is an overview of the Java code I have:

// An interface and an implementation class:
public interface MyInterface<T1, T2> { ... }
public class MyImplementation implements MyInterface<int, String> { ... }

// Another class
public class MyClass<T3, T4> { ... }

// Function I want to call
void doStuff(MyInterface i) {
  MyClass<int, String> n;
}

// I want to call the function like this:
MyInterface mi = new MyImplementation();
doStuff(mi);

What I can't figure out is if I can get MyClass<int, String> n; to somehow use the generic types from the MyImplementation class passed in to doStuff()? In this case, n would automatically use <int, String> because that's what MyImplementation uses.


Solution

  • Yes, you can.

    Let's move away from nebulous hypotheticals and take real classes: Collection<T>, Map<K, V>, and Function<F, T>. Let's say you want to write a method in the Map type (or interface, doesn't matter, a signature is a signature) that takes a 'key converter' (a thing that converts Ks into something else), returning a collection of the something-else, which consists of each key in the map, thrown through the converter, and added to a collection.

    class MapImpl<K, V> implements Map<K, V> {
        public <T> Collection<T> convertKeys(Function<K, T> converter) {
            List<T> out = new ArrayList<T>();
            for (K key : keySet()) out.add(converter.apply(key));
            return out;
        }
    }
    

    A lot of concepts are being used here:

    • The implementation doesn't lock in the types of K and V. You don't just inherit typevars from interfaces you implement, so, MapImpl gets its own K,V which are also used as the K,V for the interface. That covers line 1.
    • The convertKeys method introduces its own unique typevar, in addition to the K,V it already gets. That's because.. well, that's how the method works: The map's keys have some type, the values have some other type, and this converter converts to some third type. Three types: K, V, and T. A method can introduce new vars just for the method, that's what the <T> is all about in line 2.
    • Any time you name a type name, if that type is generified, you MUST toss <> after it and put in appropriate things. Or don't put in appropriate things which means: Hey, compiler, figure it out if you can (the so called diamond operator). In your snippet, you use MyInterface i as method param type and that's bad: MyInterface has generics, so it must have <> behind it. In this case, you have to add things because there is no way the compiler can try to figure things out.

    Going back to your code, it might look like:

    public <K, V> void doStuff(MyInterface<K, V> i) {
        MyClass<K, V> n;
    }
    

    NB: Remember, generics link things. That final snippet is simply saying: There is a link between the first typearg of the MyInterface part of the 'i' parameter's type, and the first typearg of the MyClass part of the 'n' local variable. I don't know what that type is. I do know it is the same type. Generics are completely useless unless the typevar occurs in 2 or more places.

    NB2: If you then want to get real fancy, you start thinking about co/contra/invariance. For example, in the key converter story, if you have a converter that can convert any object into something else that'd be cool too. In fact, a converter that can convert either Ks, or any supertype of Ks, that'd all be suitable. So, really, you end up with: public <T> Collection<T> convertKeys(Function<? super K, ? extends T> converter) {} - but that kind of advanced variance engineering is a nice bonus, feel free to skip those bits in your source until you run into trouble because you didn't take it into consideration.