Skip to content

Latest commit

 

History

History
404 lines (327 loc) · 24.1 KB

File metadata and controls

404 lines (327 loc) · 24.1 KB

Занятие 9. Обобщенное программирование. Коллекции

Обобщенные типы

Обобщение — параметризированный тип. При объявлении класса, интерфейса, метода и конструктора можно указать один или несколько параметров типа, вместо которых при создании объекта будут подставляться реально существующие ссылочные типы.

Когда один алгоритм можно реализовать для различных типов данных, то предпочтительнее создать обобщенный класс, который получает тип данных в качестве параметра. Это позволит реализовать такой алгоритм в обобщенном виде для всех поддерживаемых типов. Обычно параметры типа записываются заглавными буквами, например, T, V, K.

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

public class Box<T> {
    private T thing;

    public T get() {
        return thing;
    }
    public void put(T thing) {
        this.thing = thing;
    }
}

Например, можно создать объект типа Box<Integer>, который будет хранить только целые числа типа Integer.

Box<Integer> box = new Box<>(); // Здесь аргумент типа --- Integer 
box.put(1); // Вложить целое число
Integer x = box.get(); // Получить целое число
box.put(1.0); // Ошибка компиляции: невозможно добавить вещественное число
box.put("1"); // Ошибка компиляции: невозможно добавить строку

Можно создать объект типа Box<String>, который будет хранить только строки типа String.

Box<String> box = new Box<>(); // Здесь аргумент типа --- String
box.put("my text"); // Вложить строку
String s = box.get(); // Получить строку
box.put(1.0); // Ошибка компиляции: невозможно добавить вещественное число
box.put(1); // Ошибка компиляции: невозможно добавить целое число

Параметров типа может быть несколько. В следующем примере приводится обобщенный класс Pair с двумя параметром типа K и V.

public class Pair<K,V> {
    K key;
    V value;
    Pair(K key, V value) {
        this.key = key;
        this.value = value;
    }
}

При создании объектов обобщенного класса передаются аргументы типов, в том количестве и порядке, в котором параметры типов используются при объявлении этого класса.

Pair<Integer, String> pair1 = new Pair<>(1, "Ivanov");
Pair<String, String> pair2 = new Pair<>("+79081112233", "Petrov");

В обобщенном классе параметр типа может также использоваться для объявления массивов. В следующем примере реализуется обобщенный список на основе статистического массива.

public class StaticArrayList<I> {
    private I items[];
    private int last;

    public StaticArrayList(int size) {
        items = (I[]) new Object[size];
    }

    public boolean add(I item) {
        if (last < items.length) {
            items[last] = item;
            ++last;
            return true;
        } else {
            return false;
        }
    }

    public I get(int index) {
        if (index >= 0 && index < items.length)
            return items[index];
        else
            return null;
    }

    public int size() {
        return items.length;
    }
}

При создании объекта данного списка указывается аргумент типа. Например, в следующем примере создается обобщенный список прямоугольников типа Rectangle.

StaticArrayList<Rectangle> list = new StaticArrayList<>(3);
list.add(new Rectangle(0,0,10,10));
list.add(new Rectangle(10,10,20,10));
list.add(new Rectangle(20,20,10,30));

for (int i = 0; i < list.size(); i ++)
    System.out.println(list.get(i));

Ограниченные типы

Параметр типа можно ограничить, указав суперкласс. В результате, параметр типа может заменяться на сам суперкласс или любой из его подклассов. Например, в следующем примере, параметр типа органичен суперклассом Number. Это означает, что при создании объектов данного обобщенного класса в качестве аргументов можно указывать только сам суперкласс Number и его подклассы Byte, Double, Float, Integer, Long, Short и другие.

public class NumericBox<N extends Number> {
    private N number;

    NumericBox(N number) {
        this.number = number;
    }

    public N get() {
        return number;
    }

    // Квадратный корень
    public double sqrt() {
        return Math.sqrt(number.doubleValue());
    }

    // Возведение в степень
    public double pow(int n) {
        return Math.pow(number.doubleValue(), n);
    }
}

public class MyMainClass {
    public static void main(String[] args) {
        NumericBox<Integer> nb1 = new NumericBox<>(100);
        System.out.println(nb1.sqrt());
        System.out.println(nb1.pow(2));

        NumericBox<Float> nb2 = new NumericBox<>(4F);
        System.out.println(nb2.sqrt());
        System.out.println(nb2.pow(2));
    }
}

Обобщенные методы

Можно объявлять обобщенные методы, при этом сам класс, в котором они объявляются не обязательно будет обобщенным.

import java.util.*;

public class MyMainClass {
    public static void main(String[] args) {
        List<Number> numbers = Arrays.asList(1.5, 2, 3L, 4F);
        System.out.println(sum(numbers));
    }

    public static <T extends Number> double sum(Collection<T> numbers) {
        double sum = 0;

        for (Number n : numbers)
            sum += n.doubleValue();

        return sum;
    }
}

Шаблоны аргументов

