Java приведение типов классов

Приведение типов объектов в Java

Система типов Java состоит из двух видов типов: примитивов и ссылок.

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

2. Примитив против эталона

Хотя примитивные преобразования и приведение ссылочных переменных могут выглядеть одинаково, это совершенно разные концепции .

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

 double myDouble = 1.1;   int myInt = (int) myDouble;    assertNotEquals(myDouble, myInt); 

После преобразования в приведенном выше примере переменная myInt равна 1 , и мы не можем восстановить из нее предыдущее значение 1.1 .

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

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

Ссылка похожа на дистанционное управление объектом. Пульт имеет больше или меньше кнопок в зависимости от его типа, а сам объект хранится в куче. Когда мы делаем кастинг, мы меняем тип пульта, но не меняем сам объект.

3. Обновление

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

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

Чтобы продемонстрировать восходящее приведение, давайте определим класс Animal :

 public class Animal     public void eat()    // .   >   > 

Теперь давайте расширим Animal :

 public class Cat extends Animal     public void eat()    // .   >    public void meow()    // .   >   > 

Теперь мы можем создать объект класса Cat и присвоить его ссылочной переменной типа Cat :

И мы также можем присвоить его ссылочной переменной типа Animal :

В приведенном выше присваивании имеет место неявное повышение приведения.

Мы могли бы сделать это явно:

Но нет необходимости делать явное приведение дерева наследования. Компилятор знает, что cat — это Animal , и не выводит никаких ошибок.

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

Используя восходящее преобразование, мы ограничили количество методов, доступных для экземпляра Cat , но не изменили сам экземпляр. Теперь мы не можем делать ничего специфичного для Cat — мы не можем вызывать meow() для переменной animal .

Хотя объект Cat остается объектом Cat , вызов meow() вызовет ошибку компилятора:

 // animal.meow(); The method meow() is undefined for the type Animal 

Чтобы вызвать meow() , нам нужно привести животное в нисходящее состояние , и мы сделаем это позже.

Но сейчас мы опишем, что дает нам приведение вверх. Благодаря восходящему преобразованию мы можем воспользоваться преимуществами полиморфизма.

3.1. Полиморфизм

Давайте определим еще один подкласс Animal , класс Dog :

 public class Dog extends Animal     public void eat()    // .   >   > 

Теперь мы можем определить метод feed() , который обращается со всеми кошками и собаками как с животными :

 public class AnimalFeeder     public void feed(ListAnimal> animals)    animals.forEach(animal ->    animal.eat();   >);   >   > 

Мы не хотим, чтобы AnimalFeeder заботился о том, какое животное находится в списке — кошка или собака . В методе feed() все они являются животными .

Неявное преобразование происходит, когда мы добавляем объекты определенного типа в список животных :

 ListAnimal> animals = new ArrayList>();  animals.add(new Cat());  animals.add(new Dog());   new AnimalFeeder().feed(animals); 

Мы добавляем кошек и собак, и они неявно преобразуются в тип Animal . Каждая кошка — животное , и каждая собака — животное . Они полиморфны.

Кстати, все объекты Java полиморфны, потому что каждый объект является как минимум Object . Мы можем присвоить экземпляр Animal ссылочной переменной типа Object , и компилятор не будет жаловаться:

 Object object = new Animal(); 

Вот почему все объекты Java, которые мы создаем, уже имеют методы, специфичные для объекта , например toString() .

Восходящее преобразование в интерфейс также распространено.

Мы можем создать интерфейс Mew и заставить Cat реализовать его:

 public interface Mew    public void meow();   >    public class Cat extends Animal implements Mew     public void eat()    // .   >    public void meow()    // .   >   > 

Теперь любой объект Cat также можно преобразовать в Mew :

Кошка Мяу ; _ upcasting является законным и выполняется неявно.

Следовательно, Cat — это Mew , Animal , Object и Cat . В нашем примере его можно присвоить ссылочным переменным всех четырех типов.

3.2. Переопределение

В приведенном выше примере метод eat() переопределен. Это означает, что хотя eat() вызывается для переменной типа Animal , работа выполняется методами, вызываемыми для реальных объектов — кошек и собак:

 public void feed(ListAnimal> animals)    animals.forEach(animal ->    animal.eat();   >);   > 

Если мы добавим логирование в наши классы, то увидим, что вызываются методы Cat и Dog :

web - 2018-02-15 22:48:49,354 [main] INFO com.foreach.casting.Cat - cat is eating web - 2018-02-15 22:48:49,363 [main] INFO com.foreach.casting.Dog - dog is eating 

Подводить итоги:

  • Ссылочная переменная может ссылаться на объект, если объект того же типа, что и переменная, или если он является подтипом.
  • Обновление происходит неявно.
  • Все объекты Java являются полиморфными и могут рассматриваться как объекты супертипа благодаря восходящему преобразованию.

