Введение в программирование

Полиморфизм и Generics

Полиморфизм

Содержание

Полиморфизм

  • Один код, разные типы
  • Специальный (ad-hoc)
    • Для каждого типа свое поведение
    • Времени компиляции
    • Времени исполнения
  • Универсальный
    • Одинаковое поведение для всех типов

Пример полиморфизма (1)

  • Операция сложения (+)
    • 1 + 1
      
    • 1.0 + 1.0
      
    • 1.0 + 1
      
  • Перегрузка по обоим параметрам
  • Перегрузка для разных типов и автоматизированное приведение типов
  • Тип полиморфизма?
    • Специальный полиморфизм времени компиляции

Пример полиморфизма (2)

  • Функция String.format(string, object...)
    • String.format("%d", 10);
      
    • String.format("Hello, %s!", "world");
      
  • Перегрузка времени исполнения по всем параметрам, кроме первого и явная передача информации о типах
  • Тип полиморфизма?
    • Специальный полиморфизм времени исполнения

Пример полиморфизма (3)

  • Определение типа во время исполнения
    • void draw(Shape shape) {
          if (shape instanceof Rect) …
          if (shape instanceof Circle) …
      }
      
  • draw(shape) – перегрузка времени исполнения
  • Тип полиморфизма?
    • Специальный полиморфизм времени исполнения

Пример полиморфизма (4)

  • Наследование в ООП
    • void drawWithColor(Shape shape, Color color) {
          shape.setColor(color);
          shape.draw();
      }
      
  • drawWithColor
    • Полиморфизм включения по первому параметру
    • Универсальный полиморфизм
  • draw(this)
    • Перегрузка времени исполнения по первому параметру
    • Специальный полиморфизм времени исполнения

Пример полиморфизма (5)

  • Шаблоны в C++
    • template <typename T> 
      bool contains(T x, T* a, int size) {
          for (int i = 0; i < size; i++) {
              if (x == a[i]) {
                  return true;
              }
          }
          return false;
      }
      
  • Перегрузка по всем параметрам
  • Тип полиморфизма?
    • Специальный полиморфизм времени компиляции

Пример полиморфизма (6)

  • Generics в Java
    • <T> boolean contains(T item, T[] array) {
          for (T value : array) {
              if (item.equals(value)) {
                  return true;
              }
          }
          return false;
      }
      
  • Параметрический полиморфизм по T
    • Универсальный полиморфизм

Введение в Generics

Содержание

Особенности Generics

  • Строгая типизация
  • Единая реализация
    • Параметрический полиморфизм
  • Отсутствие информации о типе

Пример generic-определения

  • Интерфейс
    interface Stack<E> {
        void push(E element);
        E pop();
        E peek();
        …
    }
    
  • Класс
    class ArrayStack<E> implements Stack<E> {
        ...
    }
    

Generic стек

  • Stack<E> ─ стек элементов типа E
  • Раньше
    • Stack stack = new ArrayStack();
      stack.push(1);
      Integer i = (Integer) stack.pop();
      
  • Теперь
    • Stack<Integer> stack = new ArrayStack<Integer>();
      stack.push(1);
      Integer i = stack.pop();
      
  • Java 7+
    • Stack<Integer> stack = new ArrayStack<>();
      

Пример реализация generic-класса

class ArrayStack<E> implements Stack<E> {
    private E[] elements;
    private int size;
     
    public ArrayStack() {
        elements = (E[]) new Object[10];
    }
     
    public E peek() {
        return elements[size - 1];
    }
    …

Несовместимость generic-типов

  • Generic-типы не совместимы по присваиванию
    Stack<Integer> si = new ArrayStack<>();
    
    Stack<Object> so = si;
    
  • Почему?
    • Иначе — ошибки
      so.push("hello");
      // ClassCastException
      Integer li = si.peek();
      

Как писать Generic-код

Содержание

Проблема 1

  • Метод
    void dump(Stack<Object> stack) {
        while (!stack.isEmpty()) {
            Object o = stack.pop();
            System.out.println(o);
        }
    }
    
