Что такое дженерики в java
Перейти к содержимому

Что такое дженерики в java

  • автор:

Полезности Java: что должен знать программист о дженериках

Java – перспективный и относительно простой в освоении язык программирования. Его может освоить даже новичок в соответствующей сфере. Несмотря на то, что Джава – это «способ общения» с программным обеспечением и «железом» устройства типа ООП, работать с ним не составляет никакого труда.

Данный вариант идеально подходит для:

  • игрового софта;
  • «серьезных» программ и утилит;
  • веб-программирования.

Последнее направление является наиболее популярным. Web-programming – основная стезя Джава-семейства. Тип языка, при помощи которого можно писать браузерные расширения. Обладает разнообразными функциями и возможностями. Некоторые элементы семейства требуют отдельного внимания. Пример – дженерики (generic). О них мы расскажем далее в статье.

Преимущества Джавы: что учесть новичку

Но сначала стоит обратить внимание на некоторые особенности выбранного типа «способа общения» с программным обеспечением. Джава обладает существенными преимуществами перед остальными вариантами:

  • простой и понятный синтаксис;
  • быстрое осваивание «с нуля»;
  • не требует никаких навыков программирования – подойдет даже тем, кто далек от информационных технологий;
  • наличие ООП;
  • дополнительные библиотеки;
  • собственный движок, а также наличие множества платформ для создания игрового контента;
  • относительно небольшие исходные коды получаемых приложений;
  • свободное распространение.

Пользователи, решившие работать с соответствующим типом language, будут сталкиваться с принципом «меньше писать, больше выполнять». Посредством циклов и других функций удается значительно уменьшить размер исходной кодификации. Проверять ее работоспособность тоже будет не слишком трудно.

Терминологический вопрос – основные понятия

Перед тем, как рассмотрим дженерики в Джаве, необходимо разобраться с некоторыми понятиями в программировании. Соответствующие данные пригодятся как новичкам, так и тем, кто уже пытался создавать собственные утилиты.

Запомнить рекомендуется следующую информацию:

  • алгоритм – правила и инструкции, их сочетания, предназначенные для решения поставленной задачи;
  • API (интерфейс программирования) – правила, процедуры, протоколы, необходимые при создании программного софта;
  • аргументы – значения, передаваемые в функции и команды;
  • символы – минимальные единицы отображения данных, равные одной букве или символу;
  • класс – шаблон, описывающий тот или иной объект в программировании, набор элементов с общими свойствами;
  • константы – тип значений, не изменяемых в процессе выполнения программной кодификации;
  • типы данных – классификация информации того или иного характера;
  • массивы – группы и списки (пример — list integer) схожих типов значений информации, подлежащие группировке;
  • декларация – оператор, описывающий переменные, функции и иные идентификаторы;
  • фреймворк – готовый пример кода, используемый при создании собственных приложений;
  • цикл – последовательность инструкций, отвечающих за выполнение одних и тех же манипуляций несколько раз;
  • операнд – объекты, которыми можно манипулировать;
  • оператор – элемент кода, управляющий теми или иными операндами;
  • переменная – элементарное хранилище информации при создании кодификаций;
  • методы – функции и процедуры, которые относятся к тому или иному типу/классу объектов, своеобразные действия, которые «умеет» выполнять элемент кода.

Отдельное внимание необходимо уделить понятию дженерика. С его использованием программирование на Джаве стало значительно проще. И разобраться с соответствующим элементом при грамотном подходе не составит никакого труда. Для лучшего понимания рекомендуется обладать хотя бы первоначальными навыками программирования.

Дженерик – что это такое

При использовании string и других объектов в процессе коддинга, разработчики задействуют всевозможные классы, методы и типы информации. Для каждого варианта разрабатывались собственные алгоритмы и решения.

Java, начиная с 1995 года, активно развивался, дорабатывался, совершенствовался. Так появились дженерики. Впервые о них услышали в Java 5. С тех пор подобные элементы весьма активно применяются на практике.

Generic – понятие обширное. Обозначает языковые возможности (функции), позволяющие применять на практике универсальные методы и типы. Своеобразное обобщение.

Общие типы отличаются от обычных:

  • до появления дженериков программеры использовали коллекции для хранения всех типов объектов;
  • начиная с Java 5, за счет generics объекты делятся и хранятся «обособлено» друг от друга.

Иногда тип данных ввода не выступает в качестве фиксированного. Указываемыми единицами информации могут служить string, числа (с плавающими запятыми в том числе), буквенные записи. Для ввода переменной правильного (нужного) типа требуется внедрить предварительные проверки. Если же воспользоваться дженериками, соответствующая операция проведется автоматически в процессе компиляции. Тип данных будет установлен по умолчанию. Используется эта «функция» весьма активно, особенно продвинутыми разработчиками.

Кратко о главном в дженериках: наглядное пособие

Generic носит название родовых типов. Соответствующие элементы позволяют безопасно пользоваться классами коллекций. Лучше всего изучать их на наглядных образцах кодификаций. Вот пример кода без дженериков:

Полезности Java: что должен знать программист о дженериках

Здесь происходит следующее:

  • компиляция при запуске утилиты осуществляется нормально;
  • ClassCastException будет брошен в процессе исполнения кода;
  • один из объектов выступает как integer, а должен являться string.

В Java 5 и более поздних версиях стал актуален следующий код:

Полезности Java: что должен знать программист о дженериках

В процессе создания списка (list string, new arraylist) указано, что тип элементов, задействованных в нем – это string. Если попытаться добавить иные варианты, при компиляции пользователь увидит на экране сообщение об ошибке.

