1. Все классы унаследованы от Object

Все классы в Java неявно (скрытно) унаследованы от класса Object.

Что такое наследование и как оно работает в Java, мы разберем в квесте Java Core. Сейчас же мы рассмотрим один простой факт, который из этого следует:

Переменной типа Object можно присвоить объект любого класса. Пример:

Код Примечание
Object o = new Scanner(System.in);
В переменную o сохранена ссылка на объект типа Scanner
Object o = new String();
В переменную o сохранена ссылка на объект типа String
Object o = new Integer(15);
В переменную o сохранена ссылка на объект типа Integer
Object o = "Привет";
В переменную o сохранена ссылка на объект типа String

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

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

Код Примечание
Object o = new Scanner(System.in);
int x = o.nextInt();
Программа не скомпилируется. У класса Object нет метода nextInt().
Object o = new Scanner(System.in);

Scanner console = (Scanner) o;

int x = console.nextInt();
Так будет работать.

Тут мы сохраняем ссылку на объект типа Scanner в переменную типа Scanner с помощью оператора приведения типа.

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

Тип имя1 = (Тип) имя2;

Где имя1 – это имя переменной типа Тип, а имя2 – это имя переменной типа Object, которая хранит ссылку на объект типа Тип.

Приведение типа

Если типы переменной и объекта не совпадают, возникнет ошибка ClassCastException. Пример:

Код Примечание
Object o = new Integer(5);
String s = (String) o;
Во время выполнения возникнет ошибка:
тут кинется исключение ClassCastException

В Java есть способ обойти эту ошибку: существует способ проверить, какой на самом деле тип находится внутри переменной:

имя instanceof Тип

Оператор instanceof проверяет, является ли переменная имя объектом типа Тип.

Пример — нахождение строки среди массива данных:

Код Примечание
Object[] objects = {10, "Привет", 3.14};

for (int i = 0; i < objects.length; i++)
{
   if (objects[i] instanceof String)
   {
      String s = (String) objects[i];
      System.out.println(s);
   }
}
Autoboxing превратит эти значения в Integer, String и Double.

Цикл по массиву объектов

Если объект имеет тип String

Сохраняем его в переменную типа String
Выводим переменную на экран.

undefined
13
Задача
Java Syntax Pro, 13 уровень, 7 лекция
Недоступна
Охотники за привидением: в погоне за типами
В методе main создается и заполняется список разными объектами и передается методу checkElementType(ArrayList). Твоя задача — реализовать этот метод, который должен определить тип каждого элемента списка, а также: 1. Для типа String вызывать printString(); 2. Для типа Integer вызывать print

2. Причина возникновения шаблонов (коллекции)

Возвращаемся к коллекциям.

Когда Java-разработчики только создавали класс ArrayList, они хотели сделать его универсальным, чтобы в нем можно было хранить объекты любого типа. Поэтому для хранения элементов они воспользовались массивом типа Object.

Сильная сторона такого подхода в том, что в коллекцию можно добавить объект любого типа.

Ну а слабых сразу несколько.

Недостаток 1.

Всегда приходилось писать оператор преобразования типа, когда доставали элементы из коллекции:

Код Примечание
ArrayList numbers = new ArrayList();


for (int i = 0; i < 10; i++)
   numbers.add(i * 10);


int sum = 0;
for (int i = 0; i < 10; i++)
{
   sum = sum + (Integer) numbers.get(i);
}
Создаем объект-коллекцию для хранения ссылок на объекты типа Object

Заполняем коллекцию цифрами 10, 20, ... 100;



Суммируем элементы коллекции


Нужно использовать приведение типа

Недостаток 2.

Не было гарантии, что в коллекции хранятся элементы определенного типа

Код Примечание
ArrayList numbers = new ArrayList();


for (int i = 0; i < 10; i++)
   numbers.add(i * 2.5);


int sum = 0;
for (int i = 0; i < 10; i++)
{
   sum = sum + (Integer) numbers.get(i);
}
Создаем объект-коллекцию для хранения ссылок на объекты типа Object

Заполняем коллекцию числами типа Double:
0.0, 2.5, 5.0, ...


Суммируем элементы коллекции


Будет ошибка: тип Double нельзя привести к типу Integer

Данные в коллекцию могут заполняться где угодно:

  • в другом методе
  • в другой программе
  • загружаться из файла
  • получаться по сети

Недостаток 3.

Данные коллекции можно случайно поменять по незнанию.

Вы можете передать коллекцию, заполенную вашими данными в какой-то метод, а этот метод, написанный совсем другим программистом, добавит в вашу коллекцию свои данные.

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


3. Дженерики

Дженерики в Java

Все эти проблемы устраняет такая классная вещь в Java как дженерики (Generics).

Под дженериками в Java подразумевают возможность добавлять к типам типы-параметры. Таким образом получаются сложные составные типы. Такой составной тип в общем случае выглядит так:

ОсновнойТип<ТипПараметр>

Все вместе — это именно тип. И он может использоваться там, где обычно можно использовать типы.