  • Вызовы
    • dump(new ArrayStack<Object>(…));
      
    • dump(new ArrayStack<Integer>(…));
      

Решение 1 – wildcard

  • Метод
    void dump(Stack<?> stack) {
        while (!stack.isEmpty()) {
            Object o = stack.pop();
            System.out.println(o);
        }
    }
    
  • Вызовы
    • dump(new ArrayStack<Object>(…));
      
    • dump(new ArrayStack<Integer>(…));
      

Проблема 2

  • Метод
    void draw(Stack<Shape> stack) {
        while (!stack.isEmpty()) {
            Shape shape = stack.pop();
            shape.draw();
        }
    }
    
  • Вызовы
    • draw(new ArrayStack<Shape>(…));
      
    • draw(new ArrayStack<Circle>(…));
      

Решение 2 – bounded wildcard

  • Метод
    void draw(Stack<? extends Shape> stack) {
        while (!stack.isEmpty()) {
            Shape shape = stack.pop();
            shape.draw();
        }
    }
    
  • Вызовы
    • draw(new ArrayStack<Shape>(…));
      
    • draw(new ArrayStack<Circle>(…));
      

Проблема 3

  • Метод
    void pushAll(Object[] a, Stack<Object> stack) {
        for (int i = 0; i < a.length; i++) {
            stack.push(a[i]);
        }
    }
    
  • Примеры использования
    • pushAll(new Object[10], new ArrayStack<Object>());
      
    • pushAll(new String[10], new ArrayStack<Object>());
      
    • pushAll(new String[10], new ArrayStack<String>());
      
    • pushAll(new Object[10], new ArrayStack<String>());
      

Решение 3 – generic-метод

  • Метод
    <T> void pushAll(T[] a, Stack<T> c) {
        for (int i = 0; i < a.length; i++) {
            stack.push(a[i]);
        }
    }
    
  • Примеры использования
    • pushAll(new Object[10], new ArrayStack<Object>());
      
    • pushAll(new String[10], new ArrayStack<Object>());
      
    • pushAll(new String[10], new ArrayStack<String>());
      
    • pushAll(new Object[10], new ArrayStack<String>());
      

Проблема 4

  • Метод
    <T> void moveAll(Stack<T> s1, Stack<T> s2) {
        while (!s1.isEmpty()) {
            T o = s1.pop();
            s2.push(o);
        }
    }
    
  • Примеры использования
    • moveAll(new AS<Integer>(…), new AS<Integer>());
      
    • moveAll(new AS<Integer>(…), new AS<Object>());
      
    • moveAll(new AS<Object>(…), new AS<Integer>());
      

Решение 4 – bounded type argument

  • Метод
    <T, S extends T>
    void moveAll(Stack<S> s1, Stack<T> s2) {
        while (!s1.isEmpty()) {
            s2.push(s1.pop());
        }
    }
    
  • Примеры использования
    • moveAll(new AS<Integer>(…), new AS<Integer>());
      
    • moveAll(new AS<Integer>(…), new AS<Object>());
      
    • moveAll(new AS<Object>(…), new AS<Integer>());
      

Решение 4 – bounded wildcard

  • Метод
    <T> void
    moveAll(Stack<? extends T> s1, Stack<T> s2) {
        while (!s1.isEmpty()) {
            s2.push(s1.pop());
        }
    }
    
  • Примеры использования
    • moveAll(new AS<Integer>(…), new AS<Integer>());
      
    • moveAll(new AS<Integer>(…), new AS<Object>());
      
    • moveAll(new AS<Object>(…), new AS<Integer>());
      

Проблема 5

  • Метод
    <T extends Comparable<T>>
    T max(Stack<T> stack) {
        ...
    }
    
  • Примеры использования
    • Integer i = max(new AS<Integer>(…));
      
    • class Test implements Comparable<Object> {…}
      
      Test t = max(new AS<Test>(…));
      

Решение 5 – upper bounded wcard

