Титул & Здравствуйте, добро пожаловать на курс посвящённый инструментарию разработчика на джава \\\hline
Отбивка & в сегодняшней лекции коснёмся одного из ключевых понятий программирования на языке джава -- обобщённого программирования. \\\hline
На прошлом уроке &На прошлом уроке мы поговорили об интерфейсах, рассмотрели понятие и принцип работы, поговорили о ключевом слове implements; коротко посмотрели на наследование и множественное наследование интерфейсов, реализацию, значения по умолчанию и частичная реализацию интерфейсов. Буквально два слова я сказал о функциональных интерфейсах и чуть подробнее об анонимных классах. \\\hline
На этом уроке &На этом уроке поговорим о более крутом полиморфизме, чем интерфейсы. Казалось бы, куда уж круче, но сегодня на повестке обобщения или если у вас нет отторжения от неизбежных в разработке англицизмов -- дженерики. Сначала попробуем обойтись без них, потом сделаем тоже самое с ними, сравним. Обсудим работу дженерик интерфейсов, Обобщённые методы и подстановочные символы, а именно, ограничения сверху и снизу, дополнительно проговорим о таком явлении как загрязнение кучи. \\\hline
08-02 & Может показаться, что мы снова будем как-то всесторонне рассматривать интерфейсы и абстрактные классы, отсутствие реализаций и всё такое, но нет. Обобщения это не про отсутствие реализации, а про создание некоторого общего подхода к реализации одинаковых алгоритмов, но с разными данными. Запутал? Конечно! подойдём с другой стороны. Обобщения -- это некие конструкции, позволяющие работать данными не задумываясь что там именно лежит внутри структуры или метода (строки, числа или коты). То есть обобщения позволяют единообразно работать с разными типами данных. Можно сказать, что обобщения -- это некий поклон в сторону динамической типизации. Попробуем начать разбираться на примере. \\\hline
08-03 & Как было сказано, обобщения позволяют единообразно работать с разными типами данных, поэтому создадим некоторый класс, который сможет хранить внутри любые данные джава. А любые данные в джава, как мы помним -- это объект. В таком случае необходимо создать класс Box, у которого будет единственное поле типа Object, в которое можно будет записать объект абсолютно любого типа. Далее, для начала, на примере чисел, напишем в метод мейне создадим два экземпляра и положим туда пару чисел. Надо бы например научиться складывать их. Пишем b1.getObj() + b2.getObj(). Как думаете, что там за ошибка скрывается за красным подчёркиванием среды разработки? там предупреждение среды о том, что оператор сложения не определён для объектов. Проблема в том, что я знаю, что тут лежат целые числа, а джава не особо. Чего не хватает? правильно, приведения типов к Integer.
%% 2
можем создать пару других коробок со строками, и будет производиться уже не складывание чисел, а конкатенация строк. Проблема в том что при каждом вытаскивании данных из коробки надо делать приведение типов, чтобы указать какие именно там лежат данные. Можно попробовать эту проблему решить если мы например назовём нашу переменную в венгерской нотации, тогда при использовании спустя пятьдесят или сто строк мы будем видеть что переменная вроде как интовая или строковая.
%% 3
Кроме постоянных перепроверок и кастов есть вторая проблема, она состоит в том, что мы, естественно, можем положить в первую коробку что угодно, например, строку и вполне ожидаемо получим ClassCastException. Допустим кто-то не понял мысль что ibox это коробка с интом, и хочет положить туда строку. Джава никак не запретит этого потому что строка это точно такой же равноправный объект. Как быть? первое, что приходит на ум - это try-catch но если можно какую-то ситуацию исправить ифом то лучше защититься ифом, конечно, получим примерно такой совсем не громоздкий код, как на слайде. Потому что перед строками с приведением типов желательно делать проверку на эти самые типы. Третья, самая неприятная проблема состоит в том, что все проблемы подобного рода проявляют себя только в рантайме, то есть у конечного пользователя перед глазами, когда мы уже никак это исправить не можем. \\\hline
08-04 &К чему мы в итоге пришли? к тому что в принципе возможно создавать классы, которые могут работать с любыми типами данных, но при любом обращении к таким классам нам неплохо бы делать достаточно сложные проверки, чтобы каждый раз не вываливаться с исключениями. До появления дженериков в принципе так и делалось. Таким образом мы совершенно логично подошли к дженерикам, которые должны решить часть наших проблем. Обобщения появились в джава 1.5. Дженерики или обобщения -- это особые средства языка Java для реализации обобщённого или можно встретить термин шаблонизированного программирования: особого подхода к описанию данных и алгоритмов, позволяющего работать с различными типами данных без изменения внешнего описания. Обобщения добавляют в Java безопасность типов и делают управление проще. Исключается необходимость применять явные приведения типов, так как благодаря обобщениям все приведения выполняются неявно, в автоматическом режиме, платформой джава. Все дженерики работают только со ссылочными типами данных но это не проблема потому что для всех примитивов есть обёртки в виде ссылочного типа.
% 02
допустим пишем всеми любимые аркадные стрелялки. у нас есть корабль, враги, наши пули, пули врагов, просто какие-то летающие помехи, влияющие на здоровье. За минуту активной игры может насоздаваться до полутысячи разных объектов и каждый раз их отдавать на сборку мусора - удовольствие сомнительное, потому что сборка это очень дорогостоящий процесс, поэтому принято создавать так называемые пулы объектов, в которых лежит 300 пуль, десяток врагов, полсотни камней, и в этом пуле объекты просто переключаются из активного в неактивное состояние. А класс непосредственно игрового объекта наследует поведение дженерика, чтобы не описывать его каждый раз, то есть содержит основную общую для всех объектов логику -- достать из пула объект как будто он новый, положить в пул использованный объект, отработать столкновение объекта с другим объектом, подвигать объект по координатной сетке, и так далее.
08-05 & Чтобы понять почему больше не надо делать явное приведение типов, и откуда берется какая-то безопасность, напишем в треугольных скобках букву <T>. в принципе, там может быть любая буква, или даже нескольно, но принято писать именно T, чтобы обозначить Type, Тип. Мы не можем на этапе описания класса сказать что это будет за тип (число, строка или кот), но точно знаем что какой-то тип будет. пишем поле этого типа, геттер-сеттер-конструктор. Соответственно если мы напишем Т не в треугольных скобках при описании класса, то джава будет думать что это какой то реально существующий класс, который она просто не видит. А так мы намекаем, что это обобщение и тип будет задаваться при создании объекта. Естественно поменять его будет нельзя потому что джава это язык сильной статической типизации.
% 2
При вызове конструктора, обратите внимание, будет указано, что конструктор ждёт инт, где же Т? а вот при указании типа в левой части, этот тип подставился во все места класса, где компилятор увидел букву Т. При получении значений из integerBox и stringBox не требуется преобразование типов, integerBox.getValue() сразу возвращает Integer, а stringBox.getValue() - String. То есть приведение типов выполняется неявно и автоматически.
Первая проблема решена.
% 3
Если объект создан как Integer, то мы не сможем записать в него строку. При попытке написать такую строку кода, получится ошибка на этапе компиляции. То есть обобщения отслеживают корректность используемых типов данных. Попробуем положить в одну из коробок строку -- компилятор не даёт нам это сделать. Вторая проблема -- проверка на инстансоф и третья -- допущение и выбрасывание ошибок в рантайме -- решились сами собой. Ну и на самом деле несмотря на то что мы пишем все вот эти красивые скобки и не делаем проверки -- внутри самой джавы это всё равно превратится в те же самые приведения и проверки, которые мы только что закомментировали и от которых отказались. \\\hline
08-06 & По соглашению переменные типа именуются одной буквой в верхнем регистре. Это сильно отличается от соглашения об именовании переменных, классов и интерфейсов. Без такого отличия было бы трудно отличить переменную типа от класса и интерфейса. Буквы в фигурных скобках - их там совершенно неожиданно может быть больше одной, разделённых запятой. По конвенциям буквы обычно пишутся первые от английского слова, лучше всего обозначающего то, что там внутри -- T = type, E = entity (element), K, V = key, value, N = numeric
Но всё таки можете ставить такие, какие Вам нравятся и даже писать многобуквенные обозначения параметра, но должен предупредить, что это достаточно грубое нарушение стиля написания кода. Линейкой по пальцам никто бить не станет, но и всерьёз рассматривать к себе в команду скорее всего не будут. \\\hline
08-07 & Вроде всё так классно, что аж не хочется переключаться на этот слайд, но надо. Дженерики, конечно, хороши, но всё же накладывают на работу с собой некоторые ограничения. Как думаете, что произойдёт, если я попытаюсь написать метод номер один внутри нашего дженерика?
А что надо написать в теле метода, чтобы создался объект класса Т? правильный ответ -- ничего. !Ограничение дженерика в том, что невозможно внутри дженерика создать экземпляр параметризующего класса Т!, потому что на этапе компиляции мы об этом классе вообще ничего не знаем, из чего он будет состоять, какие у него будут конструкторы и будут ли конструкторы. Ведь даже конструктора без аргументов там может не быть (как известно, если есть другие то пустой не создаётся). Это ограничение возможно обойти, используя паттерн абстрактная фабрика, но это совсем другая история.
Также нельзя создавать внутри дженерика массив из обобщённого типа данных. Но это тоже не так уж критично потому что всегда можно подать такой массив снаружи.
А можем ли мы создать статическое поле типа Т? Конечно же нет, всё по той же причине -- конкретный тип для параметра Т мы узнаём только при создании *объекта* дженерика, то есть у нас получается слишком много противоречий, чтобы понять какое значение будет у статического поля.
08-08 & Посмотрим, на этот раз, внимательно, на то как принято работать с объектами обобщённых классов. При обращении к обобщённому классу нужно заменить параметры типа на конкретные классы или интерфейсы, например строку, целое число или кота. Может показаться, что обращение к параметризованному типу похоже на обычный вызов метода, но вместо передачи аргумента в метод в круглых скобках, передается аргумент типа в треугольных, например Integer или Стринг в класс GBox, в описанном нами случае.
Параметр типа и аргумент типа -- это два разных понятия, как и при вызове методов, помните, там было различие, кто параметр, а кто аргумент метода? Когда объявляется обобщённый тип GBox<T>, то T является параметром типа, а когда происходит обращение к обобщённому типу, передается аргумент типа, например Integer. Это довольно похоже на различие в методах, поэтому будет несложно запомнить.
Как и любое другое объявление переменной просто GBox<Integer> integerBox сам по себеНЕ создаёт экземпляр класса GBox. Такой код просто объявляет идентифиактор GBox, но сразу уточняет, что это будет коробка с целыми числами. Такой уже созданный идентификатор обычно называется параметризованным типом. То есть, получается, что мы уже знаем примитивные типы, ссылочные типы, интерфейсные типы, а теперь ещё и параметризированные. Интересно.
Чтобы создать экземпляр класса, используется ключевое слово new, как обычно, и в дополнение указывается, что создаётся не просто GBox, а обобщённый, поэтому пишется <Integer> в треугольных скобках между именем вызываемого конструктора и скобками с параметрами этого конструктора. Но компиляторы почти сразу, а именно, с седьмой версии, оказались достаточно умными, чтобы понять, какой именно тип нужно подставить в треугольные скобки (по-умному это называется выведением типа из контекста), поэтому, если тип совпадает с аргументом типа в идентификаторе, в скобках экземпляра его можно не писать, как на 43й строке. Это называется бриллиантовый оператор. После создания экземпляра становится возможно обращаться к методам, тут всё как обычно.
Дополнительно обращаю ваше внимание на то, что в описанном нами случае невозможно создать экземпляр без начального значения, это привычное поведение, потому что при создании класса GBox был создан конструктор, принимающий в качестве параметра значение, а значит конструктор по-умолчанию перестал создаваться неявно. \\\hline
08-09 & Никаких ограничений, кроме здравого смысла, на количество параметризированных типов не накладывается. Часто можно встретить обобщения с двумя типами, например, в коллекциях, хранящих пары ключ-значение. Там параметризированные типы так и называются ки и вэлью. Достаточно написать список из нужных внутри класса типов через запятую. Также, как нет ограничений на использование типов внутри угловых скобок, кроме упомянутого выше, что типы должны быть ссылочными. На слайде видно, что использование только что написанного обобщённого GBox вполне допустимо. Единственное, за чем, на мой личный взгляд, важно следить, это чтобы код оставался читаемым и не пестрел множественными треугольниками скобок. \\\hline
08-10 & Сырой тип -- это имя обобщённого класса или интерфейса без аргументов типа, то есть это, фактически, написание идентификатора и вызов конструктора обобщённого класса как обычного, без треугольных скобок. Можно часто увидеть использование сырых типов в старом коде, поскольку многие классы, например из фреймворка коллекций, до Java 5 были необобщёнными, как вы помните, обобщения появились только в пятой джаве, а люди умудрялись программировать и до этого эпохального события. Когда используются сырые типы, по сути получается то же самое поведение, которое было до введения обобщений в Java. Как видно на первом снимке экрана -- метод, возвращающий объект из коробки, возвращает объект.
Это довольно логично, потому что никто не указал ни идентификатору ни конструктору, что именно планируется в эту коробку складывать. Ситуация меняется, если указать тип, мы это уже видели, не лишним будет посмотреть ещё раз на втором снимке экрана.
Поэтому GBox -- это сырой тип обобщённого типа GBox<T>. Однако необобщённый класс или интерфейс НЕ являются сырыми типами. Для совместимости со старым кодом допустимо присваивать параметризованный тип своему собственному сырому типу, как это сделано на третьем снимке экрана. Компилятор, конечно сразу начинает довольно интенсивно сигнализировать о том, что наш код не совсем современно написан, но в целях демонстрации я думаю, он потерпит.
Также, если попытаться присвоить параметризованному типу сырой тип, то буквально каждое слово в программе будет с предупреждением, или если попытаться вызвать обобщённый метод в сыром типе. Предупреждения показывают, что сырой тип обходит проверку обобщённого типа, что откладывает обнаружение ошибки на время выполнения программы. Поэтому, если в программе видим много предупреждений среды разработки -- перестаём работать с сырыми типами, а если вдруг всё-таки нужно именно сырой тип, и сыпятся какие-то ошибки, например, невозможность использования каких-то присущих только обобщённым типам методов, то, соответственно, приводим типы \\\hline
08-11 & Как уже упоминалось, при использовании сырого типа можно столкнуться с предупреждениями среды, которые на самом деле предупреждения компилятора, и обычно имеют вид, представленный в верхней части слайда. Термин "unchecked" означает непроверенные, то есть компилятор не имеет достаточного количества информации для обеспечения безопасности типов. По умолчанию этот вид предупреждений выключен, поэтому компилятор в терминале на самом деле даёт подсказку. Чтобы увидеть все "unchecked" предупреждения нужно перекомпилировать код с опцией -Xlint:unchecked. Конечно, среды проектирования обо многом предупреждают, иногда даже о большем, чем может предупредить компилятор, но часто разработчики на эти предупреждения не обращают внимания, особенно когда проект большой и предупреждения говорят о вещах, которые действительно нельзя изменить. Поэтому, я считаю, что знать о разных способах сделать свой код лучше и надёжнее -- полезно, и ручная компиляция в терминале -- один из них. Конечно, это не значит, что всем нужно обязательно закрыть среду разработки и с этих пор работать только в терминале. Мои слова означают только то, что к написанному Вами коду нужно относиться внимательно и придирчиво его перепроверять. \\\hline
08-12 & Обобщённые методы похожи на обобщённые классы, но параметры типа относятся к методу, а не к классу. Допустимо делать обобщёнными статические и нестатические методы, а также конструкторы \\\hline
08-13 & Синтаксис обобщённого метода включает параметры типа внутри угловых скобок, которые указываются перед возвращаемым типом. На слайде видим метод, который работает с созданной нами коробкой. Здесь, также, как и при описании класса необходимо указать Т в угловых скобках с единственной целью -- показать компилятору, что Т, которые он найдёт далее в параметрах и теле метода -- это не класс Т, а обобщённый параметр. Если Т перед возвращаемым значением не написать -- будет ошибка компиляции, и подсказка, говорящая о невозможности найти класс Т. \\\hline
08-14 & Пусть вас это не сбивает с толку, мы вроде бы только что начали говорить про обобщённые методы, но чтобы продолжить я думаю, нам нужно немного синхронизироваться, по традиции, я прошу вас ответить на несколько вопросов. Обобщения – это способ описания или можно ещё сказать создания общих 1. классов для разных пакетов; 2. алгоритмов для разных типов данных; 3. библиотек для разных приложений.
Конечно же это способ создания единообразно хранимых и используемых алгоритмов для разных типов данных, только что мы описывали алгоритм хранения всего чего угодно в коробке.
Что именно значит буква в угловых скобках? 1. название обобщённого класса 2. имя класса, с которым будет работать обобщение 3. название параметра, используемого в обобщении
Верный ответ третий, никакое это не имя класса и не название обобщения, это, можно сказать, заполнитель, плейсхолдер, который будет использоваться в обобщении для того, чтобы вместо него подставилось имя класса, переданное в аргументе.
очевидно, третий вариант отпадает, поскольку это оксюморон, а верный вариант ответа -- нет. нельзя передавать в угловых скобках примитивы. Многие причины, по которым этого нельзя делать, станут очевидны из дальнейшего повествования, не переключайтесь. \\\hline
08-15 & Передавать один или несколько типов данных через запятую -- это интересно и забавно, но что если нам нужно, чтобы в обобщённом аргументе было возможно использовать только строго определённые типы данных? О, я понимаю, как это звучит, я только что развязал вам руки и сказал что вы не особенно то ограничены строгостью типов, и сразу же говорю, что надо что-то там ограничивать. Конечно надо! В качестве простого примера можно взять нашу пресловутую коробку и сказать, что теперь она может не только хранить в себе какие-то данные, но и, скажем, складывать их. И если со сложением чисел и конкатенацией строк проблем не должно быть, то как складывать котиков или потоки ввода-вывода -- понятно становится не сразу. Применение bounded type parameters позволяет более точно задавать ограничения на используемые типы данных, что помогает писать более безопасный и читаемый код. Вместо того, чтобы допускать все возможные типы, которые могут быть переданы в параметры метода или класса, можно ограничиться только теми, которые соответствуют определенным требованиям. Именно поэтому мы говорим, что в некоторых случаях имеет смысл ограничить типы, которые можно использовать в качестве аргументов в параметризованных типах.
Например, в коробке предлагается сделать так, чтобы в ней хранились только числа и прочие интересные наследники класса Number. Подобное ограничение можно сделать с помощью ограниченного параметра типа (bounded type parameters). Для тех, кому лень смотреть в англо-русский словарь, напомню, что намбэ -- это и есть число. Разве что ограничение переведено более неоднозначно, параметр типа у нас получается связанным. видимо, по рукам и ногам, не меньше.
Чтобы объявить ограниченный сверху параметр типа нужно после имени параметра указать ключевое слово extends, а затем указать верхнюю границу (upper bound), которой в данном примере является класс Number. В случае, который можно наблюдать на слайде, в коробку можно положить всё, что намбэ и всё, что так или иначе может быть сведено к нему, то есть его самого и все его частные случаи.
Опять же, написанное достаточно точно и легко переводится с английского на русский -- коробка, в которую можно положить данные какого-то типа, являющиеся числом или расширяющего возможности числа.
В примере, который мы рассматриваем, типы, которые можно использовать в параметризованных классах Box, ограничены наследниками класса Number. Если попытаться указать, например, Box<String>, то возникнет ошибка компиляции. Аналогичным образом можно создавать обобщённые методы с ограничением, тогда компилятор, кажется, вообще хочет сказать, в каком мире строка это наследник числа? Также возможно задать несколько границ. при этом, важно помнить, что здесь как и в объявлении классов, может быть только один класс-наследник и он указывается первым, все остальные границы могут быть только интерфейсами и указываются через знак амерсанд, то есть возможно указать что наследование будет от классов и интерфейсов, например, <N extends Number \& Serializable \& Clonable \& Comparable> и т д. Непривычно то, что несмотря на то что Number или как на слайде -- птица -- это класс, а Serializable или на слайде Man и Animal это интерфейс -- мы всё равно пишем extends (спасибо уважаемые разработчики джавы за отсутствие путаницы, да). само собой, на всякий случай, повторю ещё раз, если унаследоваться в угловых скобках от двух классов, а не от интерфейсов -- будет ошибка. Множественное наследование запрещено во всей джаве без исключений. \\\hline
08-16 & Как мы уже успели неоднократно убедиться, возможно присвоить объекту одного типа объект другого типа, если эти типы совместимы. Например, можно присвоить объект типа Integer переменной типа Object, так как Object является одним из супертипов Integer. В объектно-ориентированной терминологии это называется связью «является» (“is a”). Собственно можно просто перевести словосочетание «целое число является объектом» с русского на английский и получить корректное объектно-ориентированное утверждение -- integer is an object.
Так как Integer является Object-ом, то такое присвоение разрешено. Но Integer также является и Number, поэтому код, в котором инты или даблы передаются в качестве аргументов метода тоже корректен. Такое поведение, как было показано несколькими слайдами выше, верно для обобщений.
То есть, ещё раз, предположим, что существует метод, описанный без каких-то вайлдкардов, но с дженерик параметром. Какой тип аргумента он будет принимать? Если посмотреть на сигнатуру, то можно увидеть, что он принимает один аргумент с типом GBox<Number>. Получается, что метод очень хороший. так ли это? Можно ли передать Box<Integer> или Box<Double> , как можно можно было бы ожидать? Нет, нельзя, так как Box<Integer> и Box<Double> не являются потомками Box<Number> всё ломается... странно. почему это выглядит так странно? Это частое недопонимание принципов работы обобщений, и это важно знать. Наследование не работает в дженериках так, как оно работает в обычной джаве. Коробка с Number не может в себе хранить коробку с Integer. Дженерик нас защищает от того, чтобы мы попытались положить в коробку строк - не строку. То есть мы, допустим, кладём туда инт, как наследника Number, а потом бац и внезапно флоут. Получится путаница. так что именно таким образом наследоваться не получится.
Из этого можем сделать вывод что если мы нашему методу дадим коробку из интов -- он не будет работать. Поэтому в аргументе надо указать что это будет любой тип, но наследник Number, то есть использовать маску <вопросительный знак extends Number>. Ограничивать маску можно как сверху так и снизу, ограничение сверху это extends - то есть класс и его наследники, или <? super Number> - то есть класс и его родители (в случае с намбером только обжект). таким образом, дженерики защищают нас и самих себя от путаницы и не дают складывать в одни и те же контейнеры разные типы данных. То есть внутри метода мы не можем положить в коробку конкретный тип, допустим, флоут. потому что дженерик думает «а что если у меня там инты будут, у меня ж тогда всё сломается?» и на всякий случай запрещает.
Поскольку коробку с чем то ещё кроме Number и его наследников мы создавать не можем, маскИрование при вызове метода будет избыточно, и можно просто указать private static void boxTest(GBox n) без параметра в угловых скобках. Маски обычно нужны когда мы хотим поставить какое то ограничение. в этой ситуации мы уже ограничили коробки числами при создании коробок.
Что мы в результате видим: мы вроде как создаём псевдо-динамическую типизацию... на самом деле никаких дженериков и уж тем более переменных типов данных в джаве не существует. Всё что мы видим это так называемый синтаксический сахар. То есть, когда вы используете дженерики, во время компиляции произойдёт так называемое стирание, и все эти обобщённые типы данных преобразуются в обжекты и соответствующие проверки и приведения типов. Зачем нам это знать? На будущее весьма пригодится, потому что если мы возьмём у нашего дженерика getClass.getName() - он будет просто типом, информация из треугольных скобок удалится и её не будет. То есть, ещё раз, сама по себе Java не знает ни о каких дженериках. Ну и из этого логично следует, что однажды указав что-то в треугольных скобках, мы не сможем это дело поменять на какой то другой тип данных \\\hline
08-17 & Я только что упомянул, что ограничивать типы можно как сверху, так и снизу. Что ж, никто меня за язык не тянул, надо придумать какой-нибудь хороший пример, где без таких ограничений всё будет печально ломаться. Поговорим на примере дженерик методов. Зачем в принципе нужны такие методы? когда нужно объединить несколько похожих, но всё же разных типов данных. Как вы думаете, вот мы подаём на вход два типа - инт и флоат. какой будет выбран в итоге для работы внутри метода? *правильно, ближайший старший для них обоих - Number*. и виртуальная машина не возьмёт объект потому что объект не является наследником Number'a. как думаете, зачем такие сложности? ведь можно просто подать на вход два Number'a и не выдумывать.
С ходу сложно придумать какой то пример, но давайте попробуем решить такую задачу: даны два списка - один с интами другой с намберами, и нам очень захотелось написать метод, который будет перекидывать из одного списка числа в другой. опишем для этого несложный метод, пока что не обращайте внимание на наличие тут коллекции, она нужна чтобы не описывать скучные методы хранения, добавления и прочие. есть два сценария - копировать намберы в инты и инты в намберы, как думаете, оба ли эти варианта окажутся рабочими, и если не оба, то какой будет правильным?
Классно, если сразу быстро догадались, правильный рабочий вариант -- второй, копировать более точный тип в более общий. заодно, оцените, насколько хороший получился метод, как вы считаете? запускаем с копированием интов в намберы и наоборот. вроде всё хорошо и восьмая джава нас простит, приведя такие типы, но мы всё же попробуем её сломать.
опишем два класса: естественно, животное и наследник животного кот, какие вообще можно было написать другие классы непонятно. И создадим обобщённые списки на их основе. далее воспользуемся уже написанным методом копирования списков, мы же пока что предполагаем, что он хороший, надо бы в этом удостовериться. Давайте немного подольше посмотрим на этот код, он не сложный, в нём почти нет пугающих дженериков, всё должно быть хорошо и понятно, несложный метод, кот, животное, два списка, копирование, вывод на экран
то коту мы добавляем метод голос, опять же ничего сложного, просто метод в котором кот будет мяукать и раз уж джава утверждает что получившийся в результате слияния список отличный - вызовем у одного из товарищей из списка метод голос. Видим, что на этапе компиляции всё хорошо и джава не видит никаких проблем, запускаем код
а вот и проблемка, да? при попытке вызвать у более общего животного метод более частного кота у нас исключение невозможности приведения типов. оказывается энимал не так уж и легко может находиться в одном списке с котами, а значит, к нам на помощь спешат кто? верно, привет дженерики.
Мы вынуждены сказать, что метод будет работать только с каким-то одним типом <T> и списки будут <T> и в форе тоже будем перебирать Т. Напоминаю, что если не указать Т в объявлении метода то джава не поймёт что это обобщённый метод и будет искать какой то тип Т описанный в библиотеках и естественно его не найдёт. но тогда получается что всё, что мы можем -- это в список котов класть котов, а в список животных класть животных. Вернулись к тому с чего начали? но кот же наследник животного почему бы не класть в список животных котов? верно, значит нам надо сказать методу, что источником может быть кто то, кто тип или его наследники, а приёмником Тип или его родители, и пусть метод, виртуальная машина и другие механизмы уже сам разбираются, кто подходит под эти параметры. Соответственно, в методе мейн можно заметить, что копировать животных в котов не получится. Теперь неверные варианты будут отсекаться на этапе компиляции, и джава просто не даст нам написать как то не так. Честно говоря, не думаю, что это кому то может прям сильно пригодиться в ежедневной работе, а загуглить по мере возникновения задачи вроде бы несложно, но всё же знать в какую сторону гуглить -- было бы неплохо. \\\hline
08-18 & Выведение типов -- это возможность компилятора Java автоматически определять аргументы типа на основе контекста, чтобы вызов получился возможным. \\\hline
08-19 & Алгоритм выведения типов определяет типы аргументов, а также, если это применимо, тип, в который присваивается результат или в котором возвращается результат. Далее алгоритм пытается найти наиболее конкретный тип, который работает со всеми аргументами и подходит к данной ситуации. Это достаточно несложная автоматизация, о которой особенно задумываться не нужно, потому что это как раз механизм, помогающий программисту не думать. Допустим, мы добавили к коту реализацию интерфейса Serializable. и попробуем сделать что-то странное -- написать метод, который будет работать с одним и только одним типом данных Т. Как видим, никакие аргументы не связаны наследованием. В таком методе выведение типов определяет, что вторые аргументы метода пик, а именно Cat и ArrayList, передаваемые в методы pick имеют тип Сериалайзабл, но этого недостаточно, потому что первый аргумент тоже должен быть того же типа. Внезапно оказывается, что строка -- это тоже сериалайзабл и внутри метода наименьший общий делитель (в смысле наиточнейший общий тип) будет определён не как обжект, а как сериализуемое. \\\hline
Выведение типов и обобщённые методы -- В описании обобщённых методов я уже говорил о выведении типов, которое делает возможным вызов обобщённого метода так, будто это обычный метод, без указания типа в угловых скобках. Можно долго и скучно писать очередной пример, смысл которого сведётся к тому, что вы видите на экране, есть коты, есть коробки, есть метод, который кладёт котов в коробки. Очевидно, что обобщённый метод addBox объявляет один параметр типа U. В большинстве случаев компилятор Java может вывести параметры типа вызова обобщённого метода, в результате чаще всего вовсе не обязательно их указывать. Например, чтобы вызвать обобщённый метод addBox, можно указать параметры типа как это указано на второй строке либо можно вовсе опустить их, и тогда компилятор Java автоматически выведет тип Cat из аргументов метода при вызове как на третьей строке.
Выведение типов и создание экземпляра обобщённого класса. Можно заменить аргументы типа, необходимые для вызова конструктора обобщённого класса пустым множеством параметров типа (пустые треугольные скобки, также называемые, напомню, бриллиантовой операцией), так как компилятор может вывести аргументы типа из контекста. Можно сказать, что это довольно привычная ситуация, потому что все примеры создания объектов, которые мы рассматриваем, включают в себя выведение типа конструктора и бриллиантовую операцию.
Выведение типа и обобщённые конструкторы обобщённых и необобщённых классов. Очевидно, что конструкторы могут быть обобщёнными как в обобщённых, так и в необобщённых классах. Вот, например, в этой ситуации мы можем явно указать что у коробки будет обобщённый аргумент Кот, ау конструктора -- вообще какой-то другой аргумент, например, строка или число. Компилятор выведет тип String для этого формального параметра U, так как фактически переданный аргумент является экземпляром класса String. Опять же, в джава 1.7 и выше предпочтительно явно использовать бриллиантовую операцию. Важно запомнить, что алгоритм выведения типа использует только аргументы вызова, целевые типы и возможно очевидный ожидаемый возвращаемый тип для выведения типов. Алгоритм выведения не использует последующий код программы. \\\hline
08-21 & Целевые типы. Целевой тип выражения -- это тип данных, который компилятор Java ожидает в зависимости от того, в каком месте находится выражение. \\\hline
08-22 & Компилятор Java пользуется целевыми типами для вывода параметров типа вызова обобщённого метода. Выражаясь проще -- ещё на шажок приближает нас к динамической типизации, позволяя не особенно думать, к чему приводить типы в методах дженерик классов. Например, как в методе Box.emptyBox() со сто пятнадцатой строки. В методе мейн на 121й строке инициализация ожидает экземпляр Box<String>. Этот тип данных является целевым типом. Поскольку метод emptyBox возвращает значение обобщённого типа Box<T>, компилятор сам сообразит, что аргумент T будет типом String. Это работает как в Java 7, так и в более поздних версиях. Конечно, можно указывать аргумент типа напрямую, но в данном случае в этом нет необходимости. \\\hline
08-23 & Перед тем, как продолжить, предлагаю немного расслабиться, припомнить о чём уже успели поговорить и ответить буквально на два несложных вопроса. Что из перечисленного является недопустимым? Слишком плохо будут восприниматься на слух варианты ответов, поэтому скажу, чем они отличаются -- в идентификаторе тип с ограничением по намбэ, в конструкторе намбэ, интеджер и стринг и есть хороший четвёртый вариант -- всё допустимо
Правильный ответ: 3. Первое допустимо по очевидной причине, число -- это точно число. Целое число является дочерним классом числа, поэтому (2) допустимо. Но String не является дочерним классом Number, поэтому (3) недопустимо.
Хорошо, предположим ситуацию немного сложнее, параметры метода ограничены с одной стороны для источника и с другой стороны для назначения, вызов метода вы также можете видеть на экране. Какой тип данных будет взят в качестве Т в параметре метода? Animal Cat или Object
как и было сказано ранее -- будет подобран такой тип, который будет подходить под эти ограничения, а поскольку никто не может быть супертипом объекта, а кот -- это не супертип животного, очевидно, в качестве Т будет взят энимал. Эта замечательная информация пригодится нам для следующей темы.
08-24 & Есть ощущение, что я недостаточно подробно рассказал про подстановочные символы. Со всей ответственностью, помощью всех источников интернета, Герберта Шилдта и личного опыта -- исправляюсь. \\\hline
08-25 &В обобщённом коде знак вопроса, называемый подстановочным символом, означает неизвестный тип. Подстановочный символ может использоваться в разных ситуациях: как параметр типа, поля, локальной переменной, иногда в качестве возвращаемого типа. Подстановочный символ никогда не используется в качестве аргумента типа для вызова обобщённого метода, создания экземпляра обобщённого класса или супертипа. А вот подробнее о том, как можно его использовать -- сейчас и поговорим. \\\hline
08-26 & Я уже упоминал, что можно ограничить передаваемый тип сверху, чтобы уточнить и ограничить тип. Можно использовать подстановочный символ, ограниченный сверху, чтобы ослабить ограничения переменной. Например, если необходимо написать метод, который работает с коробками Integer-ов, коробками Double-ов и коробками Number-ов, этого можно достичь с помощью ограниченного сверху подстановочного символа. Чтобы объявить ограниченный сверху подстановочный символ, нужно воспользоваться символом вопроса "?", с последующим ключевым словом extends, с последующим ограничением сверху. Не лишним будет дополнительно проговорить, что в этом контексте extends означает как расширение класса, так и реализацию интерфейса.
Чтобы написать метод, который работает с коробками, в которых Number и дочерними типами от Number, например Integer, Double и Float, можно указать Box<? extends Number>. В то время, как Box<Number> ввёл бы более жёсткое ограничение, чем Box<? extends Number>, потому что конструкция Box<Number> соответствует только коробкам типа Number, в то время, как если это будет просто аргумент, а не дженерик аргумент -- никаких проблем с наследованием не будет, программирование не сломалось, а Box<? extends Number> соответствует коробкам типа Number и коробкам всех подклассов намбэ.
Картинки -- это конечно интересно и всё такое, но надо бы и в коде дополнительно рассмотреть. Будем опять мучить традиционный Animal, у которого есть поле name и переопределённый метод toString() для вывода информации, также сразу добавим котика с пёсиком, прямых наследников животного.
Подготовим коробочку и соответствующий метод, выводящий информацию о её содержимом. Внутри метода сделаем ограниченный сверху подстановочный символ <? extends Animal>, где вместо Animal-ом может быть любой тип, а значит эта подстановка будет соответствовать Animal и любому подтипу Animal. Метод printInfo может выводить информацию о коробке, внутри которой мы будем выводить информацию об объекте, содержащемся в ней. А объект, как мы помним в нашем случае является наследником Animal, включая сам класс Animal.
Осталось только создать котика, пёсика, аккуратно положить их в коробки и вывести информацию о коробках нашим удобным методом. Видим, как великолепно дженерики справляются со своей задачей. А как мы помним со слайов чуть ранее, если бы мы просто запросили в параметре Животное, передать кота не получилось бы. \\\hline
08-27 & Если просто использовать подстановочный символ, то получится подстановочный символ без ограничений. Box<?> означает коробку с неизвестным содержимым (неизвестным типом). Казалось бы, зачем такой синтаксис существует? Во первых, потому что если Вы используете обобщённые типы, Вы можете продолжать их использовать, не особенно заботясь о том, что Вам в данной конкретной ситуации не нужно использовать обобщённую функциональность. Отсюда можно сделать вывод о том, что неограниченный подстановочный символ полезен в двух случаях: Если нужен метод, который может быть реализован с помощью функциональности класса Object. Когда код использует методы обобщённого класса, которые не зависят от параметра типа. В программах, использующих АПИ рефлексии языка конструкция тип Класс с подстановочным символом без ограничений используется чаще других конструкций, потому что большинство методов объекта Класс не зависят от расположенного внутри Т. Но более подробно о классе Класс позже, на лекциях по рефлексии.
Можно обратить внимание, что, например, метод с предыдущего слайда, принтинфо, не использует никаких методов животного, Цель метода printInfo — вывод в консоль информации об объекте в коробке любого типа
поэтому в параметре метода можно заменить коробку с наследниками животного на коробку с чем угодно, потому что всё равно там будет использоваться только метод коробки туСтринг. В то же время не лишним будет напомнить, что нельзя указать в обобщённом аргументе Обжект, потому что это будет означать именно обжект, а не обжект и его наследников, как это бывает в обычной программе. Важно запомнить, что Box с Object в треугольных скобках и Box с вопросительным знаком в треугольных скобках -- это НЕ одно и то же. \\\hline
08-28 & Ограниченный снизу подстановочный символ ограничивает неизвестный тип так, чтобы он был либо указанным типом, либо одним из его предков. Более наглядно -- на слайде, более понятно уже не получится -- либо сам тип либо его предки по иерархии наверх вплоть до обжекта. В целом, в обобщённых конструкциях можно указать либо только верхнюю границу для подстановочного символа, либо только нижнюю, но также можно указать оба ограничения сразу, хотя для этого варианта придётся продумать довольно нетривиальную стратегию передачи параметра, потому что напрямую сделать это как показано на слайде -- не выйдет.
То есть, если написать тот же самый метод принтИнфо, с параметром коробка и обобщённым аргументом не экстендс, а супер животное, то этот код также не будет работать, потому что метод будет ожидать не животное и наследников, а животное и родителей, то есть обжект. \\\hline
08-29 & Обобщённые классы или интерфейсы связаны не только из-за связи между их типами. Однако можно использовать подстановочные символы (wildcards) для создания связи между обобщёнными классами и интерфейсами. С обычными необобщёнными классами Кота и Животного имеет смысл писать какой-нибудь простой незатейливый код, который мы уже десятки, если не сотни раз видели и писали сами. Этот код показывает, что наследование работает по правилу подчинённых типов: класс Cat является подклассом класса Animal, и расширяет его.
И правило не работает для обобщённых типов. Но, если Cat является дочерним типом для Animal, то какая связь между Коробкой с <Cat> и Коробкой с <Animal>? Несмотря на то, что Cat является подтипом Animal, Box<Cat> не является подтипом Box<Animal>. Это разные типы. Общим предком для Box<Animal> и Box<Cat> является Box с подстановочным символом <?>.
Для того чтобы создать такую, хорошо визуализируемую связь между этими классами коробок, чтобы код мог иметь доступ к методам Animal через Box<Cat>, как раз и используется он, подстановочный символ. Так как Cat является дочерним типом от Animal, и животное в коробке является коробкой, в которой тип Animal, теперь существует связь между котом в коробке, точнее, коробкой с объектом типа Cat) и коробкой с животным. \\\hline
08-30 &В некоторых случаях компилятор может вывести тип подстановочного символа. Коробка может быть определена как коробка с неограниченным подстановочным символом, но при вычислении выражения компилятор выведет конкретный тип из кода, такой сценарий называется захватом подстановочного символа. Что это нам даёт? То, что в большинстве случаев нет нужды беспокоиться о захвате подстановочного символа, кроме случаев, когда в сообщении об ошибке появляется фраза “capture of”. В примере на слайде компилятор обрабатывает параметр коробки как тип Object. Когда метод testError вызывает свой важный метод, компилятор не может подтвердить тип объекта, который будет класться в коробку, и генерирует ошибку. Когда возникает этот тип ошибки, это обычно означает, что компилятор думает, что вы присваиваете неправильный тип переменной. Обобщения были добавлены в Java именно для этого -- чтобы усилить безопасность типов на этапе компиляции.
Мы точно уверены, что код пытается выполнить безопасную операцию, но у него не очень получается. Тогда как обойти ошибку компиляции? Её возможно исправить написав приватный вспомогательный метод, в англоязычной литературе так и называется, прайват хелпер метод, который захватывает подстановочный символ. Благодаря вспомогательному методу компилятор использует выведение типа для определения, что T является захваченной переменной в вызове и пример успешно компилируется. По соглашению они даже так и именуются -- имя метода которому надо помочь и слово хелпер на конце. \\\hline
08-31 & Когда использовать ограниченный сверху подстановочный символ, и когда использовать ограниченный снизу подстановочный символ, -- определить зачастую бывает довольно сложно, особенно новичкам в проектировании. Чтобы как-то логично завершить разговор о подстановочных символах, кратко резюмирую вышесказанное.
Ближайшие пару-тройку минут, я призываю вас думать о переменных так, будто они реализуют два возможных поведения в логике программы -- входная и выходная переменные. То есть к примеру у нас есть метод, который будет что-то куда-то копировать, таких методов чуть больше, чем очень много, начиная от системного копирования массива и копирования массивов, через копирование содержимого коллекций и до самописных перекладываний джейсонов с места на место. Входная переменная -- это то, что предоставляет данные для обработки, а выходная -- это то, куда в результате будут отправлены обработанные данные. Некоторые переменные могут быть одновременно входными и выходными, такой случай тоже рассмотрим.
Входные переменные желательно всегда определять с ограниченным сверху подстановочным символом, используя ключевое слово extends, потому что мы никогда точно не знаем, какая именно реализация интерфейса к нам прилетит.
Выходные переменные определяются с ограниченным снизу подстановочным символом, используя ключевое слово, соответственно, super. это нужно для того, чтобы как раз ограничить интерфейсную часть взаимодействия с переменной для дальнейших манипуляций, максимально лишить её специфики, а значит сделать применимой для большего числа принимающих сторон.
Если ко входной переменной можно обращаться только используя методы класса Object, использовать стоит неограниченный подстановочный символ, потому что нам от дженерика, по сути, ничего не нужно, только защита от совсем уж неправильного входящего значения.
Если переменная должна использоваться как входная и как выходная одновременно, то НЕ стоит использовать подстановочный символ, потому что потом где-то понадобится обкладываться хелперами и дополнительными проверками, а это весьма утомительное занятие.
Не стоит использовать подстановочные символы в возвращаемых типах, потому что это будет принуждать других программистов разбираться с подстановочными символами и возможно как-то долго писать хелперы, а как мы помним, это весьма утомительно. \\\hline
08-32 & Раз уж завершили беседу о подстановочном символе, давайте проверим, что запомнилось. Отвечаем как всегда не раздумывая, вопросы как всегда очень простые. Ограниченный снизу подстановочный символ позволяет передать в качестве аргумента типа -- только родителей типа -- только наследников типа -- сам тип и его родителей -- сам тип и его наследников
Конечно же сам тип и его родителей, потому что вниз по иерархии мы тип ограничили. Используем для такого ограничения ключевое слово супер. Второй вопрос, кстати, будет о нём. Конструкция когда в угловых скобках написан вопросительный знак супер Object -- не скомпилируется -- не имеет смысла -- не позволит ничего передать в аргумент типа -- не является чем-то необычным
Это был вопрос с подвохом, сразу отметаешь третий вариант, потому что конструкция синтаксически верна, дальше на первый взгляд кажется, что не имеет смысла, потом начинаешь думать что не скомпилируется, в результате понимаешь что всё скомпилируется, но всё равно смысла не видишь, однако это всё равно ограничение по типу обжект и его супертипам. да, у обжекта нет супертипов, но сам обжект то есть, так что правильный ответ четвёртый -- это вполне легальная конструкция в джава, почему бы нет. \\\hline
08-33 & Обобщения были введены в язык программирования Java для обеспечения более жёсткого контроля типов во время компиляции и для поддержки обобщённого программирования. Для реализации обобщения компилятор Java применяет стирание типа. Также коротко проговорим про загрязнение кучи\\\hline
08-34 & Стирание типа (Type Erasure). что это? это внутренний механизм языка. давно мы в шестерёнках не копались, почему бы не возродить эту чудесную традицию? Механизм стирания типа фактически заменяет все параметры типа в обобщённых типах их границами или Object-ами, если параметры типа не ограничены. Сгенерированный байткод содержит только обычные классы, интерфейсы и методы, никаких дженериков в байт-коде нет и быть не может. Вставляет явное приведение типов где необходимо, чтобы сохранить безопасность типа. Генерирует связующие методы, чтобы сохранить полиморфизм в расширенных обобщённых типах, тех, которые экстендед, например, коробки. Также, стирание типа гарантирует, что никакие новые классы не будут созданы для параметризованных типов, следовательно обобщения не приводят к накладным расходам в рантайме.
Получается, что в наших коробках, когда мы пишем параметр Т, это неограниченный ничем параметр, а значит компилятор заменит его обжектом. Если напишем Т экстендс Животное -- произойдёт замена на энимал. \\\hline
08-35 & Компилятор Java также стирает параметры типа обобщённых методов. Обобщённый метод в котором также используется неограниченный Т будет заменён компилятором на Обжект. Из-за того, что декомпилятор идеи слишком умный мы не сможем увидеть это глазами в класс файле, поэтому я привожу результат стирания типа для метода просто текстом. в качестве доказательства можно внимательно посмотреть на текст выдаваемой компилятором ошибки, как мы помним, если сделать разные сигнатуры у одинаково названных методов -- это называется перегрузкой методов, однако в приведённом примере перегрузки не происходит, а выдаётся ошибка, говорящая о том, что у приведённых двух методов одинаковые стирания.
Аналогично классам для методов происходит стирание типа при расширении ключевым словом экстендс -- параметр в угловых скобках заменяется на максимально возможного родителя.
Стирание типа имеет последствия, связанные с произвольным количеством параметров (varargs).
Материализуемые типы -- это типы, информация о которых полностью доступна во время выполнения, такие как примитивы, необобщённые типы, сырые типы, обращения к неограниченным подстановочным символам.
Нематериализуемые типы -- это типы, информация о которых удаляется во время компиляции стиранием типов, например, обращения к обобщённым типам, которые не объявлены с помощью неограниченных подстановочных символов. Во время выполнения о нематериализуемых типах нет всей информации. Примеры нематериализуемых типов: Box<String> и Box<Number>. Виртуальная машина Java не может узнать разницу между ними во время выполнения. В некоторых ситуациях нематериализуемые типы не могут использоваться, например, в выражениях instanceof или в качестве элементов массива. \\\hline
08-36 & Загрязнение кучи (heap pollution) возникает, когда переменная параметризованного типа ссылается на объект, который не является параметризованным типом. Такая ситуация возникает, если программа выполнила некоторую операцию, которая генерирует предупреждение unchecked warning во время компиляции. Предупреждение unchecked warning генерируется, если правильность операции, в которую вовлечён параметризованный тип (например приведение типа или вызов метода) не может быть проверена. Например, загрязнение кучи возникает при смешивании сырых типов и параметризованных типов, или при осуществлении непроверяемых преобразований типа. В обычных ситуациях, когда код компилируется сразу и полностью, компилятор генерирует unchecked warning, чтобы привлечь внимание к загрязнению кучи. Если компилируются различные части кода отдельно, то становится трудно определить потенциальную угрозу загрязнения кучи. Если всегда производить компиляцию кода так, чтобы не было предупреждений, то загрязнение кучи не сможет произойти. То есть, дополнительно привлекаю ваше внимание к проблеме игнорирования разработчиками ворнингов, очень часто разработчик думает, что раз работает, то ничего менять не надо, а потом все очень сильно удивляются, почему программа тормозит.\\\hline
08-37 &Ну и напоследок, раз уж мы все поняли какие обобщения классные и прониклись их безопасностью, стоит дополнительно проговорить об их ограничениях. То, что я скажу дальше, так или иначе уже сказано, это, можно сказать, некоторое резюме вышесказанного в части наиболее частых ошибок при работе с дженериками. \\\hline
08-38 & Первое и самое очевидное -- нельзя использовать при создании экземпляра -- примитив, думаю, уже понятно почему, к примитиву нельзя применить стирание типа, потому что примитив нельзя неявно привести к обжекту. выход из ситуации -- использование классов-обёрток.
Нельзя создавать экземпляры параметров типа, тут, думаю, тоже всё достаточно прозрачно, на этапе исполнения никаких дженериков нет, а на что именно заменить Т, чтобы его создать на этапе компиляции -- не очень понятно. в качестве выхода из ситуации -- просто передавать свеже созданный объект в дженерик методы снаружи.
Статические поля класса являются общими для всех объектов этого класса, поэтому статические поля с типом параметра типа запрещены. Если бы статические поля с типом параметра типа были бы разрешены, то код, в котором мы для коробок с животным, котом и собакой создаём объекты и никак не передаём в коробку животное, кота и собаку -- был бы весьма сбивающим с толку. То есть ещё раз -- так как статическое поле value является общим для коробки с животным, коробки с котом и коробки с собакой, то какого типа value? Оно не может быть Animal, Cat и Dog в одно и то же время, поэтому нельзя создавать статические поля с типом параметра типа. Обычно ещё нельзя использовать приведение типа к параметризованному типу, если он не использует неограниченный подстановочный символ.
А давайте лучше выведем в правило -- нельзя использовать приведения типов для обобщённых объектов. Так как компилятор стирает все параметры типа из обобщённого кода, то нельзя проверить во время выполнения, какой параметризованный тип используется для обобщённого типа. Во время выполнения нет параметров типа, поэтому нет возможности различить Box<Integer> и Box<Animal>. Наибольшее, что можно сделать — это использовать подстановочный символ для проверки.
Нельзя создавать массивы параметризованных типов. Думаю, тут тоже довольно очевидна причина -- внутри массива мы манипулируем данными по ссылкам, а ссылка на строку, котика и число выглядит для джавы совершенно одинаково, то есть получается, что мы лишаемся всех проверок на этапе компиляции и получаем много неприятностей в виде попыток присвоить ошибочные данные в рантайме. Пока я говорю, внимательно посмотрите на слайд я там постарался дополнительно доступно объяснить что произойдёт, ситуация очень плохая, аж мурашки по коже
Обобщённый класс не может расширять класс Throwable напрямую или не напрямую. Метод не может ловить (catch) экземпляр параметра типа. Однако можно использовать параметр типа в throws. вот тут непонятно, то ли недоглядели, то ли сделали на будущее, то ли специально решили оставить для внимательных -- выкинуть тип нельзя, но объявить в выкидываемых можно. немного сбивает с толку.
Класс не может иметь два перегруженных метода, которые будут иметь одинаковую сигнатуру после стирания типов -- этот пример мы уже видели несколько слайдов назад, но, думаю, будет не лишним его повторить. такой код не скомпилируется. \\\hline
08-39 & Итак, финальные итоги: мы довольно много и достаточно глубоко поговорили о дженериках. Посмотрели на простые примеры, подглядели под капот и поговорили о шестерёнках. Я постарался максимально просто объяснить на примитивных примерах как работают сбивающие с толку вайлдкарды и почему разработчики пришли к бриллиантовому оператору.\\\hline
08-40 &В качестве практического задания, давайте сделаем следующее: Напишем метод, который меняет два элемента массива местами.(массив может быть любого ссылочного типа);
И попробуем решить такую, сравнительно большую задачу:
- Есть классы Fruit -> Apple, Orange; (больше не надо)
- Класс Box в который можно складывать фрукты, коробки условно сортируются по типу фрукта, поэтому в одну коробку нельзя сложить и яблоки, и апельсины; Для хранения фруктов внутри коробки можете использовать ArrayList;
- Сделать метод getWeight() который высчитывает вес коробки, зная количество фруктов и вес одного фрукта(вес яблока - 1.0f, апельсина - 1.5f, не важно в каких единицах);
- Внутри класса коробки сделать метод compare, который позволяет сравнить текущую коробку с той, которую подадут в compare в качестве параметра, true - если их веса равны, false в противном случае(коробки с яблоками мы можем сравнивать с коробками с апельсинами);
- Написать метод, который позволяет пересыпать фрукты из текущей коробки в другую коробку(помним про сортировку фруктов, нельзя яблоки высыпать в коробку с апельсинами), соответственно в текущей коробке фруктов не остается, а в другую перекидываются объекты, которые были в этой коробке; \\\hline
08-41 & Возможно, вы заметили, что я стараюсь рассказывать не о большом количестве механизмов, но о каждом механизме языка так глубоко, как это возможно на данном этапе. Всё потому что вдохновляюсь Львом Николаевичем, чью цитату Вы видите на экране. До скорых встреч.\\\hline
This will ensure that the type parameter "T" can only be instantiated with types that are both a subclass of "Number" and implement the "Comparable" interface with an argument that is a superclass of "Integer".