it-roy-ru.com

Полиморфизм в Java: почему мы устанавливаем родительскую ссылку на дочерний объект?

Я хочу понять сценарий использования установки родительской ссылки на дочерний объект . Пример: класс Dog расширяет класс Animal. (Нет интерфейсов, имейте это в виду) Обычно я создаю объект Dog следующим образом:

Dog obj = new Dog();

Теперь, так как Dog является подклассом Animal, он уже имеет доступ ко всем методам и переменным Animal. Тогда какая разница:

Animal obj = new Dog(); 

Пожалуйста, предоставьте правильный вариант использования с фрагментом кода его использования. Никаких теоретических статей о «Полиморфизме» или «Кодировании интерфейсов», пожалуйста!

Код:

public class Polymorphism {
    public static void main(String[] args){
        Animal obj1 = new Dog();
        Dog obj2 = new Dog();
        obj1.shout(); //output is bark..
        obj2.shout(); //output is bark..        
    }   
}

class Animal{
    public void shout(){
        System.out.println("Parent animal's shout");
    }       
}

class Dog extends Animal{
    public void shout(){
        System.out.println("bark..");
    }
}

class Lion extends Animal{
    public void shout(){
        System.out.println("roar..");
    }
}

class Horse extends Animal{
    public void shout(){
        System.out.println("neigh");
    }
}

Вывод одинаков для обоих случаев. Тогда почему мы устанавливаем родительскую ссылку на дочерний объект?

10
Subhadeep Banerjee

Хорошо. Я думаю, что получил свой ответ.

public class Polymorphism {
    public static void main(String[] args){
        Animal obj1 = new Horse();
        Horse obj2 = new Horse();

        obj1.shout();    //output is neigh..
        obj2.shout();    //output is neigh..
        obj1.winRaces(); /*But this is not allowed and throws compile time error, 
                           even though the object is of Animal type.*/ 
    }   
}

class Animal{
    public void shout(){
        System.out.println("Parent animal's shout");
    }       
}

class Horse extends Animal{
    public void shout(){
        System.out.println("neigh..");
    }
    public void winRaces(){
        System.out.println("won race..");
    }
}

Таким образом, когда мы используем родительскую ссылку для объекта дочернего класса, мы не можем получить доступ к каким-либо конкретным методам в дочернем классе (которые отсутствуют в родительском классе) с использованием этого объекта.

1
Subhadeep Banerjee

Позвольте мне написать код.

List<String> list = new ArrayList<String>;
list.doThis();
list.doThat();

Ой, подождите .. Я сошел с ума. Я хочу использовать LinkedList вместо ArrayList

List<String> list = new LinkedList<String>;
list.doThis();
list.doThat();

Да, я должен изменить только часть декларации. Не нужно трогать весь мой код. Благодаря программированию интерфейсов и суперклассам.

8
Suresh Atta

Подумайте в общем, вы будете знать концепцию Java casting/oop.

Dog - это тип Animal, поэтому вы можете назначить его животному. 

Но вы не можете присвоить AnimalDog. Потому что это может быть любое другое животное, например Cat. Если вы уверены, что объект Dog, вы можете преобразовать его в Animal. Если Animal имеет тип Dog, вы не можете волшебным образом привести его к Goat.

3
Raghavendra

Это реализация принципа, который гласит:

Программа для интерфейса, а не для реализации.

Например, если вы разрабатываете метод для приема ссылки типа Animal, то в будущем вы можете легко передать ему реализацию = Cat (при условии, конечно, что Cat является подтипом Animal).

Что значит -

public void doSomethingWithAnimal(Animal animal) {
    // perform some action with/on animal
}

гораздо более гибкий, чем -

public void doSomethingWithAnimal(Dog d) {
    // your code
}

потому что для первого метода вы можете легко сделать что-то вроде -

doSomethingWithAnimal(new Cat());

если вы когда-нибудь решите создать новый тип Cat, наследуя от Animal.

2
MD Sayem Ahmed

Хотя есть несколько хороших ответов (среди «ме»), кажется, что ни один не был приемлем для вас. Возможно, они слишком теоретические или содержат детали, которые вас не интересуют. Так что попробуйте еще раз:


