





1. Все классы унаследованы от Object
Все классы в Java неявно (скрытно) унаследованы от класса Object
.
Что такое наследование и как оно работает в Java, мы разберем в квесте Java Core. Сейчас же мы рассмотрим один простой факт, который из этого следует:
Переменной типа Object
можно присвоить объект любого класса. Пример:
Код | Примечание |
---|---|
|
В переменную o сохранена ссылка на объект типа Scanner |
|
В переменную o сохранена ссылка на объект типа String |
|
В переменную o сохранена ссылка на объект типа Integer |
|
В переменную o сохранена ссылка на объект типа String |
На этом хорошие новости заканчиваются. Компилятор не следит за тем, объект какого именно типа был сохранен в переменную типа Object
, поэтому вызвать методы, которые были у сохраненного объекта, но которых нет у переменной типа Object
нельзя.
Если нужно вызвать методы такого объекта, то сначала ссылку на него нужно сохранить в переменную правильного типа, а только потом вызвать методы у этой переменной:
Код | Примечание |
---|---|
|
Программа не скомпилируется. У класса Object нет метода nextInt() . |
|
Так будет работать. Тут мы сохраняем ссылку на объект типа Scanner в переменную типа Scanner с помощью оператора приведения типа.
|
Просто так переменную типа Object
нельзя присвоить переменной типа Scanner, даже если переменная типа Object
хранит ссылку на объект типа Scanner
. Зато это можно сделать, если использовать уже известный вам оператор приведения типа. В общем виде выглядит это так:
Тип имя1 = (Тип) имя2;
Где имя1
– это имя переменной типа Тип
, а имя2
– это имя переменной типа Object
, которая хранит ссылку на объект типа Тип
.
Приведение типа
Если типы переменной и объекта не совпадают, возникнет ошибка ClassCastException
. Пример:
Код | Примечание |
---|---|
|
Во время выполнения возникнет ошибка: тут кинется исключение ClassCastException |
В Java есть способ обойти эту ошибку: существует способ проверить, какой на самом деле тип находится внутри переменной:
имя instanceof Тип
Оператор instanceof
проверяет, является ли переменная имя
объектом типа Тип
.
Пример — нахождение строки среди массива данных:
Код | Примечание |
---|---|
|
Autoboxing превратит эти значения в Integer , String и Double .Цикл по массиву объектов Если объект имеет тип String Сохраняем его в переменную типа String Выводим переменную на экран. |
2. Причина возникновения шаблонов (коллекции)
Возвращаемся к коллекциям.
Когда Java-разработчики только создавали класс ArrayList
, они хотели сделать его универсальным, чтобы в нем можно было хранить объекты любого типа. Поэтому для хранения элементов они воспользовались массивом типа Object
.
Сильная сторона такого подхода в том, что в коллекцию можно добавить объект любого типа.
Ну а слабых сразу несколько.
Недостаток 1.
Всегда приходилось писать оператор преобразования типа, когда доставали элементы из коллекции:
Код | Примечание |
---|---|
|
Создаем объект-коллекцию для хранения ссылок на объекты типа Object Заполняем коллекцию цифрами 10 , 20 , ... 100 ;Суммируем элементы коллекции Нужно использовать приведение типа |
Недостаток 2.
Не было гарантии, что в коллекции хранятся элементы определенного типа
Код | Примечание |
---|---|
|
Создаем объект-коллекцию для хранения ссылок на объекты типа Object Заполняем коллекцию числами типа Double :0.0 , 2.5 , 5.0 , ...Суммируем элементы коллекции Будет ошибка: тип Double нельзя привести к типу Integer |
Данные в коллекцию могут заполняться где угодно:
- в другом методе
- в другой программе
- загружаться из файла
- получаться по сети
Недостаток 3.
Данные коллекции можно случайно поменять по незнанию.
Вы можете передать коллекцию, заполненную вашими данными в какой-то метод, а этот метод, написанный совсем другим программистом, добавит в вашу коллекцию свои данные.
По названию коллекции непонятно, какие именно типы данных можно в ней хранить. А даже если и дать переменной такое название, ссылку на нее можно передать в десяток методов, и уж там-то точно об изначальном имени переменной ничего не будет известно.
3. Дженерики
Все эти проблемы устраняет такая классная вещь в Java как дженерики (Generics).
Под дженериками в Java подразумевают возможность добавлять к типам типы-параметры. Таким образом получаются сложные составные типы. Такой составной тип в общем случае выглядит так:
ОсновнойТип<ТипПараметр>
Все вместе — это именно тип. И он может использоваться там, где обычно можно использовать типы.
Код | Описание |
---|---|
|
Создание переменных |
|
Создание объектов |
|
Создание массивов |
В такую коллекцию можно сохранить только переменные типа Integer
:
Код | Описание |
---|---|
|
Коллекция типа ArrayList с элементами типа Integer Так можно И так можно: сработает autoboxing
А так нельзя: ошибка компиляции |
Как создавать свои классы с типами-параметрами, вы изучите в квесте Java Collections. Сейчас же мы разберем, как этим пользоваться и как это работает.
4. Как работают Generics
На самом деле Generics работают до ужаса примитивно.
Компилятор просто заменяет тип с параметром на него же, только без параметра. А при взаимодействии с его методами добавляет операцию приведения типа к типу-параметру:
Код | Что сделает компилятор |
---|---|
|
|
|
|
|
|
|
|
Допустим, у нас был код метода, который суммирует числа в коллекции целых чисел:
Код | Что сделает компилятор |
---|---|
|
|
Т.е. по сути дженерики — это такая разновидность синтаксического сахара, как и autoboxing, только побольше. При autoboxing компилятор за нас добавляет методы для преобразования типа int
к Integer
и обратно, а для generics добавляет операторы приведения типа.
После того, как компилятор скомпилировал ваш код с дженериками, в нем все классы с параметрами были преобразованы просто в классы и операторы приведения типа. Информация о том, какие изначально были типы-параметры у переменных сложных типов, потерялась. Этот эффект еще называют стиранием типов.
Иногда программистам, которые пишут свои классы с типами-параметрами, очень не хватает информации о типах, которые туда передаются в качестве параметров. Как с этим борются и что из этого выходит, вы узнаете в квесте Java Collections.
5. Несколько фактов о дженериках
Еще несколько интересных фактов о дженериках.
У классов может быть не один тип параметр, а несколько. Выглядит это примерно так:
ОсновнойТип<ТипПараметр1, ТипПараметр2, ТипПараметр3>
Собственно говоря, в этом нет ничего удивительного. Там, где компилятор может добавить оператор приведения к одному типу, он может добавить и несколько таких.
Примеры:
Код | Примечание |
---|---|
|
первый параметр метода 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);






ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