Frage

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());
War es hilfreich?

Lösung

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...

1) Stick with what you have:

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 to java.lang.String

2) Use Guava:

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.

3) Use the Ol' Plain n' Simple (and wait for Java 8):

List<String> strings = new ArrayList<>(options.size());
for (Option<String> option : options) {
    strings.add(option.getReferencedObject());
}

An often overlooked but pleasantly viable option!

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top