Обобщение — параметризированный тип. При объявлении класса, интерфейса, метода и конструктора можно указать один или несколько параметров типа, вместо которых при создании объекта будут подставляться реально существующие ссылочные типы.
Когда один алгоритм можно реализовать для различных типов данных, то предпочтительнее создать обобщенный класс, который получает тип данных в качестве параметра. Это позволит реализовать такой алгоритм в обобщенном виде для всех поддерживаемых типов. Обычно параметры типа записываются заглавными буквами, например, 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) — «последним пришёл, первым вышел».
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или другой), наполнить его числами разных типов; добавить несколько объектов этого класса в систему кэширования в качестве значений; обратиться к некоторым из них (по ключу и через поиск значений); переполнить емкость, так чтобы сработала политика вытеснения; сменить политику вытеснения; снова переполнить емкость, так чтобы сработала выбранная политика вытеснения. Изменение состояния системы кэширования можно показать в отладчике или через вывод вхождений (ключ-значение) системы кэширования в командную строку.
- Какие параметры типа есть в вашем коде?
- Какие обобщения (обобщенные типы) есть в вашем коде?
- Какие аргументы типа есть в вашем коде?
- Какие ограниченные обобщенные типы есть в вашем коде?
- Как органичен тот или иной обобщенный тип в вашем коде?
- Какой конкретный тип будет подставлен вместо параметра типа в вашем коде при компиляции?
- Используете ли вы шаблоны аргументов в вашем коде?
- Есть ли в вашем коде обобщенные методы?
- Есть ли в вашем коде обобщенные конструкторы?
- Есть ли в вашем коде обобщенные интерфейсы?
- Как вы реализуете обобщенные интерфейсы?
- Какие коллекции есть в Java?
- Что такое список: как хранит данные, как реализован доступ к данным?
- Что такое очередь: как хранит данные, как реализован доступ к данным?
- Что такое стек: как хранит данные, как реализован доступ к данным?
- Что такое множество: как хранит данные, как реализован доступ к данным?
- Что такое отображение: как хранит данные, как реализован доступ к данным?
- Какие статические методы есть в вашем коде?
- Какие локальные переменные есть в вашем коде?
- Выделите в коде инструкции перехода?
- Какие классы-обертки есть в вашем коде?
- От какого суперкласс унаследованы все классы-обертки числовых примитивных типов?
- Какую сигнатуру имеет тот или иной метод?
- Чем динамический массив отличается от связанного списка?
- Какие уровни доступа имеют те или иные члены вашего класса?
- Где будут доступны те или иные члены вашего класса?
- Чем емкость коллекции отличается от ее размера?
- Как обычно именуются параметры типа?
- Каким образом значения простых типов данных могут добавляться в коллекции, если коллекции могут хранить только объекты?
- Используется ли автоупаковка/автораспаковка простых типов данных в вашем коде?