  • Метод
    <T extends Comparable<? super T>>
    max(Stack<T> stack) {
        …
    }
    
  • Примеры использования
    • Integer I = max(new AS<Integer>(…));
      
    • class Test implements Comparable<Object> {…}
      
      Test t = max(new AS<Test>(…));
      

Проблема 6

  • /** Duplicates top element of the stack. */
    // Часть интерфейса
    void dup(Stack<?> stack) {
        // Как реализовать?
    }
    
  • void dup(Stack<?> stack) {
        stack.push(stack.peek());
    }
    

Решение 6 – wildcard capture

// Часть интерфейса
void dup(Stack<?> stack) {
    dupImpl(stack);
}
 
// Настоящая реализация
private <T> void dupImpl(Stack<T> stack) {
    stack.push(stack.peek());
}

Generics и вариантность

Содержание

Источник

  • Источник данных
    • interface Reader<T> {
          T read();
      }
      
  • Преобразования
    • Reader<Object> objR = ...
      Reader<String> strR = objR;
      String data = strR.read();
      
    • Reader<String> strR = ...
      Reader<Object> objR = strR;
      Object data = objR.read();
      
  • Ковариантность
    • S ⁚> T Reader<S> ⁚> Reader<T>

Приемник

  • Приемник данных
    • interface Writer<T> {
          write(T value);
      }
      
  • Преобразования
    • Writer<Object> objW = ...
      Writer<String> strW = objW;
      strW.write(new String());
      
    • Writer<String> strW = ...
      Writer<Object> objW = strW;
      objW.write(new Object());
      
  • Контравариантность
    • S ⁚> T Writer<S> <⁚ Writer<T>

Источник и приемник

  • Источник и приемник
    • Stack<T>
  • Преобразования
    • Stack<Object> os = ...
      Stack<String> ss = os;
      ss.push(new Object());
      String s = ss.pop();
      
    • Stack<String> ss = ...
      Stack<Object> os = ss;
      os.push(new Object());
      Object os = os.pop();
      
  • Инвариантность
    • S ⁚> T Stack<S> не связан с Stack<T>

Вариантность

  • Ковариантность
    • Согласованность с отношением включения
    • S ⁚> T G<S> ⁚> G<T>
    • Только источник
  • Контрвариантность
    • Антисогласованность с отношением включения
    • S ⁚> T G<S> <⁚ G<T>
    • Только приемник
  • Инвариантность
    • Независимость от отношения включения
    • S ⁚> T G<S> не связан с G<T>
    • Источник и приемник

Правила

  • Только источник
    • Ковариантность
    • ? extends T
  • Только приемник
    • Контрвариантность
    • ? super T
  • Источник и приемник
    • Инвариантный

Примеры

  • Только источник
    • Reader<String> strR = ...
      Reader<? extends Object> objR = strR;
      Object data = objR.read();
      
  • Только приемник
    • Writer<Object> objW = ...
      Writer<? super String> strW = objW;
      strW.write(new String());
      
  • Источник и приемник
    • Нельзя

Вариантность функций

  • Функция
    • interface Function<T, R> {
           R apply(T argument);
      }
      
  • Совместимость по присваиванию
    • Function<T1, R1> f1;
      Function<T2, R2> f2 = f1;
      
    • Function<T1, R1> <⁚ Function<T2, R2>
  • Что можно сказать о T1 ⁚? T2 и R1 ⁚? R2?
    • T1 ⁚> T2 ─ контрвариантность аргумента
    • R1 <⁚ R2 ─ ковариантность результата

Приведение типов для функций

  • Вариантность
    • Контрвариантность аргумента
    • Ковариантность результата
  • static <T, R> Function<T, R> cast(
        Function<? super T, ? extends R> f
    ) {
        return new Function<T, R>() {
            public R apply(T argument) {
                return f.apply(argument);
            }
        };
    }
    

Реализация

Содержание

Пример кода

List<String> list = new ArrayList<>(…);
String max = list.get(0);
for (int i = 1; i < list.size(); i++) {
    String next = list.get(i);
    if (next.compareTo(max) > 0) {
        max = next;
    }
}

Стирание информации о типах

List list = new ArrayList(…);
String max = (String) list.get(0);
for (int i = 1; i < list.size(); i++) {
    String next = (String) list.get(i);
    if (next.compareTo(max) > 0) {
        max = next;
    }
}

Generic – один класс