4. Понижение

Что, если мы хотим использовать переменную типа Animal для вызова метода, доступного только для класса Cat ? А вот и уныние. Это приведение от суперкласса к подклассу.

Давайте посмотрим на пример:

Мы знаем, что переменная animal относится к экземпляру Cat . И мы хотим вызвать метод Cat meow () для животного . Но компилятор жалуется, что для типа Animal не существует метода meow() . «

Чтобы вызвать meow() , мы должны преобразовать animal в Cat :

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

Давайте перепишем предыдущий пример AnimalFeeder с методом meow () :

 public class AnimalFeeder     public void feed(ListAnimal> animals)    animals.forEach(animal ->    animal.eat();   if (animal instanceof Cat)    ((Cat) animal).meow();   >   >);   >   > 

Теперь мы получаем доступ ко всем методам, доступным классу Cat . Посмотрите журнал, чтобы убедиться, что функция meow() действительно вызывается:

web - 2018-02-16 18:13:45,445 [main] INFO com.foreach.casting.Cat - cat is eating web - 2018-02-16 18:13:45,454 [main] INFO com.foreach.casting.Cat - meow web - 2018-02-16 18:13:45,455 [main] INFO com.foreach.casting.Dog - dog is eating 

Обратите внимание, что в приведенном выше примере мы пытаемся преобразовать только те объекты, которые действительно являются экземплярами Cat . Для этого воспользуемся оператором instanceof .

4.1. оператор instanceof

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

 if (animal instanceof Cat)    ((Cat) animal).meow();   > 

4.2. ClassCastException

Если бы мы не проверяли тип с помощью оператора instanceof , компилятор не жаловался бы. Но во время выполнения будет исключение.

Чтобы продемонстрировать это, давайте удалим оператор instanceof из приведенного выше кода:

 public void uncheckedFeed(ListAnimal> animals)    animals.forEach(animal ->    animal.eat();   ((Cat) animal).meow();   >);   > 

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

java.lang.ClassCastException: com.foreach.casting.Dog не может быть приведен к com.foreach.casting.Cat

Это означает, что мы пытаемся преобразовать объект, являющийся экземпляром Dog , в экземпляр Cat .

ClassCastException всегда выбрасывается во время выполнения, если тип, к которому мы приводим, не соответствует типу реального объекта.

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

 Animal animal;   String s = (String) animal; 

Компилятор говорит: «Невозможно привести тип Animal к String».

Чтобы код компилировался, оба типа должны находиться в одном дереве наследования.

  • Понижающее приведение необходимо для получения доступа к членам, специфичным для подкласса.
  • Даункастинг выполняется с помощью оператора приведения.
  • Чтобы безопасно понизить объект, нам нужен оператор instanceof .
  • Если реальный объект не соответствует типу, к которому мы приводим его, во время выполнения будет выброшено исключение ClassCastException .

5. Метод приведения()

Есть еще один способ приведения объектов с помощью методов класса :

 public void whenDowncastToCatWithCastMethod_thenMeowIsCalled()    Animal animal = new Cat();   if (Cat.class.isInstance(animal))    Cat cat = Cat.class.cast(animal);   cat.meow();   >   > 

В приведенном выше примере вместо операторов cast и instanceof используются методы cast( ) и isInstance() соответственно. «

Обычно методы cast() и isInstance() используются с универсальными типами.

Создадим класс AnimalFeederGeneric с методом feed() , который «кормит» только один вид животных, кошек или собак, в зависимости от значения параметра type:

 public class AnimalFeederGenericT>    private ClassT> type;    public AnimalFeederGeneric(ClassT> type)    this.type = type;   >    public ListT> feed(ListAnimal> animals)    ListT> list = new ArrayListT>();   animals.forEach(animal ->    if (type.isInstance(animal))    T objAsType = type.cast(animal);   list.add(objAsType);   >   >);   return list;   >    > 

Метод feed() проверяет каждое животное и возвращает только те, которые являются экземплярами T .

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

Сделаем T равным Cat и убедимся, что метод возвращает только котов:

 @Test   public void whenParameterCat_thenOnlyCatsFed()    ListAnimal> animals = new ArrayList>();   animals.add(new Cat());   animals.add(new Dog());   AnimalFeederGenericCat> catFeeder  = new AnimalFeederGenericCat>(Cat.class);   ListCat> fedAnimals = catFeeder.feed(animals);    assertTrue(fedAnimals.size() == 1);   assertTrue(fedAnimals.get(0) instanceof Cat);   > 

6. Заключение

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

Как всегда, код для этой статьи доступен на GitHub .

Источник

Читайте также:  Выбрать расширение файла php
Оцените статью