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

Nar*_*Pal 58 java oop inheritance casting upcasting

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

假设我有一个班级Parent:

public class Parent {

    int name;
}
Run Code Online (Sandbox Code Playgroud)

并有另一堂课Child.java:

public class Child extends Parent{

    int salary;
}
Run Code Online (Sandbox Code Playgroud)

最后是我的Main.java类

public class Main {

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

如果我做一个像儿童的对象

Child child = new Child():
Run Code Online (Sandbox Code Playgroud)

然后child对象可以访问这两个name and salary变量.

我的问题是:

Parent parent = new Child();
Run Code Online (Sandbox Code Playgroud)

name提供父类变量的访问权限.那么这条线的确切用途是什么?

 Parent parent = new Child();
Run Code Online (Sandbox Code Playgroud)

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

Parent parent = new Child();
Run Code Online (Sandbox Code Playgroud)

Joh*_*tts 41

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

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

public String getEmployeeDetails() {
    return "Name: " + name;
}
Run Code Online (Sandbox Code Playgroud)

我们可以覆盖该方法Child以提供更多详细信息:

@Override
public String getEmployeeDetails() {
    return "Name: " + name + " Salary: " + salary;
}
Run Code Online (Sandbox Code Playgroud)

现在,您可以编写代码,获取任何信息可用,对象是一条线ParentChild:

parent.getEmployeeDetails();
Run Code Online (Sandbox Code Playgroud)

以下代码:

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();
}
Run Code Online (Sandbox Code Playgroud)

将导致输出:

Name: 1
Name: 2 Salary: 2000
Run Code Online (Sandbox Code Playgroud)

我们用a Child作为Parent.它具有Child该类独有的专门行为,但是当我们打电话时,getEmployeeDetails()我们可以忽略差异,并专注于如何ParentChild相似.这称为亚型多态性.

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

当你真正想要使用a Child作为一个Parent这是一个容易的限制,你的代码将可用Parent于其所有子类型.如果不可接受,请输入参考类型Child.


ani*_*nio 10

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

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

public class Rectangle extends Shape
{ 
  public void draw();
  public void doRectangleAction();
}
Run Code Online (Sandbox Code Playgroud)

现在,如果你有:

List<Shape> myShapes = new ArrayList<Shape>();
Run Code Online (Sandbox Code Playgroud)

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

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

真的,我认为你需要阅读更多有关OOP的信息.一本好书应该有所帮助:http://www.amazon.com/Design-Patterns-Explained-Perspective-Object-Oriented/dp/0201715945

  • 在您引用的示例中,您使用的是“抽象类”还是“接口”?因为一个简单的类不能有未实现的方法。 (2认同)

Rom*_*n C 7

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

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

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

Parent parent = new Child();  
Run Code Online (Sandbox Code Playgroud)

相反的是向下倾斜.

Child child = (Child)parent;
Run Code Online (Sandbox Code Playgroud)

因此,如果您创建实例Child并向下投影,则Parent可以使用该类型属性name.如果你创建的实例Parent你可以像以前的情况一样做但你不能使用,salary因为在那里没有这样的属性Parent.返回上一个可以使用salary但只有向下转换的情况Child.

有更详细的解释

  • 子孩子=(子)父母;这将导致 ClassCastException。您不能将父对象向下转换为子引用变量。 (2认同)

kap*_*and 6

这很简单。

Parent parent = new Child();
Run Code Online (Sandbox Code Playgroud)

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

Child child = new Child();
Run Code Online (Sandbox Code Playgroud)

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

事实是,不需要在声明时立即初始化非最终字段。通常,这是在运行时完成的,因为通常您无法确切知道所需的实现。例如,假设您拥有一个以类Transport为首的类层次结构。和三个子类:CarHelicopterBoat。还有另一种Tour具有field 的类Transport。那是:

class Tour {
   Transport transport;
}  
Run Code Online (Sandbox Code Playgroud)

只要用户尚未预订行程且未选择特定的交通工具,就无法初始化此字段。第一次

其次,假设所有这些类都必须具有一个方法,go()但是具有不同的实现。您可以默认在超类中定义一个基本实现,Transport并在每个子类中拥有自己的唯一实现。通过这种初始化,Transport tran; tran = new Car();您可以调用该方法tran.go()并获得结果,而不必担心特定的实现。它将从特定的子类调用重写的方法。

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

public class Airplane extends Transport {
    //implementation
}
Run Code Online (Sandbox Code Playgroud)

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

  • 交通方面的一个很好的例子...很容易理解。谢谢你! (2认同)

Aks*_*edi 6

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

  • 这个答案很有道理!谢谢 (3认同)

Swa*_*kar 5

我知道这是一个非常古老的线程,但我曾经遇到过同样的疑问。

所以这个概念和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();
    }
}
Run Code Online (Sandbox Code Playgroud)

输出:100

汽车启动

车辆停止

发生这种情况是因为stop()它是静态方法并且无法被覆盖。因此,绑定stop()发生在编译时,并且start()是非静态的,在子类中被重写。因此,有关对象类型的信息仅在运行时可用(后期绑定),因此start()调用 Car 类的方法。

同样在这段代码中,vehicle.value我们得到了100输出,因为变量初始化不属于后期绑定。方法重写是java支持运行时多态性的方式之一

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

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