Для того чтобы метод мог поддерживать параметры различных обобщенных типов можно использовать метасимвол <?>.

import java.util.*;

public class MyMainClass {
    public static void main(String[] args) {
        List<Number> numbers = Arrays.asList(10, 20, 30, 40, 50);
        printCollection(numbers);
    }

    public static void printCollection(Collection<?> c) {
        for (Object e : c)
            System.out.println(e);
    }
}

Коллекции

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

«Java Collections Framework» — набор связанных классов и интерфейсов, реализующих широко используемые структуры данных — коллекции: список, очередь, множество, отображение и др. Основные интерфейсы:

Коллекция Описание
Collection Позволяет работать с группами объектов. Находится на вершине иерархии коллекций
Deque Двусторонняя очередь
List Список хранит последовательность однотипных объектов
Queue Односторонняя очередь управляет специальными типами списков, где элементы удаляются только из начала списка
Set Множество содержит уникальные элементы
Map Отображение сопоставляет уникальным ключам значения

Иерархия коллекций

Пример использования списка на основе динамического массива.

import java.util.*;

public class MyMainClass {
    public static void main(String[] args) {
        // Создать список строк на основе динамического массива
        List<String> countryNames = new ArrayList<>();

        //
        countryNames.add("France");
        countryNames.add("Russia");
        countryNames.add("Angola");

        for (int i = 0; i < countryNames.size(); i ++) {
            String countryName = countryNames.get(i);
            System.out.printf("element %d = %s%n", i, countryName);
        }

        // Упорядочить список в естественном порядке 
        // (лексикографически для строк)
        Collections.sort(countryNames);

        // Получить итератор для перебора элементов списка
        Iterator<String> iterator = countryNames.iterator();
        while(iterator.hasNext()) {
            String countryName = iterator.next();
            System.out.println(countryName);
        }
    }
}

Пример использования списка на основе связанного списка.

import java.util.*;

public class MyMainClass {
    public static void main(String[] args) {
        // Создать список строк на основе связанного списка
        List<Number> numbers = new LinkedList<>();

        // Наполнить список
        numbers.add(10);
        numbers.add(15.0);
        numbers.add(3L);

        // Перебрать список
        for (Number n: numbers)
            System.out.printf("%s is %s%n", n, n.getClass());
     }
}

Автоупаковка/Автораспаковка

Автоупаковка — это автоматическое преобразование примитивных типов в соответствующие им классы-обертки.
Автораспаковка — это обратное преобразование классов-оберток в примитивные типы.

Примитивные типы и их обертки:

Примитивный тип Размер (бит) Класс-обертка Размер по умолчанию Диапазон значений Кэшируемый диапазон
byte 8 Byte 0 -128 до 127 -128 до 127
short 16 Short 0 -32,768 до 32,767 -128 до 127
int 32 Integer 0 -2³¹ до 2³¹-1 -128 до 127
long 64 Long 0L -2⁶³ до 2⁶³-1 -128 до 127
float 32 Float 0.0f ±1.4E-45 до ±3.4E+38 Не кэшируется
double 64 Double 0.0d ±4.9E-324 до ±1.7E+308 Не кэшируется
char 16 Character '\u0000' '\u0000' до '\uffff' 0 до 127
boolean ~1 Boolean false true, false true, false

Все классы-обертки:

  • Являются иммутабельными (неизменяемыми);
  • Являются финальными (нельзя наследоваться);
  • Реализуют Comparable;
  • Имеют кэш для часто используемых значений.

Способы автоупаковки:

// Примитивный -> Обертка (автоупаковка)
Integer intObj = Integer.valueOf(100);  // Явно
Integer intObj2 = 100;                  // Неявно (автоупаковка)

Способы автораспаковки:

// Обертка -> Примитивный (автораспаковка)
int primitive = intObj.intValue();      // Явно
int primitive2 = intObj;                // Неявно (автораспаковка)

Особенности кэширования:

// Кэшированные значения (== возвращает true)
Integer a = 127;
Integer b = 127;
System.out.println(a == b); // true

// НЕ кэшированные значения (== возвращает false)
Integer c = 128;
Integer d = 128;
System.out.println(c == d); // false
System.out.println(c.equals(d)); // true - использовать equals!

Константы классов-оберток:

System.out.println(Integer.MAX_VALUE);    // 2147483647
System.out.println(Integer.MIN_VALUE);    // -2147483648
System.out.println(Integer.SIZE);         // 32 (бита)
System.out.println(Integer.BYTES);        // 4 (байта)

System.out.println(Character.MAX_VALUE);  // '\uffff'
System.out.println(Character.MIN_VALUE);  // '\u0000'

Полезные методы классов-оберток:

// Парсинг строк
int number = Integer.parseInt("123");
double value = Double.parseDouble("3.14");
boolean flag = Boolean.parseBoolean("true");

// Преобразование в строку
String str1 = Integer.toString(456);
String str2 = Double.toString(2.718);
String str3 = Character.toString('A');

