重载和覆盖

重载(overload):对于类的方法(包括从父类中继承的方法),方法名相同参数列表不同的方法之间就构成了重载关系。这里有两个问题需要注意:

(1)       什么叫参数列表?参数列表又叫参数签名,指三样东西:参数的类型参数的个数参数的顺序。这三者只要有一个不同就叫做参数列表不同。

(2)       重载关系只能发生在同一个类中吗?非也。这时候你要深刻理解继承,要知道一个子类所拥有的成员除了自己显式写出来的以外,还有父类遗传下来的。所以子类中的某个方法和父类中继承下来的方法也可以发生重载的关系。

大家在使用的时候要紧扣定义,看方法之间是否是重载关系,不用管方法的修饰符和返回类型以及抛出的异常,只看方法名和参数列表。而且要记住,构造器也可以重载。

 

覆盖 (override):也叫重写,就是在当父类中的某些方法不能满足要求时,子类中改写父类的方法。当父类中的方法被覆盖了后,除非用super关键字,否则就无法再调用父类中的方法了。

发生覆盖的条件:

1、“三同一不低” 子类和父类的方法名称参数列表返回类型必须完全相同,而且子类方法的访问修饰符的权限不能比父类

2、子类方法不能抛出比父类方法更多的异常。即子类方法所抛出的异常必须和父类方法所抛出的异常一致,或者是其子类,或者什么也不抛出

3、被覆盖的方法不能是final类型的。因为final修饰的方法是无法覆盖的。

4、被覆盖的方法不能为private。否则在其子类中只是新定义了一个方法,并没有对其进行覆盖。

5、被覆盖的方法不能为static。所以如果父类中的方法为静态的,而子类中的方法不是静态的,但是两个方法除了这一点外其他都满足覆盖条件,那么会发生编译错误。反之亦然。即使父类和子类中的方法都是静态的,并且满足覆盖条件,但是仍然不会发生覆盖,因为静态方法是在编译的时候把静态方法和类的引用类型进行匹配。

 

方法的覆盖和重载具有以下相同点:

都要求方法同名

都可以用于抽象方法和非抽象方法之间

 

方法的覆盖和重载具有以下不同点:

方法覆盖要求参数列表(参数签名)必须一致,而方法重载要求参数列表必须不一致。

方法覆盖要求返回类型必须一致,方法重载对此没有要求。

方法覆盖只能用于子类覆盖父类的方法,方法重载用于同一个类中的所有方法(包括从父类中继承而来的方法)

方法覆盖对方法的访问权限和抛出的异常有特殊的要求,而方法重载在这方面没有任何限制。

父类的一个方法只能被子类覆盖一次,而一个方法可以在所有的类中可以被重载多次。

 

另外,对于属性(成员变量)而言,是不能重载的,只能覆盖。

动态分派:覆盖

静态分派:重载

静态分配类型属于多分派类,动态分派类型属于单分派类型。


JAVA中的方法调用

Java程序设计语言提供了两种基本的方法:实例方法和类(或静态)方法。这两种方法的区别在于:[6]

1) 实例方法在被调用之前,需要一个实例,而类方法不需要

2) 实例方法使用动态(迟)绑定,而类方法使用静态(早)绑定

当Java虚拟机调用一个类方法时,它会基于对象引用的类型(通常在编译时可知)来选择所调用的方法。相反,当虚拟机调用一个实例方法时,它会基于对象实际的类(只能在运行时得知)来选择所调用的方法。

Java虚拟机使用两种不同的指令分别调用这两种方法。对于实例方法,使用invokevirtual指令,对于类方法,使用invokestatic指令。这两种指令如表所示:

方法调用

操作码

操作数

说明

Invokevirtual

Indexbyte1, indexbyte2

把objectref(对象引用)和args(参数)从栈中弹出,调用常量池索引指向的实例方法

Invokestatic

Indexbyte1, indexbyte2

把args从栈中弹出,调用常量池索引指向的类方法

尽管通常使用invokevirtual指令调用实例方法,但在某些特定的情况中,也会使用另外两种操作码——invokespecial和invokeinterface,如表所示

方法调用

操作码

操作数

说明

Invokespecial

Indexbyte1, indexbyte2

把objectref和args从栈中弹出,调用常量池索引指向的实例方法

Invokeinterface

Indexbyte1, indexbyte2

把objectref和args从栈中弹出,调用常量池索引指向的实例方法

当根据引用的类型来调用实例方法,而不是根据对象的类来调用的时候,通常使用invokespecial指令。这又分为三种情况:

1)实例初始化(<init>())方法

2)私有方法

3)使用super关键字所调用的方法

当给出一个接口的引用时,使用invokeinterface来调用一个实例方法。

Java虚拟机总是直接调用类初始化(<clinit>())方法,类的初始化方法永远不会被任何字节码调用。在Java虚拟机指令集中,没有任何调用<clinit>()方法的指令,如果class文件尝试使用指令来调用<clinit>()方法,会导致虚拟机抛出异常。

到方法的引用最初是符号化的。所有的调用指令(例如invokevirtual和invokestatic)都指向一个最初包含符号引用的常量池入口,当Java虚拟机遇到一条调用指令时,如果还没有解析符号引用,那么虚拟机把解析符号引用作为执行指令调用执行过程中的一部分。要解析一个符号引用,Java虚拟机要确定被符号化引用的方法,然后再用一个直接引用来代替符号引用。直接引用就如同偏移量指针一样,如果将来再次使用该引用,它可以使虚拟机更快地调用这个方法。

一旦解析了一个方法后,Java虚拟机就准备调用它。如果这个方法是一个实例方法,它必须在一个对象中被调用。对每一次实例方法的调用,虚拟机需要在栈里存在一个对象引用(objectref)。如果该方法需要参数,那么除了objectref,虚拟机还需要在栈中存在该方法所需要的参数(args)。如果这个方法是一个类方法,虚拟机不再需要objrectref,因为虚拟机不会在对象上调用一个类方法,栈中存在的将只有args。Objectref和args(或者在类方法的情况下只能args)必须在调用指令执行前,被其他指令压入所调用方法的操作数栈。


javap -v OverideDemo 执行代码如下:


如何实现分别调用的是override方法?原因在于invokervirtual指令根据对象的vtable在运行时定位method。