104

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

假设我有一堂课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();

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

 Parent parent = new Child();

而且当它使用动态多态性时,为什么在这样做之后无法访问子类的变量

Parent parent = new Child();
4

11 回答 11

69

首先,澄清一下术语:我们将一个Child对象分配给一个类型为 的变量ParentParent是对碰巧是 a 的子类型的对象的Parent引用Child

它仅在更复杂的示例中有用。想象一下,您添加getEmployeeDetails到类 Parent:

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

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

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

现在您可以编写一行代码来获取任何可用的详细信息,无论对象是 aParent还是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

我们使用 aChild作为Parent. 它具有该类独有的特殊行为Child,但是当我们调用时,getEmployeeDetails()我们可以忽略差异并专注于如何ParentChild相似。这称为子类型多态性

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

当您真正想将 aChild用作 a时,Parent这是一个易于使用的限制,您的代码将可用于Parent及其所有子类型。如果这不可接受,请指定引用的类型Child

于 2012-08-28T12:59:42.387 回答
28

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

于 2015-06-30T00:49:21.367 回答
16

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

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 是否真的是一个矩形。

这是您在以通用方式处理对象和专门处理对象之间进行的交易。

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

于 2012-08-28T12:57:30.020 回答
12

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

它使您可以自由地从不同的子类实现中抽象出来。结果限制了您使用父功能。

但是,这种类型的分配称为向上转换。

Parent parent = new Child();  

反之则是垂头丧气。

Child child = (Child)parent;

因此,如果您创建实例Child并将其向下转换为Parent您可以使用该类型属性name。如果您创建实例,Parent您可以执行与前一个案例相同的操作,但您不能使用salary,因为Parent. 返回上一个可以使用的情况,salary但前提是向下转换为Child.

还有更详细的解释

于 2012-08-28T12:55:57.903 回答
8

这很简单。

Parent parent = new Child();

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

Child child = new Child();

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

事实上,不需要在声明时立即初始化非最终字段。通常这是在运行时完成的,因为您通常无法确切知道您需要什么实现。例如,假设您有一个以类Transport为开头的类层次结构。和三个子类CarHelicopterBoat。还有另一个类Tour有 field 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()第二种情况下。

于 2012-08-28T13:03:57.463 回答
2

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

Child c = new Child();

你将你的实现绑定到 Child 类,你不能再改变它了。否则,如果您使用:

Parent p = new Child();

只要 Child 扩展 Parent,您以后就可以在不修改代码的情况下对其进行更改。

使用接口也可以完成同样的事情:Parent 不再是一个类,而是一个 java 接口。

一般来说,您可以在 DAO 模式中使用此方法,在这种模式中您希望拥有多个依赖于数据库的实现。你可以看看 FactoryPatter 或 AbstractFactory Pattern。希望这可以帮到你。

于 2012-08-28T12:57:44.507 回答
2

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

所以这个概念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();重要的地方以及为什么您无法使用上述参考访问子类变量。

于 2020-04-05T15:17:48.467 回答
1

假设您想要一组 Parent 类的实例,以及一组扩展 Parent 的子类 Child1、Child2、Child3。在某些情况下,您只对更通用的父类实现感兴趣,而不关心子类引入的更具体的东西。

于 2012-08-28T12:57:41.620 回答
1

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

  1. 直接原因是隐藏复杂性,以便调用者不需要经常更改(用外行的话来说是被黑客入侵和劫持)。这很有意义,特别是如果您的目标是避免产生错误。而且您修改代码的次数越多,您就越有可能让其中一些代码爬到您身上。另一方面,如果您只是扩展代码,那么您将不太可能出现错误,因为您一次只专注于一件事,而您的旧代码不会更改或仅更改一点。想象一下,您有一个简单的应用程序,允许医疗行业的员工创建个人资料。为简单起见,我们假设我们只有全科医生、外科医生和护士(当然,实际上还有很多更具体的职业)。对于每个职业,您想存储一些一般信息和一些特定于该专业人士的信息。例如,Surgeon 可能具有像 firstName、lastName、yearsOfExperience 这样的一般字段作为一般字段,但也有特定字段,例如存储在列表实例变量中的专业化,如内容类似于“骨外科”、“眼科手术”等的列表。护士不会有任何这些,但可能会列出他们熟悉的程序,全科医生会有自己的细节。因此,你如何保存一个细节的配置文件。但是,您不希望 ProfileManager 类知道这些差异,因为随着应用程序扩展其功能以涵盖更多医学专业,例如物理治疗师、心脏病学家、肿瘤学家等,它们将不可避免地随着时间而改变和增加。你想要你的 ProfileManger 做的只是说 save(),不管它正在保存谁的配置文件。因此,通常的做法是将其隐藏在接口、抽象类或父类后面(如果您计划允许创建普通医疗员工)。在这种情况下,让我们选择一个 Parent 类并将其命名为 MedicalEmployee。在幕后,它可以引用任何上述扩展它的特定类。当 ProfileManager 调用 myMedicalEmployee.save() 时, save() 方法将被多态(多结构)解析为最初用于创建配置文件的正确类类型,例如 Nurse 并在其中调用 save() 方法班级。或父类(如果您计划允许创建普通医疗员工)。在这种情况下,让我们选择一个 Parent 类并将其命名为 MedicalEmployee。在幕后,它可以引用任何上述扩展它的特定类。当 ProfileManager 调用 myMedicalEmployee.save() 时, save() 方法将被多态(多结构)解析为最初用于创建配置文件的正确类类型,例如 Nurse 并在其中调用 save() 方法班级。或父类(如果您计划允许创建普通医疗员工)。在这种情况下,让我们选择一个 Parent 类并将其命名为 MedicalEmployee。在幕后,它可以引用任何上述扩展它的特定类。当 ProfileManager 调用 myMedicalEmployee.save() 时, save() 方法将被多态(多结构)解析为最初用于创建配置文件的正确类类型,例如 Nurse 并在其中调用 save() 方法班级。

  2. 在许多情况下,您并不真正知道在运行时需要什么实现。从上面的示例中,您不知道全科医生、外科医生或护士是否会创建个人资料。但是,您知道,无论如何,您都需要在完成后保存该配置文件。MedicalEmployee.profile() 正是这样做的。它被每种特定类型的 MedicalEmployee(全科医生、外科医生、护士)复制(覆盖)

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

于 2020-01-29T02:40:49.607 回答
-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())

}

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

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

于 2020-12-03T13:37:25.003 回答
-3

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

Child child = new Child();

应该管用。或者

Parent child = new Child();
((Child)child).salary = 1;
于 2012-08-28T12:51:58.957 回答