Символы подстановки (Wildcards: ?, extends, super)
Часть раздела Объектно-ориентированное программирование путешествия по Java на Coddy. Урок 57 из 87.
Параметры типа bounded ограничивают обобщения определёнными типами, но что делать, если вы работаете с уже существующими обобщёнными типами и не знаете или не хотите знать точный параметр типа? Именно здесь появляются подстановочные знаки.
Неограниченный шаблон ? обозначает неизвестный тип. Он полезен, когда нужно принимать тип с любыми параметрами:
public static void printList(List<?> list) {
for (Object item : list) {
System.out.println(item);
}
}
printList(new ArrayList<String>()); // Работает
printList(new ArrayList<Integer>()); // РаботаетПодстановочный знак с верхней границей ? extends Type принимает указанный тип или любой из его подклассов. Используй его, когда нужно читать из обобщённой структуры:
public static double sumNumbers(List<? extends Number> numbers) {
double sum = 0;
for (Number n : numbers) {
sum += n.doubleValue();
}
return sum;
}
sumNumbers(Arrays.asList(1, 2, 3)); // Список Integer
sumNumbers(Arrays.asList(1.5, 2.5)); // Список DoubleПодстановочный знак с нижней границей ? super Type принимает указанный тип или любой из его суперклассов. Используй его, когда тебе нужно записывать данные в обобщённую структуру:
public static void addIntegers(List<? super Integer> list) {
list.add(1);
list.add(2);
}
List<Number> numbers = new ArrayList<>();
addIntegers(numbers); // Работает - Number является супертипом IntegerПолезное правило: используй extends, когда только читаешь (производитель), и используй super, когда только записываешь (потребитель). Это известно как принцип PECS: Producer Extends, Consumer Super.
Задание
ЛегкоДавайте создадим систему кормления в зоопарке, демонстрирующую все три типа подстановочных знаков! Вы создадите иерархию животных и еды, а затем напишете служебные методы, использующие неограниченные, ограниченные сверху и ограниченные снизу подстановочные знаки для обработки различных сценариев кормления.
Вы организуете код в четырёх файлах:
Food.java: Создайте простую иерархию классов для еды. Начните с базового классаFood, содержащего закрытое полеname(String), конструктор для его инициализации и методgetName(). Затем создайте в этом же файле два подкласса:Meatextends Food (конструктор вызываетsuper(name)) иVegetableextends Food (по той же схеме).Animal.java: Создайте базовый классAnimalс закрытым полемspecies(String), конструктором и методомgetSpecies(). Создайте два подкласса:CarnivoreиHerbivore, каждый из которых расширяет Animal и вызывает родительский конструктор.ZooFeeder.java: Именно здесь подстановочные знаки раскрывают свои возможности! Создайте служебный класс с тремя статическими методами:printInventory(List<?> items)— использует неограниченный подстановочный знак для вывода любого списка. Пройдите по списку и выведите каждый item с помощьюtoString(), по одному в строке.calculateTotalFood(List<? extends Food> foods)— использует ограниченный сверху подстановочный знак для чтения из списка любого типа Food. Верните count элементов какint. Это демонстрирует чтение из producer.addMeatToStock(List<? super Meat> stock, String meatName)— использует ограниченный снизу подстановочный знак для записи в список, способный хранить Meat. Создайте новый объектMeatс указанным именем и добавьте его в stock. Это демонстрирует запись в consumer.Main.java: Объедините все компоненты системы кормления в зоопарке! Вы получите два входных значения: имя мяса (String) и имя овоща (String).Сначала создайте
ArrayList<Food>и добавьте объектMeatс именем мяса и объектVegetableс именем овоща. ВыведитеFood inventory:, затем вызовитеprintInventoryс этим списком.Затем выведите пустую строку, после чего выведите
Total food items: [count], используяcalculateTotalFoodдля списка еды.Наконец, создайте новый
ArrayList<Food>с именемmeatStock. Выведите пустую строку, затемAdding to meat stock.... ВызовитеaddMeatToStockс этим списком и строкой"Beef". Затем выведитеMeat stock after adding:и вызовитеprintInventoryдля meat stock.
Вы получите два входных значения по порядку: имя мяса и имя овоща.
Переопределите toString() в вашем классе Food, чтобы он возвращал название еды, благодаря чему вывод будет работать корректно. Обратите внимание, что каждый тип подстановочного знака служит своей цели: ? обеспечивает максимальную гибкость, когда нужно только читать как Object, ? extends используется при чтении конкретных типов, а ? super — при записи в коллекцию!
Попробуйте сами
import java.util.Scanner;
import java.util.ArrayList;
import java.util.List;
class Main {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
String meatName = scanner.nextLine();
String vegetableName = scanner.nextLine();
// TODO: Создайте ArrayList<Food> и добавьте объекты Meat и Vegetable
// TODO: Print "Food inventory:" then call printInventory
// TODO: Print blank line, then "Total food items: [count]" using calculateTotalFood
// TODO: Создайте новый ArrayList<Food> под названием meatStock
// TODO: Print blank line, then "Adding to meat stock..."
// TODO: Вызовите addMeatToStock с meatStock и "Beef"
// TODO: Print "Meat stock after adding:" then call printInventory on meatStock
}
}
В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.
Все уроки раздела Объектно-ориентированное программирование
1Основы ООП
Внешние файлыВведение в ООПКлассы и объектыКлючевое слово thisМетодыПоля (атрибуты)Метод-конструкторПерегрузка конструкторовИтоги — Простой калькулятор4Наследование
Основы наследования (extends)Ключевое слово superПереопределение методов (@Override)Цепочка конструкторовКласс ObjectОдиночное и многоуровневое наследованиеПочему нет множественного наследования классовИтоги — Иерархия сотрудников7Специальные методы и класс Object
Метод toString()equals() и hashCode()Метод clone()compareTo() и ComparableИнтерфейс ComparatorПовторение — Кастомная сортировка2Модификаторы доступа и инкапсуляция
Обзор уровней доступаМетоды геттеры и сеттерыСокрытие данныхКлючевое слово finalИтоги — Менеджер банковского счета5Полиморфизм
Основы перегрузки методовПереопределение методов (Run-Time)Upcasting и DowncastingОператор instanceofАбстрактные классы и методыИтоги: Калькулятор фигур8Продвинутые концепции ООП
Композиция против наследованияАгрегация против композицииВнутренние, вложенные и анонимные классыEnums и методы перечисленийRecords (Java 16+)Sealed-классы (Java 17+)11Паттерны проектирования. Часть 1
Введение в паттерны проектированияПаттерн SingletonПаттерн FactoryПаттерн BuilderПаттерн ObserverПаттерн Strategy3Свойства класса и статические члены
Переменные экземпляра и статические переменныеСтатические методыСтатические блокиКонстанты (static final)Итоги: Счетчик и утилиты6Интерфейсы и абстрактные классы
Введение в интерфейсыРеализация интерфейсовРеализация нескольких интерфейсовDefault и Static методы в интерфейсахАбстрактные классы vs ИнтерфейсыФункциональные интерфейсыИтоги — Платежная система9Обобщения
Введение в обобщенияОбобщенные классыОбобщенные методыОграниченные параметры типаСимволы подстановки (Wildcards: ?, extends, super)Итоги — Обобщенный контейнер12Паттерны проектирования. Часть 2
Паттерн «Команда»Паттерн «Адаптер»Паттерн «Декоратор»Паттерн «Шаблонный метод»Паттерн «Состояние»Паттерн «Компоновщик»Паттерн «Итератор»Потренируйтесь самостоятельно: Онлайн-компилятор Java