为什么我们在Java中为子对象分配父引用?

时间:2012-08-28 12:49:31

标签: java oop inheritance casting upcasting

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

假设我有一个班级Parent

public class Parent {

    int name;
}

还有另一个班级Child.java

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

只允许访问Parent类的name变量。 那么这条线的确切用途是什么?

 Parent parent = new Child();

当它使用动态多态时,为什么在执行此操作后无法访问子类的变量

Parent parent = new Child();

11 个答案:

答案 0 :(得分:38)

首先,澄清术语:我们将Child对象分配给Parent类型的变量。 Parent是对恰好是Parent的子类型的对象的引用,Child

它仅适用于更复杂的示例。想象一下,您将getEmployeeDetails添加到类Parent:

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()时,我们可以忽略差异并关注ParentChild的相似之处。这称为subtype polymorphism

您的更新问题询问Child.salary对象存储在Child引用中时无法访问Parent的原因。答案是“多态”和“静态类型”的交集。因为Java在编译时是静态类型的,所以您可以从编译器获得某些保证,但是您必须在交换中遵循规则,否则代码将无法编译。这里,相关的保证是子类型的每个实例(例如Child)都可以用作其超类型的实例(例如Parent)。例如,当您访问employee.getEmployeeDetailsemployee.name时,可以保证在任何非空对象上定义方法或字段,该对象可以分配给{{1}类型的变量employee }}。为了保证这一点,编译器在决定您可以访问的内容时只考虑该静态类型(基本上是变量引用的类型Parent)。因此,您无法访问在对象的运行时类型Parent上定义的任何成员。

当您真正想要将Child用作Child时,这是一个容易受到限制,您的代码可用于Parent及其所有子类型。如果不可接受,请创建引用类型Parent

答案 1 :(得分:10)

它允许您通过公共父接口访问所有子类。这有利于运行所有子类上可用的常见操作。需要一个更好的例子:

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

您可以将列表中的每个项目作为Shape引用,您不必担心它是否为Rectangle或其他类型,比如说Circle。你可以对待它们;你可以画出所有这些。你不能调用doRectangleAction,因为你不知道Shape是否真的是一个矩形。

这是您在以通用方式处理对象和处理特定对象之间的交易。

我觉得你真的需要阅读更多关于OOP的内容。一本好书应该有所帮助:http://www.amazon.com/Design-Patterns-Explained-Perspective-Object-Oriented/dp/0201715945

答案 2 :(得分:7)

如果将父类型分配给子类,则表示您同意使用父类的公共功能。

它使您可以自由地从不同的子类实现中进行抽象。因此,您可以使用父功能限制您。

但是,这种类型的赋值称为upcasting。

Parent parent = new Child();  

相反的是向下倾斜。

Child child = (Child)parent;

因此,如果您创建Child的实例并将其向下转换为Parent,则可以使用该类型属性name。如果您创建Parent的实例,则可以执行与先前案例相同的操作但不能使用salary,因为Parent中没有此类属性。返回可以使用salary的上一个案例,但仅限于向下转换为Child

There's more detail explanation

答案 3 :(得分:5)

这很简单。

Parent parent = new Child();

在这种情况下,对象的类型为Parent。 Ant Parent只有一个属性。它是name

Child child = new Child();

在这种情况下,对象的类型是Child。 Ant Child有两个属性。他们是namesalary

事实是,没有必要在声明时立即初始化非最终字段。通常这是在运行时完成的,因为通常您无法确切知道您需要什么样的实现。例如,假设您有一个类Transport的类层次结构。还有三个子类:CarHelicopterBoat。还有另一个类Tour,其中包含字段Transport。那就是:

class Tour {
   Transport transport;
}  

只要用户未预订旅行且未选择特定类型的运输,您就无法初始化此字段。这是第一次。

其次,假设所有这些类必须具有方法go()但具有不同的实现。您可以在超类Transport中默认定义基本实现,并在每个子类中拥有唯一的实现。使用此初始化Transport tran; tran = new Car();,您可以调用方法tran.go()并获取结果,而无需担心具体实现。它将从特定子类调用覆盖方法。

此外,您可以在任何使用超类实例的地方使用子类的实例。例如,您希望提供租用运输工具的机会。如果不使用多态,则必须为每种情况编写许多方法:rentCar(Car car)rentBoat(Boat boat)等等。同时,polymorphism允许您创建一个通用方法rent(Transport transport)。您可以传递Transport的任何子类的对象。此外,如果随着时间的推移你的逻辑会增加,你需要在层次结构中创建另一个类?使用多态时,您无需更改任何内容。只需扩展类Transport并将新类传递给方法:

public class Airplane extends Transport {
    //implementation
}

rent(new Airplane())。在第二种情况下new Airplane().go()

答案 4 :(得分:5)

编译程序时,基类的引用变量获取内存,编译器会检查该类中的所有方法。因此它检查所有基类方法,但不检查子类方法。现在,在运行时创建对象时,只能运行已检查的方法。 如果在子类中重写了该函数运行的方法。子类的其他函数没有运行,因为编译器在编译时没有识别它们。

答案 5 :(得分:2)

