I'm using generics to store reference to arbitrary object.
class Option<TypeT>{
TypeT o;
Option(TypeT t){
this.o = o;
}
TypeT getReferencedObject(){
return o;
}
}
I want to use lambdaJ to extract those objects from a collection. Right now i'm using pretty ugly double casting (List<TypeT>)(List<?>)
and i'm looking for a better/cleaner solution. The problem lies in the on( sth ).getReferencedObject()
statement, which cannot be parameterized like on(Option<String>.class).getReferencedObject()
List<Option<String>> options = Arrays.asList(new Option<String>("AAA"), new Option<String>("BBB"));
List<String> string = (List<String>)(List<?>) extract(options, on(Option.class).getReferencedObject());
This issue stems from a limitation of the language itself - there are no class literals for concrete parameterized types, which is a result of Java's using type erasure to implement generics.
An example of a concrete parameterized type would be Option<String>
. There is no Option<String>.class
, only Option.class
.
Parameterized types can be represented at runtime by ParameterizedType
instances, which can be acquired using reflection. Some libraries take advantage of this in order to type-safely represent generic types, for example Guava's TypeToken<T>
.
But unfortunately that doesn't help here. The reason is that LambdaJ's on
method uses a dynamic proxy to represent the specified type. This is so that calling something like this:
on(Option.class).getReferencedObject()
can work its LambdaJ magic behind the scenes. Creating such a proxy ultimately boils down to a call to the Java API's Proxy.newProxyInstance
, or else a custom proxy implementation like net.sf.cglib.proxy.Enhancer
. In either case, Class
instances specifically are used to represent the proxied interfaces/classes. So to sum it up, on
can't accept anything but a cold hard Class<T>
.
What can you do? Your options are limited...
We know this is ugly and it's definitely bug prone, but it works:
@SuppressWarnings("unchecked") //okay because options is a List<Option<String>>
List<String> strings =
(List<String>)(List<?>)extract(options, on(Option.class).getReferencedObject());
Out of curiosity, I tried this instead:
@SuppressWarnings("unchecked") //not really okay, but let's see what happens
Class<Option<String>> onWhat = (Class<Option<String>>)(Class<?>)Option.class;
List<String> strings = extract(options, on(onWhat).getReferencedObject());
But that got me a runtime exception:
java.lang.ClassCastException
:net.sf.cglib.empty.Object$$EnhancerByCGLIB$$9d9c54e1
cannot be cast tojava.lang.String
List<String> strings = Lists.transform(
options,
new Function<Option<String>, String>() {
@Override
public String apply(Option<String> option) {
return option.getReferencedObject();
}
}
);
This is looking rather noisy, but the Function
can be abstracted away at least:
class Option<T> {
...
public static final class Functions {
//Impl note: Effective Java item 27
private static final Function<?, ?> GET_REFERENCED_OBJECT =
new Function<Option<?>, Object>() {
@Override
public Object apply(Option<?> option) {
return option.getReferencedObject();
}
};
private Functions() { }
public static <T> Function<Option<T>, T> getReferencedObject() {
//okay for any T, since Option<T>.getReferencedObject must return a T
@SuppressWarnings("unchecked")
final Function<Option<T>, T> withNarrowedTypes =
(Function<Option<T>, T>)GET_REFERENCED_OBJECT;
return withNarrowedTypes;
}
}
}
Which makes the call site cleaner:
List<String> strings = Lists.transform(
options,
Option.Functions.<String>getReferencedObject()
);
Note that Lists.transform
returns a transformed view of the original List
. If you want a copy like LambdaJ's extract
returns, making one is easy enough:
List<String> strings = Lists.newArrayList(Lists.transform(
options,
Option.Functions.<String>getReferencedObject()
));
This is all much more verbose but it gives you generic type safety and you get to drop the dynamic proxy sorcery behind the scenes.
List<String> strings = new ArrayList<>(options.size());
for (Option<String> option : options) {
strings.add(option.getReferencedObject());
}
An often overlooked but pleasantly viable option!