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

я задаю довольно простой вопрос, но я немного запутался в этом.

Предположим у меня есть класс Parent:

public class Parent {

    int name;
}

и есть другой класс Child.java:

public class Child extends Parent{

    int salary;
}

и, наконец, мой главный.класс java

public class Main {

    public static void main(String[] args)
    {
        Parent parent = new Child();
        parent.name= "abcd";
    }
}

если я сделаю дочерний объект, как

Child child = new Child():

затем

8 ответов


во-первых, уточнение терминологии: мы назначаем Child объект переменной типа Parent. Parent является ссылкой на объект, который является подтипом Parent, a Child.

это полезно только в более сложном примере. Представьте, что вы добавляете getEmployeeDetails родительскому классу:

public String getEmployeeDetails() {
    return "Name: " + name;
}

мы могли бы переопределить этот метод в Child более подробно:

@Override
public String getEmployeeDetails() {
    return "Name: " + name + " Salary: " + salary;
}

теперь вы можете написать одну строку кода, которая получает какие бы детали ни были доступны, является ли объект Parent или Child:

parent.getEmployeeDetails();

следующий код:

Parent parent = new Parent();
parent.name = 1;
Child child = new Child();
child.name = 2;
child.salary = 2000;
Parent[] employees = new Parent[] { parent, child };
for (Parent employee : employees) {
    employee.getEmployeeDetails();
}

приведет к выходу:

Name: 1
Name: 2 Salary: 2000

мы использовали Child как Parent. У него было специализированное поведение, уникальное для Child класс, но когда мы позвонили getEmployeeDetails() мы могли бы игнорировать разницу и сосредоточиться на том, как Parent и Child похожи. Это называется полиморфизм подтипа.

ваш обновленный вопрос спрашивает, почему Child.salary недоступно, когда Childобъект хранится в Parent ссылка. Ответ-пересечение "полиморфизма"и" статического типирования". Поскольку Java статически типизируется во время компиляции, вы получаете определенные гарантии от компилятора, но вынуждены следовать правилам exchange, иначе код не будет компилироваться. Здесь уместной гарантией является то, что каждый экземпляр подтипа (например,Child) можно использовать как экземпляр супертипа (например,Parent). Для экземпляр, вам гарантируется, что при доступе employee.getEmployeeDetails или employee.name метод или поле определяется для любого ненулевого объекта, который может быть назначен переменной employee типа Parent. Чтобы сделать эту гарантию, компилятор учитывает только этот статический тип (в основном, тип ссылки на переменную, Parent) при принятии решения, что вы можете получить доступ. Таким образом, вы не можете получить доступ к членам, определенным в типе среды выполнения объекта,Child.

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


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

public class Shape
{
  private int x, y;
  public void draw();
}

public class Rectangle extends Shape
{ 
  public void draw();
  public void doRectangleAction();
}

теперь, если у вас есть:

List<Shape> myShapes = new ArrayList<Shape>();

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

это торговля, которую вы делаете между обработкой объектов в общем виде и обработкой конкретно.

На самом деле я думаю, что вам нужно больше узнать об ООП. Хорошая книга должна помочь: http://www.amazon.com/Design-Patterns-Explained-Perspective-Object-Oriented/dp/0201715945


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

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

однако, этот тип назначения называется upcasting.

Parent parent = new Child();  

противоположность downcasting.

Child child = (Child)parent;

Итак, если вы создаете экземпляр Child и опустил его до Parent вы можете использовать этот тип атрибута name. Если вы создаете экземпляр Parent вы можете сделать то же самое, что и в предыдущем случае, но вы не можете использовать salary потому что нет такого атрибута Parent. Вернитесь к предыдущему случаю, который может использовать salary но только если downcasting до Child.

Есть более подробное объяснение


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


все просто.

Parent parent = new Child();

в этом случае тип объекта составляет Parent. АНТ Parent имеет только одно свойство. Это name.

Child child = new Child();

и в этом случае тип объекта составляет Child. АНТ Child имеет два свойства. Они name и salary.

дело в том, что нет необходимости инициализировать не окончательное поле сразу при объявлении. Обычно это делается во время выполнения, потому что часто вы не можете точно знать, что именно реализация вам понадобится. Например, представьте, что у вас есть иерархия классов С класс Transport в голову. И три подкласса:--12-->, Helicopter и Boat. И есть еще один класс Tour, который имеет поле Transport. То есть:

class Tour {
   Transport transport;
}  

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

во-вторых, предположить, что все эти классы должны иметь метод go() но с другой реализации. Вы можете определить базовую реализацию по умолчанию в суперклассе Transport и собственные уникальные разработки в каждом подклассе. С этой инициализацией Transport tran; tran = new Car(); вы можете вызвать метод tran.go() и получить результат, не беспокоясь о конкретной реализации. Он вызовет переопределенный метод из определенного подкласса.

кроме того, вы можете использовать экземпляр подкласса везде, где используется экземпляр суперкласса. Например, вы хотите предоставить возможность арендовать транспорт. Если вы не используете полиморфизм, вам нужно написать много методов для каждого случая: rentCar(Car car), rentBoat(Boat boat) и так далее. При этом полиморфизм позволяет создать один универсальный метод rent(Transport transport). Вы можете передать в него объект любого подкласса Transport. Кроме того, если со временем ваша логика будет увеличиваться, и вам нужно будет создать другой класс в иерархии? При использовании полиморфизма вам не нужно ничего менять. Просто расширьте класс Transport и передать новый класс В метод:

public class Airplane extends Transport {
    //implementation
}

и rent(new Airplane()). И new Airplane().go() во втором случае.


предположим, вы хотите иметь массив экземпляров родительского класса и набор дочерних классов Child1, Child2, Child3, расширяющих родителя. Бывают ситуации, когда вас интересует только реализация родительского класса, которая является более общей и не заботится о более конкретные вещи введено дочерними классами.


эта ситуация происходит, когда у вас есть несколько реализаций. Позвольте мне объяснить. Предположим, у вас есть несколько алгоритмов сортировки, и вы хотите выбрать во время выполнения тот, который нужно реализовать, или вы хотите дать кому-то еще возможность добавить его реализацию. Для решения этой проблемы вы обычно создаете абстрактный класс (Родительский) и имеете другую реализацию (дочернюю). Если вы пишете:

Child c = new Child();

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

Parent p = new Child();

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

то же самое можно сделать с помощью интерфейсов: Parent больше не класс, а интерфейс java.

В общем случае вы можете использовать этот подход в шаблоне DAO, где вы хотите иметь несколько зависимых от БД реализаций. Вы можете взглянуть на FactoryPatter или AbstractFactory шаблон. Надеюсь, это поможет вам.


вы объявляете parent как Parent, поэтому java предоставит только методы и атрибуты родительского класса.

Child child = new Child();

должны работать. Или

Parent child = new Child();
((Child)child).salary = 1;