В цикле for приведение типов не используется. О ClassCastException можно не беспокоиться. Программа заработает исправно.

Способы применения

Что собой представляют дженерики в Джаве, понятно. Но для того, чтобы лучше в них разбираться, требуется обозначить сферы применения оных. Существуют различные варианты развития событий. Наглядные примеры помогут лучше усвоить соответствующий материал даже новичкам.

На данный момент generic может использоваться следующими способами:

  • типовыми классами;
  • интерфейсами;
  • методами;
  • конструкторами.

Также имеют место при наследовании. Далее каждый вариант будет рассмотрен более подробно. Приведенные примеры – это лишь шаблоны, на которые рекомендуется опираться при самостоятельном изучении выбранного направления.

Типовые классы

Класс будет называться generic, если он отвечает за объявления одной или нескольких переменных типа (string, int и так далее). Соответствующие виды принято называть параметрами типа класса Джава. Чтобы лучше понимать их, стоит рассмотреть пример.

class Genericclass < private Object x; public void set(Object x) < this.x = x; >public Object get() < return x; >>

Здесь происходит следующее:

  • создается класс со свойством X;
  • тип свойства – это объект;
  • после инициализации класс применяется только этим конкретным типом.

Так, если хочется, чтобы экземпляр класса имел значение типа string, программисту предстоит установить и получить единственный string. В предложенном примере соответствующих ограничений нет. Связано это с тем, что произошло объявление типа свойства для объекта. Разработчики способны устанавливать любые элементы и ждать любой «вид» возвращаемого значения при активации метода get.

Полезности Java: что должен знать программист о дженериках

Приведенный выше пример позволяет применять обозначенное ранее ограничение. При подобных обстоятельствах разработчик может быть уверен в том, что класс задействуют максимально грамотно и правильно.

Вот самый простой пример Genericclass:

Genericclass instance = new Genericclass(); instance.set("Edureka"); instance.set(10); //

Подобный вариант актуален и для интерфейсов.

Интерфейсы и классы – как используют generic

Для того, чтобы облегчить коддинг, можно использовать при описании интерфейсов и classes generics. Для этого применяются угловые скобочки (<>). Они помогают указывать «разновидность» параметра задействованного объекта.

Вот вариант создания class без generics:

package ua.com.prologistic; public class OldSchool < private Object t; public Object get() < return t; >public void set(Object t) < this.t = t; >public static void main(String args[]) < OldSchool type = new OldSchool (); type.set("Str"); String str = (String) type.get(); //приведение типов может стать причиной ClassCastException >>

Здесь при применении class не нужно приводить «вид» объекта. В случае с generics кодификация получит такую интерпретацию:

package ua.com.prologistic; public class GenericsType  < private T t; public T get()< return this.t; >public void set(T t1) < this.t=t1; >public static void main(String args[]) < GenericsTypetype = new GenericsType<>(); type.set("Str"); //ошибок не будет GenericsType type1 = new GenericsType(); type1.set("Str"); //код нормально выполнится type1.set(10); //работа автоупаковки > >
  • за classcastexception в main() беспокоиться не нужно – «мейн» выступает методов GenericsType;
  • если отказаться от соответствующей прописи, компилятор сообщит об этом.

В последнем случае имеет место следующий расклад:

  • type не прописывается при создании экземпляра class;
  • автоматически type получает «значение» object;
  • string можно использовать в качестве objects, как и иные элементы new integer.

В конечном итоге придется прибегнуть к приведению types. Им выступит OldSchool из предложенного примера.

Только интерфейс

Интерфейс Comparable – хороший пример применения рассматриваемого элемента в программировании. Выглядит код так:

Полезности Java: что должен знать программист о дженериках

На основе этого варианта можно освоить принципы функционирования generics в Джаве относительно интерфейсов, без дополнительных составляющих. Это – база, помогающая применять рассматриваемый элемент в classes и interface. Еще и при помощи задействования нескольких параметров. Пример – интерфейс map. Подойдет string вида:

Методы-конструкторы

Еще один вариант развития событий актуален, когда нет необходимости в параметризации всего класса. При подобных обстоятельствах рассмотренные ранее примеры теряют свою актуальность. Можно значительно упростить процесс написания исходного кода за счет применения generics исключительно в методах или конструкторах.

Вот пример, который поможет разобраться, как реализовывается соответствующий подход:

Полезности Java: что должен знать программист о дженериках

Как можно заметить, приведенная кодификация предусматривает два способа применения. Один – обычный, другой – максимально простой. Остановиться программист может на том, который кажется ему наиболее комфортным, удобным.

Внимание: не всегда упрощенный вариант подходит при написании контента. В основном он встречается в элементарных приложениях. Для сложных проектов рекомендуется пользоваться «обычным» способом представления generics в методах/конструкторах.

Вопрос наследования

Джава – современный, удобный, продвинутый язык, оснащенный огромным функционалом. Здесь дозволено назначать переменные других переменных. Такой расклад возможен, когда первый элемент выступает в качестве подкласса второго.

Для реализации подобных приемов используется наследование. Код имеет примерно следующую структуру:

package ua.com.prologistic; public class GenericsInheritance < public static void main(String[] args) < String str = "abc"; Object obj = new Object(); obj = str; // работает, потому что String выступает как наследник Object MyClassmyClass1 = new MyClass(); MyClass myClass2 = new MyClass(); myClass2 = myClass1; // компиляции не будет, MyClass не выступает как MyClass obj = myClass1; // MyClass наследник Object > public static class MyClass<> >

