我问了一个非常简单的问题,但我对此有点困惑。
假设我有一个类Parent
:
public class Parent {
int name;
}
并有另一个类Child
:
public class Child extends Parent{
int salary;
}
最后是我的主.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();
首先,澄清术语:我们将一个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()
时,我们可以忽略差异,而专注于Parent
和Child
的相似之处。这称为亚型多态性。
更新后的问题询问为什么当Child
对象存储在Parent
引用中时无法访问Child.salary
。答案是"多态性"和"静态类型"的交集。因为Java在编译时是静态类型的,所以你可以从编译器那里得到一定的保证,但你必须遵循规则作为交换,否则代码将无法编译。在这里,相关的保证是子类型的每个实例(例如 Child
( 可以用作其超类型的实例(例如 Parent
(。例如,可以保证当您访问employee.getEmployeeDetails
或employee.name
方法或字段是在可以分配给类型为 Parent
的变量employee
的任何非 null 对象上定义的。为了保证这一点,编译器在决定可以访问的内容时只考虑该静态类型(基本上是变量引用的类型Parent
(。因此,您不能访问在对象的运行时类型上定义的任何成员,Child
.
当您真的想将Child
用作Parent
时,这是一个很容易接受的限制,并且您的代码将可用于Parent
及其所有子类型。如果不可接受,请将引用的类型设为Child
。
编译程序时,基类的引用变量将获取内存,编译器会检查该类中的所有方法。因此,它检查所有基类方法,但不检查子类方法。现在,在运行时创建对象时,只有检查的方法才能运行。如果在函数运行的子类中重写了方法。子类其他函数不会运行,因为编译器在编译时未识别它们。
它允许您通过公共父接口访问所有子类。这对于运行所有子类上可用的常见操作非常有用。需要一个更好的例子:
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
如果将父类型分配给子类,则表示您同意使用父类的共同功能。
它使您可以自由地从不同的子类实现中抽象出来。因此,使用父功能会限制您。
但是,这种类型的分配称为向上转换。
Parent parent = new Child();
相反的是向下。
Child child = (Child)parent;
因此,如果创建 Child
的实例并将其向下转换为Parent
则可以使用该类型属性name
。如果您创建Parent
实例,则可以执行与以前情况相同的操作,但不能使用salary
,因为Parent
中没有此类属性。返回到可以使用salary
但仅当向下转换为Child
的上一个情况。
有更详细的解释
很简单。
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 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();
重要的地方,以及为什么您无法使用上述参考访问子类变量。
当您有多个实现时,就会发生这种情况。让我解释一下。假设您有多种排序算法,并且您想在运行时选择要实现的算法,或者您想为其他人提供添加其实现的功能。为了解决这个问题,你通常创建一个抽象类(Parent(并有不同的实现(Child(。如果你写:
Child c = new Child();
您将实现绑定到 Child 类,并且无法再更改它。否则,如果您使用:
Parent p = new Child();
只要 Child 扩展了 Parent,您就可以在将来更改它而无需修改代码。
使用接口也可以做同样的事情:Parent 不再是一个类,而是一个 java 接口。
通常,您可以在 DAO 模式中使用此方法,其中您希望拥有多个依赖于数据库的实现。你可以看看FactoryPatter或AbstractFactory Pattern。希望这能帮助你。
假设您希望拥有父类的实例数组,以及一组扩展父类的子类 Child1、Child2、Child3。在某些情况下,您只对父类实现感兴趣,这是更通用的,而不关心子类引入的更具体的内容。
我认为上面的所有解释对于刚接触面向对象编程(OOP(的人来说有点太技术性了。几年前,我花了一段时间才解决这个问题(作为Jr Java开发人员(,我真的不明白为什么我们使用父类或接口来隐藏我们实际调用的实际类。
-
直接原因是隐藏复杂性,这样调用者就不需要经常更改(用外行的话来说,被黑客入侵和劫持(。这很有意义,特别是如果您的目标是避免创建错误。而且你修改的代码越多,你就越有可能让其中一些代码爬到你身上。另一方面,如果你只是扩展代码,你不太可能有错误,因为你一次专注于一件事,而你的旧代码不会改变或只是改变一点点。想象一下,您有一个简单的应用程序,允许医疗行业的员工创建配置文件。为简单起见,让我们假设我们只有全科医生、外科医生和护士(当然,实际上还有更多具体的职业(。对于每个职业,您希望存储一些常规信息,以及一些特定于该专业人员的信息。例如,外科医生可能有一般字段,如名字、姓氏、经验年限作为一般字段,但也有特定字段,例如存储在列表实例变量中的专业,如列表,其内容类似于"骨外科"、"眼科手术"等。护士不会有任何这些,但可能有他们熟悉的程序清单,普通执业者会有自己的细节。因此,您如何保存细节的配置文件。但是,您不希望 ProfileManager 类知道这些差异,因为随着应用程序扩展其功能以涵盖更多医学专业(例如物理治疗师、心脏病专家、肿瘤学家等(,它们将不可避免地随着时间的推移而变化和增加。您希望您的配置文件管理器所做的只是说 save((,无论它正在保存谁的配置文件。因此,通常的做法是将其隐藏在接口、抽象类或父类后面(如果您计划允许创建普通医疗员工(。在本例中,让我们选择一个父类并将其称为 MedicalEmployee。在幕后,它可以引用扩展它的上述任何特定类。当 ProfileManager 调用 myMedicalEmployee.save(( 时,save(( 方法将以多态(多结构方式(解析为最初用于创建配置文件的正确类类型,例如 Nurse 并在该类中调用 save(( 方法。
-
在许多情况下,您并不真正知道运行时需要什么实现。从上面的示例中,您不知道全科医生、外科医生或护士是否会创建配置文件。但是,您知道无论如何,完成后都需要保存该配置文件。MedicalEmployee.profile(( 正是这样做的。它由每个特定类型的医疗员工复制(覆盖( - 全科医生、外科医生、护士、
-
上面 (1( 和 (2( 的结果是,您现在可以添加新的医疗专业,在每个新类中实现 save((,从而覆盖 MedicalEmployee 中的 save(( 方法,并且您根本不需要修改 ProfileManager。
我有一个简单的例子来回答疑问。问题是,我们将父引用与子类对象一起使用(父级 p = 新子项(((覆盖父方法。
以这个例子为例。
class Parent{
public static void Method1(){
System.out.println("Parent Static Method 1");
}
public void Method2(){
System.out.println("Parent Non Static Method 2");
}
public static void ParentMethod3(){
System.out.println("Parent Static Method 3");
}
public void ParentMethod4(){
System.out.println("Parent Non Static Method 4");
}
}
class Child extends Parent{
public static void Method1(){
System.out.println("Child Static Method 1");
}
public void Method2(){
System.out.println("Child Non Static Method 2");
}
public static void ChildMethod3(){
System.out.println("Child Static Method 3");
}
public void ChildMethod4(){
System.out.println("Child Non Static Method 4");
}
}
现在让我们执行:
class ParentChild {
public static void main(String[] args) {
//case1:Parent Reference with Parent Object
Parent parent = new Parent();
parent.Method1(); //output: Parent Static Method 1
parent.Method2(); //output: Parent Non Static Method 2
parent.ParentMethod3(); //output: Parent Static Method 3
parent.ParentMethod4(); //output: Parent Non Static Method 4
//case2:Child Reference with Child Object
Child child = new Child();
child.Method1(); //output: Child Static Method 1
child.Method2(); //output: Child Non Static Method 2
child.ChildMethod3(); //output: Child Static Method 3
child.ChildMethod4(); //output: Child Non Static Method 4
//case3:Parent Reference with Child Object
Parent pc = new Child();
pc.Method1(); //output: Parent Static Method 1
pc.Method2(); //output: Child Non Static Method 1
pc.ParentMethod3(); //output: Parent Static Method 3
pc.ParentMethod4(); //output: Parent Non Static Method 4
// pc.ChildMethod3(); //Cannot call Child Methods from Parent Reference
// pc.ChildMethod4(); //Cannot call Child Methods from Parent Reference`
}
}`
如您所见,父引用正在调用父方法子引用正在调用子方法
现在,请参阅带有子类的父引用。 我们调用方法2来自父类。但是,它给了我们孩子的方法。 所以,我们是在这里做覆盖。
为什么 Method1(( 没有调用子方法?因为它是一个静态方法.
静态意味着,它不会改变。因此,覆盖是行不通的。 这个概念称为方法隐藏。
.这个概念可能仍然模糊不清。首先将此代码添加到 IDE,运行它,然后返回到此行。
参见,通过使用 Parent 引用和 Parent 对象,我们可以调用 Parent方法如案例 1 所示,通过使用子引用和子对象,我们可以调用子方法,如案例 2 所示,但在案例 3 中,通过使用父方法引用和子对象,我们可以调用父方法和子方法。我们不必在这里创建 2 个对象,当工作可以只需创建 1 个对象并节省内存即可完成。 所以,它主要是如果您使用的是@Overriding概念,则很有用。 在以下情况下可能很有用扩展抽象类和接口。
我们有一个
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((,get指定
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())
}
现在它是一种松散的耦合,因为您可以拥有不同类型的员工,例如:软件工程师,经理,人力资源,食品储藏室员工等
因此,您可以为父引用提供对象,而不考虑不同的员工对象
您将 parent 声明为 Parent,因此 java 将仅提供 Parent 类的方法和属性。
Child child = new Child();
应该工作。或
Parent child = new Child();
((Child)child).salary = 1;