Чому ми призначаємо батьківське посилання на дочірній об’єкт у Java?


84

Я задаю досить просте запитання, але я трохи розгублений у цьому.

Припустимо, у мене є клас Parent:

public class Parent {

    int name;
}

І мати ще один клас Child:

public class Child extends Parent{

    int salary;
}

І нарешті мій клас Main.java

public class Main {

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

Якщо я зроблю дитині такий предмет

Child child = new Child():

Тоді childоб'єкт може отримати доступ до обох name and salaryзмінних.

Моє запитання:

Parent parent = new Child();

надає доступ лише до nameзмінної батьківського класу. То яке саме використання цього рядка ??

 Parent parent = new Child();

А також коли він використовує динамічний поліморфізм, то чому змінна дочірнього класу недоступна після цього

Parent parent = new Child();

1
Прочитайте приклад з тваринами тут: en.wikipedia.org/wiki/…
mrswadge

У вашому прикладі це насправді не допомагає. Але подумайте, що у вас є метод, який робить щось із посиланням на батьків. Наприклад, десь у класі Main. Він бере посилання на батьківський об’єкт і робить з ним речі. Мало б сенс також приймати підкласи Parent, як об'єкт Child, хоча це робить лише батьківські речі. Більше того, вивчіть динамічне прив’язування, щоб отримати більш цікаві речі.
Андрій Барсан,

Це nameправильний тип?
Роман С

Відповіді:


53

По-перше, уточнення термінології: ми присвоюємо 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

Ми використовували a Childяк Parent. Він мав спеціалізовану поведінку, унікальну для Childкласу, але коли ми телефонували, getEmployeeDetails()ми могли ігнорувати різницю та зосереджуватися на тому, як Parentі Childсхожі. Це називається політипом підтипу .

Ваше оновлене запитання задає питання про те, чому Child.salaryнедоступний, коли Childоб’єкт зберігається у Parentпосиланні. Відповідь - перетин "поліморфізму" та "статичного набору тексту". Оскільки Java статично набирається під час компіляції, ви отримуєте певні гарантії від компілятора, але ви змушені дотримуватися правил в обмін, інакше код не компілюється. Тут відповідною гарантією є те, що кожен екземпляр підтипу (наприклад Child) може бути використаний як екземпляр його надтипу (наприклад Parent). Наприклад, вам гарантується, що при доступі employee.getEmployeeDetailsабо employee.nameметод або поле визначено для будь-якого ненульового об’єкта, який може бути призначений змінній employeeтипуParent. Щоб забезпечити цю гарантію, компілятор враховує лише той статичний тип (в основному, тип посилання на змінну Parent), вирішуючи, до чого ви можете отримати доступ. Таким чином , ви не можете отримати доступ до будь-яких членам, які визначені на тип виконання об'єкта, Child.

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


22

При компіляції програми посилальна змінна базового класу отримує пам'ять, а компілятор перевіряє всі методи цього класу. Отже, він перевіряє всі методи базового класу, але не методи дочірнього класу. Тепер під час виконання, коли об’єкт створюється, можуть працювати лише перевірені методи. У випадку, якщо метод перевизначений у дочірньому класі, ця функція запускається. Інші функції дочірнього класу не запускаються, оскільки компілятор не розпізнав їх під час компіляції.


1
Ця відповідь має сенс! Дякую
користувач7098526

13

Це дозволяє отримати доступ до всіх підкласів через загальний батьківський інтерфейс. Це вигідно для запуску загальних операцій, доступних для всіх підкласів. Потрібний кращий приклад:

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, оскільки ви не знаєте, чи є Shape насправді прямокутником.

Це ваша торгівля, яку ви робите між тим, як обробляти предмети загальним чином, і обробляти конкретно.

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


2
У наведеному прикладі ви використовуєте "абстрактний клас" або "інтерфейс"? Тому що простий клас не може мати нереалізованих методів.
HQuser

1
@HQuser - добре помічений, здається, хоче пояснити більше про поліморфізм у контексті питання, але, безумовно, це потребує змін.
Вівек

11

Якщо ви призначите батьківський тип підкласу, це означає, що ви погоджуєтесь використовувати загальні риси батьківського класу.

Це дає вам свободу абстрагуватися від різних реалізацій підкласу. Як результат обмежує вас батьківськими функціями.

Однак цей тип розподілу називається підвищуванням.

Parent parent = new Child();  

Навпаки - зниження.

Child child = (Child)parent;

Отже, якщо ви створюєте екземпляр Childта знижуєте його, Parentви можете використовувати цей атрибут type name. Якщо ви створюєте екземпляр з Parentвас, ви можете зробити те саме, що і в попередньому випадку, але ви не можете використовувати, salaryоскільки в атрибуті Parent. Повернення до попереднього випадку, який можна використовувати, salaryале лише за умови зниження до Child.

Там є більш детальне пояснення


Отже, для цього я можу зробити Parent parent = new Parent (). Навіщо це робити? Plz допомогти мені.
Narendra Pal

Правда, але все одно не пояснює, чому ви цього хотіли б.
Джон Уоттс,

Чому це робити, залежить від того, що ви хочете зробити.
Роман С

Якщо ви призначаєте, Parentви можете використовувати, Parentякщо ви призначаєте (якщо можете), Childви можете використовувати Child.
Роман С

1
Дитина дитина = (Дитина) батько; це призведе до ClassCastException. Ви не можете понизити батьківський об’єкт до дочірньої посилальної змінної.
Мухаммед Салман Фарук

7

Це просто.

Parent parent = new Child();

У цьому випадку тип об'єкта Parent. Мураха Parentмає лише одні властивості. Це name.

Child child = new Child();

І в цьому випадку тип об’єкта Child. Мураха Childмає дві властивості. Вони nameі salary.

Справа в тому, що немає необхідності ініціалізувати не остаточне поле відразу при оголошенні. Зазвичай це робиться під час виконання, оскільки часто ви не можете точно знати, яка саме реалізація вам знадобиться. Наприклад, уявіть, що у вас є ієрархія класів з класом Transportна чолі. І три підкласу: Car, 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()в другому випадку.


що я знаю, але моє питання полягає в тому, чому це робити? Якщо ми можемо зробити Parent p = new Parent (), щоб отримати батьківське посилання.
Narendra Pal

Це поліморфізм . Це дуже корисно. Поліморфізм робить вашу програму більш гнучкою та розширюваною. Як саме це може бути корисним? Дивіться мої приклади в оновленій відповіді .
Капельчик

1
Чудовий приклад з транспортом ... зрозумілим легко. Дякую!
ChanwOo Park,

2

Ця ситуація трапляється, коли у вас є кілька реалізацій. Дозволь пояснити. Припустимо, у вас є кілька алгоритмів сортування, і ви хочете вибрати під час виконання той, який потрібно реалізувати, або ви хочете надати комусь іншому можливість додати його реалізацію. Щоб вирішити цю проблему, ви зазвичай створюєте абстрактний клас (Parent) і маєте різну реалізацію (Child). Якщо ви пишете:

Child c = new Child();

ви прив'язуєте свою реалізацію до класу Child і більше не можете її змінити. В іншому випадку, якщо ви використовуєте:

Parent p = new Child();

поки Child розширює Parent, ви можете змінити його в майбутньому, не змінюючи код.

Те саме можна зробити за допомогою інтерфейсів: Parent - це вже не клас, а інтерфейс Java.

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


1

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


1

Я думаю, що всі пояснення, наведені вище, є занадто технічними для людей, які не знайомі з об’єктно-орієнтованим програмуванням (ООП). Роками тому мені знадобився час, щоб обернутися цим (як Jr Java Developer), і я справді не розумів, чому ми використовуємо батьківський клас або інтерфейс, щоб приховати фактичний клас, який насправді називаємо, під ковдрами.

