理解方法的调用
x.f(args),隐式参数x声明为C类的对象,下面是详细的描述:
编译器查看对象的声明类型和方法名。假设要调用
x.f(args),隐式参数x声明为C类的对象。需要注意的是:有可能存在多个名字为f,但参数类型不一样的方法(重载)。编译器将会一一列举所有C类中名为f的方法和其超类中访问属性为public且名为f的方法(超类的私有方法不可访问)
至此,编译器已获得所有可能被访问的候选方法
接下来,编译器将查看调用方法时提供的参数类型。如果在所有名为f的方法中存在一个与提供的参数类型完全匹配,就选择这个方法。这个过程叫
重载解析(overloading resolution).例如,对于调用x.f("Hello"),编译器将会挑选f(String),而不是f(int)由于允许类型转换,所以这个过程可能会很复杂。如果编译器没有找到与参数类型匹配的方法,或者发现经过类型转换后有多个方法与之匹配,就会报告一个错误。
如果在子类中定义了一个与超类答名相同的方 法, 那么子类中的这个方法就覆盖了超类中的这个相同签名的方法。
不过, 返回类型不是签名的一部分, 因此, 在覆盖方法时, 一定要保证返回类型 的兼容性。 允许子类将覆盖方法的返回类型定义为原返回类型的子类型
如果是 private 方法 static 方法、 final 方法或者构造器, 那么编译器将可以准确地知道应该调用哪个方法, 我们将这种调用方式称 为静态绑定 (static binding) 。 与此对应的是, 调用的方法依赖于隐式参数的实际类型,并且 在运行时实现动态绑定
当程序运行 并且采用动态绑定调用方法时, 虚拟机一定调用与x所引用对象的实际类型最合适的那个类的方法。 假设x的实际类型是 D, 它是 C 类的子类。 如果 D 类定义了 方法
f(String), 就直接调用它;否则, 将在 D 类的超类中寻找 f(String), 以此类推。
每次调用方法都要进行搜索 时间开销相当大。 因此, 虚拟机预先为每个类创建了一个方法表 (method table), 其中列出了所有方法的签名和实际调用的方法。 这样一来, 在真正 调用方法的时候, 虚拟机仅查找这个表就行了。 在前面的例子中, 虚拟机搜索D类的方法 表 以便寻找与调用 f(Sting) 相匹配的方法。 这个方法既有可能是 D.f(String), 也有可能是X.f(String), 这里的 X 是 D 的超类。 这里需要提醒一点, 如果调用 super.f(param), 编译器将 对隐式参数超类的方法表进行搜索。
在覆盖一个方法的时候,子类方法不能低于超类方法的可见性。 特别是, 如果超类方法是public, 子类方法一定要声明为public。 经常会发生这类错误:在声明子类方法的时 候, 遗漏了public修饰符。 此时, 编译器将会把它解释为试图提供更严格的访问权限。
阻止继承: final类和方法
注释:前面曾经说过, 域也可以被声明为final。 对于final域来说, 构造对象之后就不允许改变它们的值了。 不过, 如果将一个类声明为final, 只有其中的方法自动地成为final, 而不包括域。
在早期的Java中,如果一个方法没有被覆盖并且很短, 编译器就能够对它进行优化处理,这个过程为称为内联(inlining)。例如, 内联调用e.getName()将被替换为访问e.name域。 这是 一项很有意义的改进, 这是由于CPU在处理调用方法的指令时,使用的分支转移会扰乱预取指令的策略
然而, 如果getName在另外一个类中被覆盖那么编译器就无法知道覆盖的代码将会做什么操作 因此也就不能对它进行内联处理了。
虚拟机中的即时编译器比传统编译器的处理能力强得多。 这种编译器可以准确地知道类之间的继承关系, 并能够检测出类中是否真正地存在覆盖给定的方法。 如果方法
很简短、被频繁调用且没有真正地被覆盖 那么即时编译器就会将这个方法进行内联处理,如果虚拟机加载了另外一个子类, 而在这个子类中包含了对内联方法的覆盖, 那么将会发生什么情况呢?优化器将取消对覆盖方法的内联。 这个过程很慢,但却很少发生。
强制类型转换
Manager boss = (Manager) staff [OJ;
进行类型转换的唯一原因是:在暂时忽视对象的实际类型之后, 使用对象的全部功能。
将一个值存入变量时, 编译器将检查是否允许该操作。 将一个了类的引用赋给一个超类变量,编译器是允许的但将一个超类的引用赋给一个子类变量, 必须进行类型转换,这样才能通过运行时的检查
如果试图在继承链上进行向下的类型转换, 并且 “谎报” 有关对象包含的内容, 会发生什么情况呢?
Manager boss = (Manager) staff[l]; // Error
运行这个程序时, Java 运行时系统将报告这个错误, 并产生一个 ClassCastException异常。 如果没有捕获这个异常, 那么程序就会终止。 因此, 应该养成这样一个良好的程序设计习惯:在进行类型转换之前, 先查看一下是否能够成功地转换。 这个过程简单地使用 instanceof操作符就可以实现。 例如:
if (staff[l] instanceof Manager){ boss = (Manager) staff [1]; ...}
最后 , 如果这个类型转换不可能成功, 编译器就不会进行这个转换
如果 x 为 null, 进行下列测试
x i nstanceof C
不会产生异常, 只是返回 false。 之所以这样处理是因为 null 没有引用任何对象, 当然也不会引用C类型的对象。