Что нельзя параметризовать java
Перейти к содержимому

Что нельзя параметризовать java

  • автор:

Дженерики в исключениях – что можно, а что нельзя?

1. Можно выбрасывать исключение generic-типа.
Тип-параметр T может использоваться в throws , переменная типа T может использоваться в throw . Недавно мы уже говорили об этом.

2. Нельзя использовать дженерик в catch.
Множественные блоки catch должны идти без повторений, в определенном порядке – от специфичного класса к более базовому. Стирание типов-параметров в связи с этими правилами добавило бы путаницу, не неся особой пользы.

3. Нельзя параметризовать класс-исключение типами.
Если вы попытаетесь скомпилировать конструкцию вида class MyException extends Throwable <> , то увидете ошибку generic class may not extend java.lang.Throwable .

4. Можно реализовывать исключением generic-интерфейс.
Исключение вполне может быть например Comparable или Iterable . Механизм обработки исключений работает на классах, никак не затрагивая интерфейсы.

Обобщения (Generic)

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

Рассмотрим пример с обобщением.

 class Gen  < T ob; // объявление объекта типа T // Передать конструктору ссылку на объект типа T Gen(T o) < ob = o; >// Вернуть ob T getob() < return ob; >// Показать тип T void showType() < System.out.println("Тип T: " + ob.getClass().getName()); >> // Код для кнопки // Работаем с обобщённым классом // Создаём Gen-ссылку для Integer Gen iOb; // Создаём объект Gen iOb = new Gen(77); // Показать тип данных, используемый iOb iOb.showType(); // Получить значение iOb int value = iOb.getob(); System.out.println("Значение " + value); // Создадим объект Gen для String Gen strOb = new Gen("Обобщённый текст"); // Показать тип данных, используемый strOb strOb.showType(); // Получить значение strOb String str = strOb.getob(); System.out.println("Значение: " + str); 

В результате мы получим:

Типом T является java.lang.Integer Значение: 77 Типом T является java.lang.String Значение: Обобщённый текст

Изучим код. Мы объявили класс в следующей форме:

class Gen

В угловых скобках используется T — имя параметра типа. Это имя используется в качестве заполнителя, куда будет подставлено имя реального типа, переданного классу Gen при создании реальных типов. То есть параметр типа T применяется в классе всякий раз, когда требуется параметр типа. Угловые скобки указывают, что параметр может быть обобщён. Сам класс при этом называется обобщённым классом или параметризованным типом.

Далее тип T используется для объявления объекта по имени ob:

 T ob; // объявляет объект типа T 

Вместо T подставится реальный тип, который будет указан при создании объекта класса Gen. Объект ob будет объектом типа, переданного в параметре типа T. Если в параметре T передать тип String, то экземпляр ob будет иметь тип String.

Рассмотрим конструктор Gen().

 Get(T o)

Параметр o имеет тип T. Это значит, что реальный тип параметра o определяется типом, переданным параметром типа T при создании объекта класса Gen.

Параметр типа T также может быть использован для указания типа возвращаемого значения метода:

 T getob()

В именах переменных типа принято использовать заглавные буквы. Обычно для коллекций используется буква E, буквами K и V — типы ключей и значение (Key/Value), а буквой T (и при необходимости буквы S и U) — любой тип.

Как использовать обобщённый класс. Можно создать версию класса Gen для целых чисел:

 Gen iOb; 

В угловых скобках указан тип Integer, т.е. это аргумент типа, который передаётся в параметре типа T класса Gen. Фактически мы создаём версию класса Gen, в которой все ссылки на тип T становятся ссылками на тип Integer.

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

 iOb = new Gen(77); 

Полная версия записи может быть такой:

 iOb = new Gen(new Integer(88)); 

Но такая запись избыточна, так как можно использовать автоматическую упаковку значения 77 в нужный формат.

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

 int value = iOb.getob().intValue(); // избыточный код 

Обобщения работают только с объектами. Поэтому нельзя использовать в качестве параметра элементарные типы вроде int или char:

 Gen intOb = new Gen(44); // нельзя! 

Хотя объекты iOb и strOb имеют тип Gen , они являются ссылками на разные типы и их сравнивать нельзя.

 iOB = strOb; // нельзя! 

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

Обобщённый класс с двумя параметрами