  1. Безпосередня причина полягає в тому, щоб приховати складність, щоб абоненту не потрібно було часто змінюватись (хакнути і розбивати, якщо говорити непрофесійно). Це має великий сенс, особливо якщо ви ставите за мету уникнути створення помилок. І чим більше ви модифікуєте код, тим більша ймовірність того, що деякі з них підкрадуться до вас. З іншого боку, якщо ви просто розширюєте код, набагато менше шансів, що у вас будуть помилки, оскільки ви концентруєтеся на одній справі за раз, а ваш старий код не змінюється або змінюється лише трохи. Уявіть, що у вас є простий додаток, який дозволяє працівникам медичної професії створювати профілі. Для простоти, припустимо, що у нас є лише загальні терапевти, хірурги та медсестри (насправді існує набагато більше конкретних професій, звичайно). Для кожної професії Ви хочете зберегти загальну інформацію, а також конкретну інформацію для цього професіонала. Наприклад, хірург може мати загальні поля, такі як ім’я, прізвище, yearsOfExperience як загальні поля, але також конкретні поля, наприклад спеціалізації, що зберігаються у змінній екземпляра списку, наприклад, Список із вмістом, подібним до «Кісткової хірургії», «Хірургії ока» тощо. Медсестра не мала б нічого з цього, але могла б мати перелічені процедури, з якими вони знайомі, Генеральний працівник мав би свої особливості. В результаті, як ви зберігаєте профіль конкретного. Однак ви не хочете, щоб ваш клас ProfileManager знав про ці відмінності, оскільки вони неминуче змінюватимуться і збільшуватимуться з часом, оскільки ваша програма розширює свої функціональні можливості, охоплюючи більше медичних професій, наприклад, фізіотерапевта, кардіолога, онколога тощо. Все, що ви хочете, щоб ваш ProfileManger, це просто сказати save (), незалежно від того, чий профіль він зберігає. Таким чином, загальноприйнятою практикою є приховування цього і за інтерфейсом, і за абстрактним класом, або за батьківським класом (якщо ви плануєте дозволити створення загального медичного працівника). У цьому випадку давайте виберемо клас батьків і назвемо його MedicalE Employee. Під обкладинками він може посилатися на будь-який із вищезазначених конкретних класів, які його поширюють. Коли ProfileManager викликає myMedicalEfficiee.save (), метод save () буде поліморфно (багатоструктурно) вирішено до правильного типу класу, який використовувався для створення профілю спочатку, наприклад, Nurse і викликати метод save () клас. або батьківський клас (якщо ви плануєте дозволити створення загального медичного працівника). У цьому випадку давайте виберемо клас батьків і назвемо його MedicalE Employee. Під обкладинками він може посилатися на будь-який із вищезазначених конкретних класів, які його поширюють. Коли ProfileManager викликає myMedicalEfficiee.save (), метод save () буде поліморфно (багатоструктурно) вирішено до правильного типу класу, який використовувався для створення профілю спочатку, наприклад, Nurse і викликати метод save () клас. або батьківський клас (якщо ви плануєте дозволити створення загального медичного працівника). У цьому випадку, давайте виберемо клас батьків і назвемо його MedicalE Employee. Під обкладинками він може посилатися на будь-який із вищезазначених конкретних класів, які його поширюють. Коли ProfileManager викликає myMedicalEfficiee.save (), метод save () буде поліморфно (багатоструктурно) вирішено до правильного типу класу, який використовувався для створення профілю спочатку, наприклад, Nurse і викликати метод save () клас.