Дженерики в Java – это удобно и практично. Но разработчикам требуется выяснить, как происходит понимание программного кода и его унификация. Соответствующий спектр знаний поможет отслеживать ошибки, если они имеются, а также избегать оные.

Соглашение об именовании – в помощь разработчику

Generics имеют собственное соглашение об именовании. Это – некий «свод правил», который применяется при внедрении рассматриваемого элемента в кодификацию. Выражаются преимущественно прописными буквами, за счет чего удается без особых затруднений отделить оные от переменных в Джаве.

Соглашение об именовании весьма обширное. Там нет «заумных» фраз типа «extends number, int I, public static t» и так далее. В основном дело предстоит иметь с «самостоятельными буквами». Чаще всего на практике встречаются следующие варианты:

  • K – ключ (имеет место в map);
  • T – тип;
  • E – элемент (имеет широкое распространение в Java Collection Framework);
  • B – значение (тоже встречается в map);
  • S, U, V и так далее – 2-ой, 3-ий, 4-ый…тип.

Больше информации можно узнать из сопутствующей документации. У Java с ней проблем нет, как и у generic. Также помощи всегда можно попросить у продвинутых разработчиков – комьюнити Джавы весьма лояльно и дружелюбно к новичкам.

Курсы – лучший подход к изучению

Что такое generic в Java, понятно. Это – метод обобщения, который упрощает коддинг. Иногда «с нуля» освоить его бывает проблематично. В таком случае целесообразно посетить специализированные компьютерные курсы.

Опытные специалисты и современные программисты расскажут, что такое public static void, interfaces, generic, t extends и не только. Материал подается в понятной и удобной форме. Возможно дистанционное обучение. Можно выбрать узкую специализацию или рассмотреть выбранное направление всесторонне. Имеются курсы как для новичков, так и для более опытных программеров.

Дженерики Java – это не так трудно, если грамотно подойти к изучению темы. Курсы доступны всем желающим. В конце обучения выдаются сертификаты установленного образца.

Дженерики Java

Дженерики (или обобщения) — это параметризованные типы.

Параметризованные типы позволяют объявлять классы, интерфейсы и методы, где тип данных, которыми они оперируют, указан в виде параметра. Используя дженерики, можно создать единственный класс, например, который будет автоматически работать с разными типами данных.

Классы, интерфейсы или методы, имеющие дело с параметризованными типами, называются параметризованными или обобщениями, параметризованными (обобщенными) классами или параметризованными (обобщёнными) методами.

Обобщения добавили в язык безопасность типов.

  1. Параметризованные классы
  2. Ограниченные типы
  3. Применение метасимвольных аргументов
  4. Параметризованные методы и конструкторы
  5. Параметризованные интерфейсы
  6. Иерархии параметризованных классов
  7. Использование оператора instanceof c параметризованными классами
  8. Ограничения присущие обобщениям

1. Параметризованные классы

Следующий пример демонстрирует использование параметризованного класса, который описывает матрицу:

public class Matrix  < private T[] array; public Matrix(T[] array) < this.array = array.clone(); >public static void main(String[] args) < MatrixdoubleMatrix = new Matrix<>(new Double[2]); Matrix integerMatrix = new Matrix<>(new Integer[4]); Matrix byteMatrix = new Matrix<>(new Byte[7]); > > 

В объявлении Matrix integerMatrix Integer является аргументом типа.

Java не создает разные версии класса Matrix или любого другого параметризованного класса. Имеется только одна версия класса Matrix , которая существует в прикладной программе.

Дженерики работают только с объектами! Следующий код является неправильным:

Gen strOb = new Gen (53); // Ошибка, нельзя использовать примитивные типы 

Т обозначает имя параметра типа. Это имя используется в качестве заполнителя вместо которого в дальнейшем подставляется имя конкретного типа, передаваемого классу Matrix при создании объекта. Это означает, что обозначение Т применяется в классе Matrix всякий раз, когда требуется параметр типа. Всякий раз, когда объявляется параметр типа, он указывается в угловых скобках.

Обобщенные типы отличаются в зависимости от типов-аргументов. Следующий код не допустим:

doubleMatrix = integerMatrix; // Не верно! 

Несмотря на то, что doubleMatrix и integerMatrix имеют тип Matrix , они являются ссылками на разные типы, потому что типы их параметров отличаются.

Обобщенный класс может быть объявлен с любым количеством параметров типа. Например:

public class TwoGen  < private T obT; private V obV; public TwoGen(T obT, V obV) < this.obT = obT; this.obV = obV; >public void showTypes() < System.out.println("Тип T: " + obT.getClass().getName()); System.out.println("Тип V: " + obV.getClass().getName()); >public T getObT() < return obT; >public V getObV() < return obV; >> public class SimpleGen < public static void main(String[] args) < TwoGentwoGen = new TwoGen<>(88, "Generics"); twoGen.showTypes(); System.out.println("Значение T: " + twoGen.getObT()); System.out.println("Значение V: " + twoGen.getObV()); > > 

2. Ограниченные типы

Указывая параметр типа, можно наложить ограничение сверху в виде верхней границы, где объявляется супер класс, от которого должны быть унаследованы все аргументы типов. С этой целью вместе с параметром указывается ключевое слово extends :

class Gen

Параметр типа Т может быть заменен только указанным супер классом или его подклассами.

Рассмотрим пример использования ограниченного типа:

