Javaでジェネリック型のインスタンスを作成することは可能ですか?私は答えがno
( タイプ消去のせいで )であることを見たことに基づいて考えていますが、誰かが私が足りないものを見ることができるなら私は興味があるでしょう:
class SomeContainer<E>
{
E createContents()
{
return what???
}
}
編集:それは スーパータイプトークン 私の問題を解決するために使用することができますが、以下の答えのいくつかが示しているように、それは多くのリフレクションベースのコードが必要です。
誰かがIan Robertsonの Artima Article と劇的に異なる何かを思いついたかどうかを確認するためにしばらくの間、これを開いたままにしておきます。
あなたは正しいです。 new E()
はできません。しかし、あなたはそれをに変更することができます
private static class SomeContainer<E> {
E createContents(Class<E> clazz) {
return clazz.newInstance();
}
}
それは痛みです。しかしそれはうまくいきます。ファクトリパターンでラップすると、もう少し許容範囲が広くなります。
これが助けになるならば、しかし、あなたが(匿名で含む)ジェネリック型をサブクラス化するとき、型情報はリフレクションを通して利用可能です。例えば。、
public abstract class Foo<E> {
public E instance;
public Foo() throws Exception {
instance = ((Class)((ParameterizedType)this.getClass().
getGenericSuperclass()).getActualTypeArguments()[0]).newInstance();
...
}
}
したがって、Fooをサブクラス化すると、Barのインスタンスが得られます。
// notice that this in anonymous subclass of Foo
assert( new Foo<Bar>() {}.instance instanceof Bar );
しかし、それは大変な作業であり、サブクラスに対してのみ機能します。でも便利かもしれません。
Java 8では、 Supplier
という機能的なインターフェースを使って、これを簡単に実現できます。
class SomeContainer<E> {
private Supplier<E> supplier;
SomeContainer(Supplier<E> supplier) {
this.supplier = supplier;
}
E createContents() {
return supplier.get();
}
}
このクラスはこのように構成します。
SomeContainer<String> stringContainer = new SomeContainer<>(String::new);
その行の構文String::new
は コンストラクタリファレンス です。
コンストラクタが引数を取る場合は、代わりにラムダ式を使用できます。
SomeContainer<BigInteger> bigIntegerContainer
= new SomeContainer<>(() -> new BigInteger(1));
次のものに予算を渡すには、何らかの種類の抽象ファクトリーが必要です。
interface Factory<E> {
E create();
}
class SomeContainer<E> {
private final Factory<E> factory;
SomeContainer(Factory<E> factory) {
this.factory = factory;
}
E createContents() {
return factory.create();
}
}
package org.foo.com;
import Java.lang.reflect.ParameterizedType;
import Java.lang.reflect.Type;
/**
* Basically the same answer as noah's.
*/
public class Home<E>
{
@SuppressWarnings ("unchecked")
public Class<E> getTypeParameterClass()
{
Type type = getClass().getGenericSuperclass();
ParameterizedType paramType = (ParameterizedType) type;
return (Class<E>) paramType.getActualTypeArguments()[0];
}
private static class StringHome extends Home<String>
{
}
private static class StringBuilderHome extends Home<StringBuilder>
{
}
private static class StringBufferHome extends Home<StringBuffer>
{
}
/**
* This prints "String", "StringBuilder" and "StringBuffer"
*/
public static void main(String[] args) throws InstantiationException, IllegalAccessException
{
Object object0 = new StringHome().getTypeParameterClass().newInstance();
Object object1 = new StringBuilderHome().getTypeParameterClass().newInstance();
Object object2 = new StringBufferHome().getTypeParameterClass().newInstance();
System.out.println(object0.getClass().getSimpleName());
System.out.println(object1.getClass().getSimpleName());
System.out.println(object2.getClass().getSimpleName());
}
}
ジェネリッククラスの中で型引数の新しいインスタンスが必要な場合は、コンストラクタにそのクラスを要求させます。
public final class Foo<T> {
private Class<T> typeArgumentClass;
public Foo(Class<T> typeArgumentClass) {
this.typeArgumentClass = typeArgumentClass;
}
public void doSomethingThatRequiresNewT() throws Exception {
T myNewT = typeArgumentClass.newInstance();
...
}
}
使用法:
Foo<Bar> barFoo = new Foo<Bar>(Bar.class);
Foo<Etc> etcFoo = new Foo<Etc>(Etc.class);
長所:
短所:
Foo<L>
proofではありません。初心者のために... type引数クラスがデフォルトのコンストラクタを持っていない場合、newInstance()
は激怒を投げます。とにかくこれはすべての既知の解決策に当てはまります。あなたは今これをすることができます、そしてそれはたくさんのリフレクションコードを必要としません。
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()) {};
}
}
もちろん、リフレクションを必要とするコンストラクタを呼び出す必要がある場合でも、それは非常によく文書化されていますが、このトリックはそうではありません。
これがTypeTokenの JavaDocです 。
もっと機能的なアプローチを考えてみてください。何もないところからEを作成するのではなく(明らかにコードの匂いです)、作成方法を知っている関数を渡します。
E createContents(Callable<E> makeone) {
return makeone.call(); // most simple case clearly not that useful
}
From Javaチュートリアル - ジェネリックスの制限 :
型パラメータのインスタンスを作成することはできません。たとえば、次のコードではコンパイル時エラーが発生します。
public static <E> void append(List<E> list) {
E elem = new E(); // compile-time error
list.add(elem);
}
回避策として、リフレクションを介して型パラメータのオブジェクトを作成できます。
public static <E> void append(List<E> list, Class<E> cls) throws Exception {
E elem = cls.newInstance(); // OK
list.add(elem);
}
次のようにappendメソッドを呼び出すことができます。
List<String> ls = new ArrayList<>();
append(ls, String.class);
これは私が思いついたオプションです、それは助けるかもしれません:
public static class Container<E> {
private Class<E> clazz;
public Container(Class<E> clazz) {
this.clazz = clazz;
}
public E createContents() throws Exception {
return clazz.newInstance();
}
}
編集:あるいは、このコンストラクタを使用することができます(しかしそれはEのインスタンスが必要です):
@SuppressWarnings("unchecked")
public Container(E instance) {
this.clazz = (Class<E>) instance.getClass();
}
次のようにインスタンス化中にクラス名を2回入力したくない場合は、
new SomeContainer<SomeType>(SomeType.class);
あなたはファクトリーメソッドを使用することができます:
<E> SomeContainer<E> createContainer(Class<E> class);
のように:
public class Container<E> {
public static <E> Container<E> create(Class<E> c) {
return new Container<E>(c);
}
Class<E> c;
public Container(Class<E> c) {
super();
this.c = c;
}
public E createInstance()
throws InstantiationException,
IllegalAccessException {
return c.newInstance();
}
}
残念ながら、Javaはあなたがやりたいことを許しません。 公式の回避策を参照してください :
型パラメータのインスタンスを作成することはできません。たとえば、次のコードではコンパイル時エラーが発生します。
public static <E> void append(List<E> list) {
E elem = new E(); // compile-time error
list.add(elem);
}
回避策として、リフレクションを介して型パラメータのオブジェクトを作成できます。
public static <E> void append(List<E> list, Class<E> cls) throws Exception {
E elem = cls.newInstance(); // OK
list.add(elem);
}
次のようにappendメソッドを呼び出すことができます。
List<String> ls = new ArrayList<>();
append(ls, String.class);
コンパイル時にEを使って作業しているときは、実際のジェネリック型 "E"(リフレクションを使うかジェネリック型の基本クラスを使って作業する)を気にする必要はないので、サブクラスでEのインスタンスを指定します。
Abstract class SomeContainer<E>
{
abstract protected E createContents();
public doWork(){
E obj = createContents();
// Do the work with E
}
}
**BlackContainer extends** SomeContainer<Black>{
Black createContents() {
return new Black();
}
}
TypeToken<T>
クラスを使用します。
public class MyClass<T> {
public T doSomething() {
return (T) new TypeToken<T>(){}.getRawType().newInstance();
}
}
あなたが使用することができます:
Class.forName(String).getConstructor(arguments types).newInstance(arguments)
しかし、あなたはパッケージを含む正確なクラス名を供給する必要があります。 Java.io.FileInputStream
。私はこれを使って数学式パーサを作成しました。
@ Noahの答えの改善。
変更理由
a]順序を変更した場合に使用するジェネリック型が複数ある場合は安全です。
b]クラスのジェネリック型シグネチャは時々変わりますので、ランタイムの原因不明の例外に驚くことはありません。
堅牢なコード
public abstract class Clazz<P extends Params, M extends Model> {
protected M model;
protected void createModel() {
Type[] typeArguments = ((ParameterizedType) this.getClass().getGenericSuperclass()).getActualTypeArguments();
for (Type type : typeArguments) {
if ((type instanceof Class) && (Model.class.isAssignableFrom((Class) type))) {
try {
model = ((Class<M>) type).newInstance();
} catch (InstantiationException | IllegalAccessException e) {
throw new RuntimeException(e);
}
}
}
}
またはワンライナーを使用
ワンラインコード
model = ((Class<M>) ((ParameterizedType) this.getClass().getGenericSuperclass()).getActualTypeArguments()[1]).newInstance();
それは可能だと思いましたが、かなりがっかりしました。それはうまくいきませんが、それでも共有する価値があると思います。
多分誰かが修正することができます:
import Java.lang.reflect.InvocationHandler;
import Java.lang.reflect.Method;
import Java.lang.reflect.Proxy;
interface SomeContainer<E> {
E createContents();
}
public class Main {
@SuppressWarnings("unchecked")
public static <E> SomeContainer<E> createSomeContainer() {
return (SomeContainer<E>) Proxy.newProxyInstance(Main.class.getClassLoader(),
new Class[]{ SomeContainer.class }, new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Class<?> returnType = method.getReturnType();
return returnType.newInstance();
}
});
}
public static void main(String[] args) {
SomeContainer<String> container = createSomeContainer();
[*] System.out.println("String created: [" +container.createContents()+"]");
}
}
それは作り出します:
Exception in thread "main" Java.lang.ClassCastException: Java.lang.Object cannot be cast to Java.lang.String
at Main.main(Main.Java:26)
at Sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at Sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.Java:57)
at Sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.Java:43)
at Java.lang.reflect.Method.invoke(Method.Java:601)
at com.intellij.rt.execution.application.AppMain.main(AppMain.Java:120)
26行目は[*]
のものです。
唯一の実行可能な解決策は@JustinRuddによるものです。
Robertsonの記事で議論されているのと同じようなテクニックを使用してあなたのためにE
を解決できる様々なライブラリがあります。これは、Eで表される生のクラスを解決するために TypeTools を使用するcreateContents
の実装です。
E createContents() throws Exception {
return TypeTools.resolveRawArgument(SomeContainer.class, getClass()).newInstance();
}
これは、getClass()がSomeContainerのサブクラスに解決され、サブクラスにキャプチャされていない場合、実行時にEの実際のパラメータ化された値が消去されるため、失敗することを想定しています。
あなたがnew E()
を意味するならば、それは不可能です。そして私はそれが常に正しいとは限らないことを付け加えます - Eがpublicの引数なしのコンストラクタを持っているかどうかあなたはどうやってわかりますか?しかし、あなたはいつでも作成方法を知っている他のクラスに作成を委任することができます - それはClass<E>
またはこのようなあなたのカスタムコードであることができます
interface Factory<E>{
E create();
}
class IntegerFactory implements Factory<Integer>{
private static int i = 0;
Integer create() {
return i++;
}
}
return (E)((Class)((ParameterizedType)this.getClass().getGenericSuperclass()).getActualTypeArguments()[0]).newInstance();
次のコードでこれを実現できます。
import Java.lang.reflect.ParameterizedType;
public class SomeContainer<E> {
E createContents() throws InstantiationException, IllegalAccessException {
ParameterizedType genericSuperclass = (ParameterizedType)
getClass().getGenericSuperclass();
@SuppressWarnings("unchecked")
Class<E> clazz = (Class<E>)
genericSuperclass.getActualTypeArguments()[0];
return clazz.newInstance();
}
public static void main( String[] args ) throws Throwable {
SomeContainer< Long > scl = new SomeContainer<>();
Long l = scl.createContents();
System.out.println( l );
}
}
これは、 TypeTools を使用してcreateContents
で表される生のクラスを解決するE
の実装です。
E createContents() throws Exception {
return TypeTools.resolveRawArgument(SomeContainer.class, getClass()).newInstance();
}
このアプローチは、SomeContainer
がサブクラス化されているので、E
の実際の値が型定義に取り込まれる場合にのみ機能します。
class SomeStringContainer extends SomeContainer<String>
そうでなければ、Eの値は実行時に消去され、回復できません。
あなたにできることは-
最初に、その汎用クラスの変数を宣言します
2.次に、そのコンストラクタを作成し、そのオブジェクトをインスタンス化します
次に、使用したい場所で使用します
例
1
private Class<E> entity;
2
public xyzservice(Class<E> entity) {
this.entity = entity;
}
public E getEntity(Class<E> entity) throws InstantiationException, IllegalAccessException {
return entity.newInstance();
}
3。
E e = getEntity(entity);
あなたが言ったように、あなたは本当にタイプ消去のためにそれをすることができません。リフレクションを使用してそれを行うことができますが、多くのコードと多くのエラー処理が必要です。
これが助けに遅すぎないことを願っています!
Javaは型保証されており、Objectのみがインスタンスを作成できます。
私の場合はcreateContents
メソッドにパラメータを渡すことができません。私の解決策はすべての以下の答えの代わりに拡張を使うことです。
private static class SomeContainer<E extends Object> {
E e;
E createContents() throws Exception{
return (E) e.getClass().getDeclaredConstructor().newInstance();
}
}
これは私がパラメータを渡すことができない私の例です。
public class SomeContainer<E extends Object> {
E object;
void resetObject throws Exception{
object = (E) object.getClass().getDeclaredConstructor().newInstance();
}
}
汎用クラスをオブジェクトタイプなしで拡張すると、リフレクションを使用してランタイムエラーが発生します。ジェネリック型をオブジェクトに拡張するには、このエラーをコンパイル時エラーに変換します。
これは、@ noah、@ Lars Bohl、その他によって既に言及されているParameterizedType.getActualTypeArguments
に基づく、改善された解決策です。
実装における最初の小さな改善。 Factoryはインスタンスを返すべきではなく、型を返すべきです。 Class.newInstance()
を使用してインスタンスを返すとすぐに使用範囲が狭まります。引数なしのコンストラクタだけがこのように呼び出すことができるからです。より良い方法は、型を返し、クライアントにどのコンストラクタを起動させたいかを選択させることです。
public class TypeReference<T> {
public Class<T> type(){
try {
ParameterizedType pt = (ParameterizedType) this.getClass().getGenericSuperclass();
if (pt.getActualTypeArguments() == null || pt.getActualTypeArguments().length == 0){
throw new IllegalStateException("Could not define type");
}
if (pt.getActualTypeArguments().length != 1){
throw new IllegalStateException("More than one type has been found");
}
Type type = pt.getActualTypeArguments()[0];
String typeAsString = type.getTypeName();
return (Class<T>) Class.forName(typeAsString);
} catch (Exception e){
throw new IllegalStateException("Could not identify type", e);
}
}
}
使用例です。 @Lars Bohlは拡張を介して具体化された一般的なものを取得するための唯一の方法を示しました。 @noahは{}
でインスタンスを作成することによってのみです。これは両方のケースを実証するためのテストです。
import Java.lang.reflect.Constructor;
public class TypeReferenceTest {
private static final String NAME = "Peter";
private static class Person{
final String name;
Person(String name) {
this.name = name;
}
}
@Test
public void erased() {
TypeReference<Person> p = new TypeReference<>();
Assert.assertNotNull(p);
try {
p.type();
Assert.fail();
} catch (Exception e){
Assert.assertEquals("Could not identify type", e.getMessage());
}
}
@Test
public void reified() throws Exception {
TypeReference<Person> p = new TypeReference<Person>(){};
Assert.assertNotNull(p);
Assert.assertEquals(Person.class.getName(), p.type().getName());
Constructor ctor = p.type().getDeclaredConstructor(NAME.getClass());
Assert.assertNotNull(ctor);
Person person = (Person) ctor.newInstance(NAME);
Assert.assertEquals(NAME, person.name);
}
static class TypeReferencePerson extends TypeReference<Person>{}
@Test
public void reifiedExtenension() throws Exception {
TypeReference<Person> p = new TypeReferencePerson();
Assert.assertNotNull(p);
Assert.assertEquals(Person.class.getName(), p.type().getName());
Constructor ctor = p.type().getDeclaredConstructor(NAME.getClass());
Assert.assertNotNull(ctor);
Person person = (Person) ctor.newInstance(NAME);
Assert.assertEquals(NAME, person.name);
}
}
注: /このクラスを抽象化することでインスタンスが作成されるときにTypeReference
のクライアントに常に{}
を使用させることができます:public abstract class TypeReference<T>
。私はそれをしていない、ただ消去されたテストケースを示すために。