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 aStrategy Pattern
orChain of Responsibility
Pattern to encapsulate the logic of what type to create based on theClassName
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:
- Works great for classes that have a default no arg constructor.
- Works better than using straight reflection if there are no default no arg constructors.
- Should play well with Guice allowing you to use the
.getRawType()
generatedClass<T>
to pass togetInstance()
of an Injector. have not tried this yet, I just thought of it! - 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);
精彩评论