public class Average  < private T[] array; public Average(T[] array) < this.array = array; >public double average() < double sum = 0.0; for (T value : array) < sum += value.doubleValue(); >return sum / array.length; > > public class AverageDemo < public static void main(String[] args) < Integer[] intArray = ; Average integerAverage = new Average<>(intArray); System.out.println("Среднее значения для Integer " + integerAverage.average()); Double[] doubleArray = ; Average doubleAverage = new Average<>(doubleArray); System.out.println("Среднее значения для Double " + doubleAverage.average()); // Не откомпилируется, // потому что String не является наследником Number /* String[] strArray = ; Average strAverage = new Average<>(strArray); System.out.println("Среднее значения для String " + strAverage.average());*/ > >

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

public class MyClass

Ограничение может включать в себя как тип класса, так и типы одного или нескольких интерфейсов:

class Gen

Тип класса должен быть задан первым. Накладывая на обобщенный тип ограничение, состоящее из класса и одного или нескольких интерфейсов, для их объединения следует воспользоваться логической операцией &: Таким образом, любой тип, передаваемый параметру Т , должен быть подклассом, производным от класса MyClass и реализующим интерфейсы MyInterface1 и MyInterface2 .

3. Применение метасимвольных аргументов

Представьте, что мы хотим добавить метод для сравнения средних значений массивов в класс Average из примера 3. Причем типы массивов могут быть разные:

Integer intArray[] = ; Double doubleArray[] = ; Average iob = new Average<>(intArray); Average dob = new Average<>(doubleArray); if (iob.sameAvg(dob)) < System.out.println("are the same.");>else

Так как Average параметризованный тип, какой тип параметра вы укажете для Average , когда создадите параметр метода типа Average ? Напрашивается следующий вариант:

boolean sameAvg(Average ob)

Но это не сработает, так как в этом случае метод sameAvg будет принимать аргументы только того же типа, что и существующий объект:

if (iob.sameAvg(iob)) < System.out.println("are the same.");>else

Чтобы создать обобщенную версию метода sameAvg() , следует воспользоваться другим средством обобщений Jаvа – метасимвольным аргументом. Метасимвольный аргумент обозначается знаком ? и представляет неизвестный тип.

boolean sameAvg(Average ob)

Мета символ не оказывает никакого влияния на тип создаваемых объектов класса Average . Это определяется оператором extends в объявлении класса Average. Мета символ просто совпадает с любым достоверным объектом класса Average .

Метасимвольные аргументы могут быть ограничены почти таким же образом, как и параметры типов. Ограничивать метасимвольный аргумент особенно важно при создании обобщенного типа, оперирующего иерархией классов. Например:

public class Average2  < private T[] array; public Average2(T[] array) < this.array = array.clone(); >public double average() < double sum = 0.0; for (T value : array) < sum += value.doubleValue(); >return sum / array.length; > boolean sameAvg(Average2 ob) < return average() == ob.average(); >> public class AverageDemo2 < public static void main(String[] args) < Integer[] intArray = ; Average2 iob = new Average2<>(intArray); System.out.println("Среднее значения для Integer " + iob.average()); Double[] doubleArray = ; Average2 dob = new Average2<>(doubleArray); System.out.println("Среднее значения для Double " + dob.average()); Float[] floatArray = ; Average2 fob = new Average2<>(floatArray); System.out.println("Среднее значения для Float " + fob.average()); System.out.print("Средние значения для iob и dob "); if (iob.sameAvg(dob)) < System.out.println("одинаковые."); >else < System.out.println("разные."); >System.out.print("Средние значения для iob и fob "); if (iob.sameAvg(fob)) < System.out.println("одинаковые."); >else < System.out.println("разные."); >> >

4. Параметризованные методы и конструкторы

В методах параметризованного класса можно использовать параметр типа, а следовательно, они становятся параметризованными относительно параметра типа.

Но можно объявить параметризованный метод, в котором непосредственно используется один или несколько параметров типа. Более того, можно объявить параметризованный метод, входящий в не параметризованный класс. Например:

public class GenMethodDemo < /** * Является ли объект x элементом массива array * * @param x * @param array * @param * @param * @return */ public static boolean isIn(T x, V[] array) < for (V element : array) < if (x.equals(element)) < return true; >> return false; > public static void main(String[] args) < Integer[] intArray = ; if (isIn(2, intArray)) < System.out.println("2 входит в массив intArray"); >if (!isIn(7, intArray)) < System.out.println("7 не входит в intArray"); >System.out.println(); String[] strArray = ; if (isIn("two", strArray)) < System.out.println("two входит в массив strArray"); >if (!isIn("seven", strArray)) < System.out.println("seven не входит в массив strArray"); >> >

Конструкторы также могут быть обобщенными, даже если их классы таковыми не являются. Например:

public class GenConstructor < private double value; public GenConstructor(T arg) < value = arg.doubleValue(); >public void showValue() < System.out.println("value: " + value); >> public class GenConstructorDemo < public static void main(String[] args) < GenConstructor genConstructor1 = new GenConstructor(100); GenConstructor genConstructor2 = new GenConstructor(123.5F); genConstructor1.showValue(); genConstructor2.showValue(); >>

5. Параметризованные интерфейсы

В дополнение к обобщенным классам и методам вы можете объявлять параметризованные интерфейсы. Параметризованные интерфейсы специфицируются так же, как и обобщенные классы:

public interface MyInterface  < T someMethod(T t); >public class MyClass implements MyInterface  < @Override public T someMethod(T t) < return t; >public static void main(String[] args) < MyInterfaceobject = new MyClass<>(); String str = object.someMethod("some string"); > >

6. Иерархии параметризованных классов