// Сравнение
Integer x = 10, y = 20;
System.out.println(x.compareTo(y)); // -1 (x < y)
System.out.println(Integer.compare(x, y)); // -1

Рекомендации по реализации стека

Стек — абстрактный тип данных, список однотипных элементов, организованных по принципу «Last In, First Out» (LIFO) — «последним пришёл, первым вышел».

Организация стека в виде одномерного упорядоченного по адресам массива. Показаны операции вталкивания и выталкивания данных из стека операциями push и pop.

Задания

9-1 — 1 балл

  • Разработать обобщенный стек на основе массива (методы push, pop, peek, size, clear, empty).
  • Емкость стека должна увеличиваться динамически при полном заполнении.
  • Продемонстрировать работу стека следующим образом: создать стек целых чисел, наполнить его целыми числами. Выполнить методы методы push, pop, peek, size, clear, empty. Изменение состояния стека можно показать в отладчике или через вывод элементов стека в командную строку.

9-2 — 1 балл

  • Разработать обобщенный стек на основе связанного списка (методы push, pop, peek, size, clear, empty).
  • Дополнить класс (стек) статическим методом создания связанного списка из элементов стека.
  • Стек должен хранить только числа: использовать ограниченное обобщение от Number.
  • Дополнить класс (стек) статическим обобщенным методом вычисления среднего значения элементов стека.
  • Дополнить класс (стек) статическим обобщенным методом с ограниченным метасимволом (<? extends Number>) для печати элементов стека в командную строку.
  • Продемонстрировать работу стека следующим образом: создать стек чисел любых типов, наполнить его числами разных типов. Выполнить методы методы push, pop, peek, size, clear, empty. Изменение состояния стека можно показать в отладчике или через вывод элементов стека в командную строку.

9-3 — 2 балла

  • Создать обобщенную систему кэширования на основе словаря, которая хранит данные любого типа и поддерживает основные политики вытеснения LRU/LFU.
  • Реализовать политику вытеснения LRU (Least Recently Used): вытесняются данные, к которым дольше всего не обращались.
  • Реализовать политику вытеснения LFU (Least Frequently Used): вытесняются данные, к которым обращались реже всего.
  • Политика вытеснения может быть назначена системе кэширования.
  • При добавлении нового элемента, если размер кэша превышает емкость, должен быть удален один элемент согласно текущей политике вытеснения.
  • Реализовать поиск по значению.
  • При обращении к данным этом нужно обновить метаданные (время доступа, счетчик) для найденных элементов, так как их «прочитали».
  • Продемонстрировать работу систему кэширования следующим образом: создать сущностный класс (User, Rectangle или другой), наполнить его числами разных типов; добавить несколько объектов этого класса в систему кэширования в качестве значений; обратиться к некоторым из них (по ключу и через поиск значений); переполнить емкость, так чтобы сработала политика вытеснения; сменить политику вытеснения; снова переполнить емкость, так чтобы сработала выбранная политика вытеснения. Изменение состояния системы кэширования можно показать в отладчике или через вывод вхождений (ключ-значение) системы кэширования в командную строку.

Вопросы

  1. Какие параметры типа есть в вашем коде?
  2. Какие обобщения (обобщенные типы) есть в вашем коде?
  3. Какие аргументы типа есть в вашем коде?
  4. Какие ограниченные обобщенные типы есть в вашем коде?
  5. Как органичен тот или иной обобщенный тип в вашем коде?
  6. Какой конкретный тип будет подставлен вместо параметра типа в вашем коде при компиляции?
  7. Используете ли вы шаблоны аргументов в вашем коде?
  8. Есть ли в вашем коде обобщенные методы?
  9. Есть ли в вашем коде обобщенные конструкторы?
  10. Есть ли в вашем коде обобщенные интерфейсы?
  11. Как вы реализуете обобщенные интерфейсы?
  12. Какие коллекции есть в Java?
  13. Что такое список: как хранит данные, как реализован доступ к данным?
  14. Что такое очередь: как хранит данные, как реализован доступ к данным?
  15. Что такое стек: как хранит данные, как реализован доступ к данным?
  16. Что такое множество: как хранит данные, как реализован доступ к данным?
  17. Что такое отображение: как хранит данные, как реализован доступ к данным?
  18. Какие статические методы есть в вашем коде?
  19. Какие локальные переменные есть в вашем коде?
  20. Выделите в коде инструкции перехода?
  21. Какие классы-обертки есть в вашем коде?
  22. От какого суперкласс унаследованы все классы-обертки числовых примитивных типов?
  23. Какую сигнатуру имеет тот или иной метод?
  24. Чем динамический массив отличается от связанного списка?
  25. Какие уровни доступа имеют те или иные члены вашего класса?
  26. Где будут доступны те или иные члены вашего класса?
  27. Чем емкость коллекции отличается от ее размера?
  28. Как обычно именуются параметры типа?
  29. Каким образом значения простых типов данных могут добавляться в коллекции, если коллекции могут хранить только объекты?
  30. Используется ли автоупаковка/автораспаковка простых типов данных в вашем коде?

Дополнительные ресурсы