Для примера, который вы описали, это не имеет значения. Если у вас действительно есть только метод с двумя строками, как

void doit()
{
    Animal x = new Dog();
    x.shout();
}

тогда вы могли бы также написать

void doit()
{
    Dog x = new Dog();
    x.shout();
}

и это будет не иметь прямой недостаток.


Можно даже обобщить это утверждение: для ссылки, которая используется только локально , это не имеет значения. Когда вы объявляете ссылку в методе и только используете эту ссылку в этом методе, и не / передаете ее другим методам, тогда нет прямого преимущества в объявлении ее как Animal вместо как Dog. Вы можете оба.

Но...

даже если вас это не интересует, я не могу это пропустить:

... использование родительского типа является частью лучшей практики: 

Вы всегда должны использовать наименее конкретный тип, достаточный для того, что вы хотите сделать

У этого есть различные технические причины, касающиеся абстракции, обобщения, гибкости, применения полиморфизма, и можно даже пойти так далеко, чтобы назвать это своего рода «гигиеной типа». 

И это явно также относится к случаю, когда ссылка используется только локально: если вы не хотите вызывать методы, специфичные для типа Dog, а хотите вызывать только методы из класса Animal, тогда вы следует прояснить это, объявив переменную как Animal - просто потому, что это наименее конкретный тип, который вам нужен. Таким образом, в этих случаях есть преимущество косвенного использования типа Animal, а именно то, что ясно, что в следующем коде будут использоваться только методы класса Animal и none класса Dog

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

1
Marco13

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

Я часто использую:

List<Object> aList = new ArrayList<>();

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

0
Thom

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

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

Итак, представьте, у вас есть метод

int getAge(Animal a) {
   return Days.toYears(currentDate() - a.dateOfBirth());
}

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


Но, если вам случится понять вышесказанное, все же спросите конкретно почему бы написать

Animal a = new Dog();

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

List<String> strings = new ArrayList<>();

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

0
Marko Topolnik

Глядя на вопрос: -

Polymorphism in Java: Why do we set parent reference to child object?

В методе, как показано ниже (Factory Pattern):

public Animal doSomething(String str){
if(str.equals("dog")){
    return new Dog();
    }
else if(str.equals("cat")){
    return new Cat();
    }
else {
    return new Animal();
    }
}

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

Полный пример приведен ниже:

package com.test.factory;

public class Animal{
    public void shout(){
        System.out.println("Parent animal's shout");
    }       
}

package com.test.factory;

public class Dog extends Animal{

    @Override
    public void shout(){
        System.out.println("bark..");
    }
}

package com.test.factory;

public class Horse extends Animal{

    @Override
    public void shout(){
        System.out.println("neigh");
    }
}

package com.test.factory;

public class Lion extends Animal{

    @Override
    public void shout(){
        System.out.println("roar..");
    }
}

    package com.test.factory;

    public class AnimalFactory {

        public Animal createAnimal(String str){

            if(str.equals("dog")){
                return new Dog();
                }
            else if (str.equals("horse")){
                return new Horse();
                }

            else if(str.equals("lion")){
                return new Lion();
                }
            else{
                return new Animal();
            }
        }

    }

    package com.test.factory;


  package com.test.factory;


public class Polymorphism {
    public static void main(String[] args){

        AnimalFactory factory = new AnimalFactory();
        Animal animal = factory.createAnimal("dog");
        animal.shout();
        animal = factory.createAnimal("lion");
        animal.shout();
        animal = factory.createAnimal("horse");
        animal.shout();
        animal = factory.createAnimal("Animal");
        animal.shout();

    }   
}


    Output is :-
    bark..
    roar..
    neigh
    Parent animal's shout

AnimalFactory имеет метод createAnimal, который возвращает Animal. Так как Собака, Лев и Лошадь - все животные. Таким образом, мы можем создавать объекты Dog, Lion и Horse, используя возвращаемый тип Animal. То, что мы достигли, используя возвращаемый тип Animal

Animal animal = new Dog();
Animal animal = new Lion();
Animal animal = new Horse();

что невозможно без возвращаемого типа Animal. 

Если я использую тип возврата как Dog в методе createAnimal, тогда он не может вернуть Lion или Horse и так далее. 

0
Goyal Vicky