JavaRush /Курсы /Java Collections /Generics: Class<T>

Generics: Class<T>

Java Collections
5 уровень , 7 лекция
Открыта

— Привет! Я продолжу лекцию Элли про Generic’и. Готов слушать?

— Ага.

— Тогда начинаем.

Факт первый. У методов класса тоже могут быть свои типы-параметры.

— Да, я знаю.

— Нет, я имею ввиду именно свои типы-параметры:

Пример
class Calculator
{
  T add(T a, T b); //сложить
  T sub(T a, T b); //отнять
  T mul(T a, T b); //умножить
  T div(T a, T b); //делить
}

Это типы-параметры именно метода(ов). У класса параметров нет. Можно даже объявить методы статическими и вызывать их без использования объекта.

— Ясно. Смысл типов-параметров в методах такой же, как и в классах?

— Ага. Но есть и кое-что новое.

Как ты уже знаешь, в описании типа можно использовать wildcard. Тогда представь себе ситуацию:

Пример 1
public void doSomething(List<? extends MyClass> list) 
{
 for(MyClass object : list)
 { 
  System.out.println(object.getState()); //тут все работает отлично.
 }
}

А вот, что случится, если мы захотим добавить в коллекцию новый элемент:

Пример 2
public void doSomething(List<? extends MyClass> list) 
{
 list.add(new MyClass()); //ошибка!
}

Дело в том, что в общем случае в метод doSomething можно передать List с типом элементов не MyClass, а любой из наследников MyClass. А в такой список заносить объекты MyClass уже нельзя!

— Ага. И что же делать?

— Ничего. Прямо в этой ситуации – ничего не сделаешь. Но это дало разработчикам Java повод для размышлений. И они придумали новое ключевое слово – super.

Выглядит его использование практически так же:

List<? super MyClass> list

Но между extends и super есть существенное различие.

«? extends T» обозначает, что класс должен быть наследником T.

«? super T» обозначает, что класс должен быть предком T.

— Ух ты. А где это используется?

— «? super T» используется, когда метод собирается добавлять в коллекцию объект типа T. Тогда это может быть коллекция типа T или любого типа-предка T.

— Ага. Ссылку на объект типа T можно же присвоить любому родительскому типу для T.

— Честно говоря – этот подход используется не очень часто. Тем более, что у него есть и обратная сторона. Пример:

Примеры
public void doSomething(List<? super MyClass> list)
{
 for(MyClass object : list) //ошибка!
 { 
  System.out.println(object.getState()); 
 }
}
public void doSomething(List<? super MyClass> list)
{
 list.add(new MyClass()); //тут все работает отлично.
}

Теперь не работает первый пример.

Т.к. коллекция list может быть даже List<Object> (Object самый верхний родитель MyClass), то фактически мы пишем такой код, а так писать нельзя:

Пример 1
List<Object> list; 

for(MyClass object : list) //ошибка!
{ 
 System.out.println(object.getState()); 
}

— Ясно. Спасибо за интересную лекцию.

— Пожалуйста.

Комментарии (83)
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ
18 апреля 2025
PECS? Не, не слышали.
Anonymous #2798867 Уровень 43
1 августа 2025
Это прекрасно!
Андрей Уровень 51
14 мая 2024
Дело в том, что в общем случае в метод doSomething можно передать List с типом элементов не MyClass, а любой из наследников MyClass. А в такой список заносить объекты MyClass уже нельзя! Вот эта фраза может привести в заблуждение. На самом деле: 1. Подразумевается, что в метод может быть передана коллекция элементов MyClass или его потомков. Но мы не знаем заранее, какая точно. Например, завтра может быть создан новый потомок MyClass, и в метод будет передаваться коллекция с этим новым потомком. 2. Это значит, что мы не можем добавлять в коллекцию ничего, кроме null Если непонятно (скорей всего непонятно)), гуглите и читайте про принцип PECS
Кот Уровень 39
20 июня 2024
Не согласен. 1. Здесь сказано, что мы можем добавить в список только НАСЛЕДНИКОВ класса Myclass, а объект MyClass добавить не можем. Например есть класс описывающий машину, скажем Лада-Веста:

public class LadaVesta {}
И тут на заводе создали модификацию Лады-Веста:

public class LadaVestaModified extends LadaVesta {}
И если у нас есть такой метод:

public void addCar(List<? extends LadaVesta> list)
{
 list.add(new LadaVesta()); //получаем ошибку, т.к мы можем добавлять только наследников LadaVesta. 
}
Андрей Уровень 51
20 июня 2024
Спасибо за коммент. Я случайно вместо "потомков" написал "предков". Но в твоем сообщении ошибка, о которой я и говорил. Смотри, если мы создадим еще один класс-потомок:

public class LadaVestaModifiedAutomatic extends LadaVestaModified{}
А затем вызовем метод для коллекции из этих машин:

public static void main(String[] args){
addCar(new ArrayList<LadaVestaModifiedAutomatic>());
}
public static void addCar(List<? extends LadaVesta> list)
{
 list.add(new LadaVestaModified()); // так нельзя делать, потому что переданная коллекция хранит LadaVestaModifiedAutomatic
}
Поэтому поместить внутрь нее мы можем только null.
Gans Electro Уровень 5
28 декабря 2023
GPT: Давайте разберемся более подробно. Когда вы используете List<? extends MyClass>, вы говорите компилятору, что ваш список содержит элементы, которые являются подтипами MyClass (или MyClass сам). Допустим, у вас есть следующие классы:

class MyClass {}
class MySubClass extends MyClass {}
Теперь предположим, у вас есть метод:

public void addToMyList(List<? extends MyClass> myList) {
    // Что произойдет, если вы попытаетесь добавить объект MyClass в myList?
    // myList.add(new MyClass()); // Ошибка компиляции
}
В методе addToMyList вы не можете безопасно добавить объект MyClass в myList. Почему? Потому что myList может быть фактически экземпляром List<MySubClass>, и если бы компилятор разрешал вам добавлять MyClass в такой список, это могло бы привести к проблемам. Например:

List<MySubClass> mySubList = new ArrayList<>();
addToMyList(mySubList); // Прошло бы без ошибок (так как MySubClass - подтип MyClass)
//Теперь myList содержит MySubClass, но попробуем добавить MyClass myList.add(new MyClass()); // Была бы ошибка времени выполнения, так как myList - фактически MySubClass Таким образом, wildcard с ограничением сверху гарантирует только возможность чтения элементов из списка, но не добавления в него. Если вы хотите иметь возможность добавлять элементы в список, вы можете использовать wildcard с ограничением снизу (List<? super MyClass>) или просто указать точный тип (List<MyClass>).
Алексей Барищук Уровень 45 Expert
28 мая 2024
как настроить этот чат?
Gans Electro Уровень 5
30 мая 2024
Там ничего сложного, можно зарегаться через гугл акк
Maxim B Уровень 2
1 декабря 2023
Название 7 и 9 лекций перепутаны местами
Serhii Diakonov Уровень 43 Expert
24 апреля 2023
мучал я GPT по поводу вопроса в чем же разница параметров (List<? extends MyClass> list) и (List<T extends MyClass> list). по сути это одно и тоже, что ? что Т обе условные неизвестные переменные для типа который там будет. ничего внятного ГПТ не ответил кроме этого: оба варианта (использование параметра List<? extends MyClass> list и List<T extends MyClass> list) предоставляют ограничения на типы элементов списка, которые могут быть переданы в качестве аргумента, и на то, что можно сделать с элементами списка внутри метода. Разница заключается в том, что List<? extends MyClass> list позволяет только чтение элементов списка, а List<T extends MyClass> list позволяет чтение и запись элементов в список, при этом гарантируя, что элементы списка будут совместимы с типом T, который является подклассом MyClass.
Gans Electro Уровень 5
13 декабря 2023
T это только один определенный тип. Например если определить класс животных (List<T extends Animal> list) и потом передать в него список котов List<Cat extends Animal> то только с котами и можно работать, а если передать список собак то работать с собаками. А в (List<? extends MyClass> list) можно передать список в котором всякие различные животные с общим предком
Adm Уровень 49
6 апреля 2024
Вы не сможете создать List<T extends Animal> list = new ArrayList<>(); // Компилятор поругает Только так List<? extends Animal> list = new ArrayList<>();
Adm Уровень 49
6 апреля 2024

