Java inferred generic types

2019-07-18 10:25发布

问题:

I'm looking for a similar concept of inferring captured generic types, similar to the following method snippet, however instead for a class that captures generic types:

public <X, Y, Z> static void someMethod(ObjectInterface<X, Y, Z> object) {
    // code that uses inferred generic type parameters X, Y and Z...
}

The code in this snippet will capture types and assign them to the generic parameter types X, Y and Z. This allows the use of the generic type variables inside the body of the code and makes the method more flexible in use. In this snippet, if the method is called without specifying the types (i.e. not parameterized), then Java will infer the types, that is someMethod(instaceOfImplementedObject) will work and the types will be inferred.

My problem is, I have the following (simplified) structure for an object interface and objects that implement the interface:

public interface ObjectInterface<X, Y, Z> {
    //...
}

class ImplementedObject implements ObjectInterface<SomeType1, SomeType2, SomeType3> {
    //...
}

Then I have other classes that have to capture quite a few generic type variables, of which one of them is an object that implements ObjectInterface<X, Y, Z>. Within such a class, I also need to have a handle on the types (X, Y, Z) that is defined in the object that was captured.

The following (not ideal, and very simplified) code works:

public class ClassWorks<X, Y, Z, N extends ObjectInterface<X, Y, Z>> {
    // code body uses X, Y, Z and N...
}

However, this is very cumbersome for the person trying to use/initiate this class, even in this simplified version, for example:

public class ImplementedObject implements ObjectInterface<Integer, Double, String> {
    //...
}

public class RandomExample {
    public static void main(String[] args) {
        ObjectInterface<Integer, Double, String> implementedObj = new ImplementedObject();
        ClassWorks<Integer, Double, String, ImplementedObject>> example = new ClassWorks<Integer, Double, String, ImplementedObject>(/* possible params */);
    }
}

Is there a way to "extract" or capture these types so that they are inferred instead of explicit as it is for ClassWorks in the working example? Possibly something similar to the following (Note this does not work):

pulic class WishfullClass<N extends ObjectInterface<X, Y, Z>> {
    // type N is captured; X, Y and Z is not explicitly captured.
    // code uses type N, as well as X, Y and Z
    // where, X, Y and Z is inferred somehow from N.
}

Edit: So an implemented example of WishfullClass would be:

public class ImplementedObject implements ObjectInterface<Integer, Double, String> {
    //...
}

public class WishfullExample {
    public static void main(String[] args) {
        ObjectInterface<Integer, Double, String> implementedObj = new ImplementedObject();
        WishFullClass<ImplementedObject> example = new WishfullClass<ImplementedObject>(/* possible params */);
    }
}

ie. The compiler should know that ImplementedObject from the declaration of the class that it implements ObjectInterface<Integer, Double, String> as X, Y and Z.

Please note, these above are very simplified and in the real code are not the only parameters that need capturing, so those three extra parameters make quite a big difference; also the implemented object also captures generic types,

So ideally I would like to just collectively capture the object that extend the ObjectInterface<X, Y, Z> and have X, Y and Z inferred. Is there a way to do this?

I.e. The snippet of someMethod shows how you can infer X, Y and Z for the scope of a method. My question refers to, is there a way to infer X, Y and Z for the entire scope of the class by only capturing a type that extends ObjectInterface.

I had some trouble wording/explaining this question, so if there is any uncertainty, please ask for clarification :)

回答1:

So after some further research, I found the answer in the textbook Effective Java, by Joshua Block; Item 27: Favor generic methods. What I was looking for was to simplify/reduce the reiteration of type parameters when invoking the generic constructor - i.e. make it less cumbersome and not repeating parameters that are already given.

It is indeed impossible to infer types for a constructor, however there is a way to exploit generic methods to reduce the reiteration and type parameters for constructors - by making generic factory methods for each constructor and infer the type parameters in this way instead.

This is the information that explains this entire situation, the following is quoted out of the textbook:

One noteworthy feature of generic methods is that you needn’t specify the value of the type parameter explicitly as you must when invoking generic constructors. The compiler figures out the value of the type parameters by examining the types of the method arguments. In the case of the program above, the compiler sees that both arguments to union are of type Set , so it knows that the type parameter E must be String. This process is called type inference.

As discussed in Item 1, you can exploit the type inference provided by generic method invocation to ease the process of creating parameterized type instances. To refresh your memory, the need to pass the values of type parameters explicitly when invoking generic constructors can be annoying. The type parameters appear redundantly on the left- and right-hand sides of variable declarations:

// Parameterized type instance creation with constructor`
Map<String, List<String>> anagrams = new HashMap<String, List<String>>();

To eliminate this redundancy, write a generic static factory method corresponding to each constructor that you want to use. For example, here is a generic static factory method corresponding to the parameterless HashMap constructor:

// Generic static factory method
public static <K,V> HashMap<K,V> newHashMap() {
    return new HashMap<K,V>();
}

With this generic static factory method, you can replace the repetitious declaration above with this concise one:

// Parameterized type instance creation with static factory
Map<String, List<String>> anagrams = newHashMap();

It would be nice if the language did the same kind of type inference when invoking constructors on generic types as it does when invoking generic methods. Someday it might, but as of release 1.6, it does not.



回答2:

Map<String, List<String>> anagrams = new HashMap<>();

Would do the same in Java 7 onwards. <> is diamond operator.



回答3:

I haven't got this kind of requirement earlier, though this one might can achieve if you use as a wild card(?) or use a abstract class before the WishfullClass implementation. I'll do this as below.

public abstract class AbstractObjectInterface<X, Y, Z> implements ObjectInterface<X, Y, Z> {
 // What ever you want to do here. You can expose those type variables from here as well.
}

public class WishfullClass<X, Y, Z, N> extends AbstractObjectInterface<X, Y, Z> {
 // Use N type variable here. 
}

Isn't this the requirement.

Update: Otherwise, you can explicitly implements the interface to your class as follows.

class WishfullClass<X, Y, Z, N> implements ObjectInterface<X, Y, Z> {
 // Use N type variable here. 
}