Обобщенные типы и классы Java, безопасные по отношению к типам

Безопасные Обобщенные типы и классы JavaДо появления контейнеров Java SE5 одна из главных проблем заключалась в том, что компилятор позволял вставить в контейнер объект неправильного типа. Допустим, вы храните набор объектов Apple с использованием простейшего контейнера ArrayList. Пока считайте, что ArrayList — массив, который автоматически расширяется по мере надобности. Использовать ArrayList несложно: создайте объект контейнера, вставьте в него объекты методом add() и обращайтесь к ним методом get() с указанием индекса точно так же, как с масивами, но без квадрантных скобок. Класс ArrayList также содер­жит метод size(), который сообщает, сколько элементов было добавлено в контейнер, чтобы случайное обращение к элементу за концом массива не привело к ошибке (с вы­дачей исключения времени выполнения).

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

В следующем примере в контейнер помещаются объекты Apple и Orange, которые за­тем извлекаются из него. В общем случае компилятор Java выдает предупреждение, потому что в этом примере обобщенные типы не используются. Для подавления предупреждения используется специальная аннотация Java SE5. Аннотации начинаются со знака @ и могут получать аргумент; в данном случае используется аннотация @SuppressWarnings, а аргумент указывает, что подавляться должны только «непро­веряемые» предупреждения:

// Простой пример использования контейнера (с предупреждением компилятора).
// {ThrowsException} 
import java.util.*;

class Apple {
   private static long counter; 
   private final long id = counter++; 
   public long id() { return id; }

class Orange {}

public class ApplesAndOrangesWithoutGenerics { 
   @SuppressWarnings("unchecked") 
   public static void main(String[] args) {
      ArrayList apples = new ArrayList(); 
      for(int i = 0; i < 3; i++) 
         apples.add (new Apple());
      // He мешает добавить Orange в apples:
      apples.add(new Orange());
      for(int i = 0; i < apples.size(); i++)
         ((Apple)apples.get(i)).id();
         // Объект Orange обнаруживается только во время выполнения
   }
} /* (Выполните., чтобы увидеть результат) *///:~

Apple и Orange — совершенно разные классы; они не имеют ничего общего, кроме того, что оба являются разновидностью Object (помните: если класс, от которого вы насле­дуете, не указан явно, то автоматически используется наследование от Object). Так как контейнер ArrayList содержит элементы Object, в него можно добавлять методом add() не только объекты Apple, но и объекты Orange; ни компилятор, ни исполнительная среда вам в этом не помешают. Но когда придет время прочитать то, что вы считаете объектом Apple, с использовнием метода get() класса ArrayList, вы получите ссылку на Object, которую необходимо преобразовать в Apple. Все выражение заключается в круглые скобки, чтобы преобразование было выполнено перед вызовом метода id() класса Apple; в противном случае вы получите синтаксическую ошибку.

Во время выполнения попытка преобразовать объект Orange в Apple приводит к ошибке в форме исключения (см. выше).

Далее в моих блогах вы узнаете, что создание классов с использованием механизма обобщенных типов Java может быть достаточно нетривиальным делом. Впрочем, использование го­товых обобщенных классов обычно проходит весьма прямолинейно. Например, чтобы определить контейнер ArrayList для хранения объектов Apple, следует использовать запись ArrayList<Apple> вместо простого имени ArrayList. В угловые скобки заклю­чаются параметры-типы (их может быть несколько); они определяют типы, которые могут храниться в данном экземпляре контейнера.

Использование обобщенных типов предотвращает помещение неверного типа объекта в контейнер на стадии компиляции. Рассмотрим тот же пример с использованием обобщенных типов:

import java.util.*;

public class ApplesAndOrangesWithGenerics { 
   public static void main(String[] args) { 
      ArrayList<Apple> apples = new ArrayList<Apple>(); 
      for(int i « 0; i < 3; i++) 
         apples.add(new Apple());
      // Ошибка во время компиляции:
      // apples.add(new Orange()); 
      fop(int i * 0; i < apples.size(); i++)
         System.out.println(apples.get(i).id());
      // Использование синтаксиса foreach: for(Apple c : apples)
         System.out.println(c.id());
   }
} /* Output:

0
1
2
0
1
2
*///:-

Теперь компилятор не позволяет поместить в apples объект Orange; таким образом, разработчик узнает об ошибке не во время выполнения, а во время компиляции.

Также обратите внимание на то, что при выборке данных из List преобразование типа становится лишним. Контейнер знает, какой тип в нем хранится, и автоматически вы­полняет преобразование при вызове get(). Таким образом, с обобщенными типами вы не только знаете, что компилятор проверит тип объекта, помещаемого в контейнер, но и можете использовать более компактный синтаксис при выборке объектов из контейнера.

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

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

import java.util.*;

class GrannySmith extends Apple {} 
class Gala extends Apple {} 
class Fuji extends Apple {} 
class Braeburn extends Apple {}

public class GenericsAndUpcasting { 
   public static void main(String[] args) {
      ArrayList<Apple> apples = new ArrayList<Apple>();
      apples.add(new GrannySmith());
      apples.add(new Gala());
      apples.add(new Fuji());
      apples.add(new Braeburn(>);
      for(Apple c : apples)
         System.out.println(c);
   }
} /* Output: (Sample) 

GrannySmith@7d772e
Gala@llb86e7
Fuji@35ce36
Braeburn@757aef
*///:-

Таким образом, в контейнер для объектов Apple можно поместить объект одного из субтипов Apple.

Выходные данные были получены от реализации метода toString() класса Object, которая выводит имя класса с последующим шестнадцатеричным числом без знака — представлением хеш-кода объекта (сгенерированным методом hashCode()).

Практическое задание. Создайте новый класс с именем Gerbil с полем gerbilNumber типа int, инициали­зируемым в конструкторе. Определите в нем метод hop(), который выводит значение gerbilNumber и короткое сообщение. Создайте контейнер ArrayList и добавьте в него объекты Используйте метод get() для перебора элементов и вызова hop() для каждого объекта Gerbil.

Вас заинтересует / Intresting for you:

Распространенные заблуждения о...
Распространенные заблуждения о... 1646 просмотров Ирина Светлова Thu, 21 Jun 2018, 18:35:12
Пример написания программы на ...
Пример написания программы на ... 12461 просмотров Светлана Комарова Wed, 07 Nov 2018, 08:24:40
Язык программирования Java: ос...
Язык программирования Java: ос... 3466 просмотров Ирина Светлова Sat, 09 Jun 2018, 10:11:23
Выбор среды для разработки код...
Выбор среды для разработки код... 1611 просмотров Stas Belkov Sun, 10 Jun 2018, 14:21:35
Войдите чтобы комментировать