Код Описание
ArrayList<Integer> list;
Создание переменных
list = new ArrayList<Integer> ();
Создание объектов
ArrayList<Integer>[] array;
Создание массивов

В такую коллекцию можно сохранить только переменные типа Integer:

Код Описание
ArrayList<Integer> list = new ArrayList<Integer>();
list.add(new Integer(1));
list.add(2);
list.add("Привет");
Коллекция типа ArrayList с элементами типа Integer
Так можно
И так можно: сработает
autoboxing

А так нельзя: ошибка компиляции

Как создавать свои классы с типами-параметрами, вы изучите в квесте Java Collections. Сейчас же мы разберем, как этим пользоваться и как это работает.


4. Как работают Generics

На самом деле Generics работают до ужаса примитивно.

Компилятор просто заменяет тип с параметром на него же, только без параметра. А при взаимодействии с его методами добавляет операцию приведения типа к типу-параметру:

Код Что сделает компилятор
ArrayList<Integer> list = new ArrayList<Integer>();
ArrayList list = new ArrayList();
list.add(1);
list.add( (Integer) 1 );
int x = list.get(0);
int x = (Integer) list.get(0);
list.set(0, 10);
list.set(0, (Integer) 10);

Допустим, у нас был код метода, который суммирует числа в коллекции целых чисел:

Код Что сделает компилятор
public int sum(ArrayList<Integer> numbers)
{
   int result = 0;

   for (int i = 0; i < numbers.size(); i++)
      result = result + numbers.get(i);

   return result;
}
public int sum(ArrayList numbers)
{
   int result = 0;

   for (int i = 0; i < numbers.size(); i++)
      result = result + (Integer) numbers.get(i);

   return result;
}

Т.е. по сути дженерики — это такая разновидность синтаксического сахара, как и autoboxing, только побольше. При autoboxing компилятор за нас добавляет методы для преобразования типа int к Integer и обратно, а для generics добавляет операторы приведения типа.

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

Иногда программистам, которые пишут свои классы с типами-параметрами, очень нехватает информации о типах, которые туда передаются в качестве параметров. Как с этим борются и что из этого выходит, вы узнаете в квесте Java Collections.


undefined
13
Задача
Java Syntax Pro, 13 уровень, 7 лекция
Недоступна
Дженерики и студенты
В классе Faculty есть метод printStudentNames(ArrayList), который принимает список объектов и выводит их имена. Но на данный момент программа не компилируется. Твоя задача исправить этот баг. Для этого нужно исправить сигнатуру метода printStudentNames, чтобы он принимал список объектов только типа Student (используй дженерик). Метод main не участвует в проверке.
undefined
13
Задача
Java Syntax Pro, 13 уровень, 7 лекция
Недоступна
Выводим в консоли разные типы
В классе Solution есть метод printAnything(ArrayList), который должен принимать список и выводить его содержимое в консоли. Сейчас на вход принимается только список строк. Твоя задача исправить метод так, чтобы принимался список с любыми типами данных (для этого нужно удалить соответствующий дженери

5. Несколько фактов о дженериках

Еще несколько интересных фактов о дженериках.

У классов может быть не один тип параметр, а несколько. Выглядит это примерно так:

ОсновнойТип<ТипПараметр1, ТипПараметр2, ТипПараметр3>

Собственно говоря, в этом нет ничего удивительного. Там, где компилятор может добавить оператор приведения к одному типу, он может добавить и несколько таких.

Примеры:

Код Примечание
HashMap<Integer, String> map = new HashMap<Integer, String>();
map.put(7, "Привет");
map.put(-15, "Привет");
первый параметр метода put имеет тип Integer, второй — тип String

Также сложные типы тоже можно использовать в качестве параметров. Выглядит это примерно так:

ОсновнойТип<ТипПараметр<ТипПараметрПараметра>>

Допустим, мы хотим создать список, который будет хранить списки строк. В таком случае мы получим примерно такой код:

// список приветствий
ArrayList<String> listHello = new ArrayList<String>();
listHello.add("Привет");
listHello.add("Hi");

// список прощаний
ArrayList<String> listBye = new ArrayList<String>();
listBye.add("Пока");
listBye.add("Good Bye");

// список списков
ArrayList<ArrayList<String>> lists = new ArrayList<ArrayList<String>>();
lists.add(listHello);
lists.add(listBye);

Так же сложные типы (типы с параметрами) можно использовать в качестве типов массивов. Выглядит это примерно так:

ОсновнойТип<ТипПараметр>[] array = new ОсновнойТип<ТипПараметр>[длина];

Никакой магии тут нет: технически треугольные скобки являются просто записью имени типа:

Код Аналог без параметра
ArrayList<String>[] list = new ArrayList<String>[10];
StringArrayList[] list = new StringArrayList[10];
ArrayList<Integer>[] list = new ArrayList<Integer>[10];
IntegerArrayList[] list = new IntegerArrayList[10];
ArrayList<Scanner>[] list = new ArrayList<Scanner>[10];
ScannerArrayList[] list = new ScannerArrayList[10];