Рубрики
Без рубрики

Вывод Типа И Общие Методы – [ООП и Java #7]

P.S. Эта статья написана в ответ на интересный вопрос, поднятый кем-то другим, и этот вопрос… С тегами computerscience, ооп, java, codenewbie.

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

Предположим, у нас есть следующее:

  • A Демонстрационный класс, который может содержать список предметов.
  • Элементы могут быть любого типа, указанного клиентом/вызывающим абонентом.
  • В В классе Demo есть метод map , который принимает функцию и возвращает новый список элементов в исходном списке, но измененный этой функцией.
import java.util.List;
import java.util.ArrayList;
import java.util.function.Function;

class Demo {
  List list;
  Demo(List list) {
    this.list = list;
  }

   Demo map(Function f) {
    List answer = new ArrayList();
    for (T item : this.list) {
      answer.add(f.apply(item));
    }
    return new Demo(answer);
  }
}

дополнительная информация

  • , супер T, ? расширяет U> и т.д.

    • Генетика и Ограниченные подстановочные знаки
  • Функция <...>
    • Функциональные интерфейсы ( скоро напишу статью об этом ). Достаточно сказать, что это интерфейсы только для одного метода, которые предназначены для того, чтобы сделать функции первоклассными объектами в Java. Здесь пример функции, которую мы собираемся использовать ниже, можно интерпретировать следующим образом: Функция<Объект, целое число> -> x.Хэш-код(); f – это функция, которая принимает входные данные типа Объект и выведите Целое число .

Итак, какие из Q1-10 способны компилироваться без ошибок?

// turns an input object into its hash value representation
Function f = x -> x.hashCode();

// List of strings
List strings = new ArrayList();
strings.add("a");
strings.add("b");

// Q1 - 5
Demo list = new Demo(strings).map(f);
Demo list = new Demo(strings).map(f);
Demo list = new Demo(strings).map(f);
Demo list = new Demo(strings).map(f);
Demo<> list = new Demo(strings).map(f);

// Q6 - 10
(Demo) new Demo(strings).map(f);
(Demo) new Demo(strings).map(f);
(Demo) new Demo(strings).map(f);
(Demo) new Demo(strings).map(f);
(Demo) new Demo(strings).map(f);

Ответы 1 ОК 6 ОК 2 ОК 7 ОШИБКА 3 ОК 8 ОШИБКА 4 ОШИБКА 9 ОШИБКА 5 ОШИБКА 10 ОК

Анализ

Давайте попробуем интерпретировать соответствующие параметры типа, и это должно прояснить ситуацию.

Когда мы создаем Демонстрационный объект с помощью его конструктора, нам нужно передать список, содержащий определенный тип элемента. Тип элементов становится типом T в Демонстрационном классе. Так,

// note that map() method is not applied here
// T is replaced by String
Demo listOfString = new Demo(Arrays.asList("a", "b"));

// T is replaced by Integer
List myList = new ArrayList();
myList.add(1);
Demo listOfInteger = new Demo(myList));

По сути, Демонстрационный класс выглядит следующим образом:

class Demo {
  List list;
  Demo(List list) {
    this.list = list;
  }
//...
}

class Demo {
  List list;
  Demo(List list) {
    this.list = list;
  }
//...
}

Помните, что из-за отношения инвариантности не допускается следующее:

// ERROR
Demo listOfNumber = new Demo(Arrays.asList(1, 1));

Теперь давайте рассмотрим вопрос 1-5

Вопросы, касающиеся назначения

Утверждения 1-5 пытаются сделать следующее:

  • Вызов конструктора Демо и передайте список строк
  • Вызов .карта(f) метод на вновь созданном экземпляре и возвращает новый Демонстрационный экземпляр
  • Назначьте Демонстрационный экземпляр в одну из объявленных переменных.
Конструктор

Демонстрационный список <Целое число> = новая демонстрационная версия <Строка>(строки) .карта (f) ;

Здесь нет проблем, так как мы заявляем это T в Демо это Строка и мы передали список строк. Итак, что касается конструктора, мы передаем аргумент правильного типа, и он вернет нам экземпляр Demo<Строка> .

Метод карты

Демонстрация <Целое число> Демонстрация<Строка>(строки) .карта (f) ;

Вот тут-то и возникает путаница. Давайте перечислим важные связанные фрагменты кода:

Function f = x -> x.hashCode();

class Demo {
  List list;
  Demo(List list) {
    this.list = list;
  }

   Demo map(Function f) {
    List answer = new ArrayList();
    for (T item : this.list) {
      answer.add(f.apply(item));
    }
    return new Demo(answer);
  }
}

Мы знаем, что экземпляр, из которого мы вызываем метод map , имеет Демо<Строка> , из предыдущего обсуждения конструктора.

Поэтому, когда карта вызывается, T в Демо классе все еще Строка . Поэтому мы можем просмотреть наш Демонстрационный класс с T замененным Строкой :

class Demo {
  List list;
  Demo(List list) {
    this.list = list;
  }

   Demo map(Function f) {
    List answer = new ArrayList();
    for (String item : this.list) {
      answer.add(f.apply(item));
    }
    return new Demo(answer);
  }
}

Теперь у метода map все еще есть два “неизвестных”:

  • ?
  • U

