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

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

Dog obj = new Dog();

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

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");
    }
}

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

8 ответов


Дайте мне код некоторое время.

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

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

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

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


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

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

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


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

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

в качестве примера, если вы разрабатываете метод принимает ссылку типа Animal, то в будущем вы можете легко пройти an= 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.


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


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

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

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

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

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


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

но...

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

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

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

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

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

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


ОК. Кажется, я получил ответ.

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..");
    }
}

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


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

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

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

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


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

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

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

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

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


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

Animal a = new Dog();

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

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

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


смотреть на вопрос:-

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

в методе, как показано ниже (заводской шаблон): -

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

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

полный пример приведен в разделе: -

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 return type, - это

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

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

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