开发者

Dynamic Generic Typing in Java

开发者 https://www.devze.com 2023-04-03 04:29 出处:网络
If I have a class using generic type such as public class Record<T> { private T value; public Record(T value) {

If I have a class using generic type such as

public class Record<T> {
    private T value;

    public Record(T value) {
        this.value = value;
    }
}

it is pretty straight forward to type everything during design time, if I know all types that are used such as it is the case in this example:

// I type explicitly
String myStr = "A";
Integer myInt = 1;
ArrayList myList = new ArrayList();

Record rec1 = new Record<String>(myStr);
Record rec2 = new Record<Integer>(myInt);
Record rec3 = new Record<ArrayList>(myList);

What happens if I get a list of objects from "somewhere" where I don't know the type? How do I assign the type:

// now let's assume that my values come from a list where I only know during runtime what type they have

ArrayList<Object> myObjectList = new ArrayList<Object>();
    myObjectList.add(myStr);
    myObjectList.add(myInt);
  开发者_如何转开发  myObjectList.add(myList);

    Object object = myObjectList.get(0);

    // this fails - how do I do that?
    new Record<object.getClass()>(object);


Java generics are not C++ Templates.

Java generics are a compile time feature, not a run time feature.

Here is a link to the Java generics Tutorial.

This can never work with Java:

new Record<object.getClass()>(object);

You must either use polymorphism (say, each object implements a known interface) or RTTI (instanceof or Class.isAssignableFrom()).

You might do this:

     class Record
     {
       public Record(String blah) { ... }
       public Record(Integer blah) { ... }
       ... other constructors.
     }

or you might use the Builder pattern.


Creating instances from Generic Types at Runtime

I am not entirely sure what you are trying to accomplish, but at a first glance it looks like the simplest solution is the best solution.

It could be solved with using a scripting environment ( Groovy, JavaScript, JRuby, Jython ) that could dynamically evaluate and execute arbitrary code to create the objects, but that got extremely convoluted and overly complex, just to create an object.

But unfortunately I think it has a very pedestrian solution.

As long as there is a predefined set of supported types, you can use a Factory pattern. Here I just leverage the Provider<>T interface from the javax.inject/com.google.inject package.

Q26289147_ProviderPattern.java

public class Q26289147_ProviderPattern
{
    private static final List<String> CLASS_NAMES = ImmutableList.of("String", "Integer", "Boolean");
    private static final Map<String, Provider<StrawManParameterizedClass>> PROVIDERS;

    static
    {
        final ImmutableMap.Builder<String, Provider<StrawManParameterizedClass>> imb = ImmutableMap.builder();
        for (final String cn : CLASS_NAMES)
        {
            switch (cn)
            {
                case "String":
                    imb.put(cn, new Provider<StrawManParameterizedClass>()
                    {
                        @Override
                        public StrawManParameterizedClass<String> get() { return new StrawManParameterizedClass<String>() {}; }
                    });
                    break;
                case "Integer":
                    imb.put(cn, new Provider<StrawManParameterizedClass>()
                    {
                        @Override
                        public StrawManParameterizedClass<Integer> get() { return new StrawManParameterizedClass<Integer>() {}; }
                    });
                    break;
                case "Boolean":
                    imb.put(cn, new Provider<StrawManParameterizedClass>()
                    {
                        @Override
                        public StrawManParameterizedClass<Integer> get() { return new StrawManParameterizedClass<Integer>() {}; }
                    });
                    break;
                default:
                    throw new IllegalArgumentException(String.format("%s is not a supported type %s", cn, Joiner.on(",").join(CLASS_NAMES)));
            }
        }
        PROVIDERS = imb.build();
    }

    static <T> void read(@Nonnull final StrawManParameterizedClass<T> smpc) { System.out.println(smpc.type.toString()); }

    static abstract class StrawManParameterizedClass<T>
    {
        final TypeToken<T> type = new TypeToken<T>(getClass()) {};

        @Override
        public String toString() { return type.getRawType().getCanonicalName(); }
    }

    public static void main(final String[] args)
    {
        for (final String cn : CLASS_NAMES)
        {
            read(PROVIDERS.get(cn).get());
        }
    }
}

Disclaimer:

This is just a proof of concept example, I would never use a switch statement like that in production code I would use a Strategy Pattern or Chain of Responsibility Pattern to encapsulate the logic of what type to create based on the ClassName key.

This initially looked like a generics problem, it isn't, it is a creation problem.

That said, you don't need to pass around instances of Class<?> you can get Generic Type information off of Parameterized classes at runtime with TypeToken from Guava.

You can even create instances of any generic type at runtime with TypeToken from the Guava library.

The main problem is this syntax isn't supported: Geography<myClass.newInstance()> geo; and I can't think of anyway to fake it other than the Provider implementation above.

Here is a straw man example of how to use TypeToken so that your parameterized classes will always know their types!

Q26289147.java

import com.google.common.reflect.TypeToken;

public class Q26289147
{
    public static void main(final String[] args) throws IllegalAccessException, InstantiationException
    {
        final StrawManParameterizedClass<String> smpc = new StrawManParameterizedClass<String>() {};
        final String string = (String) smpc.type.getRawType().newInstance();
        System.out.format("string = \"%s\"",string);
    }

    static abstract class StrawManParameterizedClass<T>
    {
        final TypeToken<T> type = new TypeToken<T>(getClass()) {};
    }
}

Notes:

  1. Works great for classes that have a default no arg constructor.
  2. Works better than using straight reflection if there are no default no arg constructors.
  3. Should play well with Guice allowing you to use the .getRawType() generated Class<T> to pass to getInstance() of an Injector. have not tried this yet, I just thought of it!
  4. You can use Class<T>.cast() to do casting that doesn't need @SuppressWarning("unchecked") all over the place.


If you don't know the type, you cannot enforce compile time checks with generics.

Just for the sake of using it you could say

new Record<Object>(object);
0

精彩评论

暂无评论...
验证码 换一张
取 消