Когда мы вызываем метод map , мы передаем функцию f который “предоставит” тип методу map , и они должны согласиться, чтобы метод принял f в качестве аргумента.

Давайте сравним их очень внимательно…

Function f = x -> x.hashCode();
 Demo map(Function f)  

// for better visual, I will add spaces to align them
                Function<     Object,       Integer>
 Demo map(Function) 

Ввод

Объект из функции f соответствует ? супер строка в карта первый параметр. Угадайте, что ? будет…

  • ? становится Объектом .
  • Следовательно Супер Строка объекта
  • Что само по себе является допустимым утверждением, поскольку Объект действительно является родителем Строка
 Demo map(Function)

Выход

Целое число из функции f соответствует ? расширяет U в карту второй параметр. Угадайте, что ? будет…

  • ? становится Целым числом , что неудивительно
  • Это нормально, потому что ? является подстановочным знаком и может быть любого типа
 Demo map(Function)

Угадай, что U будет…

Так как нигде нет U прямо указано, U будет выведен компилятором. Как он делает вывод?

  1. Если возвращаемый объект назначен объявленной переменной, то U становится типом, указанным назначенной переменной. операционная
  2. Если возвращаемый объект ничему не назначен, компилятору больше нечего выводить, кроме аргумента метода, который является ?

В нашем контексте

  1. U становится типом, указанным в левой части (LHS) инструкции присваивания. операционная
  2. U становится типом, который находится в ? .
Задание

Наконец, давайте рассмотрим отдельные вопросы

// Q1 - 5
Demo list = new Demo(strings).map(f);
Demo list = new Demo(strings).map(f);
Demo list = new Demo(strings).map(f);
Demo list = new Demo(strings).map(f);
Demo<> list = new Demo(strings).map(f);

Для Q1-4, по логике вывода типа, U станет Целым числом , Номер , Объект , Строка соответственно.

Просто для иллюстрации использования Q2

Demo map(Function f) 

Обратите внимание, что Целое число расширяет число само по себе является допустимым утверждением, потому что Число является родителем Целого числа .

Итак, сосредоточив внимание на выводе, мы могли бы сказать, что вызов метода в Q2 вернет Демо<Номер> , который будет назначен Демо<Номер> список , отлично!

То же самое касается Q1 и Q3.

Почему Q3 неправильный?

Demo map(Function f) 

Замена U с Строка , мы получаем это противоречие:

  • Целое число расширяет строку
  • Недействителен, потому что Строка не является родительской для Целого числа .

Ошибка, предоставленная компилятором, совершенно ясна ( обратите внимание на следующую ошибку, потому что ошибка, которую мы увидим в Q6-10, лишь немного отличается ):

incompatible types: inference variable U has incompatible bounds, 
equality constraints: java.lang.String 
lower bounds: java.lang.Integer

Q5 также является плохим утверждением. Это связано с тем, что мы должны указывать аргумент типа при объявлении объекта универсального типа.

Вопросы, касающиеся типизации

// Q6 - 10
(Demo) new Demo(strings).map(f);
(Demo) new Demo(strings).map(f);
(Demo) new Demo(strings).map(f);
(Demo) new Demo(strings).map(f);
(Demo) new Demo(strings).map(f);

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

Подобно Q1-5, нам все еще нужно сделать вывод, что U – это до того, как мы продолжим что-то возвращать. В отличие от операторов присваивания, компилятор не имеет переменных для вывода. Следовательно, он будет задействовать стратегию номер два, которая заключается в выводе из аргумента метода.

Помните, что у нас есть карта(<Супер строка объекта, ? расширяет U>) Итак, единственное, на что можно ссылаться, это ? . ? было установлено выше, чтобы быть целым числом , и следовательно, U должно быть целым числом Сейчас у нас есть

Demo map(Function f) 

Поскольку U является Целым числом , возвращаемый тип метода map становится Demo<Целое число> .

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

Иллюстрация с использованием Q7

// ERROR
(Demo) Demo  ...

// Or view it as 
List listOfIntegers = new ArrayList();
listOfIntegers.add(1);
listOfIntegers.add(2);
Demo list = new Demo(listOfIntegers); 

Ошибка компилятора теперь:

incompatible types: Demo 
cannot be converted to Demo

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

List listOfIntegers = new ArrayList();
listOfIntegers.add(1);
listOfIntegers.add(2);
// View it as 
Demo list = new Demo(listOfIntegers); 

Теперь список имеет тип Демонстрация .

Тип цели и Свидетель типа

В моих объяснениях выше не использовались технические термины, введенные в Учебник Oracle по java . Вот как я бы объяснил это, используя правильную терминологию.

Демонстрация <Число> Демонстрация<Строка> (строки).карта (f);

  • Этот оператор (LHS) ожидает экземпляр Demo<Номер>
  • Демонстрационный <Номер> является целевым типом
  • Потому что метод map(f) возвращает значение типа Demo , компилятор делает вывод, что аргумент типа U должно быть значение Номер
  • В качестве альтернативы мы также можем быть явными и указать свидетель типа прямо перед именем метода Demo<Число> Demo<Строка>(строки).<Номер> карта (f);

Рекомендации

Учебник Oracle по Java по определению типов

Оригинал: “https://dev.to/tlylt/type-inference-and-generic-methods-oop-java-7-3leg”