  2. У багатьох випадках ви насправді не знаєте, яка реалізація вам знадобиться під час виконання. З наведеного вище прикладу ви не уявляєте, чи створить профіль GeneralPractitioner, хірург або медсестра. Тим не менше, ви знаєте, що вам потрібно зберегти цей профіль після заповнення, незважаючи ні на що. MedicalEfficiee.profile () робить саме це. Він копіюється (замінюється) кожним конкретним типом медичного працівника - GeneralPractitioner, хірургом, медсестрою,

  3. Результатом (1) та (2) вище є те, що тепер ви можете додавати нові медичні професії, впроваджувати save () у кожному новому класі, тим самим замінюючи метод save () у MedicalE Employee, і вам не потрібно змінювати ProfileManager на всі.


1

Я знаю, що це дуже стара тема, але одного разу я зіткнувся з тим самим сумнівом.

Отже, концепція Parent parent = new Child();має щось спільне з раннім та пізнім прив'язуванням у Java.

Прив'язка приватних, статичних та остаточних методів відбувається під час компіляції, оскільки їх неможливо замінити, а звичайні виклики методів та перевантажені методи є прикладом раннього прив'язки.

Розглянемо приклад:

class Vehicle
{
    int value = 100;
    void start() {
        System.out.println("Vehicle Started");
    }

