Java: T obj;Тип obj.getClass () является классома не класс,Зачем? - PullRequest
9 голосов
/ 25 октября 2010

В такой функции:

<T> void foo(T obj)

Тип obj.getClass() равен Class<?>, а не Class<? extends T>. Почему?

Следующий код работает нормально:

String foo = "";
Class<? extends String> fooClass = foo.getClass();

Таким образом, подпись T#getClass(), кажется, возвращает Class<? extends T>, верно?

Почему подпись отличается, если T действительно является родовым?

Чтобы преодолеть проблему (и чтобы было более понятно, о чем я бродил), я реализовал эту функцию:

@SuppressWarnings("unchecked") static <T> Class<? extends T> classOf(T obj) {
    return (Class<? extends T>) obj.getClass();
}

Опять вопрос: зачем нужен актерский состав здесь, а не в случае String? И почему SuppressWarnings нужно? Разве из кода не всегда ясно, что он всегда сможет безопасно выполнить приведение?

Есть ли способ получить Class<? extends T> от obj? Если да, то как? Если нет, то почему?

Один из способов - использовать classOf. Это было бы безопасно, верно? Если это всегда безопасно и дает безопасный способ действительно получить Class<? extends T> (вместо Class<?>), почему в Java нет такой функции? Или есть?


Как насчет этого случая:

<T> void bar(T[] array)

array.getClass().getComponentType() снова возвращает Class<?>, а не Class<? extends T>. Почему?

Я реализовал эту функцию:

@SuppressWarnings("unchecked") static <T> Class<? extends T> classOf(T[] array) {
    return (Class<? extends T>) array.getClass().getComponentType();
}

Это снова безопасно для использования?


Чтобы уточнить, о чем мне интересно. Рассмотрим этот демонстрационный код:

static interface I<T> {
    Class<? extends T> myClass();
}

static class A implements I<A> {
    public Class<? extends A> myClass() {
        return this.getClass();
    }
}

static <T> void foo(I<T> obj) {
    Class<? extends T> clazz = obj.myClass(); // this works
}

Это отлично работает. Но то же самое не для Object#getClass().

Почему, например, не было возможности иметь общий интерфейс, такой как ClassInstance<T>, с функцией getClass(), и каждый Java-объект автоматически реализовывал это? Это будет иметь именно те улучшения, о которых я говорю, по сравнению с решением для расширения его от неуниверсального базового класса Object.

или имеющий Object в качестве универсального класса:

static abstract class Object<T> {
    abstract Class<? extends T> myClass();
}

static class B extends Object<B> {
    public Class<? extends B> myClass() {
        return this.getClass();
    }
}

static <T> void bar(Object<T> obj) {
    Class<? extends T> clazz = obj.myClass(); // this works
}

Теперь представьте myClass() как getClass() и подумайте о том, что компилятор автоматически добавит это в каждый класс. Это решило бы много проблем с кастингом.

Главный вопрос, о котором я говорю: почему это не было сделано так?


Или, если выразить это другими словами: Здесь я более подробно опишу решение такой функции classOf, которая преодолевает проблему. Почему это не было сделано, то есть оригинальная функция не такая?

(Я действительно не хочу получать ответ типа: то, как сейчас работает Java, то есть расширение от неуниверсального Object, который определяет эту функцию, делает это невозможным. Я спрашиваю, почему решается как-то иначе, чтобы это было возможно.)

Ответы [ 4 ]

6 голосов
/ 25 октября 2010

Основная проблема в том, что getClass () не возвращает класс, потому что он определен на уровне объекта. то есть он определенно определяется как класс, расширяющий объект. Они могли бы определить getClass () как.

Class<this> getClass() { /**/ }

но вместо этого

Class<?> getClass()

, что означает, что дженерики не понимают, что возвращает getClass.

4 голосов
/ 25 октября 2010

В Java дженерики - всего лишь инструмент исходного уровня для более безопасной разработки.

JVM ничего не знает о дженериках.Компилятор Java отбрасывает эту информацию, и все универсальные типы действительно являются просто ссылками на объекты во время выполнения.Чтобы компенсировать это, компилятор вставляет необходимые приведения.Эта процедура называется Тип Erasure (Google!).

List<String> x = new ArrayList<String>();
x.add("hello");
String hello = x.get(0);

становится следующим во время выполнения

List x = new ArrayList();
x.add("hello");
String hello = (String) x.get(0);

Чтобы решить вашу проблему, вы можете попытаться исследоватьотдельные элементы в вашем массиве (arr[0].getClass()).

3 голосов
/ 25 октября 2010

Здесь есть пара не совсем точных ответов. Обобщения действительно реализованы с использованием стирания типов, однако это не означает, что вся информация о типах теряется. Компилятор сотрет тип до нижней границы, которую он может.

Таким образом, стирается в String; Вот почему getClass для String возвращает Class <? расширяет строку>. Ваш неограниченный однако стирается в Object; и так getClass возвращает Class <? расширяет объект>, то есть класс <?>

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

0 голосов
/ 25 октября 2010

Из-за стирания типов среда выполнения не хранит информацию о типах для универсальных массивов.По сути, JVM внутренне обрабатывает все универсальные массивы, как если бы они были Object [].

Если вы хотите получить тип времени выполнения, лучшим вариантом может быть просто вызвать getClass () для первого элементамассив.Очевидно, вам нужно найти способ обработки пустого регистра, а также случая, когда содержащиеся объекты имеют несколько типов и т. Д.

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