Параметризованные классы могут быть частью иерархии классов так же, как и любые другие не параметризованные классы. То есть параметризованный класс может выступать в качестве супер класса или подкласса.

Ключевое отличие между параметризованными и не параметризованными иерархиями состоит в том, что в параметризованной иерархии любые аргументы типов, необходимые параметризованному супер классу, всеми подклассами должны передаваться по иерархии вверх.

public class GenericSuper  < private T ob; public GenericSuper(T ob) < this.ob = ob; >private T getOb() < return ob; >> public class GenericSub extends GenericSuper  < public GenericSub(T ob) < super(ob); >>

Подкласс параметризованного супер класса необязательно должен быть параметризованным, но в нем все же должны быть, указаны параметры типа, требующиеся его параметризованному супер классу. Подкласс может, если требуется, быть, дополнен и своими параметрами типа. Супер классом для параметризованного класса может быть класс не параметризованный.

7. Использование оператора instanceof c параметризованными классами

public class HierarchyDemo < public static void main(String[] args) < GenericSuperobject = new GenericSub<>(88); if (object instanceof GenericSuper) < System.out.println("object is instance of GenericSuper"); >if (object instanceof GenericSub) < System.out.println("object is instance of GenericSub"); >// Ошибка компиляции - информация об обобщенном типе недоступна во время выполнения /* if (object instanceof GenericSub) < System.out.println("object is instance of GenericSub"); >*/ > >

8. Ограничения присущие обобщениям

Обобщениям присущи некоторые ограничения. Рассмотрим их:

1. Нельзя создавать экземпляр по параметру типа. Ни обычный объект, ни массив:

public class GenRestriction  < private T ob; private T[] array; public GenRestriction(T ob, T[] array) < // Недопустимо. //оb = new Т(); //array = new Т[10]; this.ob = ob; this.array = array; >>

2. Нельзя создать массив специфических для типа обобщенных ссылок:

public class GenArrays < public static void main(String[] args) < // Нельзя создать массив специфичных для типа обобщенных ссылок. // GenericSub[] gens = new GenericSub[10]; GenericSub[] gens = new GenericSub[10]; gens[0] = new GenericSub<>(34); > >

3. Нельзя создавать обобщенные статические переменные и методы. Но объявить статические обобщенные методы со своими параметрами типа все же можно:

public class GenericWrongStatic < // Неверно, нельзя создать статические переменные типа Т. //public static Т оb; // Неверно, ни один статический метод не может использовать Т. /* public static T getOb() < return оb; >*/ //Но объявить статические обобщенные методы со своими параметрами типа можно public static void getOb(V v) < System.out.println(v); >>
  • Вложенные классы
  • Задания

Generics

Дженерики (generics) в языке программирования Java — это сущности, которые могут хранить в себе данные только определенного типа. Например, список элементов, в котором могут быть одни числа. Но не только: дженерик — обобщенный термин для разных структур.

Освойте профессию «Java-разработчик»

Можно представить дженерик как папку для бумаг, куда нельзя положить ничего, кроме документов определенного формата. Это удобно: помогает разделить разные данные и не допустить ситуаций, когда в сущность передается что-то не то.

Дженерик-сущности еще иногда называют параметризованными, общими или обобщенными. Такая сущность создается со специальным параметром. Параметр позволяет указать, с каким типом данных она будет работать. Отсюда и название.

В разных источниках можно услышать про «тип-дженерик», «класс-дженерик» или «метод-дженерик». Это нормально, ведь обобщение и параметризация касаются всех этих сущностей, а generics — общий термин.

Для чего нужны дженерики

С дженериками работают программисты на Java. Без этой возможности писать код, который работает только с определенным видом данных, было сложнее. Существовало два способа, и оба неоптимальные:

  • указывать проверку типа вкоде. Например, получать данные — и сразу проверять, а если они не те, выдавать ошибку. Это помогло бы отсеять ненужные элементы. Но если бы класс понадобилось сделать более гибким, например, создать его вариацию для другого типа, его пришлось бы переписывать или копировать. Не получилось бы просто передать другой специальный параметр, чтобы тот же класс смог работать еще с каким-то типом;
  • полагаться на разработчиков. Например, оставлять в коде комментарий «Этот класс работает только с числами». Слишком велик риск, что кто-то не заметит комментарий и передаст в объект класса не те данные. И хорошо, если ошибка будет заметна сразу, а не уже на этапе тестирования.

Поэтому появились дженерики: они решают эту проблему, делают написание кода проще, а защиту от ошибок надежнее.

Профессия / 14 месяцев
Java-разработчик

Освойте востребованный язык

Group 1321314345 (4)

Как работают дженерики

Чтобы вернее понять принцип работы, нужно представлять, как устроены сущности в Java. Есть классы — это как бы «чертежи» будущих сущностей, описывающие, что они делают. И есть объекты — экземпляры классов, непосредственно существующие и работающие. Класс — как схема машины, объект — как машина.

Когда разработчик создает дженерик-класс, он приписывает к нему параметр в треугольных скобках — метку. К примеру, так:

Теперь при создании объекта этого класса нужно будет указать на месте T название типа, с которым будет работать объект. Например, myClass для целых чисел или myClass для строк. Сам класс остается универсальным, то есть общим. А вот каждый его объект специфичен для своего типа.

С помощью дженериков можно создать один класс, а потом на основе него — несколько объектов этого класса для разных типов. Не понадобится дублировать код и усложнять программу. Поэтому дженерики лучше и удобнее, чем проверка типа прямо в коде — тогда для каждого типа данных понадобился бы свой класс.

Что такое raw types

