Java generics with unbounded wildcard?

2020-02-29 11:01发布

I have an interface to convert object to string:

public interface Converter<T> {
    String asString(T object);
}

And a map to store all available converters:

Map<Class<?>, Converter<?>> converterMap;

Now I have a list of heterogeneous data to convert like this:

List<?> data = fetchData();
List<String> stringData = new ArrayList<>(data.size());
for (Object datum : data) {
    stringData.add(convertrMap.get(datum.getClass()).asString(datum));
}

But this code doesn't compile:

error: method asString in interface Converter<T> cannot be applied to given types;
            stringData.add(converterMap.get(datum.getClass()).asString(datum));
  required: CAP#1
  found: Object
  reason: actual argument Object cannot be converted to CAP#1 by method invocation conversion
  where T is a type-variable:
    T extends Object declared in interface Converter
  where CAP#1 is a fresh type-variable:
    CAP#1 extends Object from capture of ?

How should I change the code?

3条回答
欢心
2楼-- · 2020-02-29 11:50

You are facing issue called wildcard capture. Java is unable to identify the type that will be received from the List<?> data. Try refactoring your code in any of the two ways

Method 1: Change your interface as below

interface Converter {
    String asString(Object object);
}

Method 2: Helper method to capture wild card by type inference

Create an helper method as below,

// Helper method created so that the wildcard can be captured
// through type inference.
private <T> void helper(List<T> data) {
    Map<Class<?>, Converter<T>> converterMap = null;
    List<String> stringData = null;

    for (T datum : data) {
        stringData.add(converterMap.get(datum.getClass()).asString(datum));
    }
}

Call this helper method as below

List<?> data = fetchData();
helper(data);
查看更多
够拽才男人
3楼-- · 2020-02-29 12:05

First, you should encapsulate the map inside a helper class like this, whose operations preserve the invariant (that Class<T> maps to Converter<T>):

public class ConverterMap {
    Map<Class<?>, Converter<?>> converterMap = new HashMap<Class<?>, Converter<?>>();
    public <T> void addConverter(Class<T> clazz, Converter<T> converter) {
        converterMap.put(clazz, converter);
    }
    @SuppressWarnings("unchecked")
    public <T> Converter<T> getConverter(Class<T> clazz) {
        return (Converter<T>)converterMap.get(clazz);
    }
}

Now, to break down the task, let's take the small step of writing a function that takes any object and converts it based on the converter map (assuming the object's class is in the converter map):

ConverterMap cm = new ConverterMap;
private static String convert(Object x);

This seems simple, but is harder than it looks, because you will run into a special case of the Java type system in how it types .getClass(). You will have the problem of convincing the compiler that x is an instance of the parameter of x.getClass(). The best way to solve this is:

@SuppressWarnings("unchecked")
private static <T> String convert2(Class<T> clazz, Object x) {
    return cm.getConverter(clazz).asString((T)x);
    // you can alternately do clazz.cast(x) instead of the unchecked cast (T)x
}
private static String convert(Object x) {
    return convert2(x.getClass(), x);
}

And then you can solve the rest of the problem:

for (Object datum : data) {
    stringData.add(convert(datum));
}
查看更多
男人必须洒脱
4楼-- · 2020-02-29 12:07

You should change the code like this:

public interface Converter {
    String asString(Object object);
}

I think it will work.

查看更多
登录 后发表回答