为什么我们要给 Java 中的子对象分配一个父引用呢?

我问的是一个非常简单的问题,但我对此有点困惑。

假设我有一个类 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();
172148 次浏览

You declare parent as Parent, so java will provide only methods and attributes of the Parent class.

Child child = new Child();

should work. Or

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

If you assign parent type to a subclass it means that you agree with to use the common features of the parent class.

It gives you the freedom to abstract from different subclass implementations. As a result limits you with the parent features.

However, this type of assignment is called upcasting.

Parent parent = new Child();

The opposite is downcasting.

Child child = (Child)parent;

So, if you create instance of Child and downcast it to Parent you can use that type attribute name. If you create instance of Parent you can do the same as with previous case but you can't use salary because there's not such attribute in the Parent. Return to the previous case that can use salary but only if downcasting to Child.

There's more detail explanation

It allows you to access all subclasses through a common parent interface. This is beneficial for running common operations available on all subclasses. A better example is needed:

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


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

Now if you have:

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

You can reference every item in the list as a Shape, you don't have to worry if it is a Rectangle or some other type like let's say Circle. You can treat them all the same; you can draw all of them. You can't call doRectangleAction because you don't know if the Shape is really a rectangle.

This is a trade of you make between treating objects in a generic fashion and treating the specifically.

Really I think you need to read more about OOP. A good book should help: http://www.amazon.com/Design-Patterns-Explained-Perspective-Object-Oriented/dp/0201715945

Let's say you'd like to have an array of instances of Parent class, and a set of child classes Child1, Child2, Child3 extending Parent. There're situations when you're only interested with the parent class implementation, which is more general, and do not care about more specific stuff introduced by child classes.

This situation happens when you have several implementations. Let me explain. Supppose you have several sorting algorithm and you want to choose at runtime the one to implement, or you want to give to someone else the capability to add his implementation. To solve this problem you usually create an abstract class (Parent) and have different implementation (Child). If you write:

Child c = new Child();

you bind your implementation to Child class and you can't change it anymore. Otherwise if you use:

Parent p = new Child();

as long as Child extends Parent you can change it in the future without modifying the code.

The same thing can be done using interfaces: Parent isn't anymore a class but a java Interface.

In general you can use this approch in DAO pattern where you want to have several DB dependent implementations. You can give a look at FactoryPatter or AbstractFactory Pattern. Hope this can help you.

First, a clarification of terminology: we are assigning a Child object to a variable of type Parent. Parent is a reference to an object that happens to be a subtype of Parent, a Child.

It is only useful in a more complicated example. Imagine you add getEmployeeDetails to the class Parent:

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

We could override that method in Child to provide more details:

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

Now you can write one line of code that gets whatever details are available, whether the object is a Parent or Child:

parent.getEmployeeDetails();

The following code:

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

Will result in the output:

Name: 1
Name: 2 Salary: 2000

We used a Child as a Parent. It had specialized behavior unique to the Child class, but when we called getEmployeeDetails() we could ignore the difference and focus on how Parent and Child are similar. This is called subtype polymorphism.

Your updated question asks why Child.salary is not accessible when the Childobject is stored in a Parent reference. The answer is the intersection of "polymorphism" and "static typing". Because Java is statically typed at compile time you get certain guarantees from the compiler but you are forced to follow rules in exchange or the code won't compile. Here, the relevant guarantee is that every instance of a subtype (e.g. Child) can be used as an instance of its supertype (e.g. Parent). For instance, you are guaranteed that when you access employee.getEmployeeDetails or employee.name the method or field is defined on any non-null object that could be assigned to a variable employee of type Parent. To make this guarantee, the compiler considers only that static type (basically, the type of the variable reference, Parent) when deciding what you can access. So you cannot access any members that are defined on the runtime type of the object, Child.

When you truly want to use a Child as a Parent this is an easy restriction to live with and your code will be usable for Parent and all its subtypes. When that is not acceptable, make the type of the reference Child.

It's simple.

Parent parent = new Child();

In this case the type of the object is Parent. Ant Parent has only one properties. It's name.

Child child = new Child();

And in this case the type of the object is Child. Ant Child has two properties. They're name and salary.

The fact is that there's no need to initialize non-final field immediately at the declaration. Usually this’s done at run-time because often you cannot know exactly what exactly implementation will you need. For example imagine that you have a class hierarchy with class Transport at the head. And three subclasses: Car, Helicopter and Boat. And there's another class Tour which has field Transport. That is:

class Tour {
Transport transport;
}

As long as an user hasn't booked a trip and hasn't chosen a particular type of transport you can't initialize this field. It's first.

Second, assume that all of these classes must have a method go() but with a different implementation. You can define a basic implementation by default in the superclass Transport and own unique implementations in each subclass. With this initialization Transport tran; tran = new Car(); you can call the method tran.go() and get result without worrying about specific implementation. It’ll call overrided method from particular subclass.

