возможно ли расширить EnumMap с помощью класса Generic? [Дубликат]

Если ваши изображения названы, вы можете ссылаться на них через DOM и изменять источник.

document["imgName"].src="../newImgSrc.jpg";

или

document.getElementById("imgName").src="../newImgSrc.jpg";
542
задан Peter Mortensen 27 September 2015 в 07:35
поделиться

17 ответов

Короткий ответ заключается в том, что в Java нет способа узнать тип стандартных параметров типа Java. Я рекомендую прочитать главу о стирании стилей в учебнике Java Tutorial для более подробной информации.

Популярным решением для этого является передача 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
    }
}
442
ответ дан Zsolt Török 16 August 2018 в 01:53
поделиться
  • 1
    Этот ответ действительно дает правильное решение, но неточно утверждать, что во время выполнения не существует способа найти общий тип. Оказывается, стирание типа намного сложнее, чем простое стирание. Мой ответ показывает, как получить классы общего типа. – Ben Thurley 22 November 2012 в 12:58
  • 2
    @BenThurley Укротительный трюк, но насколько я вижу, он работает только в том случае, если для него существует универсальный супертип. В моем примере вы не можете получить тип T в Foo & lt; T & gt ;. – Zsolt Török 10 June 2013 в 10:54
  • 3
    Справедливая точка относительно универсального супертипа. Я не могу придумать случая, когда я использовал дженерики без него, кроме вопросов экзамена. Я добавил дополнительную информацию к своему ответу, чтобы сделать ее более ясной. Благодарю. – Ben Thurley 17 June 2013 в 12:09
  • 4
    @ses Я еще не видел язык. Они все так или иначе компромиссны. Любой, кто говорит вам иначе, еще не научился. ;) – Ben Thurley 14 September 2013 в 23:47
  • 5
    Это не сработает, если вы @Autowire, где весна будет искать пустой конструктор – madhairsilence 20 February 2017 в 07:30

Я искал способ сделать это сам, не добавляя дополнительной зависимости от пути к классам. После некоторого исследования я обнаружил, что он доступен , если у вас есть общий супертип. Это было хорошо для меня, поскольку я работал с слоем DAO с супертипом общего уровня. Если это соответствует вашему сценарию, то это самый аккуратный подход IMHO.

Большинство дженериков используют случаи, с которыми я столкнулся, имеют какой-то общий супертип, например. List<T> для ArrayList<T> или GenericDAO<T> для DAO<T> и т. д.

Pure Java solution

Статья Доступ к родовым типам во время выполнения в Java объясняет, как вы можете это сделать, используя чистую Java.

Spring solution

В моем проекте использовалась 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 ";
    }
179
ответ дан Ben Thurley 16 August 2018 в 01:53
поделиться
  • 1
    проясните смысл аргументов resolveTypeArgument – gstackoverflow 14 December 2015 в 17:21
  • 2
    getClass () - это метод java.lang.Object, который вернет класс определенного объекта во время выполнения, это объект, для которого вы хотите разрешить тип. AbstractHibernateDao.class - это просто имя базового класса или суперкласса иерархии классов общего типа. Оператор импорта включен, чтобы вы могли легко находить документы и проверять себя. Это страница docs.spring.io/spring/docs/current/javadoc-api/org/… – Ben Thurley 14 December 2015 в 17:30
  • 3
    Каково весеннее решение с версия 4.3.6 и выше. Это не работает с весной 4.3.6. – Erlan 3 March 2017 в 08:20
  • 4
    @ Ерлан, ты уверен? Я просто проверил документы, и этот класс все еще присутствует, и методы здесь не устарели. – Ben Thurley 22 June 2017 в 11:20
  • 5
    Ссылка в «Чистом Java-решении» нарушено, теперь blog.xebia.com/acessing-generic-types-at-runtime-in-java – Nick Breen 26 September 2017 в 21:33
  • 6
    – Ben Thurley 5 September 2018 в 11:24

Однако есть небольшая лазейка: если вы определите свой Foo класс как абстрактный. Это означало бы, что вам нужно создать экземпляр класса следующим образом:

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 );
    }
}
78
ответ дан bert bruynooghe 16 August 2018 в 01:53
поделиться
  • 1
    Это лучший ответ здесь! Кроме того, для чего это стоит, это стратегия, которую Google Guice использует для привязки классов с помощью TypeLiteral – ATG 6 June 2014 в 00:44
  • 2
    Обратите внимание, что каждый раз, когда используется этот метод построения объекта, создается новый анонимный класс. Другими словами, два объекта a и b, созданные таким образом, будут расширять один и тот же класс, но не иметь одинаковые классы экземпляров. a.getClass() != b.getClass() – Martin Serrano 20 March 2015 в 18:04
  • 3
    Существует один сценарий, в котором это не работает. Если Foo должен реализовать интерфейс, такой как Serializable, то анонимный класс не будет Serializable, если не будет экземпляр класса. Я попытался обойти это, создав сериализуемый заводский класс, который создает анонимный класс, полученный из Foo, но по какой-то причине getActualTypeArguments возвращает тип generic вместо фактического класса. Например: (новый FooFactory & lt; MyType & gt; ()). CreateFoo () – Lior Chaga 7 May 2015 в 09:25

Стандартный подход / обходное решение / решение заключается в добавлении объекта 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();
    }
 }
33
ответ дан Paul Whelan 16 August 2018 в 01:53
поделиться
  • 1
    Но, похоже, это не может быть @autowired в фактическом использовании, любой способ обойти? – Alfred Huang 2 February 2018 в 08:16
   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
        }
   }
1
ответ дан Abel ANEIROS 16 August 2018 в 01:53
поделиться