  • Stack<String> ss = new AS<String>();
    Stack<Integer> si = new AS<Integer>();
    ss.getClass() == si.getClass() // True
    
    ss instanceof AS // True
    
    ss instanceof AS<String> // Запрещено
    

Преобразование типов

  • Уничтожение информации о типе
    • Stack s = new ArrayStack<String>();
      
  • Добавление информации о типе
    • Stack<String> s = (Stack<String>) new ArrayStack();
      
    • Stack<String> s1 = new ArrayStack();
      
  • Unchecked warning
    • Ответственность программиста
    • @SuppressWarnings("unchecked")
      Stack<String> s1 = new ArrayStack();
      

Ограничения Generic

  • Невозможно создать массив параметра типа
    • Stack<T> s;
      
    • T[] elements; // Object[]
      
    • new T[10];
      
  • Невозможно создать массив Generic-классов
    • new ArrayStack<Stack<Integer>>();
      
    • Stack<?>[] stacks = new Stack<?>[10];
      
    • new Stack<Integer>[10];
      

Дополнительные возможности

Содержание

Явное указание типов при вызове

  • Исходная функция
    • Function<Object, Integer> foi = …;
      
  • Преобразованная функция
    • Function<String, Number> fso = Function.cast(foi);
      
    • Function<String, Number> fso =
          Function.<String, Number>cast(foi);
      
  • Map
    • Stack<Number> ns = stack.map(Function.cast(foi));
      
    • Stack<Number> ns = stack.<Number>map(
          Function.<String, Number>cast(foi));
      
    • Stack<Number> ns = stack.map(
          Function.<String, Number>cast(foi));
      

Исключения

  • Объявление
    • interface Reader<T, E extends Exception> {
          T next() throws E;
          boolean hasNext() throws E;
      }
      
  • Использование
    • <T, E  extends Exception> List<T>
      readAll(Reader<T, E> reader) throws E {
          final List<T> result = new ArrayList<>();
          while (reader.hasNext()) {
              result.add(reader.next());
          }
          return result;
      }
      

Несколько ограничений (1)

  • class StrangeExample<
        I,
        C extends Collection<I> & Comparable<? super C>
    > {
        C coll;
         
        void add(I item) {
            coll.add(item);
        }
         
        int compare(C other) {
            return coll.compareTo(other);
        }
    }
    

Наследование (1)

  • Опеределение
    class ListBuilder<T> {
        private final List<T> list = new ArrayList<>();
        ListBuilder<T> add(T item) {
            list.add(item);
            return this;
        }
        List<T> build() { return list; }
    }
    
  • Использование
    List<String> hello = new ListBuilder<String>()
        .add("hello")
        .add("world")
        .build();
    

Наследование (2)

  • Опеределение
    class AllBuilder<T> extends ListBuilder<T> {
        AllBuilder<T> addAll(Collection<T> items) {
            for (T item : items) {
                add(item);
            }
            return this;
        }
    }
    
  • Использование
    List<String> hello = new AllBuilder<String>()
        .add("hello")
        .addAll(List.of("world"))
        .build();
    

Ссылка на свой тип

  • Предок
    class ListBuilder<B extends ListBuilder<B, T>, T> {
        B add(T item) { /* ... */ return self(); }
        B self() { return (B) this; }
    
  • Наследник
    class AllBuilder<B extends AllBuilder<B, T>, T>
      extends ListBuilder<B, T> {
        B addAll(List<T> item) { /* ... */ return self(); }
    
  • Реализация
    class ConcreteBuilder<T> extends
        AllBuilder<ConcreteBuilder<T>, T> {
    

Заключение

Содержание

Ссылки

Вопросы

???