Параметры типа java generics и операции с этими типами

В поисках ответа на интересную ситуацию, с которой я недавно столкнулся, я столкнулся со следующим вопросом: Тип безопасности, генерические файлы Java и запросы

Я написал следующий класс (очищенный бит)

public abstract class BaseDaoImpl<T extends Serializable> extends HibernateDaoSupport implements BaseDao<T> {

    /**
     * Finds and Returns a list of persistent objects by a collection of criterions
     * @param criterions
     * @return list of persistent objects
     * @throws DBException
     */
    @SuppressWarnings("unchecked")
    protected List<T> findByCriteria(Collection<Criterion> criterions) throws DBException {
        try {
            DetachedCriteria criteria = DetachedCriteria.forClass(T.class); // BAD!!!
            for (Criterion criterion : criterions) {
                criteria.add(criterion);
            }

            List<T> result = getHibernateTemplate().findByCriteria(criteria);
            return result;
        }
        catch (Exception e) {
            throw new DBException(T.class + " lookup by " + criterions + " failed", e); // BAD!!!
        }
    }
}

Для некоторых (возможно, веских причин) T.class вызывает ошибку времени компиляции.

Мой первый вопрос: почему?

Если я изменил его на T.getClass(), который, очевидно, не должен компилироваться, потому что no 'T', когда "расширен" или проходит через "erasure", должен иметь статический метод, такой как. Eclipse IDE предоставляет следующее компиляционное сообщение:

Невозможно сделать статическую ссылку на нестатический метод getClass() из Тип объекта

Мой второй вопрос - почему? И что означает эта ошибка?

Наконец, было бы наиболее оптимальным решение для этого, как указано выше в ссылке (точнее, моей интерпретации)?

public abstract class BaseDaoImpl<T extends Serializable> extends HibernateDaoSupport implements BaseDao<T>, MyGenericHelper<T> {

    /**
     * Finds and Returns a list of persistent objects by a collection of criterions
     * @param criterions
     * @return list of persistent objects
     * @throws DBException
     */
    @SuppressWarnings("unchecked")
    protected List<T> findByCriteria(Collection<MyCriterion> criterions) throws DBException {
        try {
            DetachedCriteria criteria = DetachedCriteria.forClass(getGenericClass()); // BAD!!!
            for (Criterion criterion : criterions) {
                criteria.add(criterion);
            }

            List<T> result = getHibernateTemplate().findByCriteria(criteria);
            return result;
        }
        catch (Exception e) {
            throw new DBException(getGenericClass() + " lookup by " + criterions + " failed", e); // BAD!!!
        }
    }
}

public interface MyGenericHelper<T extends Serializable>  {
    public Class<T> getGenericClass();
}

Спасибо!

Ответы

Ответ 1

Причина, по которой T.class недоступна, заключается в том, что T стирается во время компиляции, поэтому во время выполнения он не существует, чтобы получить класс.

Типичный взлом вокруг этого заключается в создании метода factory:

 public static <T extends Serializable> BaseDAOImpl<T> createBaseDAO(Class<T> klass) {
       return new BaseDAOImpl<T>(klass);
 }

И затем в конструкторе сохраните переменную klass в качестве поля и укажите ее, когда вам это нужно.

Вы можете использовать свой интерфейс (лично я бы просто пошел с защищенным абстрактным методом), если вы хотите сохранить конструктор no-arg.

 protected abstract Class<T> getGenericClass();

EDIT: В случае общего абстрактного суперкласса есть несколько вариантов. У одного есть конструктор, а затем подклассы просто должны его вызвать (не имея конструктора no-arg). Что-то вроде этого:

  protected BaseDAOImpl(Class<T> klass) {
       //store the parameter in a field.
  }

Тогда статический метод не имеет значения, так как вам нужно создать подкласс. Статический метод factory больше используется, когда на основе класса вы можете вернуть правильную реализацию (так что у вас есть factory, а не только стратегия).

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

 public IntegerImpl extends BaseDAOImpl<Integer> {}

тогда общий тип сохраняется в классе. Существует действительно уродливый взлом, чтобы получить это, используя класс. Я экспериментировал с этим, и он работал:

  (Class<?>) ((ParameterizedType)  this.getClass().getGenericSuperclass()).getActualTypeArguments()[0]

Но он делает огромные предположения о иерархии наследования и не должен использоваться ни в чем серьезном, если его вообще можно избежать, но я включаю его для полной картины того, что происходит.

Ответ 2

Для некоторых (вероятно, серьезных причин) T.class вызывает ошибку времени компиляции.

Мой первый вопрос: почему?

В момент компиляции компилятор не знает тип T (в этом случае он может быть Integer, String), T.class не может быть поддержан, чтобы вернуть фактический класс.

Однако во время выполнения информация типа удаляется из-за типа стирания

Cannot make a static reference to the non-static method getClass() from

тип Object

Мой второй вопрос - почему? И что действительно ли эта ошибка означает?

Hm.. getClass() - метод класса (нестатический), для которого требуется объект. T не является объектом, это тип, поэтому он терпит неудачу. Они не статичны, потому что уже есть ключевое слово XXX.class, из которого вы можете получить объект Class.