Общее наследование и вызов GetMethod (). GetReturnType () - PullRequest
6 голосов
/ 28 октября 2011

В моем текущем проекте у меня есть классы, которые смоделированы следующим образом. В какой-то момент такой метод, как getReturnTypeForGetId(), вызывается для классов A и B. Вызов метода с A возвращает Integer, как и ожидалось, но B возвращает Serializable.

Что мне здесь не хватает? Меня укусила какая-то отвратительная вещь, связанная со стиранием, или я просто упускаю какой-то типичный контекстный удар?

РЕДАКТИРОВАТЬ: Добавление переопределенного метода getId() к B устраняет проблему, но я все же хотел бы понять, с чем сталкиваюсь.

import java.io.Serializable;

public class WeirdTester {
    static interface Identifiable<T extends Serializable> {
        T getId();
        void setId(final T id);
    }

    static abstract class BaseEntity<T extends Serializable> implements Identifiable<T> {
        private T id;
        public T getId() { return id; }
        public void setId(final T id) { this.id = id; }
    }

    static class A implements Identifiable<Integer> {
        private Integer id;
        public Integer getId() { return id; }
        public void setId(final Integer id) { this.id = id; }
    }

    static class B extends BaseEntity<Integer> {}

    @SuppressWarnings("unchecked")
    private static <T extends Serializable, Q extends Identifiable<T>> Class<T> getReturnTypeForGetId(
            final Class<Q> clazz) throws Exception {
        return (Class<T>) clazz.getMethod("getId", (Class[])null).getReturnType();
    }

    public static void main(final String[] args) throws Exception {
        System.out.println(getReturnTypeForGetId(A.class));
        // CONSOLE: "class java.lang.Integer"
        System.out.println(getReturnTypeForGetId(B.class));
        // CONSOLE: "interface java.io.Serializable"
    }
}

Ответы [ 5 ]

2 голосов
/ 28 октября 2011

В скомпилированном классе A есть несколько getId методов.Вы получаете метод моста для ковариантного возвращаемого типа («фикция» языка, не отраженного в виртуальной машине).Спецификация для Class.getMethod говорит, что он вернет метод с наиболее конкретным типом возврата (при условии, что он существует).Он делает это для A, но для B метод не переопределяется, поэтому javac избегает синтеза ненужного метода моста.

Фактически, для этого примера вся информация все еще находится в файлах классов.(Ранее я сказал, что это не было стерто . Это не правда, но стирание не означает, что его там нет!) Общая информация, однако, немного сложна для извлечения (она будет вIdentifiable.class.getGenericReturnType(), Identifiable.class.getTypeParameters(), BaseEntity.class.getGenericInterfaces, BaseEntity.class.getTypeParameters() и B.getGenericSuperclass (я думаю!)).

Используйте javap, чтобы точно увидеть, что у вас есть в файлах классов.

2 голосов
/ 28 октября 2011

В классе A вы переопределяете getId, чтобы вернуть Integer.

В классе B вы не переопределяете getId, поэтому метод getId в B - это метод BaseEntity.Из-за стирания этот возвращается Serializable.

1 голос
/ 28 октября 2011

id в BaseEntity: private и ' Serializable или расширение Serializable '.

Class B (которыйрасширяет BaseEntity) ничего не знает об этом поле.Если он определил свой собственный id и не переопределил getId () / setId (...), эти два метода продолжат использовать BaseEntity.id

Если вы добавите этот метод в BaseEntity:

public void setId2(final Serializable id) {
        this.id = (T) id;
}

это позволяет вам установить BaseEntity.id для любого Serializable.

В следующем тесте вы можете затем установить поле идентификатора, например, Float значение ивсе компилируется и остается неизменным getId () удобно возвращает значение Float.

B b = new B();
b.setId2(2.1F);
System.out.println( b.getId() ); //prints out 2.1

Поэтому, если вы делаете то, что делаете, и спрашиваете «Какой тип возврата B.getId() method ', тогда, если вы не переопределите метод getId () в классе B (который заставит его использовать тип функции Integer и вернет Integer наверняка. Обратите внимание, что BaseEntity.id даже не будет виден B тогда!) отраженияответ не целочисленный, а общий Serializable.Потому что любой метод Serializable действительно может быть получен из метода getId ().

1 голос
/ 28 октября 2011

Ответ - действительно стирание типа.Помните, что дженерики - это всего лишь хитрость, подсказка в не скомпилированном коде Java.Компилятор удаляет все, что с ними связано, для создания байт-кода.Поэтому, когда вы используете отражение в методе getId, вы получаете только необработанный тип.

http://download.oracle.com/javase/tutorial/java/generics/erasure.html

Но если вы запрашиваете класс реального объекта, возвращаемого этим методом (B.getId), без использования отражения, из-за того, как он построен, вы получите Integer.

0 голосов
/ 28 октября 2011

Java допускает так называемое «сужение» типов возвращаемых значений. Вот почему ваш пример работает вообще:

Serializable getId()

может быть переопределено с любым сериализуемым типом возврата, например

Integer getId(), поскольку Integer реализует Serializable, поэтому в этом случае допускается сужение.

Поскольку B не не переопределяет getId(), его getId() такой же, как унаследованный от BaseEntity Декларация

class B extends BaseEntity<Integer>

«стирается» во время компиляции до

class B extends BaseEntity

и, вуаля, мы получаем наблюдаемый результат.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...