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
15
Задача
Java Syntax Pro, 15 уровень, 6 лекция
Недоступна
Лонг дринк
При запуске программа заканчивается исключением. Но из текста исключения в консоли неясно, где оно возникло. В этой задаче тебе нужно: - обернуть в try-catch код методов addIce, addVodka, addJuice и addOrange; - в блоках catch вызвать метод printBugMethodName и передать в него stack trace. Если сд

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
15
Задача
Java Syntax Pro, 15 уровень, 6 лекция
Недоступна
Готовим коктейли
Метод printStackTrace принимает в качестве параметра массив stackTrace. Нужно вывести информацию о каждом элементе массива в формате: "Метод <имя метода> вызван из строки <номер строки> класса <имя класса> в файле <имя файла>." Информацию о каждом элементе выводи с новой строки.
undefined
15
Задача
Java Syntax Pro, 15 уровень, 6 лекция
Недоступна
Стек в домашних условиях
В классе MyStack реализуй свой стек на базе списка.

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);
undefined
15
Задача
Java Syntax Pro, 15 уровень, 6 лекция
Недоступна
Логирование стектрейса
В методе main перехвати исключение и выведи его стектрейс.