В Java есть понятие raw types. Так называют дженерик-классы, из которых удалили параметр. То есть изначально класс описали как дженерик, но при создании объекта этого класса тип ему не передали. То есть что-то вроде myClass<> — тип не указан.

Дословно это название переводится как «сырые типы». Пользоваться ими сейчас в коммерческой разработке — чаще всего плохая практика. Но в мире все еще много старого кода, который написали до появления дженериков. Если такой код еще не успели переписать, в нем может быть очень много «сырых типов». Это надо учитывать, чтобы не возникало проблем с совместимостью.

Дженерики-классы и дженерики-методы

Выше мы говорили, что дженериками могут быть разные сущности. Разберемся подробнее:

  • дженерик-классы (generic classes) это классы, «схемы» объектов с параметром. При создании объекта ему передается тип, с которым он будет работать;
  • дженерик-методы (generics methods) это методы, работающие по такому же принципу. Метод — это функция внутри объекта, то, что он может делать. Методу тип передается при вызове, сразу перед аргументами. Так можно создавать более универсальные функции и применять одну и ту же логику к данным разного типа.

Кстати, дженериками могут быть и встроенные классы или методы, и те, которые разработчик пишет самостоятельно. Например, встроенный ArrayList — список-массив — работает как дженерик.

Станьте Java-разработчиком
и создавайте сложные сервисы
на востребованном языке

Что будет, если передать дженерику не тот тип

Если объекту класса-дженерика передать не тот тип, который указали при его объявлении, он выдаст ошибку. Например, если в ходе работы экземпляра myClass в нем попытаются сохранить дробное число или даже строку, программа не скомпилируется. Вместо этого разработчик увидит ошибку: неверный тип.

Эта ошибка отличается от тех, которые возникнут, если не пользоваться дженериками. По ней сразу ясно, из-за чего она возникла и как можно ее исправить. Кроме того, она появляется сразу. Поэтому код становится легче отлаживать.

А если отправить «не тот» тип объекту без дженерика, действия с ним выполнятся с ошибкой. Но по этой ошибке не всегда очевидно, чем она вызвана. Худший вариант — код успешно запустится, но сработает неправильно: так ошибку будет найти еще сложнее.

Особенности дженериков

У дженериков есть несколько особенностей, о которых стоит знать при работе с ними. Если не учитывать эти детали, программировать будет как минимум менее удобно. А как максимум можно допустить ошибку и не понять, куда она закралась.

Выведение типа. Эта особенность касается объявления экземпляра класса, то есть создания объекта. Полная запись создания будет выглядеть так:

myClass objectForIntegers = new myClass();

objectForIntegers — это название объекта, оно может быть любым. То, что находится после знака «равно», — непосредственно команда «создать новый экземпляр класса».

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

Запись, в которой программист пользуется возможностью выведения типа, будет выглядеть так:

myClass objectForIntegers = new myClass<>();

Повторное упоминание типа опускается. Запись становится короче. Кажется, что это мелочь, но таких конструкций в коде могут быть десятки и писать полную запись всегда было бы не очень удобно.

Стирание типов. Важная деталь, которая касается работы дженериков, — они существуют только на этапе компиляции. В этом их суть: «не пропускать» данные ненужного типа в объект, а такие вещи определяет компилятор.

После компиляции код на Java превращается в байт-код. И на этом уровне никаких дженериков нет. myClass и myClass в байт-коде будут идентичны, просто с разными данными внутри.

Это называется стиранием типов. Суть в том, что внутри дженерик-класса нет информации о его параметре и после компиляции эти сведения просто исчезают. Так сделали, потому что дженерики появились в Java не сразу. Если бы информацию о параметре добавили в байт-код, это сломало бы совместимость с более старыми версиями.

О стирании типов важно помнить. Для запущенной программы в байт-коде дженериков не существует, и это может вызвать ошибки. Например, при сравнении myClass и myClass программа скажет, что они одинаковые. А иногда в объект в запущенном коде и вовсе получается передать данные другого типа.

«Дикие карты». Еще одна интересная и полезная особенность дженериков — так называемые wildcards, или «дикие карты». Это термин из спорта, означающий особое приглашение спортсмена на соревнование в обход правил. А в карточных играх так называют карты, которые можно играть вместо других, например джокера.

В основе wildcards в Java лежит такая же идея: изменить предустановленное поведение и сделать что-то в обход установленных рамок. Когда объявляется «дикая карта», в треугольных скобках вместо названия типа ставится вопросительный знак. Это означает, что сюда можно подставить любой тип.

Подставить wildcard можно не везде. Например, при создании класса это сделать не получится, а при объявлении объекта этого класса — получится. Чаще всего «дикую карту» используют при работе с переменными и с коллекциями.

Ограниченные «дикие карты». Кроме стандартной wildcard, существует еще несколько типов — ограниченные «дикие карты». С их помощью можно передать в объект данные не только конкретного типа, но и унаследованных от него — «потомков». Или же «предков» — типов, от которых был унаследован упомянутый.

Ограниченный wildcard описывается как вопросительный знак, за которым следует правило.

Есть два вида ограничений:

  • upper bounding — ограничение сверху. За вопросительным знаком следует слово extends и название типа. В такой дженерик можно передавать названный тип и его потомков;
  • lower bounding — ограничение снизу. Ситуация наоборот: за вопросительным знаком слово super и тип, а подставлять можно элементы этого типа и его предков.

Скорее всего, впервые столкнуться с дженериками придется еще в начале изучения Java, просто новичку не сразу понятно, что это такое. Со временем появляется понимание, как работает эта конструкция, и становится легче решать более сложные задачи.