public void addToMyList(List<T extends MyClass> myList) {
// так тоже нельзя
// можно только "? extends MyClass"
}
Константин Уровень 51
23 февраля 2023
Если List<? extends MyClass> включает в себя MyClass и всех потомков, то каждый тип ниже можно расширить до MyClass и у всех будут методы MyClass. Это понятно. List<? super MyClass> включает MyClass и все вышестоящие классы до Object. Тогда любому вышестоящему можно присвоить MyClass, поэтому можно в такой список добавлять объекты. Но гарантировать можно только то, что у всех классов по цепочке будут только методы Object, а не MyClass, поэтому методы MyClass вызывать нельзя. Это тоже понятно Но объясните, что мешает при List<? extends MyClass> добавлять объекты MyClass? Это выглядит как добавить животного в список животных. В чём проблема?
BucketOnHead Уровень 51 Expert
31 марта 2023
При использовании List<? extends MyClass> нельзя добавлять объекты, потому что компилятор не может гарантировать, что тип объекта, который вы пытаетесь добавить, точно соответствует типу, указанному в списке. Например, если у вас есть List<? extends Number>, то вы можете получить список List<Integer> или List<Double>, но не можете точно знать, какой из этих типов будет на самом деле использоваться. Если вы попытаетесь добавить объект типа Number или Integer, когда на самом деле используется List<Byte>, возникает логичная ошибка 🙂
Anonymous #3127078 Уровень 47
9 февраля 2023
Тема достаточно сложна для понимания, кто хочет подробно разобраться - в ЭТОМ ВИДЕО все очень подробно объяснили (если перестанет работать - ищи: "Рекурсивное расширение типа - Generics #3 - Advanced Java")
TemaCode Уровень 51
21 января 2023
иногда читаешь вот статью и ощущение что просто взяли переводчик и слябзили где то статью 😀
Denis Rogov Уровень 1 Expert
27 декабря 2022
То ли я тупой, то ли я тупой. Old > Parent > Child (Олд родитель Пэрент, Пэрент родитель Чайлда).

    public static void addIn(List<? extends Old> list) {
        list.add(new Parent());
        list.add(new Child());
    }
Ошибка до запуска кода.
Внимание! При этом:


    public static void addIn(List<? super Old> list) {
        list.add(new Parent());
        list.add(new Child());
    }
Ошибки нет.
Как так получается, что Parent и Child я не могу добавить, при условии "классы, расширяемые Old", а при параметрамх-типах "классы, предки Old" я могу их добавить, при этом напоминаю дерево наследования Old > Parent > Child (Олд родитель Пэрент, Пэрент родитель Чайлда).
Denis Rogov Уровень 1 Expert
27 декабря 2022
Я понял почему так. И помог мне Комментарий Евгения из Алматы из комментария к задаче https://javarush.com/users/2207781 Короче пасаны. Дело в том, что параметры метода показывают лишь какие List'ы мы сможем туда засунуть, с какими <типами-параметрами>. И эти типы параметры в самих параметрах метода немного путают, почему мы не можем добавлять элементы, которые, казалось бы входят в категорию <? extends Old>. Можно представить это так: Представьте, что в самом методе (внутри) java как бы заранее обрабатывает исключение, не допускает на стадии запуска кода произвести баг. Итак, почему же мы не можем в методе с параметром

public static void addIn(List<? extends Old> list)
не можем добавить, к примеру,

new Parent()
Представьте, что вы в Параметр метода передали объект

List<Child>
что соответствует условиям параметра для метода, т.к. Child является потомком(расширяет) класса Old. Но в самом методе (внутри) вы начинаете добавлять

new Parent()
Тоесть вы в

List<Child>
добавляете

new Parent()
что приводит к ошибке. Т.к. Child extends Parent, а не наоборот. Со вторым случаем, я думаю, уже понятнее. В методе

public static void addIn(List<? super Old> list)
мы принимаем List с любыми родителями Old и его самого тоже, но внутри самого метода мы можем добавлять либо

new Parent()
либо тех кто наследуется от него

new Child()
Denis Rogov Уровень 1 Expert
27 декабря 2022
т.к. если вы в этот методе передали

List<Old> 
а внутри метода начали добавлять

new Object()
java надерёт вам жопку. Я попытался объяснить, т.к. из моей головы это не выходило. А помог мне, как я уже сказал, этот комментарий:
Anonymous #3091828 Уровень 51
27 ноября 2022
логика следующая: в родителя мы можем наследника, а вот наследника в родителя уже нет.