Хобрук: Ваш путь к мастерству в программировании

Параметр неопределенного типа при вызове универсального метода

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

public class GenericClass<U> {
    private U value;

    public GenericClass(U value) {
        this.value = value;
    }
}

и следующий общий метод в другом классе MyClass:

public <T extends BT, BT> void genericMethod(T arg) {
    Object genericClass = new GenericClass<BT>(arg);
}

Какое значение получит параметр типа BT, если мы вызовем

genericMethod("text");

?

Некоторые примечания:

Код выше компилируется без ошибок и предупреждений, что мне кажется странным. Декомпиляция (с помощью IntelliJ IDEA 2016) показывает следующий код:

public <T extends BT, BT> void genericMethod(T arg) {
    new MyClass.GenericClass(arg);
}

Обратите внимание, что новый GenericClass<BT>(arg) не совпадает с new GenericClass(arg), потому что последний эквивалентен new GenericClass<T>(arg) (вывод типа), и хотя T extends BT это разные типы, а GenericClass может иметь внутреннюю логику, где точный имя типа играет важную роль (например, используется в качестве строкового ключа в некоторых картах и ​​т. д.). Поэтому для меня странно, почему компилятор молча использует вывод типа вместо того, чтобы выдать какое-то предупреждение (или, может быть, даже ошибку) о том, что параметр типа BT не указан. Может быть, я что-то упускаю. важно о дженериках в Java, однако...

14.09.2016

Ответы:


1

Первая часть этого ответа посвящена части вашего вопроса, касающейся определения типа

Единственное место, где тип выводится автоматически в вашем примере, — это вызов метода genericMethod.

Определение genericMethod объявляет два параметра универсального типа: an unbound BT и T which should be a subclass of BT.

Тип T будет выведен в соответствии с правилами, указанными в раздел 18.2.4 спецификации языка Java:

Формула ограничения вида ‹S = T›, где S и T — типы, сокращается следующим образом:

...

В противном случае, если T является переменной вывода, α и S не является примитивным типом, ограничение сводится к границе S = α.

В вашем примере вы предоставляете объект типа String в качестве аргумента типа T. Согласно приведенному выше правилу, T будет считаться равным String.

Теперь, когда T зафиксировано, вывод продолжится с BT. Этот тип определяется следующим разделом 18.3.1, в котором говорится, что выражение типа:

T extends BT, T == String

подразумевает

String extends BT

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

genericMethod<String,String>("text")

Надеюсь, это немного прояснит ситуацию.

Чтобы ответить на вторую часть вашего вопроса:

и хотя T расширяет BT, это разные типы, и GenericClass может иметь внутреннюю логику, в которой точное имя типа играет важную роль (например, используется в качестве строкового ключа в какой-то карте и т. д.).

Без добавления дополнительных ограничений компилятор будет рассматривать T как эквивалент BT и позволит вам вызывать только методы, определенные как часть типа BT.

Если вы не делаете что-то очень странное, используя отражение, код не должен зависеть от значения аргумента типа во время выполнения. Текущий GenericClass в его нынешнем виде не очень полезен, потому что U не имеет никаких ограничений, поэтому компилятор позволит вам делать только то, что допустимо для всех объектов. Вы можете рассматривать U в этом контексте так же, как Object.

14.09.2016
  • Спасибо за ответ. Что такое выводная переменная? Можете ли вы указать на это в моем примере кода? В объявлении genericMethod() я вижу только две переменные типа (T и BT) и одну обычную переменную (arg). В главе 18.1.1 я прочитал следующее: Переменные вывода — это метапеременные для типов, т. е. специальные имена, которые позволяют абстрактно рассуждать о типах. Чтобы отличить их от переменных типа, переменные вывода представлены греческими буквами, главным образом α. Однако этот термин не прояснил для меня. 15.09.2016
  • Вы пишете: String расширяет BT Это отношение позволяет компилятору связать BT со строкой, таким образом, вы получаете следующий подразумеваемый вызов: genericMethod‹String,String›(text) - Я не могу понять, почему это позволяет сделать это: String extends BT - это не то же самое, что BT == String! Вы также пишете: Без добавления дополнительных ограничений компилятор будет рассматривать T как эквивалент BT и позволит вам вызывать только методы, определенные как часть типа BT. - тут то же самое! Лучше бы компилятор выдал ошибку, что BT нигде не используется. 15.09.2016
  • Вы пишете: Текущий GenericClass в его нынешнем виде не очень полезен [...]. Вы можете рассматривать U в этом контексте так же, как Object. - Я согласен: этот образец слишком дистиллирован. В моем реальном проекте здесь стоит JAXBElement (вместо GenericClass). 15.09.2016

  • 2

    На основании этой части вашего вопроса

    GenericClass может иметь внутреннюю логику, где точное имя типа играет важную роль (например, используется в качестве строкового ключа на какой-то карте и т. д.).

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

    В целом, проверки типов, выполняемые во время компиляции, могут показаться вам немного свободными (особенно если вы, например, пришли с C++). В ответах на этот вопрос есть интересное обсуждение некоторых из них. Java generics - стирание типа - когда и что происходит

    14.09.2016
    Новые материалы

    Управление DOM для чайников вроде меня
    Одной из первых вещей, которую мы рассмотрели, когда начали изучать Javascript во Flatiron, была модель DOM. Кто он? Чем он занимается? Он больше машина, чем человек? Ну да довольно много. ДОМ..

    Что такое структура данных?
    Структура данных хранит и извлекает данные. Все, что обеспечивает эти две функции, является структурой данных . Период. Вы можете пропустить оставшуюся часть статьи, если ответ..

    мои январские чтения по программированию
    Эрик Эллиот Программирование приложения JavaScript Эл Свейгарт «Автоматизируйте скучные вещи с помощью Python» Прогрессивное веб-приложение Google..

    Создание ассоциаций секвелизации с помощью инструмента командной строки Sequelize
    Sequelize - популярный, простой в использовании инструмент объектно-реляционного сопоставления (ORM) JavaScript, который работает с базами данных SQL. Довольно просто начать новый проект с..

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

    Введение в машинное обучение для обнаружения аномалий (часть 1)
    Тщательно созданный, тщательно спроектированный ресурс для специалистов по данным. Часть 1 Главы 03 из Руководства по машинному обучению для обнаружения аномалий Внимание! Прежде чем вы..

    Начало работы с Pulumi в Digital Ocean
    Цифровой океан (ДО) — отличная альтернатива многим другим поставщикам облачных услуг. DO предоставляет простой и понятный пользовательский интерфейс, упрощающий управление инфраструктурой и..