私はジェネリックスクラスFoo<T>
を持っています。 Foo
のメソッドでは、T型のクラスインスタンスを取得したいのですが、T.class
を呼び出すことはできません。
T.class
を使用してそれを回避するための好ましい方法は何ですか?
簡単な答えは、Javaでジェネリック型パラメータのランタイム型を見つける方法がないということです。詳細については、 Javaチュートリアル の型消去に関する章を読むことをお勧めします。
これに対する一般的な解決策は、typeパラメータのClass
をジェネリック型のコンストラクタに渡すことです。
class Foo<T> {
final Class<T> typeParameterClass;
public Foo(Class<T> typeParameterClass) {
this.typeParameterClass = typeParameterClass;
}
public void bar() {
// you can access the typeParameterClass here and do whatever you like
}
}
クラスパスに追加の依存関係を追加せずに自分でこれを行う方法を探していました。いくつかの調査の結果、私はそれがisが一般的なスーパータイプを持っている限り可能であることを知りました。私は _ dao _ レイヤをジェネリックレイヤスーパータイプで扱っていたので、これは私には問題ありませんでした。これがあなたのシナリオに合うならば、それが私たちの最も新しいアプローチです。
私が遭遇したほとんどのジェネリックのユースケースはある種の一般的なスーパータイプを持っています。 List<T>
の場合はArrayList<T>
、GenericDAO<T>
の場合はDAO<T>
など.
記事{実行時にJavaで総称型にアクセスするでは、純粋なJavaを使用してそれを実行する方法について説明しています。
私のプロジェクトは Spring を使っていましたが、Springはその型を見つけるための便利な実用的方法を持っているのでさらに良いです。それは最善のように見えるので、これは私にとって最良のアプローチです。もしあなたがSpringを使っていなければあなた自身のユーティリティメソッドを書くことができると思います。
import org.springframework.core.GenericTypeResolver;
public abstract class AbstractHibernateDao<T extends DomainObject> implements DataAccessObject<T>
{
@Autowired
private SessionFactory sessionFactory;
private final Class<T> genericType;
private final String RECORD_COUNT_HQL;
private final String FIND_ALL_HQL;
@SuppressWarnings("unchecked")
public AbstractHibernateDao()
{
this.genericType = (Class<T>) GenericTypeResolver.resolveTypeArgument(getClass(), AbstractHibernateDao.class);
this.RECORD_COUNT_HQL = "select count(*) from " + this.genericType.getName();
this.FIND_ALL_HQL = "from " + this.genericType.getName() + " t ";
}
ただし、小さな抜け穴があります。Foo
クラスをabstractとして定義した場合。それはあなたがあなたのクラスを次のようにインスタンス化しなければならないことを意味するでしょう:
Foo<MyType> myFoo = new Foo<MyType>(){};
(末尾の二重括弧に注意してください。)
実行時にT
の型を取得することができます。
Type mySuperclass = myFoo.getClass().getGenericSuperclass();
Type tType = ((ParameterizedType)mySuperclass).getActualTypeArguments()[0];
ただし、mySuperclass
は実際にT
の最終型を定義するクラス定義のスーパークラスである必要があります。
また、それほど洗練されていませんが、コードでnew Foo<MyType>(){}
とnew Foo<MyType>(MyType.class);
のどちらを好むかを決める必要があります。
例えば:
import Java.lang.reflect.ParameterizedType;
import Java.lang.reflect.Type;
import Java.util.ArrayDeque;
import Java.util.Deque;
import Java.util.NoSuchElementException;
/**
* Captures and silently ignores stack exceptions upon popping.
*/
public abstract class SilentStack<E> extends ArrayDeque<E> {
public E pop() {
try {
return super.pop();
}
catch( NoSuchElementException nsee ) {
return create();
}
}
public E create() {
try {
Type sooper = getClass().getGenericSuperclass();
Type t = ((ParameterizedType)sooper).getActualTypeArguments()[ 0 ];
return (E)(Class.forName( t.toString() ).newInstance());
}
catch( Exception e ) {
return null;
}
}
}
その後:
public class Main {
// Note the braces...
private Deque<String> stack = new SilentStack<String>(){};
public static void main( String args[] ) {
// Returns a new instance of String.
String s = stack.pop();
System.out.printf( "s = '%s'\n", s );
}
}
標準的なアプローチ/回避策/解決策は、次のようにコンストラクタにclass
オブジェクトを追加することです。
public class Foo<T> {
private Class<T> type;
public Foo(Class<T> type) {
this.type = type;
}
public Class<T> getType() {
return type;
}
public T newInstance() {
return type.newInstance();
}
}
一般的な抽象スーパークラスがあるとします。
public abstract class Foo<? extends T> {}
そして、Tを拡張する一般的なBarを使ってFooを拡張する2番目のクラスがあります。
public class Second extends Foo<Bar> {}
FooクラスのクラスBar.class
を取得するには、(bert bruynooghe answerから)Type
を選択し、Class
インスタンスを使用してそれを推論します。
Type mySuperclass = myFoo.getClass().getGenericSuperclass();
Type tType = ((ParameterizedType)mySuperclass).getActualTypeArguments()[0];
//Parse it as String
String className = tType.toString().split(" ")[1];
Class clazz = Class.forName(className);
この操作は理想的ではないため、計算値をキャッシュして、これに対する多重計算を回避することをお勧めします。一般的な用途の1つは、一般的なDAOの実装です。
最後の実装:
public abstract class Foo<T> {
private Class<T> inferedClass;
public Class<T> getGenericClass(){
if(inferedClass == null){
Type mySuperclass = getClass().getGenericSuperclass();
Type tType = ((ParameterizedType)mySuperclass).getActualTypeArguments()[0];
String className = tType.toString().split(" ")[1];
inferedClass = Class.forName(className);
}
return inferedClass;
}
}
他の関数のFooクラスまたはBarクラスから呼び出されたときに返される値はBar.classです。
これが実用的な解決策です。
@SuppressWarnings("unchecked")
private Class<T> getGenericTypeClass() {
try {
String className = ((ParameterizedType) getClass().getGenericSuperclass()).getActualTypeArguments()[0].getTypeName();
Class<?> clazz = Class.forName(className);
return (Class<T>) clazz;
} catch (Exception e) {
throw new IllegalStateException("Class is not parametrized with generic type!!! Please use extends <> ");
}
}
注: スーパークラスとしてのみ使用できます
Child extends Generic<Integer>
)_または_
new Generic<Integer>() {};
)タイプ消去のためにそれはできません。スタックオーバーフローの質問も参照してくださいJavaジェネリックス - 型消去 - いつ、何が起こるか。
私は抽象ジェネリッククラスでこの問題を抱えていました。この特定のケースでは、解決策はより単純です。
abstract class Foo<T> {
abstract Class<T> getTClass();
//...
}
そして後に派生クラスでは:
class Bar extends Foo<Whatever> {
@Override
Class<T> getTClass() {
return Whatever.class;
}
}
他の人が提案したClassよりも優れた方法は、Classを使って行ったことを実行できるオブジェクトを渡すことです。たとえば、新しいインスタンスを作成します。
interface Factory<T> {
T apply();
}
<T> void List<T> make10(Factory<T> factory) {
List<T> result = new ArrayList<T>();
for (int a = 0; a < 10; a++)
result.add(factory.apply());
return result;
}
class FooFactory<T> implements Factory<Foo<T>> {
public Foo<T> apply() {
return new Foo<T>();
}
}
List<Foo<Integer>> foos = make10(new FooFactory<Integer>());
私は最近使用したこの問題に対する(醜いが効果的な)解決策を持っています。
import Java.lang.reflect.TypeVariable;
public static <T> Class<T> getGenericClass()
{
__<T> ins = new __<T>();
TypeVariable<?>[] cls = ins.getClass().getTypeParameters();
return (Class<T>)cls[0].getClass();
}
private final class __<T> // generic helper class which does only provide type information
{
private __()
{
}
}
それが可能だ:
class Foo<T> {
Class<T> clazz = (Class<T>) DAOUtil.getTypeArguments(Foo.class, this.getClass()).get(0);
}
svn/trunk/dao/src/main/Java/com/googlecode/genericdao/dao/DAOUtil.Java から2つの関数が必要です。
詳細については、ジェネリックの反映を参照してください。
そのための一般的で簡単な方法を見つけました。私のクラスでは、クラス定義内の位置に従ってジェネリック型を返すメソッドを作成しました。このようなクラス定義を仮定しましょう。
public class MyClass<A, B, C> {
}
それでは、型を保持するためにいくつかの属性を作成しましょう。
public class MyClass<A, B, C> {
private Class<A> aType;
private Class<B> bType;
private Class<C> cType;
// Getters and setters (not necessary if you are going to use them internally)
}
その後、総称定義のインデックスに基づいて型を返す総称メソッドを作成できます。
/**
* Returns a {@link Type} object to identify generic types
* @return type
*/
private Type getGenericClassType(int index) {
// To make it use generics without supplying the class type
Type type = getClass().getGenericSuperclass();
while (!(type instanceof ParameterizedType)) {
if (type instanceof ParameterizedType) {
type = ((Class<?>) ((ParameterizedType) type).getRawType()).getGenericSuperclass();
} else {
type = ((Class<?>) type).getGenericSuperclass();
}
}
return ((ParameterizedType) type).getActualTypeArguments()[index];
}
最後に、コンストラクター内でメソッドを呼び出して各タイプのインデックスを送信するだけです。完全なコードは次のようになります。
public class MyClass<A, B, C> {
private Class<A> aType;
private Class<B> bType;
private Class<C> cType;
public MyClass() {
this.aType = (Class<A>) getGenericClassType(0);
this.bType = (Class<B>) getGenericClassType(1);
this.cType = (Class<C>) getGenericClassType(2);
}
/**
* Returns a {@link Type} object to identify generic types
* @return type
*/
private Type getGenericClassType(int index) {
Type type = getClass().getGenericSuperclass();
while (!(type instanceof ParameterizedType)) {
if (type instanceof ParameterizedType) {
type = ((Class<?>) ((ParameterizedType) type).getRawType()).getGenericSuperclass();
} else {
type = ((Class<?>) type).getGenericSuperclass();
}
}
return ((ParameterizedType) type).getActualTypeArguments()[index];
}
}
他の答えで説明されているように、このParameterizedType
アプローチを使うためには、クラスを拡張する必要がありますが、それはそれを拡張する全く新しいクラスを作るための余分な作業のようです...
したがって、クラスを抽象化すると、クラスを拡張することになり、サブクラス化の要件が満たされます。 (lombokの@Getterを使用).
@Getter
public abstract class ConfigurationDefinition<T> {
private Class<T> type;
...
public ConfigurationDefinition(...) {
this.type = (Class<T>) ((ParameterizedType) this.getClass().getGenericSuperclass()).getActualTypeArguments()[0];
...
}
}
今度は新しいクラスを定義せずにそれを拡張します。 (最後の{}に注意してください...拡張されていますが、何も上書きしないでください - 必要でない限り)。
private ConfigurationDefinition<String> myConfigA = new ConfigurationDefinition<String>(...){};
private ConfigurationDefinition<File> myConfigB = new ConfigurationDefinition<File>(...){};
...
Class stringType = myConfigA.getType();
Class fileType = myConfigB.getType();
public <T> T yourMethodSignature(Class<T> type) {
// get some object and check the type match the given type
Object result = ...
if (type.isAssignableFrom(result.getClass())) {
return (T)result;
} else {
// handle the error
}
}
ジェネリックを使用しているクラス/インタフェースを拡張または実装している場合は、既存のクラス/インタフェースをまったく変更せずに、ジェネリックタイプの親クラス/インタフェースを取得できます。
3つの可能性があります
ケース1 あなたのクラスがジェネリックスを使っているクラスを拡張しているとき
public class TestGenerics {
public static void main(String[] args) {
Type type = TestMySuperGenericType.class.getGenericSuperclass();
Type[] gTypes = ((ParameterizedType)type).getActualTypeArguments();
for(Type gType : gTypes){
System.out.println("Generic type:"+gType.toString());
}
}
}
class GenericClass<T> {
public void print(T obj){};
}
class TestMySuperGenericType extends GenericClass<Integer> {
}
ケース2 あなたのクラスがGenericsを使っているインターフェースを実装しているとき
public class TestGenerics {
public static void main(String[] args) {
Type[] interfaces = TestMySuperGenericType.class.getGenericInterfaces();
for(Type type : interfaces){
Type[] gTypes = ((ParameterizedType)type).getActualTypeArguments();
for(Type gType : gTypes){
System.out.println("Generic type:"+gType.toString());
}
}
}
}
interface GenericClass<T> {
public void print(T obj);
}
class TestMySuperGenericType implements GenericClass<Integer> {
public void print(Integer obj){}
}
ケース3 あなたのインターフェースがGenericsを使用しているインターフェースを拡張しているとき
public class TestGenerics {
public static void main(String[] args) {
Type[] interfaces = TestMySuperGenericType.class.getGenericInterfaces();
for(Type type : interfaces){
Type[] gTypes = ((ParameterizedType)type).getActualTypeArguments();
for(Type gType : gTypes){
System.out.println("Generic type:"+gType.toString());
}
}
}
}
interface GenericClass<T> {
public void print(T obj);
}
interface TestMySuperGenericType extends GenericClass<Integer> {
}
この質問は古いですが、今ではgoogle Gson
を使用するのが最善です。
カスタムviewModel
を取得する例。
Class<CustomViewModel<String>> clazz = new GenericClass<CustomViewModel<String>>().getRawType();
CustomViewModel<String> viewModel = viewModelProvider.get(clazz);
ジェネリック型クラス
class GenericClass<T>(private val rawType: Class<*>) {
constructor():this(`$Gson$Types`.getRawType(object : TypeToken<T>() {}.getType()))
fun getRawType(): Class<T> {
return rawType as Class<T>
}
}
実際には、私はあなたがタイプTのあなたのクラスにフィールドを持っていると仮定します。タイプTのフィールドがなければ、ジェネリックTypeを持つことのポイントは何ですか?だから、あなたは単にそのフィールドにinstanceofをすることができます。
私の場合は、
<T>項目をリストします。
if(items.get(0)instanceofローカリティ)...
もちろん、これは可能なクラスの総数が制限されている場合にのみ機能します。