Java-разработчик

Java уже 20 лет в мировом топе языков программирования. На нем создают сложные финансовые сервисы, стриминги и маркетплейсы. Освойте технологии, которые нужны для backend-разработки, за 14 месяцев.

картинка (67)

Статьи по теме:

Дженерики (Generics) в java

Начиная с JDK 1.5, в Java появились новые возможности для программирования. Одним из таких нововведений являются Generics. Generics являются аналогией с конструкцией “Шаблонов”(template) в С++, но имеет свои нюансы. Generics позволяют абстрагировать множество типов. Наиболее распространенными примерами являются Коллекции.

Вот типичное использование такого рода (без Generics):

1. List myIntList = new LinkedList(); 2. myIntList.add(new Integer(0)); 3. Integer x = (Integer) myIntList.iterator().next();

Как правило, программист знает, какие данные должны быть в List’e. Тем не менее, стоит обратить особое внимание на Приведение типа (“Cast”) в строчке 3. Компилятор может лишь гарантировать, что метод next() вернёт Object, но чтобы обеспечить присвоение переменной типа Integer правильным и безопасным, требуется Cast. Cast не только создает беспорядки, но дает возможность появление ошибки “Runtime Error” из-за невнимательности программиста.

И появляется такой вопрос: “Как с этим бороться? ” В частности: “Как же зарезервировать List для определенного типа данных?”

Как раз такую проблему решают Generics.

1. List myIntList = new LinkedList (); 2. myIntList.add(new Integer(0)); 3. Integer x = myIntList.iterator().next();

Обратите внимание на объявления типа для переменной myIntList. Он указывает на то, что это не просто произвольный List, а List. Мы говорим, что List является generic-интерфейсом, который принимает параметр типа – в этом случае, Integer. Кроме того, необходимо обратить внимание на то, что теперь Cast выполняется в строчке 3 автоматически.

Некоторые могут задуматься, что беспорядок в коде увеличился, но это не так. Вместо приведения к Integer в строчке 3, у нас теперь есть Integer в качестве параметра в строчке 1. Здесь существенное отличие. Теперь компилятор может проверить этот тип на корректность во время компиляции.

И когда мы говорим, что myIntList объявлен как List, это будет справедливо во всем коде и компилятор это гарантирует.

Эффект от Generics особенно проявляется в крупных проектах: он улучшает читаемость и надежность кода в целом.

  1. Свойства
  2. Пример реализации Generic-класса
  3. Несовместимость generic-типов
  4. Проблемы реализации Generics
  5. Ограничения Generic
  6. Преобразование типов
  7. Примеры кода

Свойства

  • Строгая типизация
  • Единая реализация
  • Отсутствие информации о типе

Пример реализации Generic-класса

public interface List  < E get(int i); set(int i, E e); add(E e); Iteratoriterator(); … >

Для того чтобы использовать класс как Generics, мы должны прописать после имени класса , куда можно подставить любое имя, wildcard и т.д.

После того как было объявлено имя generic-типа его можно использовать как обычный тип внутри метода. И когда в коде будет объявлен, к примеру, List, то Е станет Integer для переменной list (как показано ниже).

Теперь рассмотрим чем старая реализация кода отличается от новой:

List ─ список элементов E

List list = new List(); list.add(new Integer(1)); Integer i = (Integer) list.get(0);
List list = new List(); list.add(new Integer(1)); Integer i = list.get(0);

Как видите, больше не нужно приводить Integer, так как метод get() возвращает ссылку на объект конкретного типа (в данном случае – Integer).

Несовместимость generic-типов

Это одна из самых важных вещей, которую вы должны узнать о Generics

Как говориться: “В бочке мёда есть ложка дегтя”. Для того чтобы сохранить целостности и независимости друг от друга Коллекции, у Generics существует так называемая “Несовместимость generic-типов”.

Пусть у нас есть тип Foo, который является подтипом Bar, и еще G - наследник Коллекции. То G не является наследником G.
List li = new ArrayList(); List lo = li;
lo.add(“hello”); // ClassCastException: String -> int Integer li = lo.get(0);

Проблемы реализации Generics

  • Решение 1 – Wildcard

Пусть мы захотели написать метод, который берет Collection и выводит на экран. И мы захотели вызвать dump для Integer.

void dump(Collection c) < for (Iteratori = c.iterator(); i.hasNext(); ) < Object o = i.next(); System.out.println(o); >>
List l; dump(l); List l; dump(l); // Ошибка 

В этом примере List не может использовать метод dump, так как он не является подтипом List.

Проблема в том что эта реализация кода не эффективна, так как Collection не является полностью родительской коллекцией всех остальных коллекции, грубо говоря Collection имеет ограничения.

Для решения этой проблемы используется Wildcard (“?”). Он не имеет ограничения в использовании(то есть имеет соответствие с любым типом) и в этом его плюсы. И теперь, мы можем назвать это с любым типом коллекции.

void dump(Collection c) < for (Iteratori = c.iterator(); i.hasNext(); ) < Object o = i.next(); System.out.println(o); >>
  • Решение 2 – Bounded Wildcard

Пусть мы захотели написать метод, который рисует List. И у Shape есть наследник Circle. И мы хотим вызвать draw для Circle.

void draw(List c) < for (Iteratori = c.iterator(); i.hasNext(); ) < Shape s = i.next(); s.draw(); >>
List l; draw(l); List l; draw(l); // Ошибка 