Если вы расширяете или реализуете какой-либо класс / интерфейс, который использует общие элементы, вы можете получить общий тип родительского класса / интерфейса без изменения любого существующего класса / интерфейса вообще.

три варианта,

Случай 1 Когда ваш класс расширяет класс, который использует Generics

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> {
}
1
ответ дан Anil Agrawal 16 August 2018 в 01:53
поделиться

На самом деле, я полагаю, у вас есть поле в вашем классе типа T. Если нет поля типа T, в чем смысл иметь общий тип? Итак, вы можете просто сделать instanceof в этом поле.

В моем случае у меня есть

List<T> items;
в моем классе, и я проверяю, является ли тип класса «Locality»

if (items.get(0) instanceof Locality) ...

Конечно, это работает только в том случае, если общее число возможных классов ограничено.

0
ответ дан Christine 16 August 2018 в 01:53
поделиться
  • 1
    Что делать, если items.isEmpty () истинно? – chaotic3quilibrium 24 May 2013 в 20:21

Вы не можете этого сделать из-за стирания стилей. См. Также вопрос о переполнении стека Java generics - стирание типа - когда и что происходит .

8
ответ дан Community 16 August 2018 в 01:53
поделиться

Представьте, что у вас есть абстрактный суперкласс, который является общим:

public abstract class Foo<? extends T> {}

И тогда у вас есть второй класс, который расширяет Foo с помощью общего бара, который расширяет T:

public class Second extends Foo<Bar> {}

Вы можете получить класс Bar.class в классе Foo, выбрав Type (из ответа bert bruynooghe) и вызывая его с помощью экземпляра 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);

Вы должны отметить эту операцию не является идеальным, поэтому рекомендуется кэшировать вычисленное значение, чтобы избежать множественных вычислений. Одно из типичных применений - это общая реализация 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;
    }
}

Возвращаемое значение - Bar.class при вызове из класса Foo в другой функции или из класса Bar.

17
ответ дан droidpl 16 August 2018 в 01:53
поделиться
  • 1
    toString().split(" ")[1], что было проблемой, во избежание "class " – IgniteCoders 10 May 2016 в 19:09

Я нашел общий и простой способ сделать это. В моем классе я создал метод, который возвращает общий тип в соответствии с его положением в определении класса. Давайте предположим определение класса следующим образом:

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];
    }
}
3
ответ дан Juan Urrego 16 August 2018 в 01:53
поделиться

Вот рабочее решение:

@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 <> ");
    }
} 

ПРИМЕЧАНИЯ: Может использоваться только как суперкласс

  1. Должен быть расширен с помощью типизированного класса (Child extends Generic<Integer>)

OR

  1. Должен быть создан как анонимная реализация (new Generic<Integer>() {};)
10
ответ дан Lino 16 August 2018 в 01:53
поделиться
  • 1
    Работает только с 1.8 (getTypeName()) – Aure77 18 January 2016 в 15:10
  • 2
    getTypeName вызывает toString, поэтому его можно заменить на .getActualTypeArguments () [0] .toString (); – Yaroslav Kovbas 20 January 2016 в 15:50

Я использую обходное решение для этого:

class MyClass extends Foo<T> {
....
}

MyClass myClassInstance = MyClass.class.newInstance();
-4
ответ дан nikolai.serdiuk 16 August 2018 в 01:53
поделиться
  • 1
    Вопрос в том, как создать экземпляр класса T ... – ZhiXingZhe - WangYuQi 21 February 2018 в 07:08

У меня была эта проблема в абстрактном родовом классе. В этом конкретном случае решение проще:

abstract class Foo<T> {
    abstract Class<T> getTClass();
    //...
}

, а затем производного класса:

class Bar extends Foo<Whatever> {
    @Override
    Class<T> getTClass() {
        return Whatever.class;
    }
}
8
ответ дан Octavia Togami 16 August 2018 в 01:53
поделиться

Возможно:

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 .

Дополнительные пояснения см. в Отражение дженериков .

4
ответ дан Peter Mortensen 16 August 2018 в 01:53
поделиться

Как объяснялось в других ответах, чтобы использовать этот подход ParameterizedType, вам нужно расширить класс, но это похоже на дополнительную работу, чтобы сделать весь новый класс, который расширяет его ...

So , что делает класс абстрактным, он заставляет вас расширять его, тем самым удовлетворяя требование подкласса. (используя lombok's @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();
2
ответ дан Riaan Schutte 16 August 2018 в 01:53
поделиться

Лучше, чем класс, предложенный другими, - передать объект, который может сделать то, что вы сделали бы с классом, например, создать новый экземпляр.

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>());
7
ответ дан Ricky Clarkson 16 August 2018 в 01:53
поделиться
  • 1
    Элегантное решение – Stephan 17 November 2011 в 17:37
  • 2
    @ Рикки Кларксон: Я не вижу, как эта фабрика должна возвращать параметризованные foos. Не могли бы вы объяснить, как вы получаете Foo & lt; T & gt; из этого? Мне кажется, это дает только непараметризированный Foo. Разве это не Т в make10 просто Foo здесь? – ib84 13 March 2013 в 11:03
  • 3
    @ ib84 Я исправил код; Кажется, я пропустил, что Foo был параметризирован, когда я написал ответ изначально. – Ricky Clarkson 14 March 2013 в 15:04

У меня есть (уродливое, но эффективное) решение для этой проблемы, которое я использовал недавно:

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 __()
    {
    }
}
3
ответ дан Unknown6656 16 August 2018 в 01:53
поделиться
Другие вопросы по тегам:

Похожие вопросы: