diff --git a/jtc1-01-abstract.tex b/jtc1-01-abstract.tex index a4bb3a8..6ebf3ab 100644 --- a/jtc1-01-abstract.tex +++ b/jtc1-01-abstract.tex @@ -1,6 +1,6 @@ \documentclass[j-spec.tex]{subfiles} -\begin{document} +\begin{document} \sloppy \pagestyle{plain} \tableofcontents diff --git a/jtc2-02-abstract.tex b/jtc2-02-abstract.tex index 75307a5..30fac91 100644 --- a/jtc2-02-abstract.tex +++ b/jtc2-02-abstract.tex @@ -1,6 +1,6 @@ \documentclass[j-spec.tex]{subfiles} -\begin{document} +\begin{document} \sloppy \setcounter{section}{1} \pagestyle{plain} \tableofcontents diff --git a/jtc3-03-abstract.tex b/jtc3-03-abstract.tex index 61c1eb9..afe0136 100644 --- a/jtc3-03-abstract.tex +++ b/jtc3-03-abstract.tex @@ -1,10 +1,9 @@ \documentclass[j-spec.tex]{subfiles} -\begin{document} +\begin{document} \sloppy \setcounter{section}{2} \setlength{\columnsep}{22pt} \pagestyle{plain} -\sloppy \tableofcontents \section{Специализация: ООП} diff --git a/jtc4-04-abstract.tex b/jtc4-04-abstract.tex index 5090565..b00b676 100644 --- a/jtc4-04-abstract.tex +++ b/jtc4-04-abstract.tex @@ -1,10 +1,9 @@ \documentclass[j-spec.tex]{subfiles} -\begin{document} +\begin{document} \sloppy \setcounter{section}{3} \setlength{\columnsep}{22pt} \pagestyle{plain} -\sloppy \tableofcontents \section{Специализация: ООП и исключения} diff --git a/jtc5-05-abstract.tex b/jtc5-05-abstract.tex index 7fecbb6..b1cbce6 100644 --- a/jtc5-05-abstract.tex +++ b/jtc5-05-abstract.tex @@ -1,10 +1,9 @@ \documentclass[j-spec.tex]{subfiles} -\begin{document} +\begin{document} \sloppy \setcounter{section}{4} \setlength{\columnsep}{22pt} \pagestyle{plain} -\sloppy \tableofcontents \section{Специализация: тонкости работы} @@ -36,7 +35,7 @@ \subsubsection{MBR, GPT} ОС Linux, например, предоставляет возможность разбивки жесткого диска компьютера на отдельные разделы. Пользователи могут определить их границы по так называемым таблицам разделов. \textbf{Основная загрузочная запись (MBR)} и \textbf{таблица разделов GUID (GPT)} -- это два стиля формата разделов, которые позволяют компьютеру загружать операционную систему с жесткого диска, а также индексировать и упорядочивать данные. -Основная загрузочная запись (MBR) -- устаревшая форма разделения загрузочного сектора, первый сектор диска, который содержит информацию о том, как разбит диск. Он также содержит загрузчик, который сообщает компьютеру, как загрузить ОС. Main Boot Record состоит из трех частей: +Основная загрузочная запись (MBR) -- устаревшая форма разделения загрузочного сектора -- первый сектор диска, который содержит информацию о том, как разбит диск. Он также содержит загрузчик, который сообщает компьютеру, как загрузить ОС. Main Boot Record состоит из трех частей: \begin{itemize} \item Основной загрузчик -- MBR резервирует первые байты дискового пространства для основного загрузчика. Windows размещает здесь очень упрощенный загрузчик, в то время как другие ОС могут размещать более сложные многоступенчатые загрузчики. \item Таблица разделов диска -- таблица разделов диска находится в нулевом цилиндре, нулевой головке и первом секторе жёсткого диска. Она хранит информацию о том, как разбит диск. MBR выделяет 16 байт данных для каждой записи раздела и может выделить всего 64 байта. Таким образом, Main Boot Record может адресовать не более четырёх основных разделов или трёх основных раздела и один расширенный раздел. @@ -86,7 +85,7 @@ GPT: \end{itemize} \subsubsection{Windows} -Линейка файловых систем для Windows: какую роль они играют в работе системы и как они развивались. +Линейка файловых систем для Windows -- роль в работе системы и этапы развития. \textbf{FAT(16) File Allocation Table} Использовалась для MS-DOS 3.0, Windows 3.x, Windows 95, Windows 98, Windows NT/2000. Была разработана достаточно давно и предназначалась для работы с небольшими дисковыми и файловыми объемами, простой структурой каталогов. Таблица размещается в начале тома, причем хранятся две ее копии (в целях обеспечения большей устойчивости). Данная таблица используется операционной системой для поиска файла и определения его физического расположения на жестком диске. В случае повреждения и таблицы и ее копии чтение файлов операционной системой становится невозможно. @@ -329,7 +328,7 @@ if (Files.exists(file)) { \subsection{Потоки ввода-вывода, пакет \code{java.io}} Подавляющее большинство программ обменивается данными со внешним миром. Это делают любые сетевые приложения -- они передают и получают информацию от других компьютеров и специальных устройств, подключенных к сети. Можно таким же образом представлять обмен данными между устройствами внутри одной машины. Программа может считывать данные с клавиатуры и записывать их в файл, или, наоборот - считывать данные из файла и выводить их на экран. Таким образом, устройства, откуда может производиться считывание информации, могут быть самыми разнообразными – файл, клавиатура, входящее сетевое соединение и т.д. То же касается и устройств вывода – это может быть файл, экран монитора, принтер, исходящее сетевое соединение и т.п. В конечном счете, все данные в компьютерной системе в процессе обработки передаются от устройств ввода к устройствам вывода. -Реализация системы ввода/вывода осложняется не только широким спектром источников и получателей данных, но еще и различными форматами передачи информации. Ею можно обмениваться в двоичном представлении, символьном или текстовом, с применением некоторой кодировки (кодировок только для русского языка их более 4 типов), или передавать числа в различных представлениях. Доступ к данным может потребоваться как последовательный, так и произвольный. Зачастую для повышения производительности применяется буферизация. +Реализация системы ввода/вывода осложняется не только широким спектром источников и получателей данных, но еще и различными форматами передачи информации. Ею можно обмениваться в двоичном представлении, символьном или текстовом, с применением некоторой кодировки (кодировок только для русского языка существует более четырёх типов), или передавать числа в различных представлениях. Доступ к данным может потребоваться как последовательный, так и произвольный. Зачастую для повышения производительности применяется буферизация. \begin{frm} \info В Java для описания работы по вводу/выводу используется специальное понятие потока данных (stream). Поток данных это абстракция, физически никакие потоки в компьютере никуда не текут. \end{frm} @@ -392,13 +391,13 @@ if (Files.exists(file)) { \item \code{StringBufferInputStream} (deprecated). Иногда бывает удобно работать с текстовой строкой как с потоком байт. Для этого возможно воспользоваться классом \code{StringBufferInputStream}. При создании объекта этого класса необходимо передать конструктору объект \code{String}. \item Класс \code{SequenceInputStream} объединяет поток данных из других двух и более входных потоков. Данные будут вычитываться последовательно -- сначала все данные из первого потока в списке, затем из второго, и так далее. Конец потока \code{SequenceInputStream} будет достигнут только тогда, когда будет достигнут конец потока, последнего в списке. \item FilterInputStream и FilterOutputStream и их наследники. Задачи, возникающие при вводе/выводе весьма разнообразны -- это может быть считывание байтов из файлов, объектов из файлов, объектов из массивов, буферизованное считывание строк из массивов и т.д. В такой ситуации решение с использованием простого наследования приводит к возникновению слишком большого числа подклассов. Более эффективно применение надстроек (в ООП этот шаблон называется адаптер). Надстройки -- наложение дополнительных объектов для получения новых свойств и функций. Таким образом, необходимо создать несколько дополнительных объектов -- адаптеров к классам ввода/вывода. В терминах \code{java.io} их называют фильтрами. -\item Класс \code{LineNumberInputStream} во время чтения данных производит подсчет, сколько строк было считано из потока. Номер строки, на которой в данный момент происходит чтение, можно узнать путем вызова метода \code{getLineNumber()}. Также можно и перейти к определенной строке вызовом метода \code{setLineNumber(int lineNumber)}. Этот класс практически разу объявили устаревшим и вместо него используется \code{LineNumberReader} с аналогичным функционалом. +\item Класс \code{LineNumberInputStream} во время чтения данных производит подсчет, сколько строк было считано из потока. Номер строки, на которой в данный момент происходит чтение, можно узнать путем вызова метода \code{getLineNumber()}. Также можно и перейти к определенной строке вызовом метода \code{setLineNumber(int lineNumber)}. Этот класс практически сразу объявили устаревшим и вместо него используется \code{LineNumberReader} с аналогичным функционалом. \item \code{PushBackInputStream}. Этот фильтр позволяет вернуть во входной поток считанные из него данные. Такое действие производится вызовом метода \code{unread()}. Понятно, что обеспечивается подобная функциональность за счет наличия в классе специального буфера -- массива байт, который хранит считанную информацию. \item PrintStream используется для конвертации и записи строк в байтовый поток. В нем определен метод \code{print()}, принимающий в качестве аргумента различные примитивные типы Java, а также тип \code{Object}. При вызове передаваемые данные будут сначала преобразованы в строку, после чего записаны в поток. Если возникает исключение, оно обрабатывается внутри метода \code{print()} и дальше не бросается (узнать, произошла ли ошибка, можно с помощью метода \code{checkError()}). Данный класс также считается устаревшим, и вместо него рекомендуется использовать \code{PrintWriter}, однако старый класс продолжает активно использоваться, поскольку статические поля \code{out} и \code{err} класса \code{System} имеют именно это тип. \end{itemize} \subsubsection{\code{BufferedInputStream} и \code{BufferedOutputStream}} -На практике при считывании с внешних устройств ввод данных почти всегда необходимо буферизировать. \code{BufferedInputStream} содержит массив байт, который служит буфером для считываемых данных. То есть, когда байты из потока считываются, либо пропускаются (методом \code{skip()}), сначала заполняется буферный массив, причём, из потока загружается сразу много байт, чтобы не требовалось обращаться к нему при каждой операции \code{read()} или \code{skip()}. \code{BufferedOutputStream} предоставляет возможность производить многократную запись небольших блоков данных без обращения к устройству вывода при записи каждого из них. Сначала данные записываются во внутренний буфер. Непосредственное обращение к устройству вывода и, соответственно, запись в него, произойдет, когда буфер заполнится. Инициировать передачу содержимого буфера на устройство вывода можно и явным образом, вызвав метод \code{flush()}. Для наглядности заполним небольшой файл данными, буквально 10 миллионов символов. +На практике при считывании с внешних устройств ввод данных почти всегда необходимо буферизировать. \code{BufferedInputStream} содержит массив байт, который служит буфером для считываемых данных. То есть, когда байты из потока считываются, либо пропускаются (методом \code{skip()}), сначала заполняется буферный массив, причём, из потока загружается сразу много байт, чтобы не требовалось обращаться к нему при каждой операции \code{read()} или \code{skip()}. \code{BufferedOutputStream} предоставляет возможность производить многократную запись небольших блоков данных без обращения к устройству вывода при записи каждого из них. Сначала данные записываются во внутренний буфер. Непосредственное обращение к устройству вывода и, соответственно, запись в него, произойдет, когда буфер заполнится. Инициировать передачу содержимого буфера на устройство вывода можно и явным образом, вызвав метод \code{flush()}. Для наглядности заполним небольшой файл данными, буквально, миллион символов. \begin{lstlisting}[language=Java,style=JCodeStyle,caption={Сравнение простого и буферизующего потоков (шаг 1)}] String fileName = "test.txt"; @@ -589,7 +588,7 @@ try (RandomAccessFile catFile = new RandomAccessFile("cat.txt", "rw")) { } catch (IOException e) { e.printStackTrace(); } \end{lstlisting} -Подготовив всё необходимое приложение прочитало данные в буфер, сохранив число прочитанных байт, а затем прочитанные байты посимвольно были выведены в консоль. Для чтения данных из файла используется файловый канал. Объект файлового канала может быть создан только вызовом метода \code{getChannel()} для файлового объекта, поскольку нельзя напрямую создать объект файлового канала. При этом, \code{FileChannel} нельзя переключить в неблокирующий режим. +Подготовив всё необходимое, приложение прочитало данные в буфер, сохранив число прочитанных байт, а затем прочитанные байты посимвольно были выведены в консоль. Для чтения данных из файла используется файловый канал. Объект файлового канала может быть создан только вызовом метода \code{getChannel()} для файлового объекта, поскольку нельзя напрямую создать объект файлового канала. При этом, \code{FileChannel} нельзя переключить в неблокирующий режим. \subsection{String} Класс String отвечает за создание строк, состоящих из символов. Если быть точнее, заглянув в реализацию и посмотрев способ их хранения, то строки (до Java 9) представляют собой массив символов diff --git a/pics/jd-03-nowild.svg b/pics/jd-03-nowild.svg new file mode 100644 index 0000000..06217f7 --- /dev/null +++ b/pics/jd-03-nowild.svg @@ -0,0 +1,208 @@ + + + + + + + + + + Object + Number + String + Animal + Integer + Double + Float + Cat + Dog + + + + + + + + + Box <Number> + + ? + + diff --git a/pics/jd-03-wildext.svg b/pics/jd-03-wildext.svg new file mode 100644 index 0000000..7104986 --- /dev/null +++ b/pics/jd-03-wildext.svg @@ -0,0 +1,208 @@ + + + + + + + + + + Object + Number + String + Animal + Integer + Double + Float + Cat + Dog + + + + + + + + + Box <? extends Number> + + ? + + diff --git a/pics/jd-03-wildparent.svg b/pics/jd-03-wildparent.svg new file mode 100644 index 0000000..5bd8a3d --- /dev/null +++ b/pics/jd-03-wildparent.svg @@ -0,0 +1,88 @@ + + + + + + + + + + Box <?> + Box <Cat> + Box <Animal> + + + + diff --git a/pics/jd-03-wildsup.svg b/pics/jd-03-wildsup.svg new file mode 100644 index 0000000..4c7eeca --- /dev/null +++ b/pics/jd-03-wildsup.svg @@ -0,0 +1,208 @@ + + + + + + + + + + Object + Number + String + Animal + Integer + Double + Float + Cat + Dog + + + + + + + + + Box <? super Number> + + ? + + diff --git a/scenarios/jtd3-08b.tex b/scenarios/jtd3-08b.tex index 9704d81..c9199c9 100644 --- a/scenarios/jtd3-08b.tex +++ b/scenarios/jtd3-08b.tex @@ -27,7 +27,11 @@ Кроме постоянных перепроверок и кастов есть вторая проблема, она состоит в том, что мы, естественно, можем положить в первую коробку что угодно, например, строку и вполне ожидаемо получим ClassCastException. Допустим кто-то не понял мысль что ibox это коробка с интом, и хочет положить туда строку. Джава никак не запретит этого потому что строка это точно такой же равноправный объект. Как быть? первое, что приходит на ум - это try-catch но если можно какую-то ситуацию исправить ифом то лучше защититься ифом, конечно, получим примерно такой совсем не громоздкий код, как на слайде. Потому что перед строками с приведением типов желательно делать проверку на эти самые типы. Третья, самая неприятная проблема состоит в том, что все проблемы подобного рода проявляют себя только в рантайме, то есть у конечного пользователя перед глазами, когда мы уже никак это исправить не можем. \\ \hline -08-04 & К чему мы в итоге пришли? к тому что в принципе возможно создавать классы, которые могут работать с любыми типами данных, но при любом обращении к таким классам нам неплохо бы делать достаточно сложные проверки, чтобы каждый раз не вываливаться с исключениями. До появления дженериков в принципе так и делалось. Таким образом мы совершенно логично подошли к дженерикам, которые должны решить часть наших проблем. Обобщения появились в джава 1.5. Дженерики или обобщения -- это особые средства языка Java для реализации обобщённого или можно встретить термин шаблонизированного программирования: особого подхода к описанию данных и алгоритмов, позволяющего работать с различными типами данных без изменения внешнего описания. Обобщения добавляют в Java безопасность типов и делают управление проще. Исключается необходимость применять явные приведения типов, так как благодаря обобщениям все приведения выполняются неявно, в автоматическом режиме, платформой джава. Все дженерики работают только со ссылочными типами данных но это не проблема потому что для всех примитивов есть обёртки в виде ссылочного типа. \\ \hline +08-04 & К чему мы в итоге пришли? к тому что в принципе возможно создавать классы, которые могут работать с любыми типами данных, но при любом обращении к таким классам нам неплохо бы делать достаточно сложные проверки, чтобы каждый раз не вываливаться с исключениями. До появления дженериков в принципе так и делалось. Таким образом мы совершенно логично подошли к дженерикам, которые должны решить часть наших проблем. Обобщения появились в джава 1.5. Дженерики или обобщения -- это особые средства языка Java для реализации обобщённого или можно встретить термин шаблонизированного программирования: особого подхода к описанию данных и алгоритмов, позволяющего работать с различными типами данных без изменения внешнего описания. Обобщения добавляют в Java безопасность типов и делают управление проще. Исключается необходимость применять явные приведения типов, так как благодаря обобщениям все приведения выполняются неявно, в автоматическом режиме, платформой джава. Все дженерики работают только со ссылочными типами данных но это не проблема потому что для всех примитивов есть обёртки в виде ссылочного типа. +% 02 + +допустим пишем всеми любимые аркадные стрелялки. у нас есть корабль, враги, наши пули, пули врагов, просто какие-то летающие помехи, влияющие на здоровье. За минуту активной игры может насоздаваться до полутысячи разных объектов и каждый раз их отдавать на сборку мусора - удовольствие сомнительное, потому что сборка это очень дорогостоящий процесс, поэтому принято создавать так называемые пулы объектов, в которых лежит 300 пуль, десяток врагов, полсотни камней, и в этом пуле объекты просто переключаются из активного в неактивное состояние. А класс непосредственно игрового объекта наследует поведение дженерика, чтобы не описывать его каждый раз, то есть содержит основную общую для всех объектов логику -- достать из пула объект как будто он новый, положить в пул использованный объект, отработать столкновение объекта с другим объектом, подвигать объект по координатной сетке, и так далее. +\\ \hline 08-05 & Чтобы понять почему больше не надо делать явное приведение типов, и откуда берется какая-то безопасность, напишем в треугольных скобках букву . в принципе, там может быть любая буква, или даже нескольно, но принято писать именно T, чтобы обозначить Type, Тип. Мы не можем на этапе описания класса сказать что это будет за тип (число, строка или кот), но точно знаем что какой-то тип будет. пишем поле этого типа, геттер-сеттер-конструктор. Соответственно если мы напишем Т не в треугольных скобках при описании класса, то джава будет думать что это какой то реально существующий класс, который она просто не видит. А так мы намекаем, что это обобщение и тип будет задаваться при создании объекта. Естественно поменять его будет нельзя потому что джава это язык сильной статической типизации. % 2 @@ -39,792 +43,192 @@ Если объект создан как Integer, то мы не сможем записать в него строку. При попытке написать такую строку кода, получится ошибка на этапе компиляции. То есть обобщения отслеживают корректность используемых типов данных. Попробуем положить в одну из коробок строку -- компилятор не даёт нам это сделать. Вторая проблема -- проверка на инстансоф и третья -- допущение и выбрасывание ошибок в рантайме -- решились сами собой. Ну и на самом деле несмотря на то что мы пишем все вот эти красивые скобки и не делаем проверки -- внутри самой джавы это всё равно превратится в те же самые приведения и проверки, которые мы только что закомментировали и от которых отказались. \\ \hline 08-06 & По соглашению переменные типа именуются одной буквой в верхнем регистре. Это сильно отличается от соглашения об именовании переменных, классов и интерфейсов. Без такого отличия было бы трудно отличить переменную типа от класса и интерфейса. Буквы в фигурных скобках - их там совершенно неожиданно может быть больше одной, разделённых запятой. По конвенциям буквы обычно пишутся первые от английского слова, лучше всего обозначающего то, что там внутри -- T = type, E = entity (element), K, V = key, value, N = numeric -Но всё таки можете ставить такие, какие Вам нравятся и даже писать многобуквенные обозначения параметра, но должен предупредить, что это достаточно грубое нарушение стиля написания кода. Линейкой по пальцам никто бить не станет, но и всерьёз рассматривать к себе в команду не будут. \\ \hline +Но всё таки можете ставить такие, какие Вам нравятся и даже писать многобуквенные обозначения параметра, но должен предупредить, что это достаточно грубое нарушение стиля написания кода. Линейкой по пальцам никто бить не станет, но и всерьёз рассматривать к себе в команду скорее всего не будут. \\ \hline -08-07 & \\ \hline -08-08 & \\ \hline -08-09 & \\ \hline -08-10 & \\ \hline -08-11 & \\ \hline -08-12 & \\ \hline -08-13 & \\ \hline -08-14 & \\ \hline -08-15 & \\ \hline -08-16 & \\ \hline -08-17 & \\ \hline -08-18 & \\ \hline -08-19 & \\ \hline +08-07 & Вроде всё так классно, что аж не хочется переключаться на этот слайд, но надо. Дженерики, конечно, хороши, но всё же накладывают на работу с собой некоторые ограничения. Как думаете, что произойдёт, если я попытаюсь написать метод номер один внутри нашего дженерика? +% 2 -\end{longtable} +А что надо написать в теле метода, чтобы создался объект класса Т? правильный ответ -- ничего. !Ограничение дженерика в том, что невозможно внутри дженерика создать экземпляр параметризующего класса Т!, потому что на этапе компиляции мы об этом классе вообще ничего не знаем, из чего он будет состоять, какие у него будут конструкторы и будут ли конструкторы. Ведь даже конструктора без аргументов там может не быть (как известно, если есть другие то пустой не создаётся). Это ограничение возможно обойти, используя паттерн абстрактная фабрика, но это совсем другая история. +% 3 -\end{document} +Также нельзя создавать внутри дженерика массив из обобщённого типа данных. Но это тоже не так уж критично потому что всегда можно подать такой массив снаружи. +% 4 -Создание экземпляра обобщённого типа и обращение к нему -При обращении к обобщённому типу нужно заменить параметры типа на конкретные классы или интерфейсы, например Box: +А можем ли мы создать статическое поле типа Т? Конечно же нет, всё по той же причине -- конкретный тип для параметра Т мы узнаём только при создании *объекта* дженерика, то есть у нас получается слишком много противоречий, чтобы понять какое значение будет у статического поля. +% 5 -Box integerBox; +Интерфейсы обобщённого типа бывают, о них чуть позже, а вот исключение обобщённого типа создать нельзя. Впрочем, это, наверное, и к лучшему.\\ \hline +08-08 & Посмотрим, на этот раз, внимательно, на то как принято работать с объектами обобщённых классов. При обращении к обобщённому классу нужно заменить параметры типа на конкретные классы или интерфейсы, например строку, целое число или кота. Может показаться, что обращение к параметризованному типу похоже на обычный вызов метода, но вместо передачи аргумента в метод в круглых скобках, передается аргумент типа в треугольных, например Integer или Стринг в класс GBox, в описанном нами случае. -Можно подумать, что обращение к параметризованному типу похоже на обычный вызов метода, но вместо передачи аргумента в метод передается аргумент типа (type argument), например Integer в класс Box, в данном случае. +Параметр типа и аргумент типа -- это два разных понятия, как и при вызове методов, помните, там было различие, кто параметр, а кто аргумент метода? Когда объявляется обобщённый тип GBox, то T является параметром типа, а когда происходит обращение к обобщённому типу, передается аргумент типа, например Integer. Это довольно похоже на различие в методах, поэтому будет несложно запомнить. -Параметр типа и аргумент типа — это два разных понятия. Когда вы объявляете обобщённый тип Box, то здесь T является параметром типа. Когда происходит обращение к обобщённому типу, передается аргумент типа, например Integer . Это довольно похоже на различие формальных параметрах и аргументов методов. +Как и любое другое объявление переменной просто GBox integerBox сам по себе НЕ создаёт экземпляр класса GBox. Такой код просто объявляет идентифиактор GBox, но сразу уточняет, что это будет коробка с целыми числами. Такой уже созданный идентификатор обычно называется параметризованным типом. То есть, получается, что мы уже знаем примитивные типы, ссылочные типы, интерфейсные типы, а теперь ещё и параметризированные. Интересно. +% 2 -Как и любое другое объявление переменной Box integerBox НЕ создаёт экземпляр класса Box . Такой код просто объявляет переменную integerBox как Box Integer-ов. -Обращение к обобщённому типу обычно называется параметризованным типом (parameterized type). -Чтобы создать экземпляр класса, используется ключевое слово new, как обычно, и в дополнение указывается между именем класса и скобками с параметрами конструктора: +Чтобы создать экземпляр класса, используется ключевое слово new, как обычно, и в дополнение указывается, что создаётся не просто GBox, а обобщённый, поэтому пишется в треугольных скобках между именем вызываемого конструктора и скобками с параметрами этого конструктора. Но компиляторы почти сразу, а именно, с седьмой версии, оказались достаточно умными, чтобы понять, какой именно тип нужно подставить в треугольные скобки (по-умному это называется выведением типа из контекста), поэтому, если тип совпадает с аргументом типа в идентификаторе, в скобках экземпляра его можно не писать, как на 43й строке. Это называется бриллиантовый оператор. После создания экземпляра становится возможно обращаться к методам, тут всё как обычно. -Box integerBox = new Box(); +Дополнительно обращаю ваше внимание на то, что в описанном нами случае невозможно создать экземпляр без начального значения, это привычное поведение, потому что при создании класса GBox был создан конструктор, принимающий в качестве параметра значение, а значит конструктор по-умолчанию перестал создаваться неявно. \\ \hline +08-09 & Никаких ограничений, кроме здравого смысла, на количество параметризированных типов не накладывается. Часто можно встретить обобщения с двумя типами, например, в коллекциях, хранящих пары ключ-значение. Там параметризированные типы так и называются ки и вэлью. Достаточно написать список из нужных внутри класса типов через запятую. Также, как нет ограничений на использование типов внутри угловых скобок, кроме упомянутого выше, что типы должны быть ссылочными. На слайде видно, что использование только что написанного обобщённого GBox вполне допустимо. Единственное, за чем, на мой личный взгляд, важно следить, это чтобы код оставался читаемым и не пестрел множественными треугольниками скобок. \\ \hline -После создания экземпляра можно обращаться к методам: +08-10 & Сырой тип -- это имя обобщённого класса или интерфейса без аргументов типа, то есть это, фактически, написание идентификатора и вызов конструктора обобщённого класса как обычного, без треугольных скобок. Можно часто увидеть использование сырых типов в старом коде, поскольку многие классы, например из фреймворка коллекций, до Java 5 были необобщёнными, как вы помните, обобщения появились только в пятой джаве, а люди умудрялись программировать и до этого эпохального события. Когда используются сырые типы, по сути получается то же самое поведение, которое было до введения обобщений в Java. Как видно на первом снимке экрана -- метод, возвращающий объект из коробки, возвращает объект. +% 2 -// set integer. -integerBox.setType(10); -// get integer -Integer intValue = integerBox.getType(); -System.out.println(intValue); +Это довольно логично, потому что никто не указал ни идентификатору ни конструктору, что именно планируется в эту коробку складывать. Ситуация меняется, если указать тип, мы это уже видели, не лишним будет посмотреть ещё раз на втором снимке экрана. +% 3 +Поэтому GBox -- это сырой тип обобщённого типа GBox. Однако необобщённый класс или интерфейс НЕ являются сырыми типами. Для совместимости со старым кодом допустимо присваивать параметризованный тип своему собственному сырому типу, как это сделано на третьем снимке экрана. Компилятор, конечно сразу начинает довольно интенсивно сигнализировать о том, что наш код не совсем современно написан, но в целях демонстрации я думаю, он потерпит. +% 4 -Вывод: +Также, если попытаться присвоить параметризованному типу сырой тип, то буквально каждое слово в программе будет с предупреждением, или если попытаться вызвать обобщённый метод в сыром типе. Предупреждения показывают, что сырой тип обходит проверку обобщённого типа, что откладывает обнаружение ошибки на время выполнения программы. Поэтому, если в программе видим много предупреждений среды разработки -- перестаём работать с сырыми типами, а если вдруг всё-таки нужно именно сырой тип, и сыпятся какие-то ошибки, например, невозможность использования каких-то присущих только обобщённым типам методов, то, соответственно, приводим типы \\ \hline -10 +08-11 & Как уже упоминалось, при использовании сырого типа можно столкнуться с предупреждениями среды, которые на самом деле предупреждения компилятора, и обычно имеют вид, представленный в верхней части слайда. Термин "unchecked" означает непроверенные, то есть компилятор не имеет достаточного количества информации для обеспечения безопасности типов. По умолчанию этот вид предупреждений выключен, поэтому компилятор в терминале на самом деле даёт подсказку. Чтобы увидеть все "unchecked" предупреждения нужно перекомпилировать код с опцией -Xlint:unchecked. Конечно, среды проектирования обо многом предупреждают, иногда даже о большем, чем может предупредить компилятор, но часто разработчики на эти предупреждения не обращают внимания, особенно когда проект большой и предупреждения говорят о вещах, которые действительно нельзя изменить. Поэтому, я считаю, что знать о разных способах сделать свой код лучше и надёжнее -- полезно, и ручная компиляция в терминале -- один из них. Конечно, это не значит, что всем нужно обязательно закрыть среду разработки и с этих пор работать только в терминале. Мои слова означают только то, что к написанному Вами коду нужно относиться внимательно и придирчиво его перепроверять. \\ \hline +08-12 & Обобщённые методы похожи на обобщённые классы, но параметры типа относятся к методу, а не к классу. Допустимо делать обобщёнными статические и нестатические методы, а также конструкторы \\ \hline +08-13 & Синтаксис обобщённого метода включает параметры типа внутри угловых скобок, которые указываются перед возвращаемым типом. На слайде видим метод, который работает с созданной нами коробкой. Здесь, также, как и при описании класса необходимо указать Т в угловых скобках с единственной целью -- показать компилятору, что Т, которые он найдёт далее в параметрах и теле метода -- это не класс Т, а обобщённый параметр. Если Т перед возвращаемым значением не написать -- будет ошибка компиляции, и подсказка, говорящая о невозможности найти класс Т. \\ \hline -Бриллиантовая операция (Diamond operator) -Начиная с Java 7 существует также бриллиантовая операция (diamond operator), которая позволяет указывать пустые аргументы типа <> там, где компилятор может вывести тип из контекста: +08-14 & Пусть вас это не сбивает с толку, мы вроде бы только что начали говорить про обобщённые метода, но чтобы продолжить я думаю, нам нужно немного синхронизироваться, по традиции, я прошу вас ответить на несколько вопросов. Обобщения – это способ описания или можно ещё сказать создания общих 1. классов для разных пакетов; 2. алгоритмов для разных типов данных; 3. библиотек для разных приложений. -Box integerBox = new Box<>(); +... 30сек ... +Конечно же это способ создания единообразно хранимых и используемых алгоритмов для разных типов данных, только что мы описывали алгоритм хранения всего чего угодно в коробке. +Что именно значит буква в угловых скобках? 1. название обобщённого класса 2. имя класса, с которым будет работать обобщение 3. название параметра, используемого в обобщении +... 30сек ... +Верный ответ третий, никакое это не имя класса и не название обобщения, это, можно сказать, заполнитель, плейсхолдер, который будет использоваться в обобщении для того, чтобы вместо него подставилось имя класса, переданное в аргументе. -Несколько параметров типа -Для обобщенного типа можно объявлять более одного параметра, используя список, разделенный запятыми: +Возможно ли передать обобщённым аргументом примитивный тип? 1. да 2. нет 3. только строку +... 30сек ... -public class Box { - private K key; - private V value; - - public void setKey(K key) { - this.key = key; - } - - public void setValue(V value) { - this.value = value; - } - - public K getKey() { return key; } - public V getValue() { return value; } -} +очевидно, третий вариант отпадает, поскольку это оксюморон, а верный вариант ответа -- нет. нельзя передавать в угловых скобках примитивы. Многие причины, по которым этого нельзя делать, станут очевидны из дальнейшего повествования, не переключайтесь. \\ \hline +08-15 & Передавать один или несколько типов данных через запятую -- это интересно и забавно, но что если нам нужно, чтобы в обобщённом аргументе было возможно использовать только строго определённые типы данных? О, я понимаю, как это звучит, я только что развязал вам руки и сказал что вы не особенно то ограничены строгостью типов, и сразу же говорю, что надо что-то там ограничивать. Конечно надо! В качестве простого примера можно взять нашу пресловутую коробку и сказать, что теперь она может не только хранить в себе какие-то данные, но и, скажем, складывать их. И если со сложением чисел и конкатенацией строк проблем не должно быть, то как складывать котиков или потоки ввода-вывода -- понятно становится не сразу. Применение bounded type parameters позволяет более точно задавать ограничения на используемые типы данных, что помогает писать более безопасный и читаемый код. Вместо того, чтобы допускать все возможные типы, которые могут быть переданы в параметры метода или класса, можно ограничиться только теми, которые соответствуют определенным требованиям. Именно поэтому мы говорим, что в некоторых случаях имеет смысл ограничить типы, которые можно использовать в качестве аргументов в параметризованных типах. +% 2 -Использовать можно следующим образом: +Например, в коробке предлагается сделать так, чтобы в ней хранились только числа и прочие интересные наследники класса Number. Подобное ограничение можно сделать с помощью ограниченного параметра типа (bounded type parameters). Для тех, кому лень смотреть в англо-русский словарь, напомню, что намбэ -- это и есть число. Разве что ограничение переведено более неоднозначно, параметр типа у нас получается связанным. видимо, по рукам и ногам, не меньше. -public class App -{ - public static void main( String[] args ) { - Box box = new Box<>(); - box.setKey(4); - box.setValue("cats"); - - System.out.println("We have " + box.getKey() + " " + box.getValue() + " in box."); - } -} +Чтобы объявить ограниченный сверху параметр типа нужно после имени параметра указать ключевое слово extends, а затем указать верхнюю границу (upper bound), которой в данном примере является класс Number. В случае, который можно наблюдать на слайде, в коробку можно положить всё, что намбэ и всё, что так или иначе может быть сведено к нему, то есть его самого и все его частные случаи. +% 3 +Опять же, написанное достаточно точно и легко переводится с английского на русский -- коробка, в которую можно положить данные какого-то типа, являющиеся числом или расширяющего возможности числа. -Вывод: +В примере, который мы рассматриваем, типы, которые можно использовать в параметризованных классах Box, ограничены наследниками класса Number. Если попытаться указать, например, Box, то возникнет ошибка компиляции. Аналогичным образом можно создавать обобщённые методы с ограничением, тогда компилятор, кажется, вообще хочет сказать, в каком мире строка это наследник числа? Также возможно задать несколько границ. при этом, важно помнить, что здесь как и в объявлении классов, может быть только один класс-наследник и он указывается первым, все остальные границы могут быть только интерфейсами и указываются через знак амерсанд, то есть возможно указать что наследование будет от классов и интерфейсов, например, и т д. Непривычно то, что несмотря на то что Number или как на слайде -- птица -- это класс, а Serializable или на слайде Man и Animal это интерфейс -- мы всё равно пишем extends (спасибо уважаемые разработчики джавы за отсутствие путаницы, да). само собой, на всякий случай, повторю ещё раз, если унаследоваться в угловых скобках от двух классов, а не от интерфейсов -- будет ошибка. Множественное наследование запрещено во всей джаве без исключений. \\ \hline -We have 4 cats in box. +08-16 & Как мы уже успели неоднократно убедиться, возможно присвоить объекту одного типа объект другого типа, если эти типы совместимы. Например, можно присвоить объект типа Integer переменной типа Object, так как Object является одним из супертипов Integer. В объектно-ориентированной терминологии это называется связью «является» (“is a”). Собственно можно просто перевести словосочетание «целое число является объектом» с русского на английский и получить корректное объектно-ориентированное утверждение -- integer is an object. +% 02 +Так как Integer является Object-ом, то такое присвоение разрешено. Но Integer также является и Number, поэтому код, в котором инты или даблы передаются в качестве аргументов метода тоже корректен. Такое поведение, как было показано несколькими слайдами выше, верно для обобщений. +% 03 +То есть, ещё раз, предположим, что существует метод, описанный без каких-то вайлдкардов, но с дженерик параметром. Какой тип аргумента он будет принимать? Если посмотреть на сигнатуру, то можно увидеть, что он принимает один аргумент с типом GBox. Получается, что метод очень хороший. так ли это? Можно ли передать Box или Box , как можно можно было бы ожидать? Нет, нельзя, так как Box и Box не являются потомками Box всё ломается... странно. почему это выглядит так странно? Это частое недопонимание принципов работы обобщений, и это важно знать. Наследование не работает в дженериках так, как оно работает в обычной джаве. Коробка с Number не может в себе хранить коробку с Integer. Дженерик нас защищает от того, чтобы мы попытались положить в коробку строк - не строку. То есть мы, допустим, кладём туда инт, как наследника Number, а потом бац и внезапно флоут. Получится путаница. так что именно таким образом наследоваться не получится. -Параметризованный тип -Можно также заменить параметр типа на параметризованный тип, немного изменив класс Box: +Из этого можем сделать вывод что если мы нашему методу дадим коробку из интов -- он не будет работать. Поэтому в аргументе надо указать что это будет любой тип, но наследник Number, то есть использовать маску <вопросительный знак extends Number>. Ограничивать маску можно как сверху так и снизу, ограничение сверху это extends - то есть класс и его наследники, или - то есть класс и его родители (в случае с намбером только обжект). таким образом, дженерики защищают нас и самих себя от путаницы и не дают складывать в одни и те же контейнеры разные типы данных. То есть внутри метода мы не можем положить в коробку конкретный тип, допустим, флоут. потому что дженерик думает «а что если у меня там инты будут, у меня ж тогда всё сломается?» и на всякий случай запрещает. -public class Box { - private K key; - private V value; - - public Box(){} - - public Box(K key, V value) { - this.key = key; - this.value = value; - } - - public void setKey(K key) { - this.key = key; - } - - public void setValue(V value) { - this.value = value; - } - - public K getKey() { return key; } - public V getValue() { return value; } -} +Поскольку коробку с чем то ещё кроме Number и его наследников мы создавать не можем, маскИрование при вызове метода будет избыточно, и можно просто указать private static void boxTest(GBox n) без параметра в угловых скобках. Маски обычно нужны когда мы хотим поставить какое то ограничение. в этой ситуации мы уже ограничили коробки числами при создании коробок. +%% 04 +Что мы в результате видим: мы вроде как создаём псевдо-динамическую типизацию... на самом деле никаких дженериков и уж тем более переменных типов данных в джаве не существует. Всё что мы видим это так называемый синтаксический сахар. То есть, когда вы используете дженерики, во время компиляции произойдёт так называемое стирание, и все эти обобщённые типы данных преобразуются в обжекты и соответствующие проверки и приведения типов. Зачем нам это знать? На будущее весьма пригодится, потому что если мы возьмём у нашего дженерика getClass.getName() - он будет просто типом, информация из треугольных скобок удалится и её не будет. То есть, ещё раз, сама по себе Java не знает ни о каких дженериках. Ну и из этого логично следует, что однажды указав что-то в треугольных скобках, мы не сможем это дело поменять на какой то другой тип данных \\ \hline -Использовать, например, так: +08-17 & Я только что упомянул, что ограничивать типы можно как сверху, так и снизу. Что ж, никто меня за язык не тянул, надо придумать какой-нибудь хороший пример, где без таких ограничений всё будет печально ломаться. Поговорим на примере дженерик методов. Зачем в принципе нужны такие методы? когда нужно объединить несколько похожих, но всё же разных типов данных. Как вы думаете, вот мы подаём на вход два типа - инт и флоат. какой будет выбран в итоге для работы внутри метода? *правильно, ближайший старший для них обоих - Number*. и виртуальная машина не возьмёт объект потому что объект не является наследником Number'a. как думаете, зачем такие сложности? ведь можно просто подать на вход два Number'a и не выдумывать. +% 02 -public class App -{ - public static void main( String[] args ) { - Box> box = new Box<>(); - box.setKey(4); - box.setValue(new Box<>(2, "cats")); - - System.out.println("We have " + box.getKey() + " boxes with " + box.getValue().getKey() + " " + box.getValue().getValue() + " in each box."); - } -} +С ходу сложно придумать какой то пример, но давайте попробуем решить такую задачу: даны два списка - один с интами другой с намберами, и нам очень захотелось написать метод, который будет перекидывать из одного списка числа в другой. опишем для этого несложный метод, пока что не обращайте внимание на наличие тут коллекции, она нужна чтобы не описывать скучные методы хранения, добавления и прочие. есть два сценария - копировать намберы в инты и инты в намберы, как думаете, оба ли эти варианта окажутся рабочими, и если не оба, то какой будет правильным? +% 03 +Классно, если сразу быстро догадались, правильный рабочий вариант -- второй, копировать более точный тип в более общий. заодно, оцените, насколько хороший получился метод, как вы считаете? запускаем с копированием интов в намберы и наоборот. вроде всё хорошо и восьмая джава нас простит, приведя такие типы, но мы всё же попробуем её сломать. +% 04 -Вывод: +опишем два класса: естественно, животное и наследник животного кот, какие вообще можно было написать другие классы непонятно. И создадим обобщённые списки на их основе. далее воспользуемся уже написанным методом копирования списков, мы же пока что предполагаем, что он хороший, надо бы в этом удостовериться. Давайте немного подольше посмотрим на этот код, он не сложный, в нём почти нет пугающих дженериков, всё должно быть хорошо и понятно, несложный метод, кот, животное, два списка, копирование, вывод на экран +% 05 -We have 4 boxes with 2 cats in each box. +Запустив этот код мы всё равно видим что списки соединились, в списке котов появилось животное... ну что же, джава, раз ты такая умная... +% 06 +то коту мы добавляем метод голос, опять же ничего сложного, просто метод в котором кот будет мяукать и раз уж джава утверждает что получившийся в результате слияния список отличный - вызовем у одного из товарищей из списка метод голос. Видим, что на этапе компиляции всё хорошо и джава не видит никаких проблем, запускаем код +% 07 +а вот и проблемка, да? при попытке вызвать у более общего животного метод более частного кота у нас исключение невозможности приведения типов. оказывается энимал не так уж и легко может находиться в одном списке с котами, а значит, к нам на помощь спешат кто? верно, привет дженерики. +% 08 -Raw Types или сырые типы -Сырой тип (raw type) — это имя обобщённого класса или интерфейса без аргументов типа (type arguments). -Например, параметризованный тип создаётся так: +Мы вынуждены сказать, что метод будет работать только с каким-то одним типом и списки будут и в форе тоже будем перебирать Т. Напоминаю, что если не указать Т в объявлении метода то джава не поймёт что это обобщённый метод и будет искать какой то тип Т описанный в библиотеках и естественно его не найдёт. но тогда получается что всё, что мы можем -- это в список котов класть котов, а в список животных класть животных. Вернулись к тому с чего начали? но кот же наследник животного почему бы не класть в список животных котов? верно, значит нам надо сказать методу, что источником может быть кто то, кто тип или его наследники, а приёмником Тип или его родители, и пусть метод, виртуальная машина и другие механизмы уже сам разбираются, кто подходит под эти параметры. Соответственно, в методе мейн можно заметить, что копировать животных в котов не получится. Теперь неверные варианты будут отсекаться на этапе компиляции, и джава просто не даст нам написать как то не так. Честно говоря, не думаю, что это кому то может прям сильно пригодиться в ежедневной работе, а загуглить по мере возникновения задачи вроде бы несложно, но всё же знать в какую сторону гуглить -- было бы неплохо. \\ \hline -Box integerBox = new Box<>(); +08-18 & Выведение типов -- это возможность компилятора Java автоматически определять аргументы типа на основе контекста, чтобы вызов получился возможным. \\ \hline +08-19 & Алгоритм выведения типов определяет типы аргументов, а также, если это применимо, тип, в который присваивается результат или в котором возвращается результат. Далее алгоритм пытается найти наиболее конкретный тип, который работает со всеми аргументами и подходит к данной ситуации. Это достаточно несложная автоматизация, о которой особенно задумываться не нужно, потому что это как раз механизм, помогающий программисту не думать. Допустим, мы добавили к коту реализацию интерфейса Serializable. и попробуем сделать что-то странное -- написать метод, который будет работать с одним и только одним типом данных Т. Как видим, никакие аргументы не связаны наследованием. В таком методе выведение типов определяет, что вторые аргументы метода пик, а именно Cat и ArrayList, передаваемые в методы pick имеют тип Сериалайзабл, но этого недостаточно, потому что первый аргумент тоже должен быть того же типа. Внезапно оказывается, что строка -- это тоже сериалайзабл и внутри метода наименьший общий делитель (в смысле наиточнейший общий тип) будет определён не как обжект, а как сериализуемое. \\ \hline -Если убрать аргументы типа, то будет создан сырой тип: +08-20 & Коротко проговорим некоторые другие варианты выведения типов, они все примерно про одно и тоже, так что сложно быть не должно -Box box = new Box(); +Выведение типов и обобщённые методы -- В описании обобщённых методов я уже говорил о выведении типов, которое делает возможным вызов обобщённого метода так, будто это обычный метод, без указания типа в угловых скобках. Можно долго и скучно писать очередной пример, смысл которого сведётся к тому, что вы видите на экране, есть коты, есть коробки, есть метод, который кладёт котов в коробки. Очевидно, что обобщённый метод addBox объявляет один параметр типа U. В большинстве случаев компилятор Java может вывести параметры типа вызова обобщённого метода, в результате чаще всего вовсе не обязательно их указывать. Например, чтобы вызвать обобщённый метод addBox, можно указать параметры типа как это указано на второй строке либо можно вовсе опустить их, и тогда компилятор Java автоматически выведет тип Cat из аргументов метода при вызове как на третьей строке. +Выведение типов и создание экземпляра обобщённого класса. Можно заменить аргументы типа, необходимые для вызова конструктора обобщённого класса пустым множеством параметров типа (пустые треугольные скобки, также называемые, напомню, бриллиантовой операцией), так как компилятор может вывести аргументы типа из контекста. Можно сказать, что это довольно привычная ситуация, потому что все примеры создания объектов, которые мы рассматриваем, включают в себя выведение типа конструктора и бриллиантовую операцию. -Поэтому Box  — это сырой тип обобщённого типа Box . Однако необобщённый класс или интерфейс НЕ являются сырыми типами. -Можно часто увидеть использование сырых типов в старом коде, поскольку многие классы, например коллекции, до Java 5 были необобщёнными. Когда используются сырые типы, по сути получается то же самое поведение, которое было до введения обобщений в Java. -Для совместимости со старым кодом допустимо присваивать параметризованный тип своему собственному сырому типу: +Выведение типа и обобщённые конструкторы обобщённых и необобщённых классов. Очевидно, что конструкторы могут быть обобщёнными как в обобщённых, так и в необобщённых классах. Вот, например, в этой ситуации мы можем явно указать что у коробки будет обобщённый аргумент Кот, а у конструктора -- вообще какой-то другой аргумент, например, строка или число. Компилятор выведет тип String для этого формального параметра U, так как фактически переданный аргумент является экземпляром класса String. Опять же, в джава 1.7 и выше предпочтительно явно использовать бриллиантовую операцию. Важно запомнить, что алгоритм выведения типа использует только аргументы вызова, целевые типы и возможно очевидный ожидаемый возвращаемый тип для выведения типов. Алгоритм выведения не использует последующий код программы. \\ \hline -Box integerBox = new Box<>(); -Box box = integerBox; // OK +08-21 & Целевые типы. Целевой тип выражения -- это тип данных, который компилятор Java ожидает в зависимости от того, в каком месте находится выражение. \\ \hline +08-22 & Компилятор Java пользуется целевыми типами для вывода параметров типа вызова обобщённого метода. Выражаясь проще -- ещё на шажок приближает нас к динамической типизации, позволяя не особенно думать, к чему приводить типы в методах дженерик классов. Например, как в методе Box.emptyBox() со сто пятнадцатой строки. В методе мейн на 121й строке инициализация ожидает экземпляр Box. Этот тип данных является целевым типом. Поскольку метод emptyBox возвращает значение обобщённого типа Box, компилятор сам сообразит, что аргумент T будет типом String. Это работает как в Java 7, так и в более поздних версиях. Конечно, можно указывать аргумент типа напрямую, но в данном случае в этом нет необходимости. \\ \hline -Но если попытаться присвоить параметризованному типу сырой тип, то будет предупреждение (warning): +08-23 & Перед тем, как продолжить, предлагаю немного расслабиться, припомнить о чём уже успели поговорить и ответить буквально на два несложных вопроса. Что из перечисленного является недопустимым? Слишком плохо будут восприниматься на слух варианты ответов, поэтому скажу, чем они отличаются -- в идентификаторе тип с ограничением по намбэ, в конструкторе намбэ, интеджер и стринг и есть хороший четвёртый вариант -- всё допустимо -Box box = new Box(); -Box integerBox = box; // Warning +... 30 сек... +Правильный ответ: 3. Первое допустимо по очевидной причине, число -- это точно число. Целое число является дочерним классом числа, поэтому (2) допустимо. Но String не является дочерним классом Number, поэтому (3) недопустимо. -Также будет предупреждение (warning), если попытаться вызвать обобщённый метод в сыром типе: +Хорошо, предположим ситуацию немного сложнее, параметры метода ограничены с одной стороны для источника и с другой стороны для назначения, вызов метода вы также можете видеть на экране. Какой тип данных будет взят в качестве Т в параметре метода? Animal Cat или Object -Box integerBox = new Box<>(); -Box box = integerBox; -box.setKey(4); // Warning +... 30 сек... +как и было сказано ранее -- будет подобран такой тип, который будет подходить под эти ограничения, а поскольку никто не может быть супертипом объекта, а кот -- это не супертип животного, очевидно, в качестве Т будет взят энимал. Эта замечательная информация пригодится нам для следующей темы. +\\ \hline -Предупреждение показывает, что сырой тип обходит проверку обобщённого типа, что откладывает обнаружение ошибки на выполнение программы. -Еще один момент, допустим, есть такой пример: +08-24 & Есть ощущение, что я недостаточно подробно рассказал про подстановочные символы. Со всей ответственностью, помощью всех источников интернета, Герберта Шилдта и личного опыта -- исправляюсь. \\ \hline -public class App -{ - public static void main( String[] args ) { - Box> box = new Box<>(); - - box.setKey(4); - box.setValue(new Box<>(2, "cats")); - - Box boxCopy = box; - - System.out.println("We have " + box.getKey() + " boxes with " + box.getValue().getKey() + " " + box.getValue().getValue() + " in each box."); - System.out.println("We have " + boxCopy.getKey() + " boxes with " + boxCopy.getValue().getKey() + " " + boxCopy.getValue().getValue() + " in each box."); // Is not OK - } -} +08-25 & В обобщённом коде знак вопроса, называемый подстановочным символом, означает неизвестный тип. Подстановочный символ может использоваться в разных ситуациях: как параметр типа, поля, локальной переменной, иногда в качестве возвращаемого типа. Подстановочный символ никогда не используется в качестве аргумента типа для вызова обобщённого метода, создания экземпляра обобщённого класса или супертипа. А вот подробнее о том, как можно его использовать -- сейчас и поговорим. \\ \hline +08-26 & Я уже упоминал, что можно ограничить передаваемый тип сверху, чтобы уточнить и ограничить тип. Можно использовать подстановочный символ, ограниченный сверху, чтобы ослабить ограничения переменной. Например, если необходимо написать метод, который работает с коробками Integer-ов, коробками Double-ов и коробками Number-ов, этого можно достичь с помощью ограниченного сверху подстановочного символа. Чтобы объявить ограниченный сверху подстановочный символ, нужно воспользоваться символом вопроса "?", с последующим ключевым словом extends, с последующим ограничением сверху. Не лишним будет дополнительно проговорить, что в этом контексте extends означает как расширение класса, так и реализацию интерфейса. +% 02 -В данном случае будет ошибка: +Чтобы написать метод, который работает с коробками, в которых Number и дочерними типами от Number, например Integer, Double и Float, можно указать Box. В то время, как Box ввёл бы более жёсткое ограничение, чем Box, потому что конструкция Box соответствует только коробкам типа Number, в то время, как если это будет просто аргумент, а не дженерик аргумент -- никаких проблем с наследованием не будет, программирование не сломалось, а Box соответствует коробкам типа Number и коробкам всех подклассов намбэ. +% 03 -java: cannot find symbol - symbol: method getKey() - location: class java.lang.Object +Картинки -- это конечно интересно и всё такое, но надо бы и в коде дополнительно рассмотреть. Будем опять мучить традиционный Animal, у которого есть поле name и переопределённый метод toString() для вывода информации, также сразу добавим котика с пёсиком, прямых наследников животного. +% 04 +Подготовим коробочку и соответствующий метод, выводящий информацию о её содержимом. Внутри метода сделаем ограниченный сверху подстановочный символ , где вместо Animal-ом может быть любой тип, а значит эта подстановка будет соответствовать Animal и любому подтипу Animal. Метод printInfo может выводить информацию о коробке, внутри которой мы будем выводить информацию об объекте, содержащемся в ней. А объект, как мы помним в нашем случае является наследником Animal, включая сам класс Animal. +% 05 -Это происходит по той причине, что до Java 5 обобщения не знали ничего о своих типах, поэтому возвращали Object. Для возможности взаимодействовать с типами, нужно сделать каст к соответствующему типу. В данном случае нужно сделать каст к Box: +Осталось только создать котика, пёсика, аккуратно положить их в коробки и вывести информацию о коробках нашим удобным методом. Видим, как великолепно дженерики справляются со своей задачей. А как мы помним со слайов чуть ранее, если бы мы просто запросили в параметре Животное, передать кота не получилось бы. \\ \hline -System.out.println("We have " + box1.getKey() + " boxes with " + ((Box)box1.getValue()).getKey() + " " + ((Box)box1.getValue()).getValue() + " in each box."); +08-27 & Если просто использовать подстановочный символ, то получится подстановочный символ без ограничений. Box означает коробку с неизвестным содержимым (неизвестным типом). Казалось бы, зачем такой синтаксис существует? Во первых, потому что если Вы используете обобщённые типы, Вы можете продолжать их использовать, не особенно заботясь о том, что Вам в данной конкретной ситуации не нужно использовать обобщённую функциональность. Отсюда можно сделать вывод о том, что неограниченный подстановочный символ полезен в двух случаях: Если нужен метод, который может быть реализован с помощью функциональности класса Object. Когда код использует методы обобщённого класса, которые не зависят от параметра типа. В программах, использующих АПИ рефлексии языка конструкция тип Класс с подстановочным символом без ограничений используется чаще других конструкций, потому что большинство методов объекта Класс не зависят от расположенного внутри Т. Но более подробно о классе Класс позже, на лекциях по рефлексии. +пролистать два слайда назад +Можно обратить внимание, что, например, метод с предыдущего слайда, принтинфо, не использует никаких методов животного, Цель метода printInfo — вывод в консоль информации об объекте в коробке любого типа -Сообщения об ошибках “unchecked” -Как упоминалось выше, при использовании сырого типа можно столкнуться с предупреждениями вида: +пролистать обратно три слайда вперёд +% 02 -Note: .java uses unchecked or unsafe operations. -Note: Recompile with -Xlint:unchecked for details. +поэтому в параметре метода можно заменить коробку с наследниками животного на коробку с чем угодно, потому что всё равно там будет использоваться только метод коробки туСтринг. В то же время не лишним будет напомнить, что нельзя указать в обобщённом аргументе Обжект, потому что это будет означать именно обжект, а не обжект и его наследников, как это бывает в обычной программе. Важно запомнить, что Box с Object в треугольных скобках и Box с вопросительным знаком в треугольных скобках -- это НЕ одно и то же. \\ \hline +08-28 & Ограниченный снизу подстановочный символ ограничивает неизвестный тип так, чтобы он был либо указанным типом, либо одним из его предков. Более наглядно -- на слайде, более понятно уже не получится -- либо сам тип либо его предки по иерархии наверх вплоть до обжекта. В целом, в обобщённых конструкциях можно указать либо только верхнюю границу для подстановочного символа, либо только нижнюю, но также можно указать оба ограничения сразу, хотя для этого варианта придётся продумать довольно нетривиальную стратегию передачи параметра, потому что напрямую сделать это как показано на слайде -- не выйдет. +% 02 -Термин "unchecked" означает непроверенные, то есть компилятор не имеет достаточного количества информации для обеспечения безопасности типов. По умолчанию этот вид предупреждений выключен, поэтому компилятор даёт подсказку. Чтобы видеть все "unchecked" предупреждения нужно перекомпилировать код с опцией -Xlint:unchecked : +То есть, если написать тот же самый метод принтИнфо, с параметром коробка и обобщённым аргументом не экстендс, а супер животное, то этот код также не будет работать, потому что метод будет ожидать не животное и наследников, а животное и родителей, то есть обжект. \\ \hline -javac -Xlint:unchecked .java +08-29 & +Обобщённые классы или интерфейсы связаны не только из-за связи между их типами. Однако можно использовать подстановочные символы (wildcards) для создания связи между обобщёнными классами и интерфейсами. С обычными необобщёнными классами Кота и Животного имеет смысл писать какой-нибудь простой незатейливый код, который мы уже десятки, если не сотни раз видели и писали сами. Этот код показывает, что наследование работает по правилу подчинённых типов: класс Cat является подклассом класса Animal, и расширяет его. +% 02 -Предупреждения будут такого вида: +И правило не работает для обобщённых типов. Но, если Cat является дочерним типом для Animal, то какая связь между Коробкой с и Коробкой с ? Несмотря на то, что Cat является подтипом Animal, Box не является подтипом Box. Это разные типы. Общим предком для Box и Box является Box с подстановочным символом . +% 03 -App.java:23: warning: [unchecked] unchecked call to setKey(K) as a member of the raw type Box - boxCopy.setKey(4); - ^ - where K is a type-variable: - K extends Object declared in class Box -1 warning +Для того чтобы создать такую, хорошо визуализируемую связь между этими классами коробок, чтобы код мог иметь доступ к методам Animal через Box, как раз и используется он, подстановочный символ. Так как Cat является дочерним типом от Animal, и животное в коробке является коробкой, в которой тип Animal, теперь существует связь между котом в коробке, точнее, коробкой с объектом типа Cat) и коробкой с животным. \\ \hline +08-30 & В некоторых случаях компилятор может вывести тип подстановочного символа. Коробка может быть определена как коробка с неограниченным подстановочным символом, но при вычислении выражения компилятор выведет конкретный тип из кода, такой сценарий называется захватом подстановочного символа. Что это нам даёт? То, что в большинстве случаев нет нужды беспокоиться о захвате подстановочного символа, кроме случаев, когда в сообщении об ошибке появляется фраза “capture of”. - -Обобщённые методы -Обобщённые методы похожи на обобщённые классы, но параметры типа относятся к методу, а не к классу. Допустимо делать обобщёнными статические и нестатические методы, а также конструкторы. -Синтаксис обобщённого метода включает параметры типа внутри угловых скобок, которые указываются перед возвращаемым типом. - -class Utils { - - static void setIfNull(Box box, T t) { - - if (box.getValue() == null) { - box.setValue(t); - } - } -} - - -Пример вызова обобщенного метода: - -public class App { - - public static void main( String[] args ) { - - Box box = new Box<>(); - Utils.setIfNull(box, 13); - System.out.println(box.getValue()); - } -} - - -Вывод: - -13 - - -Более подробно выведение типа будет описано ниже. - -Ограниченные параметры типа -В некоторых случаях имеет смысл ограничить  типы, которые можно использовать в качестве аргументов в параметризованных типах. Например, в коробке-Box нужно сделать так, чтобы хранились только числа и разного рода (наследники Number). Подобное ограничение можно сделать с помощью ограниченного параметра типа (bounded type parameters). -Чтобы объявить ограниченный параметр типа нужно после имени параметра указать ключевое слово extends, а затем указать верхнюю границу (upper bound), которой в данном примере является класс Number. В этом контексте extends означает как extends, так и implements. - -public class Box { - public V getValue() { - return value; - } - public void setValue(V value) { - this.value = value; - } - private V value; -} - - -В этом примере ограничены возможные типы, которые можно использовать в параметризованных классах Box, наследниками класса Number. Если попытаться указать, например, Box , то возникнет ошибка компиляции. -Аналогичным образом можно создавать обобщённые методы с ограничением: - -class Utils { - - static void setIfNull(Box box, T t) { - - if (box.getValue() == null) { - box.setValue(t); - } - } -} - - -Можно указать несколько верхних границ, перечисляя их через символ «&», но при этом только один класс может быть указан в списке верхних границ, и он должен стоять первым: - -class Bird{} - -interface Animal{} - -interface Man{} - -class Box { - // ... -} - - - -Обобщения, наследование и дочерние типы -Как уже известно, можно присвоить объекту одного типа объект другого типа, если эти типы совместимы. Например, можно присвоить объект типа Integer переменной типа Object, так как Object является одним из супертипов Integer: - -Object someObject = new Object(); -Integer someInteger = new Integer(13); -someObject = someInteger;   // OK - - -В объектно-ориентированной терминологии это называется связью «является» (“is a”). Так как Integer  является Object-ом, то такое присвоение разрешено. Но Integer также является и Number, поэтому следующий код тоже корректен: - -public void someMethod(Number n) { /* ... */ } - -someMethod(new Integer(10));   // OK -someMethod(new Double(10.1));   // OK - - -Это также верно для обобщений. Можно осуществить вызов обобщённого типа, передав Number в качестве аргумента типа, и любой дальнейший вызов будет разрешён, если аргумент совместим с Number : - -Box box = new Box(); -box.add(new Integer(10));   // OK -box.add(new Double(10.1));  // OK - - -Теперь нужно рассмотреть метод: - -public void boxTest(Box n) { /* ... */ } - - -Какой тип аргумента он будет принимать? Если посмотреть на сигнатуру, то можно увидеть, что он принимает один аргумент с типом Box. Но что это означает? Можно ли передать Box или Box , как можно можно было бы ожидать? Нет, нельзя, так как Box и Box не являются потомками Box. -Это частое недопонимание принципов работы обобщений, и это важно знать. -Запомнить: Для двух типов A и B (например, Number и Integer ), MyClass не имеет никакой связи или родства с MyClass , независимо от того, как A и B связаны между собой. Общий родитель MyClass и MyClass — это Object. -Можно указать обобщённый класс или интерфейс в качестве родительского для своего класса или интерфейса. Связь между параметрами типа одного класса или интерфейса и другого определяются ключевыми словами extends и implements . -Для классов коллекций, например, ArrayList implements List , и List extends Collection . Также ArrayList является дочерним типом для List , который является дочерним типом Collection . Наследование между типами сохраняется, пока не поменяется аргумент типа. -Допустим, нужно определить свой собственный интерфейс большой коробки MegaBox, который связывает необязательное значение P с каждым элементом. Объявление может выглядеть так: - -interface MegaBox extends Box { - void setMegaBox(int index, P val); - ... -} - - -Следующие параметризованные типы являются дочерними типами для Box, но не связаны между собой: - -MegaBox -MegaBox -MegaBox - - -Выведение типов -Выведение типов — это возможность компилятора Java автоматически определять аргументы типа на основе контекста, чтобы вызов получился возможным. Алгоритм выведения типов определяет типы аргументов и, если есть, тип, в который присваивается результат или в котором возвращается результат. Далее алгоритм пытается найти наиболее конкретный тип, который работает со всеми аргументами. -В приведённом ниже примере выведение типов определяет, что второй аргумент, они же Cat и ArrayList, передаваемые в методы pick  имеют тип Serializable : - -public class App { - - static T pick(T first, T second) { return second; } - - public static void main( String[] args ) { - Serializable serializable1 = pick("d", new Cat()); - Serializable serializable2 = pick("d", new ArrayList()); - } -} - - -Непосредственно класс Cat: - -public class Cat implements Serializable { - public void voice(){ - // ... - } - // ... -} - - - -Выведение типов и обобщённые методы -В описании обобщённых методов уже рассказывалось о выведении типов, которое делает возможным вызов обобщённого метода так, будто это обычный метод, без указания типа в угловых скобках <>. Пример: -Класс Box: - -public class Box { - public T getValue() { - return value; - } - public void setValue(T value) { - this.value = value; - } - private T value; -} - - -Класс Cat: - -public class Cat { - private String name; - - public Cat(String name){ - this.name = name; - } - - @Override - public String toString() { - return "Cat with name " + name; - } -} - - -Основной класс: - -public class App { - - public static void addBox(U u, List> boxes){ - Box box = new Box<>(); - box.setValue(u); - boxes.add(box); - } - - public static void printBoxes(List> boxes){ - for(Box box : boxes){ - U value = box.getValue(); - System.out.println("In box - " + value); - } - } - - public static void main( String[] args ) { - ArrayList> catsInBoxes = new ArrayList<>(); - addBox(new Cat("Kusya"), catsInBoxes); - addBox(new Cat("Vasya"), catsInBoxes); - addBox(new Cat("Murka"), catsInBoxes); - printBoxes(catsInBoxes); - } -} - - -Этот код выведет в консоль следующее: - -In box - Cat with name Kusya -In box - Cat with name Vasya -In box - Cat with name Murka - - -Обобщённый метод addBox объявляет один параметр типа U . В большинстве случаев компилятор Java может вывести параметры типа вызова обобщённого метода, в результате чаще всего вовсе не обязательно их указывать. Например, чтобы вызвать обобщённый метод addBox, можно указать параметры типа так: - -App.addBox(new Cat("Kusya"), catsInBoxes); - - -Либо можно опустить их, и тогда компилятор Java автоматически выведет тип Cat из аргументов метода: - -addBox(new Cat("Kusya"), catsInBoxes); - - - -Выведение типов и создание экземпляра обобщённого класса -Также можно заменить аргументы типа, необходимые для вызова конструктора обобщённого класса пустым множеством параметров типа ( <> ), так как компилятор может вывести аргументы типа из контекста. -Напоминание: эта пара угловых скобок называется бриллиантовой операцией (diamond operator). -Объявление переменной на примере Box с двумя параметрами типов: - -Box> myBox = new Box>(); - - -В данном примере, можно заменить параметризованный тип конструктора пустыми угловыми скобками ( <> ): - -Box> myBox = new Box<>(); - - -Стоит обратить внимание, что для того чтобы воспользоваться выведением типов при создании экземпляра обобщённого класса, нужно использовать бриллиантовую операцию (diamond operator). В примере ниже компилятор сгенерирует предупреждение unchecked conversion warning, так как конструктор Box() обращается к сырому типу Box, а не к Box> : - -Box> myBox = new Box(); // unchecked conversion warning - - - -Выведение типа и обобщённые конструкторы обобщённых и необобщённых классов -Конструкторы могут быть обобщёнными как в обобщённых, так и в необобщённых классах. Пример: - -public class Box { - Box(U u){ - // ... - } - public T getValue() { - return value; - } - public void setValue(T value) { - this.value = value; - } - private T value; -} - - -Создание экземпляра класса Box: - -Box box = new Box("Some message"); - - -Эта инструкция создаёт экземпляр параметризованного типа Box . Инструкция явно указывает Cat в качестве формального параметра типа T обобщённого класса Box . Стоит обратить внимание, что конструктор этого обобщённого класса содержит параметр типа U. Компилятор выводит тип String для этого формального параметра U, так как фактически переданный аргумент является экземпляром класса String. -Компилятор Java 7 и более поздней версии может вывести аргументы типа создаваемого экземпляра обобщённого класса с помощью бриллиантовой операции (diamong operator). Пример: - -Box box = new Box<>("Some message"); - - -В этом примере компилятор выводит Cat для параметра типа T обобщённого класса Box. Он выводит тип String для параметра U обобщённого конструктора обобщённого класса. -_Важно запомнить, что алгоритм выведения типа использует только аргументы вызова, целевые типы и возможно очевидный ожидаемый возвращаемый тип для выведения типов. Алгоритм выведения не использует последующий код программы. - -Целевые типы -Компилятор Java пользуется целевыми типами для вывода параметров типа вызова обобщённого метода. Целевой тип выражения — это тип данных, который компилятор Java ожидает в зависимости от того, в каком месте находится выражение. Например, метод Box.emptyBox() , который присутствует в классе Box: - -public class Box { - public static final Box EMPTY_BOX = new Box<>(); - - public T getValue() { - return value; - } - - public void setValue(T value) { - this.value = value; - } - - private T value; - - static Box emptyBox(){ - return (Box) EMPTY_BOX; - } -} - - -Есть следующая инструкция присвоения: - -public static void main( String[] args ) { - Box box = Box.emptyBox(); -} - - -Эта инструкция ожидает экземпляр Box . Этот тип данных является целевым типом. Поскольку метод emptyBox  возвращает значение типа Box , компилятор выводит, что аргумент типа T будет типом String. Это работает как в Java 7, так и в Java 8. Также можно указывать аргумент типа напрямую: - -public static void main( String[] args ) { - Box box1 = Box.emptyBox(); -} - - -Но в данном случае в этом нет необходимости. Это может быть необходимо в других случаях. Допустим, есть метод: - -static void boxWithString(Box box){ - // ... -} - - -Если нужно вызвать метод boxWithString с пустой коробкой. В Java 7 следующий код не будет работать: - -boxWithString(Box.emptyBox()); - - -Компилятор Java 7 сгенерирует примерно такую ошибку: - -java: incompatible types: com.myapp.qs.Box cannot be converted to com.myapp.qs.Box - - -Компилятору необходимо значение аргумента типа для T, и он начинает с Object. В результате вызов Box.emptyBox() возвращает тип Box, который несовместим с методом boxWithString. Таким образом в Java 7 нужно указать аргумент типа так: - -boxWithString(Box.emptyBox()); - - -В Java 8 в этом больше нет необходимости. Термин целевой тип расширен и включает аргументы методов. В этом случае boxWithString-у необходим аргумент типа Box Метод Box.emptyBox() возвращает значение типа Box, компилятор выводит аргумент типа T как String, используя целевой тип Box. Таким образом в Java 8 следующая инструкция успешно скомпилируется: - -boxWithString(Box.emptyBox()); - - - -Подстановочный символ (wildcard) -В обобщённом коде знак вопроса (?), называемый подстановочным символом, означает  неизвестный тип. Подстановочный символ может использоваться в разных ситуациях: как параметр типа, поля, локальной переменной, иногда в качестве возвращаемого типа. -Подстановочный символ никогда не используется в качестве аргумента типа для вызова обобщённого метода, создания экземпляра обобщённого класса или супертипа. - -Подстановочный символ, ограниченный сверху (Upper bounded wildcard) -Можно использовать подстановочный символ, ограниченный сверху, чтобы ослабить ограничения переменной. Например, если необходимо написать метод, который работает с Box , Box  и Box , этого можно достичь с помощью ограниченного сверху подстановочного символа. -Чтобы объявить ограниченный сверху подстановочный символ, нужно воспользоваться символом вопроса "?", с последующим ключевым словом extends, с последующим ограничением сверху. Стоит запомнить, что в этом контексте extends означает как расширение класса, так и реализацию интерфейса. -Чтобы написать метод, который работает с коробками, в которых Number и дочерними типами от Number, например Integer, Double и Float, можно указать Box. Box вводит более жёсткое ограничение, чем Box, потому что оно соответствует только коробкам типа Number, а Box  соответствует коробкам типа Number и спискам всех его подклассов. -Следующий пример, есть класс Animal, у которого присутствует поле name и переопределённый метод toString() для вывода информации о классах наследниках в будущем: - -public class Animal { - private String name; - - public Animal(String name){ - this.name = name; - } - - @Override - public String toString() { - return this.getClass().getSimpleName() + " with name " + name; - } -} - - -Класс Dog, который наследуется от Animal: - -public class Dog extends Animal{ - - public Dog(String name) { - super(name); - } -} - - -И аналогичный класс Cat, который тоже наследуется от Animal: - -public class Cat extends Animal { - - public Cat(String name) { - super(name); - } -} - - -Ограниченный сверху подстановочный символ  , где Animal  — любой тип, соответствует Animal и любому подтипу Animal. Метод printInfo может выводить информацию об объекте в коробке, который является наследником Animal, включая сам класс Animal. Следующий код демонстрирует, как метод принимает коробку с котиком и коробку с собакой, после чего выводит информацию о них: - -public class App { - - static void printInfo(Box animalInBox){ - System.out.println("Information about animal: " + animalInBox); - } - - public static void main( String[] args ) { - Box catInBox = Box.emptyBox(); - catInBox.setValue(new Cat("Vasya")); - printInfo(catInBox); - - Box dogInBox = Box.emptyBox(); - dogInBox.setValue(new Dog("Kusya")); - printInfo(dogInBox); - } -} - - -Вывод: - -Information about animal: Cat with name Vasya in box -Information about animal: Dog with name Kusya in box - - - -Неограниченный подстановочный символ (Unbounded wildcard) -Если просто использовать подстановочный символ, то получится подстановочный символ без ограничений. Box означает коробку с неизвестным содержимым (неизвестным типом). -Неограниченный подстановочный символ полезен в двух случаях: - -Если нужен метод, который может быть реализован с помощью функциональности класса Object. -Когда код использует методы обобщённого класса, которые не зависят от параметра типа. Например, List.size() или List.clear(). В реальности Class  используется так часто, потому что большинство методов Class не зависят от T. - -Измененный метод printInfo : - -public class App { - - static void printInfo(Box box){ - System.out.println("Information about box: " + box); - } - - public static void main( String[] args ) { - Box catInBox = Box.emptyBox(); - catInBox.setValue(new Cat("Vasya")); - printInfo(catInBox); // java: incompatible types: com.myapp.qs.Box cannot be converted to com.myapp.qs.Box - } -} - - -Цель метода printInfo  — вывод в консоль информации об объекте в коробке любого типа, но сейчас он её не выполняет, так как он может вывести в консоль только объект типа Object. Он не может принимать в качестве параметра Box, Box, Box и какие-либо ещё, как показано в попытке передать в метод коробку с котом Вася, так как они не являются дочерними типами для Box. Чтобы сделать этот метод более общим, нужно использовать Box : - -public class App { - - static void printInfo(Box box){ - System.out.println("Information about box: " + box); - } - - public static void main( String[] args ) { - Box animalInBox = Box.emptyBox(); - animalInBox.setValue(new Animal("Vasya")); - printInfo(animalInBox); // OK - } -} - - -Box  является дочерним типом для Box для любого конкретного типа вместо Animal, поэтому можно использовать printInfo для вывода в консоль информации о коробке с любым переданным в него типом. -Важно запомнить, что Box  и Box  — это НЕ одно и то же. Можно вставить Object или любой дочерний тип от Object в Box. Но можно вставить только null в Box. - -Ограниченный снизу подстановочный символ (Lower bound Wildcard) -Ограниченный снизу подстановочный символ ограничивает неизвестный тип так, чтобы он был либо указанным типом, либо одним из его предков. -Можно указать либо только верхнюю границу для подстановочного символа, либо только нижнюю, но также можно указать оба ограничения сразу. -Допустим, что необходимо модифицировать метод printInfo для вывода информации о котиках. Чтобы максимизировать гибкость, может захотеться, чтобы метод работал с Box, Box и Box  — всё, что могут хранить экземпляры класса Cat. -Чтобы написать метод, который работает с Cat и супертипами Cat (такими как Cat, Animal и Object), можно указать Box. Вариант Box  более ограничен, чем Box, потому что он позволяет использовать только коробки с объектами типа Cat, тогда как Box соответствует коробкам с любым родительским классом от Cat и самому Cat в коробке. -Следующий код демонстрирует вывод информации о котах и животных. И не возможности вывести информацию о Мейн-кунах, которые наследуются от Cat. Новый класс MaineCoon: - -public class MaineCoon extends Cat{ - - public MaineCoon(String name) { - super(name); - } - - // ... -} - - -Реализации в главном методе: - -public class App { - - static void printInfo(Box box){ - System.out.println("Information about box: " + box); - } - - public static void main( String[] args ) { - Box animalInBox = Box.emptyBox(); - animalInBox.setValue(new Animal("Rick")); - printInfo(animalInBox); // OK - - Box catInBox = Box.emptyBox(); - catInBox.setValue(new Cat("Vasya")); - printInfo(catInBox); // OK - - Box maineCoonInBox = Box.emptyBox(); - maineCoonInBox.setValue(new MaineCoon("Kate")); - printInfo(maineCoonInBox); // Incompatible types!!! - } -} - - -Вывод: - -// Without MaineCoon -Information about box: Animal with name Rick in box -Information about box: Cat with name Vasya in box - -// With MaineCoon -java: incompatible types: com.myapp.qs.Box cannot be converted to com.myapp.qs.Box - - - -Подстановочные символы и дочерние типы -Как было описано в пункте "Обобщения, наследование и дочерние типы", обобщённые классы или интерфейсы связаны не только из-за связи между их типами. Однако можно использовать подстановочные символы (wildcards) для создания связи между обобщёнными классами и интерфейсами. -С данными обычными (необобщёнными) классами: - -class Animal { /* ... */ } -class Cat extends Animal { /* ... */ } - - -Имеет смысл написать вот такой код: - -public static void main( String[] args ) { - Cat cat = new Cat("Vasya"); - Animal animal = cat; -} - - -Этот пример показывает, что наследование следует правилу подчинённых типов: класс Cat является подклассом класса Animal, если он расширяет его. Это правило НЕ работает для обобщённых типов: - -public static void main( String[] args ) { - Box catInBox = new Box<>(); - Box animalInBox = catInBox; // Incompatible types!!! -} - - -Вывод: - -java: incompatible types: com.myapp.qs.Box cannot be converted to com.myapp.qs.Box - - -Если Cat является дочерним типом для Animal, то какая связь между Box и Box? -Не смотря на то, что Cat является подтипом Animal, Box не является подтипом Box. Это разные типы. Общим предком для Box и Box  является Box. -Для того чтобы создать такую связь между этими классами, чтобы код мог иметь доступ к методам Animal через Box, используется подстановочный символ: - -public static void main( String[] args ) { - Box catInBox = new Box<>(); - Box animalInBox = catInBox; // OK. Box child type from Box -} - - -Так как Cat является дочерним типом от Animal, и animalInBox является коробкой, в которой тип Animal, теперь существует связь между catInBox (коробка с объектом типа Cat ) и animalInBox. Следующая диаграмма показывает связь между несколькими классами List, объявленными с ограниченными сверху подстановочными символами и ограниченными снизу подстановочными символами. - - -Захват символа подстановки (Wildcard Capture) и вспомогательные методы -В некоторых случаях компилятор может вывести тип подстановочного символа. Коробка может быть определена как Box, но при вычислении выражения компилятор выведет конкретный тип из кода. Этот сценарий называется захватом подстановочного символа. -В большинстве случаев нет нужды беспокоиться о захвате подстановочного символа, кроме случаев, когда появляется фраза “capture of” в сообщении об ошибке. Следующий код выводит сообщение об ошибке, связанное с захватом подстановочного символа, при компиляции: static void testError(Box box){ @@ -834,11 +238,11 @@ static void testError(Box box){ Вывод: -java: incompatible types: int cannot be converted to capture#1 of ? +%java: incompatible types: int cannot be converted to capture#1 of ? -В этом примере компилятор обрабатываем параметр box как тип Object. Когда метод testError вызывает box.setValue(box.getValue()), компилятор не может подтвердить тип объекта, который будет класться в коробку, и генерирует ошибку. Когда возникает этот тип ошибки, это обычно означает, что компилятор верит, что вы присваиваете неправильный тип переменной. Обобщения были добавлены в Java именно для этого — чтобы усилить безопасность типов во время компиляции. -Код пытается выполнить безопасную операцию, тогда как можно обойти ошибку компиляции? Это возможно исправить написав приватный вспомогательный метод (private helper method), который захватывает подстановочный символ. В этом случае можно обойти проблему с помощью создания приватного вспомогательного метода testErrorHelper(): +В этом примере компилятор обрабатываем параметр box как тип Object. Когда метод testError вызывает box.setValue(box.getValue()), компилятор не может подтвердить тип объекта, который будет класться в коробку, и генерирует ошибку. Когда возникает этот тип ошибки, это обычно означает, что компилятор верит, что вы присваиваете неправильный тип переменной. Обобщения были добавлены в Java именно для этого — чтобы усилить безопасность типов во время компиляции. +Код пытается выполнить безопасную операцию, тогда как можно обойти ошибку компиляции? Это возможно исправить написав приватный вспомогательный метод (private helper method), который захватывает подстановочный символ. В этом случае можно обойти проблему с помощью создания приватного вспомогательного метода testErrorHelper(): private static void testErrorHelper(Box box){ box.setValue(box.getValue()); // OK @@ -848,28 +252,43 @@ static void testError(Box box){ testErrorHelper(box); } - -Благодаря вспомогательному методу компилятор использует выведение типа для определения, что T является CAP#1 ( захваченная переменная в вызове). Пример теперь успешно компилируется. -По соглашению вспомогательные методы обычно называются как "originalMethodNameHelper". +%Благодаря вспомогательному методу компилятор использует выведение типа для определения, что T является CAP#1 (захваченная переменная в вызове). Пример теперь успешно компилируется. +По соглашению вспомогательные методы обычно называются как "originalMethodNameHelper". Руководство по использованию подстановочного символа Когда использовать ограниченный сверху подстановочный символ (wildcard), и когда использовать ограниченный снизу подстановочный символ, - определить зачастую бывает довольно сложно. Здесь собраны советы по выбору необходимого ограничения для подстановочного символа. В этом обсуждении будет полезно думать о переменных, будто они представляют две функции: -Входная переменная. Предоставляет данные для кода. Для метода copy(src, dst) параметр src предоставляет данные для копирования, поэтому он считается входной переменной. +Входная переменная. Предоставляет данные для кода. Для метода copy(src, dst) параметр src предоставляет данные для копирования, поэтому он считается входной переменной. -Выходная переменная. Содержит данные для использования в другом месте. В примере с copy(src, dst) параметр dst принимает данные и является выходной переменной. +Выходная переменная. Содержит данные для использования в другом месте. В примере с copy(src, dst) параметр dst принимает данные и является выходной переменной. Некоторые переменную могут быть одновременно входными и выходными, такой случай тоже здесь рассматривается. Руководство: -Входная переменная определяется с ограниченным сверху подстановочным символом, используя ключевое слово extends. -Выходная переменная определяется с ограниченным снизу подстановочным символом, используя ключевое слово super. +Входная переменная определяется с ограниченным сверху подстановочным символом, используя ключевое слово extends. +Выходная переменная определяется с ограниченным снизу подстановочным символом, используя ключевое слово super. Если к входной переменной можно обращаться только используя методы класса Object, использовать стоит неограниченный подстановочный символ. Если переменная должна использоваться как входная и как выходная одновременно, то НЕ стоит использовать подстановочный символ. -Это руководство не охватывает использование подстановочных символов в возвращаемых из методов типах. Не стоит использовать подстановочные символы в возвращаемых типах, потому что это будет принуждать других программистов разбираться с подстановочными символами. +Это руководство не охватывает использование подстановочных символов в возвращаемых из методов типах. Не стоит использовать подстановочные символы в возвращаемых типах, потому что это будет принуждать других программистов разбираться с подстановочными символами. \\ \hline + +08-31 & \\ \hline +08-32 & \\ \hline +08-33 & \\ \hline +08-34 & \\ \hline +08-35 & \\ \hline +08-36 & \\ \hline +08-37 & \\ \hline +08-38 & \\ \hline +08-39 & \\ \hline + +\end{longtable} + + + +\end{document} Стирание типа (Type Erasure) Обобщения были введены в язык программирования Java для обеспечения более жёсткого контроля типов во время компиляции и для поддержки обобщённого программирования. Для реализации обобщения компилятор Java применяет стирание типа (type erasure) к: @@ -1005,49 +424,18 @@ static void setIfNull(Box box, T t) { /* ... */} static void setIfNull(Box box, Animal t) { /* ... */} -В разделе стирания типов обсуждается процесс, где компилятор удаляет информацию, связанную с параметрами типа и аргументами типа. Стирание типа имеет последствия, связанные с произвольным количеством параметров (varargs). -Материализуемые типы (reifiable types) — это типы, информация о которых полностью доступа во время выполнения: примитивы, необобщённые типы, сырые типы, обращения к неограниченным подстановочным символам. -Нематериализуемые типы (Non-reifiable types) — это типы, информация о которых удаляется во время компиляции стиранием типов: обращения к обобщённым типам, которые не объявлены с помощью неограниченных подстановочных символов. Во время выполнения о нематериализуемых типах (Non-reifiable types) нет всей информации. Примеры нематериализуемых типов: Box  и Box. Виртуальная машина Java не может узнать разницу между ними во время выполнения. В некоторых ситуациях нематериализуемые типы не могут использоваться, например, в выражениях instanceof или в качестве элементов массива. +В разделе стирания типов обсуждается процесс, где компилятор удаляет информацию, связанную с параметрами типа и аргументами типа. Стирание типа имеет последствия, связанные с произвольным количеством параметров (varargs). +Материализуемые типы (reifiable types) — это типы, информация о которых полностью доступа во время выполнения: примитивы, необобщённые типы, сырые типы, обращения к неограниченным подстановочным символам. +Нематериализуемые типы (Non-reifiable types) — это типы, информация о которых удаляется во время компиляции стиранием типов: обращения к обобщённым типам, которые не объявлены с помощью неограниченных подстановочных символов. Во время выполнения о нематериализуемых типах (Non-reifiable types) нет всей информации. Примеры нематериализуемых типов: Box и Box. Виртуальная машина Java не может узнать разницу между ними во время выполнения. В некоторых ситуациях нематериализуемые типы не могут использоваться, например, в выражениях instanceof или в качестве элементов массива. Загрязнение кучи (Heap pollution) -Загрязнение кучи (heap pollution) возникает, когда переменная параметризованного типа ссылается на объект, который не является параметризованным типом. Такая ситуация возникает, если программа выполнила некоторую операцию, которая генерирует предупреждение unchecked warning во время компиляции. Предупреждение unchecked warning генерируется, если правильность операции, в которую вовлечён параметризованный тип (например приведение типа или вызов метода) не может быть проверена. Например, загрязнение кучи возникает при смешивании сырых типов и параметризованных типов, или при осуществлении непроверяемых преобразований типа. +Загрязнение кучи (heap pollution) возникает, когда переменная параметризованного типа ссылается на объект, который не является параметризованным типом. Такая ситуация возникает, если программа выполнила некоторую операцию, которая генерирует предупреждение unchecked warning во время компиляции. Предупреждение unchecked warning генерируется, если правильность операции, в которую вовлечён параметризованный тип (например приведение типа или вызов метода) не может быть проверена. Например, загрязнение кучи возникает при смешивании сырых типов и параметризованных типов, или при осуществлении непроверяемых преобразований типа. В обычных ситуациях, когда код компилируется в одно и то же время, компилятор генерирует unchecked warning, чтобы привлечь внимание к загрязнению кучи. Если компилируются различные части кода отдельно, то становится трудно определить потенциальную угрозу загрязнения кучи. Если обеспечить компиляцию кода без предупреждений, то загрязнение кучи (heap pollution) не сможет произойти. Ограничения обобщений Нельзя создавать экземпляры обобщённых типов с примитивными типами в качестве аргументов типа. -Допустим, есть следующий класс пары ключ-значение: -class Pair { - -    private K key; - -    private V value; - -    public Pair(K key, V value) { - -        this.key = key; - -        this.value = value; - -    } - -    // ... - -} - - -При создании объекта Pair нельзя заменять примитивным типом формальные параметры K и V: - -Pair p = new Pair<>(8, 'a');  // compile-time error - - -Можно заменить их только непримитивными типами: - -Pair p = new Pair<>(8, 'a'); - - -Компилятор использует автоупаковку Integer.valueOf(8)  и Character('a'). Нельзя создавать экземпляры параметров типа Нельзя создать экземпляр параметра типа. Например, следующий код приведёт к ошибке компиляции: @@ -1094,10 +482,10 @@ Box catInBox = new Box<>(); Box dogInBox = new Box<>(); -Так как статическое поле value является общим для animalInBox, catInBox  и dogInBox, то какого типа value? Оно не может быть Animal, Cat и Dog в одно и то же время, поэтому нельзя создавать статические поля с типом параметра типа. Обычно нельзя использовать приведение типа к параметризованному типу, если он не использует неограниченный подстановочный символ. +Так как статическое поле value является общим для animalInBox, catInBox и dogInBox, то какого типа value? Оно не может быть Animal, Cat и Dog в одно и то же время, поэтому нельзя создавать статические поля с типом параметра типа. Обычно нельзя использовать приведение типа к параметризованному типу, если он не использует неограниченный подстановочный символ. Нельзя использовать приведения типа или instanceof с параметризованными типами -Так как компилятор Java стирает все параметры типа из обобщённого кода, то нельзя проверить во время выполнения, какой параметризованный тип используется для обобщённого типа. Во время выполнения нет параметров типа, поэтому нет возможности различить Box и Box. Наибольшее, что можно сделать — это использовать подстановочный символ для проверки. Например: +Так как компилятор Java стирает все параметры типа из обобщённого кода, то нельзя проверить во время выполнения, какой параметризованный тип используется для обобщённого типа. Во время выполнения нет параметров типа, поэтому нет возможности различить Box и Box. Наибольшее, что можно сделать — это использовать подстановочный символ для проверки. Например: Box box1 = new Box<>(); Box box2 = (Box) box1; @@ -1127,14 +515,14 @@ public static void main( String[] args ) throws Exception { } -Если бы массивы с параметризованными типами были бы разрешены, то предыдущий код не смог бы бросить исключение ArrayStoreException. +Если бы массивы с параметризованными типами были бы разрешены, то предыдущий код не смог бы бросить исключение ArrayStoreException. Нельзя создавать, ловить (catch) или бросать (throw) объекты параметризованных типов -Обобщённый класс не может расширять класс Throwable напрямую или не напрямую. Например, следующие классы не компилируются: +Обобщённый класс не может расширять класс Throwable напрямую или не напрямую. Например, следующие классы не компилируются: // Extends Throwable non-direct -class MathException extends Exception { /* ... */ }    // compilation error +class MathException extends Exception { /* ... */ } // compilation error // Extends Throwable directly class QueueFullException extends Throwable { /* ... */ // compilation error @@ -1152,13 +540,13 @@ public static void execute(Box box) { } -Однако можно использовать параметр типа в throws : +Однако можно использовать параметр типа в throws: class Parser { -    public void parse(File file) throws T {     // OK -        // ... -    } + public void parse(File file) throws T { // OK + // ... + } } @@ -1168,9 +556,9 @@ class Parser { public class Test { -    public void print(Box strBox) { } + public void print(Box strBox) { } -    public void print(Box intBox) { } + public void print(Box intBox) { } } @@ -1215,12 +603,18 @@ Box box = new Box(); Поэтому Box  — это сырой тип обобщённого типа Box . Однако необобщённый класс или интерфейс НЕ являются сырыми типами. -Вопрос: Что из следующего является недопустимым? +To specify both upper and lower bounds for a type parameter using wildcards in Java, you can use the following syntax: -ArrayList al1 = new ArrayList(); -ArrayList al2 = new ArrayList(); -ArrayList al3 = new ArrayList(); -Всё допустимо +``` +Type parameterName +``` -Правильный ответ: 3 -Целое число является дочерним классом числа, поэтому (2) допустимо. Но String не является дочерним классом Number, поэтому (3) недопустимо. \ No newline at end of file +For example, if you want to specify a type parameter named "T" that has an upper bound of "Number" and a lower bound of "Integer", you can write: + +``` +public class MyClass> { + // class implementation here +} +``` + +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". \ No newline at end of file