Проблема в том, что у нас не получиться из-за несовместимости типов. Предложенное решение используется, если метод который нужно реализовать использовал бы определенный тип и его подтипов. Так называемое “Ограничение сверху”. Для этого нужно вместо прописать .

void draw(List c) < for (Iteratori = c.iterator(); i.hasNext(); ) < Shape s = i.next(); s.draw(); >>
  • Решение 3 – Generic-Метод

Пусть вы захотели сделать метод, который берет массив Object и переносить их в коллекцию.

void addAll(Object[] a, Collection c) < for (int i = 0; i < a.length; i++) < c.add(a[i]); >>
addAll(new String[10], new ArrayList()); addAll(new Object[10], new ArrayList()); addAll(new Object[10], new ArrayList()); // Ошибка addAll(new String[10], new ArrayList()); // Ошибка 

Напомним, что вы не можете просто засунуть Object в коллекции неизвестного типа. Способ решения этой проблемы является использование “Generic-Метод” Для этого перед методом нужно объявить и использовать его.

 void addAll(T[] a, Collection c) < for (int i = 0; i < a.length; i++) < c.add(a[i]); >>

Но все равно после выполнение останется ошибка в третьей строчке :

addAll(new Object[10], new ArrayList()); // Ошибка 
  • Решение 4 – Bounded type argument

Реализуем метод копирование из одной коллекции в другую

 void addAll(Collection c, Collection c2) < for (Iteratori = c.iterator(); i.hasNext(); ) < M o = i.next(); c2.add(o); >>
addAll(new AL(), new AL()); addAll(new AL(), new AL()); //Ошибка

Проблема в том что две Коллекции могут быть разных типов (несовместимость generic-типов). Для таких случаев было придуман Bounded type argument. Он нужен если метод ,который мы пишем использовал бы определенный тип данных. Для этого нужно ввести (N принимает только значения M). Также можно корректно писать . (Принимает значения нескольких переменных)

 void addAll(Collection c, Collection c2) < for (Iteratori = c.iterator(); i.hasNext(); ) < N o = i.next(); c2.add(o); >>
  • Решение 5 – Lower bounded wcard

Реализуем метод нахождение максимума в коллекции.

> T max(Collection c)

List il; Integer I = max(il); class Test implements Comparable  List tl; Test t = max(tl); // Ошибка
  • > обозначает что Т обязан реализовывать интерфейс Comparable.

Ошибка возникает из за того что Test реализует интерфейс Comparable. Решение этой проблемы – Lower bounded wcard(“Ограничение снизу”). Суть в том что мы будет реализовывать метод не только для Т, но и для его Супер-типов(Родительских типов). Например: Если мы напишем

List list;

Мы можем заполнить его List, List или List.

> T max(Collection c)

  • Решение 6 – Wildcard Capture

Реализуем метод Swap в List

void swap(List list, int i, int j) < list.set(i, list.get(j)); // Ошибка >

Проблема в том, что метод List.set() не может работать с List, так как ему не известно как он List. Для решение этой проблемы используют “Wildcard Capture” (или “Capture helpers”). Суть заключается в том, чтобы обмануть компилятор. Напишем еще один метод с параметризованной переменной и будем его использовать внутри нашего метода.

void swap(List list, int i, int j) < swapImpl(list, i, j); > void swapImpl(List list, int i, int j)

Ограничения Generic

Также нужно запомнить простые правила для работы с Generics.

  • Невозможно создать массив параметра типа
Collection c; T[] ta; new T[10]; // Ошибка !!
  • Невозможно создать массив Generic-классов
new ArrayList>(); List[] la = new List[10]; // Ошибка !!

Преобразование типов

В Generics также можно манипулировать с информацией, хранящийся в переменных.

  • Уничтожение информации о типе
List l = new ArrayList();
  • Добавление информации о типе
List l = (List) new ArrayList(); List l1 = new ArrayList();

Примеры кода

  • Первый пример:
List ls; List li; ls.getClass() == li.getClass() // True ls instanceof List // True ls instanceof List // Запрещено
  • Второй пример:

Нахождение максимума в Коллекции Integer.

Collection c; Iterator i = c.iterator(); Integer max = (Integer) i.next(); while(i.hasNext()) < Integer next = (Integer) i.next(); if (next.compareTo(max) > 0) < max = next; >>
  • С помощью Generics
Collection c; Iterator i = c.iterator(); Integer max = i.next(); while(i.hasNext()) < Integer next = i.next(); if (next.compareTo(max) >0) < max = next; >>

Вам також може сподобатися

LibGDX: установка и настройка проекта в Android Studio | Делаем android игры

Разработка игр для android 2 2 541
Продолжаем делать игру Flappy Bird для android. Для начала немного оптимизируем нашу игру, чтобы

LibGDX: установка и настройка проекта в Android Studio | Делаем android игры

Разработка игр для android 4 2 412
Продолжаем серию уроков о том, как сделать игру Flappy Bird для android. На этом

navigation drawer

Документация по android 16 17 076
В этом уроке: Как создать макет для Navigation Drawer Как инициализировать Navigation Drawer Как

Продвинутый курс GameDev. Урок 1

Уроки по android разработке на Java 2 184
[:ru]Продолжаем курс по разработке игры для android с помощью библиотеки libGDX. на втором уроке

Инструменты андроид разработчика для прототипирования интерфейса android приложений

Инструменты android разработчика 2 12 523
В этом уроке узнаем, как получить список всех установленных приложений на Android. Но начать

android

Инструменты android разработчика 43 13 056

Если вам надоели тормоза эмулятора виртуального android устройства при разработке и тестировании андроид-приложений, в

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *