在 Java 的继承机制中,**字段(成员变量)和方法(成员函数)**的行为有所不同,这是因为 Java 的类继承机制对它们的处理方式不同。
代码示例:
public class Test {
public static void main(String[] args) {
Animal dog = new Dog();
dog.shout(); // 方法调用,输出子类中的实现
System.out.println(dog.name); // 字段访问,输出父类中的字段
}
}
假设我们有如下的类定义:
class Animal {
String name = "Animal";
void shout() {
System.out.println("Animal shout");
}
}
class Dog extends Animal {
String name = "Dog"; // Dog类中的name字段
@Override
void shout() {
System.out.println("Dog bark");
}
}
为什么字段是父类的,而方法是子类的?
-
字段(成员变量)是父类的:
- 在 Java 中,字段是静态绑定的,也就是说,字段的访问取决于引用变量的类型,而不是实际对象的类型。
- 在代码中,
dog是Animal类型的引用,它指向一个Dog类型的实例。由于字段在 Java 中并不具备多态性(即不会根据对象的实际类型动态查找字段),所以dog.name会访问Animal类中的name字段,而不是Dog类中的name字段。 - 解释: 由于编译时
dog被视为Animal类型(因为dog的声明类型是Animal),它访问的是Animal类中的name字段,即"Animal"。
-
方法是子类的:
- 方法是动态绑定的,也就是说,方法调用在运行时会根据对象的实际类型进行选择。
- 虽然
dog的引用类型是Animal,但它指向的是一个Dog类型的对象。因此,当调用dog.shout()时,Java 会动态查找Dog类中是否有shout()方法,并执行Dog类中的方法实现。 - 这就是多态的体现:即使编译时我们知道
dog是Animal类型,实际执行时会根据对象的实际类型(Dog)来决定调用哪个方法。
总结:
- 字段是静态绑定的,访问时取决于引用变量的类型(在你的例子中是
Animal类型),因此即使实际对象是Dog,字段访问的是Animal类中的字段。 - 方法是动态绑定的,调用时根据实际对象的类型(在你的例子中是
Dog),因此调用的是Dog类中的方法。
这种机制保证了 Java 的继承结构中字段和方法行为的不同:字段继承时是“编译时绑定”的,而方法是“运行时绑定”的。
在Java中,方法调用绑定指的是将一个方法调用与方法的代码实现关联起来。Java中的绑定分为两种:静态绑定(也称为早期绑定)和动态绑定(也称为晚期绑定)。
静态绑定(Static Binding)
静态绑定发生在编译时期,它基于对象的类型进行绑定。在编译时,编译器就知道要调用哪个方法。以下情况通常会发生静态绑定:
- 静态方法:静态方法是属于类的,不依赖于对象,因此它们在编译时就可以确定。
- 私有方法:私有方法是不可被继承的,因此它们也总是在编译时绑定。
- final方法:final方法不能被覆盖,所以编译器在编译时就可以确定调用哪个方法。
- 构造方法:构造方法在对象创建时调用,它们的绑定也在编译时期确定。 静态绑定的例子:
public class Base {
public static void staticMethod() {
System.out.println("Static method in Base");
}
private void privateMethod() {
System.out.println("Private method in Base");
}
public final void finalMethod() {
System.out.println("Final method in Base");
}
}
public class Derived extends Base {
// 尝试覆盖静态方法、私有方法或final方法都不会影响静态绑定
}
Base obj = new Derived();
obj.staticMethod(); // 静态绑定,调用Base类的staticMethod
obj.finalMethod(); // 静态绑定,调用Base类的finalMethod
动态绑定(Dynamic Binding)
动态绑定发生在运行时期,它基于对象的实际类型进行绑定。在运行时,虚拟机(JVM)会根据对象的实际类型来决定调用哪个方法。以下情况通常会发生动态绑定:
- 非静态方法:当调用一个非静态方法时,JVM会根据对象的实际类型来决定调用哪个方法实现。
- 非私有方法:只有非私有方法可以被覆盖,因此它们是动态绑定的候选。 动态绑定的例子:
public class Base {
public void nonStaticMethod() {
System.out.println("Non-static method in Base");
}
}
public class Derived extends Base {
@Override
public void nonStaticMethod() {
System.out.println("Non-static method in Derived");
}
}
Base obj = new Derived();
obj.nonStaticMethod(); // 动态绑定,调用Derived类的nonStaticMethod
在上面的例子中,尽管obj的引用类型是Base,但是实际对象是Derived类型的,因此调用nonStaticMethod时,会调用Derived类中覆盖后的方法。
总结
- 静态绑定在编译时期确定方法调用,它依赖于变量的声明类型。
- 动态绑定在运行时期确定方法调用,它依赖于对象的实际类型。
- 静态绑定通常用于性能优化,因为编译器可以预先知道要调用哪个方法,而动态绑定提供了更大的灵活性,允许方法在运行时根据对象的实际类型来选择调用哪个实现。