1. Предложение концепции дженериков (зачем необходимы дженерики)?
Во -первых, давайте посмотрим на следующий короткий код:
открытый класс generalSt {public static void main (string [] args) {list list = new ArrayList (); list.add ("qqyumidi"); list.add ("кукуруза"); list.add (100); for (int i = 0; i <list.size (); i ++) {string name = (string) list.get (i); // 1 System.out.println ("name:" + name); }}}Определите коллекцию типа списка, сначала добавьте в него два значения типа строки, а затем добавьте значение целочисленного типа. Это полностью разрешено, потому что в настоящее время тип списка по умолчанию является объектом. В последующем цикле легко иметь ошибки, похожие на // 1, потому что я забыл добавить значения целочисленного типа или другие причины кодирования в список. Поскольку стадия компиляции нормальная, исключение «java.lang.classcastexception» появится во время выполнения. Следовательно, такие ошибки трудно обнаружить во время процесса кодирования.
Во время процесса кодирования, как указано выше, мы обнаружили, что есть две основные проблемы:
1. Когда мы вкладываем объект в коллекцию, коллекция не запомнит тип этого объекта. Когда этот объект снова выведен из коллекции, тип компиляции измененного объекта становится типом объекта, но его тип времени выполнения все еще остается его собственным типом.
2. Следовательно, при выходе элемента сбора в // 1 1, искусственно принудительные типы должны быть преобразованы в конкретный тип цели, и легко увидеть исключение "java.lang.classcastexception".
Итак, есть ли способ заставить коллекцию запомнить различные типы элементов в коллекции, и для достижения этого, если во время компиляции нет проблем, не будет исключения «java.lang.classcastexception» во время выполнения? Ответ - использовать дженерики.
2. Что такое общий?
Generics, то есть «параметризованный тип». Когда дело доходит до параметров, наиболее знакомым является наличие конкретных параметров при определении метода, а затем передавать фактические параметры при вызове этого метода. Итак, как вы понимаете тип параметризации? Как следует из названия, это означает параметризация типа из исходного конкретного типа, аналогичного параметрам переменной в методе. В настоящее время тип также определяется как форма параметра (можно назвать формальным параметром типа), а затем конкретный тип (фактический параметр типа) передается при использовании/вызов.
Это кажется немного сложным. Во -первых, давайте посмотрим на приведенный выше пример, используя общее письмо.
открытый класс generalSt {public static void main (string [] args) { /* list list = new ArrayList (); list.add ("qqyumidi"); list.add ("кукуруза"); list.add (100); */ List <string> list = new ArrayList <string> (); list.add ("qqyumidi"); list.add ("кукуруза"); //list.add(100); // 1 Подсказывает ошибку компиляции для (int i = 0; i <list.size (); i ++) {string name = list.get (i); // 2 System.out.println ("name:" + name); }}}После использования общего письма ошибка компиляции возникает, когда вы хотите добавить объект целочисленного типа в // 1. Через список <String>, прямо ограничено, что только элементы типа строки могут быть сохранены в коллекции списков, поэтому нет необходимости отличать тип AT // 2, потому что в настоящее время коллекция может помнить информацию типа элемента, и компилятор может подтвердить, что это тип строки.
Комбинируя приведенное выше общее определение, мы знаем, что в списке <string> строка является параметром типа, то есть соответствующий интерфейс списка должен содержать формальные параметры типа. Более того, результатом возврата метода get () является непосредственно этот формальный тип параметра (то есть соответствующий параметр входящего типа). Давайте посмотрим на конкретное определение интерфейса списка:
Список публичных интерфейсов <e> extends collection <e> {int size (); логический isempty (); Boolean содержит (объект O); Итератор <e> итератор (); Object [] toarray (); <t> t [] toarray (t [] a); логическое добавление (e e); логическое удаление (объект o); Boolean Containsall (Collection <?> C); Boolean Addall (Collection <? Extends E> C); Boolean Addall (int index, collection <? Extends e> c); Boolean Removeall (Collection <?> C); Boolean antainall (Collection <?> C); void clear (); логическое равное (объект O); int hashcode (); E Get (int index); E set (int index, e element); void add (int index, e element); E удалить (int index); int indexof (Object O); int lastindexof (Object O); ListIterator <e> ListIterator (); ListIterator <e> listiceTerator (int index); Список <e> Sublist (int fromindex, int toindex);}Мы видим, что после использования общего определения в интерфейсе списка e в <e> представляет формальный параметр типа, который может получать параметры определенного типа. В этом определении интерфейса, где e появляется, это означает, что те же параметры типа, принятые снаружи, принимаются.
Естественно, ArrayList - это класс реализации для интерфейса списка, а его форма определения:
Исходя из этого, мы понимаем с точки зрения исходного кода, почему объект целочисленного типа неправильно скомпилируется при // 1, а тип, полученный при // 2, является непосредственно типом строки.
Общедоступный класс ArrayList <e> расширяет AbstractList <e> List <e>, случайный, клонируемый, java.io.serializable {public boolean add (e e) {evureCapacityEnternal (размер + 1); // Приращивание ModCount !! elementData [size ++] = E; вернуть истину; } public e get (int index) {rangecheck (index); checkforComodification (); return arraylist.his.elementData (Offset + Index); } //...Mit Другие конкретные процессы определения}3. Настройка общих интерфейсов, общих классов и общих методов
Из приведенного выше контента каждый понял конкретный процесс работы дженериков. Также известно, что интерфейсы, классы и методы также могут быть определены с использованием генериков и используются соответствующим образом. Да, при конкретном использовании его можно разделить на общие интерфейсы, общие классы и общие методы.
Пользовательские общие интерфейсы, общие классы и общие методы аналогичны списку и ArrayList в приведенном выше исходном коде Java. Следующим образом мы смотрим на простейшее общее определение класса и метода:
открытый класс generalSt {public static void main (string [] args) {box <string> name = new Box <string> ("кукуруза"); System.out.println ("name:" + name.getData ()); }} окно класса <t> {private t data; public box () {} public box (t data) {this.data = data; } public t getData () {return data; }}В процессе определения общих интерфейсов, общих классов и общих методов наши общие параметры, такие как t, e, k, v и т. Д., Часто используются для представления общих формальных параметров, поскольку они получают параметры типа, передаваемые от внешнего использования. Таким образом, для различных типов входящих параметров генерируются типы соответствующих экземпляров объекта?
открытый класс generalSt {public static void main (string [] args) {box <string> name = new Box <string> ("кукуруза"); Box <Integer> age = новая коробка <Integer> (712); System.out.println ("name class:" + name.getClass ()); // com.qqyumidi.box System.out.println ("Age Class:" + age.getClass ()); // com.qqyumidi.box System.out.println (name.getClass () == age.getClass ()); // истинный }}Исходя из этого, мы обнаружили, что при использовании общих классов, хотя различные общие аргументы передаются, различные типы не генерируются в истинном смысле. В памяти есть только один общий класс, проходящий в разных общих аргументах, то есть он по -прежнему остается исходным самым основным типом (в этом примере в этом примере). Конечно, логически мы можем понять это как несколько разных общих типов.
Причина в том, что цель концепции дженериков в Java заключается в том, что она работает только на стадии компиляции кода. Во время процесса компиляции, после правильной проверки общих результатов, будет стерта соответствующая информация об генериках. То есть успешно скомпилированный файл класса не содержит никакой общей информации. Общая информация не введет фазу времени выполнения.
Это суммировано в одном предложении: общие типы логически рассматриваются как несколько разных типов и на самом деле являются одинаковыми основными типами.
Четыре Тип подстановочного знака
После приведенного выше вывода мы знаем, что Box <cumber> и Box <Integer> на самом деле оба типа поле. Теперь нам нужно продолжать изучать вопрос. Итак, логически может ли Box <cumber> и Box <Integer> рассматриваться как общие типы с отношениями с родителями и детьми?
Чтобы прояснить эту проблему, давайте продолжим рассмотреть следующий пример:
открытый класс genericest {public static void main (string [] args) {box <cumn> name = new Box <Cumber> (99); Box <Integer> age = новая коробка <Integer> (712); getData (имя); // Метод getData (Box <Cumber>) в типе Generictest // не применим к аргументам (Box <Integer>) getData (возраст); // 1} public static void getData (box <cumber> data) {System.out.println ("data:" + data.getData ()); }}Мы обнаружили, что сообщение об ошибке появилось в коде // 1: Метод getData (Box <Cumber>) в ype Generictest не применим для аргументов (Box <Integer>). Очевидно, что, подсказывая информацию, мы знаем, что поле <число> не может рассматриваться как родительский класс Box <Integer>. Итак, в чем причина?
открытый класс generalSt {public static void main (string [] args) {box <Integer> a = new Box <Integer> (712); Box <Cumber> b = a; // 1 коробка <float> f = новая коробка <wopt> (3.14f); B.SetData (F); // 2} public static void getData (box <Cumber> data) {system.out.println ("data:" + data.getData ()); }} окно класса <t> {private t data; public box () {} public box (t data) {setData (data); } public t getData () {return data; } public void setData (t data) {this.data = data; }}В этом примере будет сообщение об ошибке в // 1 и // 2. Здесь мы можем использовать противоположный метод, чтобы объяснить его.
Предполагая, что поле <номер> можно логически рассматриваться как родительский класс блока <Integer>, тогда не будет никаких погрешных подсказок в // 1 и // 2. Затем возникает проблема. Что такое тип при получении данных через метод getData ()? Целое число? Плавать? или номер? Более того, из -за неконтролируемого порядка в процессе программирования, тип суждения должен быть принят при необходимости, и преобразование типа выполняется. Очевидно, что это противоречит идее дженериков, поэтому логически, Box <Cumber> не может рассматриваться как родительский класс Box <Integer>.
ОК, тогда давайте посмотрим на первый пример в «Типовых подстановочных знаках», мы знаем более глубокую причину его конкретных подсказок об ошибках. Так как это решить? Штаб -квартира может определить новую функцию. Это, очевидно, противоречит концепции полиморфизма в Java, поэтому нам нужен ссылочный тип, который можно логически использовать для представления родительского класса как Box <Integer>, так и Box <cumber>, и, следовательно, тип подстановочного знака возникла.
Типовые подстановочные знаки обычно используются вместо конкретных аргументов типа. Обратите внимание, что это параметр типа, а не параметр типа! И Box <?> Логически является родительским классом всех Box <Integer>, Box <cumber> ... и т. Д. Следовательно, мы все еще можем определить общие методы для выполнения таких требований.
открытый класс generalSt {public static void main (string [] args) {box <string> name = new Box <string> ("кукуруза"); Box <Integer> age = новая коробка <Integer> (712); Box <номер> number = новое поле <номер> (314); getData (имя); GetData (возраст); getData (номер); } public static void getData (box <?> data) {System.out.println ("data:" + data.getData ()); }}Иногда мы также можем слышать о верхних и нижних типах подстановочных знаков. На что это похоже?
В приведенном выше примере, если вам нужно определить метод, который функционирует, аналогично getData (), но существуют дополнительные ограничения на аргументы типа: это может быть только номерной класс и его подклассы. В настоящее время требуется верхний предел подстановочных знаков типа.
открытый класс generalSt {public static void main (string [] args) {box <string> name = new Box <string> ("кукуруза"); Box <Integer> age = новая коробка <Integer> (712); Box <номер> number = новое поле <номер> (314); getData (имя); GetData (возраст); getData (номер); // getUppernumberData (имя); // 1 getuppernumberdata (возраст); // 2 getUppernumberData (номер); // 3} public static void getData (box <?> Data) {System.out.println ("data:" + data.getData ()); } public static void getUppernumberData (box <? Extends number> data) {System.out.println ("data:" + data.getData ()); }}На этом этапе, очевидно, вызов в коде // 1 появится сообщение об ошибке, а вызов в // 2 // 3 будет нормальным.
Верхний предел типовых подстановодов определяется формой коробки <? расширяет номер>. Соответственно, нижний предел типовых подстановодов - это форма коробки <? Супер номер>, и его значение - это точно противоположность верхнему пределу подстановочных знаков типа. Я не буду объяснять это слишком много здесь.
5. Дополнительная глава
Примеры в этой статье в основном приводятся, чтобы проиллюстрировать некоторые идеи в дженеках и не обязательно имеют практическую удобство использования. Кроме того, когда дело доходит до дженериков, я считаю, что то, что вы используете больше всего, находится в коллекции. Фактически, в реальном процессе программирования вы можете использовать дженерики для упрощения разработки и хорошо обеспечить качество кода. И одно можно отметить, что в Java нет так называемого общего массива.
Для дженериков самая важная вещь - это понять идеи и цели, стоящие за ними.
Выше приведено сборник знаний и информации о дженериках Java. Мы будем продолжать добавлять соответствующую информацию в будущем. Спасибо за вашу поддержку на этом сайте!