    static void stop() {
        System.out.println("Vehicle Stopped");
    }
}

class Car extends Vehicle {

    int value = 1000;

    @Override
    void start() {
        System.out.println("Car Started");
    }

    static void stop() {
        System.out.println("Car Stopped");
    }

    public static void main(String args[]) {

        // Car extends Vehicle
        Vehicle vehicle = new Car();
        System.out.println(vehicle.value);
        vehicle.start();
        vehicle.stop();
    }
}

Вихід: 100

Автомобіль запустив

Автомобіль зупинився

Це відбувається, оскільки stop()це статичний метод, який неможливо замінити. Отже, прив’язка stop()відбувається під час компіляції і start()є нестатичною, замінюється в дочірньому класі. Отже, інформація про тип об'єкта доступна лише під час виконання (пізнє прив'язка) і, отже, start()називається метод класу Car.

Також у цьому коді vehicle.valueми надаємо нам 100результат, оскільки ініціалізація змінної не підпадає під пізнє прив'язку. Перевизначення методу - один із способів, яким Java підтримує поліморфізм часу виконання .

  • Коли перевизначений метод викликається через посилання на суперклас, Java визначає, яку версію (суперклас / підкласи) цього методу слід виконати, виходячи з типу об'єкта, на який йдеться у момент виклику. Таким чином, це визначення проводиться під час виконання.
  • Під час виконання це залежить від типу об'єкта, на який посилається (а не від типу посилальної змінної), що визначає, яка версія перевизначеного методу буде виконана

Сподіваюся, це відповідає там, де Parent parent = new Child();важливо, а також чому ви не змогли отримати доступ до змінної дочірнього класу, використовуючи наведене вище посилання.


-1

наприклад, ми маємо

class Employee

{

int getsalary()

{return 0;}

String getDesignation()

{

returndefault”;

}

}

class Manager extends Employee

{

int getsalary()

{

return 20000;

}

String getDesignation()

{

return “Manager”

}

}

class SoftwareEngineer extends Employee

{

int getsalary()

{

return 20000;

}

String getDesignation()

{

return “Manager”

}

}

тепер, якщо ви хочете встановити або отримати заробітну плату та призначення всіх працівників (тобто розробник програмного забезпечення, менеджер тощо)

ми візьмемо масив Employee і викличемо обидва методи getsalary (), getDesignation

Employee arr[]=new Employee[10];

arr[1]=new SoftwareEngieneer();

arr[2]=new Manager();

arr[n]=…….

for(int i;i>arr.length;i++)

{

System.out.println(arr[i].getDesignation+””+arr[i].getSalary())

}

зараз це свого роду вільне зчеплення, тому що у вас можуть бути різні типи співробітників, наприклад: інженер програмного забезпечення, менеджер, hr, комора

так що ви можете надати об'єкт батьківському посиланню незалежно від іншого об'єкта працівника


-3

Ви оголошуєте батьків батьківським, тому java надаватиме лише методи та атрибути класу батьків.

Child child = new Child();

повинні працювати. Або

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

1
Звичайно, це працює. OP запитував контекст, де Parent parent = new Child();це корисно / значуще.
Баз

2
Це таке саме пояснення, як і в інших відповідях, так чому ж голосувати проти цього? Я не кажу, що я не працюю, я кажу, якщо ви кажете Child c = new Parant (), ніж у вас є екземпляр Parent, а не Child, і тому у вас немає зарплати. Якщо вам потрібна заробітна плата за атрибут, вам потрібно створити екземпляр Child або передати Parent to Child ....
Таргор,
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.