Можно указать два и более параметров типа через запятую.

 class TwoGen  < T ob1; V ob2; // Передать конструктору ссылки на объекты двух типов TwoGen(T o1, V o2) < ob1 = o1; ob2 = o2; >void showTypes() < System.out.println("Тип T: " + ob1.getClass().getName()); System.out.println("Тип V: " + ob2.getClass().getName()); >T getob1() < return ob1; >V getob2() < return ob2; >// Используем созданный класс TwoGen twogenObj = new TwoGen(77, "Обобщённый текст"); // Узнаем типы twogenObj.showTypes(); // Узнаем значения int value = twogenObj.getob1(); System.out.println("Значение: " + value); String str = twogenObj.getob2(); System.out.println("Значение: " + str); > 

Обобщённые методы

Никто не запрещает создавать и методы с параметрами и возвращаемыми значениями в виде обобщений.

 public static T getSomething(T. a)

Шаблоны аргументов

Шаблон аргументов указывается символом ? и представляет собой неизвестный тип.

 boolean sameAvg(Stats ob) . 

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

Мы можем ограничить диапазон объектов, указав суперкласс.

 public void addItems(ArrayList list) 

В этом случае можно использовать классы, которые могут быть наследниками AnimalDog, Cat. А String или Integer вы уже не сможете использовать.

Пример обобщённого метода

 public static T getMiddle(T. a) < return a[a.length / 2]; >

Переменная типа вводится после модификаторов и перед возвращаемым типом.

Отдельно стоит упомянуть новинку JDK 7, позволяющую сократить код.

 MyClass mcObj = new MyClass(33, "Meow"); // старый способ в JDK 6 MyClass mcObj = new MyClass<>(33, "Meow"); // новый способ в JDK 7 

Во второй строке используются только угловые скобки, без указания типов.

Помните, что нельзя создать экземпляр типа параметра.

 class Gen  < T ob; Gen() < ob = new T(); // Недопустимо >> 

Под Android вам часто придётся использовать списочный массив, который использует обобщещние.

 ArrayList catNames = new ArrayList(); 

В Java 7, которую можно использовать в проектах для KitKat и выше, существует укороченная запись, когда справа не указывается тип (см. выше). Компилятор сам догадается по левой части выражения.

 ArrayList catNames = new ArrayList<>(); 

Вопрос по дженерикам метода и ограничению переменных типов

Сейчас активно изучаю (или даже разбираю) известное пособие Хорстмена и Корнелла по Java2. В данный момент остановился на параметризации. Дошел до пункта «Обобщенные методы» и следующий за ним «Ограничения переменных типов». И в ближайшем листинге среди прочего кода появляется такая сигнатура:

public static Pair minmax(T[] a) 

Я понимаю, что в методе применяется метод compareTo(), принадлежащий интерфейсу Comparable и поэтому мы должны «защитить» наш метод от того, чтобы туда не затесался класс, который не реализует данный интерфейс. Ок, понятно. Но почему бы тогда нельзя было написать:

public static Pair minmax(T[] a) 

С другой стороны, возможно имелось в виду, что это не объект класса Pair должен возвращать метод, а объект любого класса, реализующий Comparable? Тогда почему не написать так:

public static T minmax(T[] a) 

? Я не отрицаю, что в пособии вполне может быть указана правильная сигнатура. И если это так, то прошу подробно описать ее. Заранее спасибо за развернутые ответы!

Отслеживать
23.4k 3 3 золотых знака 50 50 серебряных знаков 70 70 бронзовых знаков
задан 8 апр 2015 в 14:10
user178511 user178511
Перефразируйте вопрос на человеческом языке.
8 апр 2015 в 14:15
вроде норм теперь
– user178511
8 апр 2015 в 14:25

@Andr Все дело в синтаксисе Java. Предложенный вами вариант некорректен. Думаю тут дело в том, что метод статический. И объявление generic типа должно быть перед определением возвращаемого типа. На счет того, что должно быть вместо Pair точно не могу ответить. Может тут опечатка, ведь должно быть Pair или может там другая причина. Но судя по названию метода, то ищется минимально и максимальное значение в массиве, так что Pair — это нормально.

8 апр 2015 в 16:12

2 ответа 2

Сортировка: Сброс на вариант по умолчанию

Вы хотите развёрнутых ответов? Их есть у меня! Немного теории по параметризованным методам.

Если метод нестатический, а класс — параметризованный, то параметр типа наследуется из описания класса:

public class A  < pubic void doSth(T t) < // метод принимает аргумент типа T System.out.println(t.getClass().getName()); >> new A().doSth("abc"); // выведет java.lang.String new A().doSth(123); // не скомпилируется 

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