Moreover you can use instance of subclass everywhere where instance of superclass is used. For example you want provide opportunity to rent your transport. If you don't use polymorphism, you have to write a lot of methods for each case: rentCar(Car car), rentBoat(Boat boat) and so forth. At the same time polymorphism allows you to create one universal method rent(Transport transport). You can pass in it object of any subclass of Transport. In addition, if over time your logic will increase up and you'll need to create another class in the hierarchy? When using polymorphism you don't need to change anything. Just extend class Transport and pass your new class into the method:

public class Airplane extends Transport {
//implementation
}

and rent(new Airplane()). And new Airplane().go() in second case.

When you compile your program the reference variable of the base class gets memory and compiler checks all the methods in that class. So it checks all the base class methods but not the child class methods. Now at runtime when the object is created, only checked methods can run. In case a method is overridden in the child class that function runs. Child class other functions aren't run because the compiler hasn't recognized them at the compile time.

I think all explanations above are a bit too technical for the people who are new to Object Oriented Programming (OOP). Years ago, it took me a while to wrap my head around this (as Jr Java Developer) and I really did no understand why we use a parent class or an interface to hide the actual class we are actually calling under the covers.

  1. The immediate reason why is to hide complexity, so that the caller does not need to change often (be hacked and jacked in laymen's terms). This makes a lot of sense, especially if you goals is to avoid creating bugs. And the more you modify code, the more likely it is that you will have some of them creep up on you. On the other hand, if you just extend code, it is way less likely that you will have bugs because you concentrate on one thing at a time and your old code does not change or changes just a bit. Imagine that you have simple application that allows the employees in the medical profession to create profiles. For simplicity, let's assume that we have only GeneralPractitioners, Surgeons, and Nurses (in reality there are many more specific professions, of course). For each profession, you want to store some general information and some specific to that professional alone. For example, a Surgeon may have general fields like firstName, lastName, yearsOfExperience as general fields but also specific fields, e.g. specializations stored in an list instance variable, like List with contents simiar to "Bone Surgery", "Eye Surgery", etc. A Nurse would not have any of that but may have list procedures they are familiar with, GeneralPractioners would have their own specifics. As a result, how you save a profile of a specifics. However, you don't want your ProfileManager class to know about these differences, as they will inevitably change and increase over time as your application expands its functionality to cover more medical professions, e.g. Physio Therapist, Cardiologist, Oncologist, etc. All you want your ProfileManger to do is just say save(), no matter whose profile it is saving. Thus, it is common practice to hide this behind and Interface, and Abstract Class, or a Parent Class (if you plan to allow creating a general medical employee). In this case, let's choose a Parent class and call it MedicalEmployee. Under the covers, it can reference any of the above specific classes that extend it. When the ProfileManager calls myMedicalEmployee.save() the save() method will be polymorphically (many-structurally) be resolved to the correct class type that was used to create the profile originally, for example Nurse and call the save() method in that class.

  2. In many cases, you don't really know what implementation you will need at runtime. From the example above, you have no idea if a GeneralPractitioner, a Surgeon, or a Nurse would create a profile. Yet, you know that you need to save that profile once completed, no matter what. MedicalEmployee.profile() does exactly that. It is replicated (overridden) by each specific type of MedicalEmployee - GeneralPractitioner, Surgeon, Nurse,

  3. The result of (1) and (2) above is that you now can add new medical professions, implement save() in each new class, thereby overriding the save() method in MedicalEmployee, and you don't have to modify ProfileManager at all.

I know this is a very old thread but I came across the same doubt once.

So the concept of Parent parent = new Child(); has something to do with early and late binding in java.

The binding of private, static and final methods happen at the compile as they cannot be overridden and the normal method calls and overloaded methods are example of early binding.

Consider the example:

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

Output: 100

Car Started

Vehicle Stopped

This is happening because stop() is a static method and cannot be overridden. So binding of stop() happens at compile time and start() is non-static is being overridden in child class. So, the information about type of object is available at the run time only(late binding) and hence the start() method of Car class is called.

Also in this code the vehicle.value gives us 100 as the output because variable initialization doesn't come under late binding. Method overriding is one of the ways in which java supports run time polymorphism.

  • When an overridden method is called through a superclass reference, Java determines which version(superclass/subclasses) of that method is to be executed based upon the type of the object being referred to at the time the call occurs. Thus, this determination is made at run time.
  • At run-time, it depends on the type of the object being referred to (not the type of the reference variable) that determines which version of an overridden method will be executed

I hope this answers where Parent parent = new Child(); is important and also why you weren't able to access the child class variable using the above reference.

for example we have a

class Employee


{


int getsalary()


{return 0;}


String getDesignation()


{


return “default”;


}


}


class Manager extends Employee


{


int getsalary()


{


return 20000;


}


String getDesignation()


{


return “Manager”


}


}


class SoftwareEngineer extends Employee


{


int getsalary()


{


return 20000;


}


String getDesignation()


{


return “Manager”


}


}

now if you want to set or get salary and designation of all employee (i.e software enginerr,manager etc )

we will take an array of Employee and call both method 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())


}

now its an kind of loose coupling because you can have different types of employees ex:softeware engineer,manager,hr,pantryEmployee etc

so you can give object to the parent reference irrespective of different employee object