静态绑定与动态绑定

在调用方法时,会出现静态绑定和动态绑定的概念。但是,到底是什么意思,需要仔细思考。为了弄清所有相关概念,首先来看一下函数调用过程:

  1. 编译器查看对象的声明类型和方法名。假设调用 x.f(param),并且隐式参数 x 声明为 C 类的对象。编译器会枚举出 C 类中名为 f 的方法和其超类中访问属性为 public 且名为 f 的方法。
  2. 编译器查看调用方法是提供的参数类型。如果在所有名为 f 的方法中存在一个与提供的参数类型完全匹配,就选择这个方法。这个过程叫做重载解析(overloading resolution)
  3. 如果是 private 方法、static 方法、final 方法或者构造器,那么编译器准确知道应该调用哪个方法,我们将这种调用方式称作静态绑定(static binding)
  4. 否则虚拟机一定调用与 x 所引用对象的实际类型最合适的那个类的方法。假设 x 的实际类型为 D,它是 C 的子类。如果 D 类定义了该方法,就直接调用它。否则将在 D 类的超类中寻找该方法,以此类推。我们将这种调用方式称作动态绑定(dynamic binding)

Java 语言中非虚方法可以通过“静态绑定“,那么 Java 中哪些方式是非虚的呢?Java 语言规范第三版说明了哪些实例方法不是虚方法:
Java Language Specification, 3rd 写道

8.4.3.3 final Methods

A method can be declared final to prevent subclasses from overriding or hiding it. It is a compile-time error to attempt to override or hide a final method.
A private method and all methods declared immediately within a final class (§8.1.1.2) behave as if they are final, since it is impossible to override them.

It is a compile-time error for a final method to be declared abstract.

行为如同“final”的方法都无法覆写,也就无法进行子类型多态;声明为 final 或 private 的方法都被属于这类。所以除了静态方法之外,声明为 final 或者 private 的实例方法也是非虚方法。其它实例方法都是虚方法。

了解相关概念后,来看一个具体的例子:
假设有 Animal 类,它有很多子类,比如 Cat、Dog 等等。

public class Animal
{
    public String shouting(int number)
    {
        return "Animal No."+ number + " is shouting!";
    }
    // other method
}

public class Cat extends Animal
{
    @Override
    public String shouting(int number)
    {
        return "Cat No."+ number + " is shouting!";
    }

    public String running(int number)
    {
        return "Cat No."+ number + " is running!";
    }
}

然后出现这样的语句:

Animal a = new Cat();

这里出现了动态绑定,其中 a 声明的类型为 Animal,而运行时的类型为 Cat,所以 a 只可以访问到 Animal 中的方法和属性(如果超类 Animal 中的方法被子类 Cat 覆写的的话,那访问的就是覆写后的方法,例子中的 shouting 方法)。
如果访问子类特有的方法,就会报错。只有当 a 被强制转化为子类 Cat 时,才可以。例如:

a.running(1); // compile error if the running() method is only for Cat
((Cat)a).running(1); // work

现在看一个例子:

public class EqualsOverloadTest 
{
    String id;

    public EqualsOverloadTest(String id)
    {
        this.id = id;
    }

    public boolean equals(EqualsOverloadTest other)
    {
        return (other!=null) && this.id.equals(other.id);
    }

    public int hashCode() 
    {
        return id.hashCode();
    }
}

问这里的的 equals 方法会起作用吗,或者说

Set set = new HashSet();
set.add(first);
set.add(second);
System.out.println(set.size());

输出结果会是几?

答案应该是 1,也就是说 equals 方法写的是错误的。这里经常会出现问题的是混淆 method overloading(方法重载)与 method overriding(方法覆写)。

  • method overloading——方法名相同但参数列表不同的情况;
  • method overriding——参数列表与返回类型都匹配的同名方法,在派生类中覆写基类实现的情况。

所以上面的 equals 方法就是方法重载,而没有覆写 Object 中的 equals 方法,自然不起作用。

需要注意的是,

  • 方法重载(method overloading)是 static binding 或者 compile-time binding
  • 方法覆写(method overriding)是 dynamic binding 或者 runtime binding。

至于为什么,参见 Java 的函数重载为什么采取静态静态绑定而非动态绑定?——RednaxelaFX的回答

相关问题:
Java Dynamic Binding Confusion——Stack Overflow
Java dynamic binding and method overriding——Stack Overflow

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值