public class A < public static void doSth(T t) < System.out.println(t.getClass().getName()); >> A.doSth("abc"); // выведет java.lang.String A.doSth(123); // выведет java.lang.Integer 

В этом случае тип T определяется в момент вызова статического метода по типу передаваемого аргумента.

Ещё один случай, когда может понадобиться явно параметризовать метод — это нестатический метод, параметр типа которого должен отличаться от параметра типа класса:

public class A  < T t; public A(T t) < this.t = t; >public void doSth(Z z) < System.out.println(t.getClass().getName()); System.out.println(z.getClass().getName()); >> new A("abc").doSth(123); // выведет java.lang.String // java.lang.Integer 

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

public class A < public static void printValue(String val) < System.out.println("String"); >public static void printValue(Integer val) < System.out.println("Integer"); >public static T getValue() < return null; >> A.printValue(A.getValue()); // не скомпилируется - неясно, какую // перегрузку printValue вызывать A.printValue(A.getValue()); // выведет String A.printValue(A.getValue()); // выведет Integer 

Теперь непосредственно к вашему вопросу. Нельзя просто написать

public static Pair minmax(T[] a) 

потому что Java не знает, что за тип T . Java не может унаследовать его от объявления класса (даже если класс параметризован), так как метод minimax статический. Поэтому нужно явно прописывать параметр типа в сигнатуре метода:

public static Pair minmax(T[] a) 

Почему нельзя написать

public static T minmax(T[] a) 

? Можно, это корректная сигнатура метода. Однако, судя по названию метода, он должен возвращать одновременно минимальное и максимальное значение переданного массива. Поэтому метод возвращает объект класса Pair , который содержит в себе два значения.

Дженерики в Java для самых маленьких: синтаксис, границы и дикие карты

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

Оля Ежак для Skillbox Media

Екатерина Степанова

Екатерина Степанова

Фулстек-разработчик. Любимый стек: Java + Angular, но в хорошей компании готова писать хоть на языке Ада.

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

Теперь представьте, что содержимое коробки вы отвозите на переработку, а перед этим каждый раз приходится отделять бумагу от прочего мусора. Не хотели бы вы заполучить такую коробку, которая не даст положить в себя что-то, кроме бумаги? Если ваш ответ «да» — вам понравятся дженерики (generics).

Содержание

  • Знакомимся с дженериками
  • Объявляем дженерик-классы и создаём их экземпляры
  • Объявляем и реализуем дженерик-интерфейсы
  • Пишем дженерик-методы
  • Ограничиваем дженерики сверху и снизу
  • Wildcards
  • Собираем понятия, связанные с дженериками

Знакомимся с дженериками

До появления дженериков программисты могли неявно предполагать, что какой-то класс, интерфейс или метод работает с элементами определённого типа.

Посмотрите на этот фрагмент кода:

Паша быстро нашёл истинных виновников и попросил их исправить заполнение списка. Но на будущее решил подстраховаться от подобных ситуаций и переписал метод с использованием дженериков. Вот так:

Обратите внимание, что во второй версии Пашиного метода item не приводится насильно к типу String. Мы просто получаем в цикле очередной элемент списка, и компилятор соглашается, что это, очевидно, будет строка. Код стал менее громоздким, читать его стало проще.

Объявляем дженерик-классы и создаём их экземпляры

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

Собираем понятия, связанные с дженериками

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

Обо всём этом и не только — в следующих статьях. А пока соберём небольшой словарик из терминов, которые связаны с использованием дженериков, — они пригодятся при чтении специальной литературы:

Термин Расшифровка
Дженерик-типы (generic types) Дженерик-класс или дженерик-интерфейс с одним или несколькими параметрами в заголовке
Параметризованный тип (parameterized types) Вызов дженерик-типа. Для дженерик-типа List параметризованным типом будет, например, List
Параметр типа (type parameter) Используются при объявлении дженерик-типов. Для Box T — это параметр типа
Аргумент типа (type argument) Тип объекта, который может использоваться вместо параметра типа. Например, для Box Paper — это аргумент типа
Wildcard Обозначается символом «?» — неизвестный тип
Ограниченный wildcard (bounded wildcard) Wildcard, который ограничен сверху — или снизу —
Сырой тип (raw type) Имя дженерик-типа без аргументов типа. Для List сырой тип — это List

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

Переменные ссылочного типа хранят адрес ячейки в памяти, в которой лежит значение этой переменной.
В этом их ключевое отличие от примитивных типов, когда в переменной хранится само значение.
Все ссылочные типы в Java наследуются от типа Object.

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

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