当您有多个实现时会发生这种情况。 让我解释。 假设您有几种排序算法,并且您希望在运行时选择要实现的算法,或者您希望向其他人提供添加其实现的功能。 要解决此问题,您通常会创建一个抽象类(Parent)并具有不同的实现(Child)。 如果你写:

Child c = new Child();

您将实施绑定到Child类,您无法再对其进行更改。否则,如果您使用:

Parent p = new Child();

只要Child扩展Parent,您可以在将来更改它而无需修改代码。

使用接口可以做同样的事情:父不再是一个类,而是一个java接口。

通常,您可以在DAO模式中使用此approch,您希望在其中具有多个与数据库相关的实现。 您可以查看FactoryPatter或AbstractFactory Pattern。 希望这可以帮到你。

答案 6 :(得分:1)

假设你想要一个Parent类的实例数组,以及一组扩展Parent的子类Child1,Child2,Child3。在某些情况下,您只对父类实现感兴趣,这种实现更为通用,并且不关心子类引入的更具体的内容

答案 7 :(得分:1)

我认为,对于面向对象编程(OOP)的新手来说,以上所有解释都有些过于技术化。几年前,我花了一些时间(作为Jr Java开发人员)来解决这个问题,我真的不明白为什么我们使用父类或接口来隐藏我们实际上在幕后调用的实际类。 >

  1. 直接原因是隐藏复杂性,以便呼叫者不需要经常进行更改(以外行的方式被砍死和劫持)。这很有道理,特别是如果您的目标是避免创建错误。而且,修改代码的次数越多,您越有可能让其中的一些爬上您的代码。另一方面,如果您仅扩展代码,那么您将很少有bug,因为您一次只专注于一件事,而您的旧代码不会更改或仅更改一点。 假设您有一个简单的应用程序,该应用程序允许医疗行业的员工创建配置文件。为简单起见,我们假设我们只有全科医生,外科医生和护士(当然,实际上有许多更具体的职业)。对于每个专业,您都希望存储一些常规信息以及一些专门针对该专业的信息。例如,外科医生可以具有诸如firstName,lastName,yearsOfExperience之类的通用字段作为通用字段,但也可以具有特定字段,例如专长存储在列表实例变量中,例如具有类似于“骨外科”,“眼外科”等内容的“列表”。护士不会拥有任何特长,但可能拥有他们熟悉的列表过程,GeneralPractioners将具有自己的详细信息。因此,您如何保存详细资料的配置文件。但是,您不希望您的ProfileManager类了解这些差异,因为随着您的应用程序扩展其功能以覆盖更多的医疗行业,例如随着时间的推移,它们将不可避免地发生变化并随着时间的推移而增加。物理治疗师,心脏科医生,肿瘤科医生等。您想要ProfileManger要做的就是说save(),无论它要保存的是谁的个人资料。因此,通常的做法是将其隐藏在“接口”,“抽象类”或“父类”的后面(如果您计划允许创建一名普通医务人员)。在这种情况下,让我们选择一个Parent类,并将其称为MedicalEmployee。在幕后,它可以引用上述扩展它的任何特定类。当ProfileManager调用myMedicalEmployee.save()时,save()方法将被多态(从结构上)解析为最初用于创建配置文件的正确类类型,例如Nurse,并在其中调用save()方法。课。

  2. 在许多情况下,您实际上并不知道在运行时需要哪种实现。从上面的示例中,您不知道全科医生,外科医生或护士是否会创建配置文件。但是,您知道无论完成什么情况都需要保存该概要文件。 MedicalEmployee.profile()正是这样做的。它由每种特定类型的MedicalEmployee-GeneralPractitioner,Surgeon,Nurse,

  3. 复制(覆盖)
  4. 上面(1)和(2)的结果是,您现在可以添加新的医学专业,在每个新类中实现save(),从而覆盖MedicalEmployee中的save()方法,而您无需完全不必修改ProfileManager。

答案 8 :(得分: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()是非静态的,在子类中被覆盖。因此,有关对象类型的信息仅在运行时可用(后期绑定),因此将调用Car类的start()方法。

在此代码中,vehicle.value也为我们提供了100作为输出,因为变量初始化不受后期绑定的约束。 方法重写是Java支持运行时多态性的方法之一

  • 当通过超类引用调用重写的方法时,Java根据调用发生时所引用的对象的类型,确定要执行该方法的哪个版本(超类/子类)。因此,此确定是在运行时进行的。
  • 在运行时,它取决于所引用对象的类型(而不是引用变量的类型),它决定将执行哪个版本的重写方法

我希望这能回答Parent parent = new Child();重要的地方,以及为什么您不能使用上述引用访问子类变量。

答案 9 :(得分:-1)

例如我们有一个

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”

}

}

现在,您是否要设置或获取所有员工的薪资和任命(即软件引擎,经理等)

我们将获取一个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())

}

现在它是一种松散耦合,因为您可以拥有不同类型的员工,例如软件工程师,经理,小时,pantryEmployee等

因此您可以将对象提供给父级引用,而不考虑不同的员工对象

答案 10 :(得分:-5)

您将parent声明为Parent,因此java将仅提供Parent类的方法和属性。

Child child = new Child();

应该有效。或

Parent child = new Child();
((Child)child).salary = 1;
相关问题