PS : java中实现多态的机制是什么?
方法的重写Overriding和重载Overloading是Java多态性的不同表现。
重写Overriding是父类与子类之间多态性的一种表现,发生在运行时。
(也就是动态绑定),根据对象类型来决定调用的具体方法。
重载Overloading是一个类中多态性的一种表现,发生在编译时。
重载方法在编译时起作用(例如,静态绑定),重写方法在运行时起作用(例如,动态绑定)。静态绑定意味着JVM在编译时决定调用的类或方法。而动态绑定时,JVM是在运行时决定调用的类或方法。动态绑定设计是多态的基础。
一 子类---父类 中成员变量的继承及this--super
访问变量看声明,访问方法看实际对象类型(new出来的类型)
二 静态方法的重写/覆盖 --(静态方法的隐藏--父类成员变量的隐藏)
看看一个程序,动态绑定与静态绑定。
- class Dog{
- publicstaticvoidbark(){
- System.out.print("woof");
- }
- }
- class Basenji extend sDog{
- publicstaticvoidbark(){}
- }
- publicclassBark{
- publicstaticvoidmain(Stringargs[]){
- Dog woofer=new Dog();
- Dog nipper=new Basenji(); // 若nipper声明为 Basenji ,则nipper.bark();输出为空
- woofer.bark(); // woof
- nipper.bark();// 也是woof ,
- }
- }
-
啊,你可能一开始就认为这应该是动态绑定吧,多态性!!那么应该只打印一个woof吧!可并非如此,可怜的小狗Basenji也叫唤了(据书中所说,此种狗在非洲,而且从来不叫唤,无法想象这世上有不叫唤的狗,呵呵)。问题就在于bark是一个静态方法,而静态方法的调用不存在任何动态绑定机制!对于静态方法,只会根据调用者的编译期类型进行调用,woofer,nipper都被声明为Dog,他们的编译期类型相同。你可能要问,子类 Basenji中的bark()方法不是覆写了父类中的bark方法,那么是怎么回事?答案是它隐藏了父类中的bark方法,静态方法是不能被覆写的,他们只能被隐藏。要想使这只可怜的小狗回归不叫唤的“正常”状态,去掉方法之前的static标签即可。
注意隐藏的含义。
三 java构造方法与成员初始化分析---正确重写,调用的是子类中重写的函数,作用于初始化之前。
package ibm;
public class TestConstructor{
public static void main(String[] args) {
Drived drived=new Drived();
System.out.println(drived.memeber);
}
}
class Base {
public Base(){
System.out.println("base constructor");
preprocess();
}
public void preprocess(){
System.out.println("base invoked");
}
}
class Drived extends Base{
public String memeber="set the string";
public Drived(){
System.out.println("drived constructor");
System.out.println(memeber);
memeber="set in constructor";
}
@Override
public void preprocess(){
System.out.println("drived invoked ");
memeber="set in proprocess ";
}
}
请问以上程序的打印输出什么?
base constructor
drived invoked
drived constructor
set the string
set in constructor
程序的执行顺序是这样的;
1 进入Drived 构造函数。
2 Drived 成员变量的内存被分配。
3 Base 构造函数被隐含调用。
4 Base 构造函数调用preprocess(),这里调用的是Drived的preprocess()(多态性)-----正确重写。
5 Drived 的preprocess设置memeber值为 “set in proprocess”。
6 Drived 的成员变量初始化被调用。
7 执行Drived 构造函数体。
Java中的声明和初始化不能看成一体的。在C++的世界中,C++并不支持成员变量在声明的时候进行初始化,其需要你在构造函数中显式的初始化其成员变量的值,看起来很土,但其实C++用心良苦。在面
向对象的世界中,因为程序以对象的形式出现,导致了我们对程序执行的顺序雾里看花。所以,在面向对象的世界中,程序执行的顺序相当的重要 。
下面是对上面各个步骤的逐条解释。
进入构造函数。
为成员变量分配内存。
除非你显式地调用super(),否则Java 会在子类的构造函数最前面偷偷地插入super() 。
调用父类构造函数。
调用preProcess,因为被子类override,所以调用的是子类的。
于是,初始化发生在了preProcess()之后。这是因为,Java需要保证父类的初始化早于子类的成员初始化,否则,在子类中使用父类的成员变量就会出现问题。
正式执行子类的构造函数(当然这是一个空函数,居然我们没有声明)。
你可以查看《Java语言的规格说明书》中的 相关章节 来了解更多的Java创建对象时的细节。
C++的程序员应该都知道,在C++的世界中在“构造函数中调用虚函数”是不行的,Effective C++ 条款9:Never call virtual functions during construction or destruction,Scott Meyers已经解
释得很详细了。在语言设计的时候,“在构造函数中调用虚函数”是个两难的问题。
1 如果调用的是父类的函数的话,这个有点违反虚函数的定义。
2 如果调用的是子类的函数的话,这可能产生问题的:因为在构造子类对象的时候,首先调用父类的构造函数,而这时候如果去调用子类的函数,由于子类还没有构造完成,子类的成员尚未初始化,这
么做显然是不安全的。
C++选择了第一种,而Java选择了第二种。
C++类的设计相对比较简陋,通过虚函数表来实现,缺少类的元信息。
而Java类的则显得比较完整,有super指针来导航到父类。
四 重载(overloading)与重写(overriding) ---正确重写的规则
这篇文章介绍的常见面试题是关于重载(overloading)方法和重写(overriding)方法的。
Q.下面代码片段的输出结果是什么?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
public
class
MethodOverrideVsOverload {
// 是重载,not 重写,重写要符合重写的规则
(此处函数的参数变了,故是重载,not 重写)
public
boolean
equals( MethodOverrideVsOverload other ) {
System.out.println(
"MethodOverrideVsOverload equals method reached"
);
return
true
;
}
public
static
void
main(String[] args) {
Object o1 =
new
MethodOverrideVsOverload();
//因为是重载,故调用方法是在编译时确定,即取决于所声明的对象类型,
// 此时,声明为object 的类型,故调用的是object的equals方法,
// 比较的是俩个对象的地址是否一样,必然不一样。
Object o2 =
new
MethodOverrideVsOverload();
// 重载,发生与运行时,即取决与所声明的类型,
// 声明为
MethodOverrideVsOverload ,调用子类的重载方法
MethodOverrideVsOverload o3 =
new
MethodOverrideVsOverload();
MethodOverrideVsOverload o4 =
new
MethodOverrideVsOverload();
if
(o1.equals(o2)){
System.out.println(
"objects o1 and o2 are equal"
);
}
if
(o3.equals(o4)){
System.out.println(
"objects o3 and o4 are equal"
);
}
}
}
|
A.输出结果是:
MethodOverrideVsOverload equals method reached
objects o3 and o4 are equal
这个问题考察了哪些概念呢?
- Java语言中,一个类只能从一个类中继承出来(也就是,单继承结构),如果没有显式的标明所继承自的类,那么自动继承自Object对象。
- 大多数的非final对象类方法都会被子类重写(overridden):
public boolean equals(Object obj); // make note of this method
public int hashCode();
public String toString();
- 重载方法在编译时起作用(例如,静态绑定),重写方法在运行时起作用(例如,动态绑定)。静态绑定意味着JVM在编译时决定调用的类或方法。而动态绑定时,JVM是在运行时决定调用的类或方法。动态绑定设计是多态的基础。更多了解编译时和运行时.
- 子类中重写父类的对应方法必须遵循下面的规则:
参数 | 不可变(译者注:包括参数类型和个数)。 |
返回类型 | 不可变,除了协变返回类型或其子类型(covariant (subtype) returns)。 |
异常 | 子类中可以抛出更少的异常,但绝对不能抛出父类中没有定义的已检查异常。 |
访问权限 | 比父类中对应方法更宽松。 |
调用 | 运行时(也就是动态绑定),根据对象类型来决定调用的具体方法。 |
现在,再回头看上面的代码,MethodOverrideVsOverload 类中的”equals(MethodOverrideVsOverload other)”方法并没有重写Object类中的”public boolean equals(Object obj)” 方法。这是因为其违背了参数规则,其中一个是MethodOverrideVsOverload 类型,而另一个是Object类型。
因此,这两个方法是重载关系(发生在编译时),而不是重写关系。
因此,当调用o1.equals(o2)时,实际上调用了object类中的public boolean equals(Object obj)方法。这是因为在编译时,o1和o2都是Object类型,而Object类的equals( … )方法是比较内存地址(例如,Object@235f56和Object@653af32)的,因此会返回false。
当调用o3.equals(o4)时,实际上调用了MethodOverrideVsOverload 类中的equals( MethodOverrideVsOverload other )方法。这是因为在编译时,o3和o4都是MethodOverrideVsOverload类型的,因此得到上述结果。
接下来还可以怎么提问呢?
Q.那怎么解决上面的那个问题呢?
A.在Java5中,新增了注解,其中包括很好用的编译时注解(compile time annotations)@override,来保证方法正确的重写了父类方法。如果在上面的代码中添加了注解,那么JVM会抛出一个编译错误。
因此,解决的方法就是给MethodOverrideVsOverload 类的boolean equals( MethodOverrideVsOverload other )方法添加@override注解。这样的话编译时就会有错误抛出来提示开发者某个方法没有正确的重写父类方法。之后,还需要修改方法的参数,将其从MethodOverrideVsOverload变成Object,具体如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
|
public
class
MethodOverrideVsOverload {
@Override // 重载
public
boolean
equals( Object other ) {
System.out.println(
"MethodOverrideVsOverload equals method reached"
);
return
true
;
}
public
static
void
main(String[] args) {
Object o1 =
new
MethodOverrideVsOverload();
//during compile time o1 is of type Object
//during runtime o1 is of type MethodOverrideVsOverload
Object o2 =
new
MethodOverrideVsOverload();
//during compile time o2 is of type Object
//during runtime o2 is of type MethodOverrideVsOverload
MethodOverrideVsOverload o3 =
new
MethodOverrideVsOverload();
//o3 is of type MethodOverrideVsOverload
// during both compile time and runtime
MethodOverrideVsOverload o4 =
new
MethodOverrideVsOverload();
//o4 is of type MethodOverrideVsOverload
// during both compile time and runtime
if
(o1.equals(o2)){
// 重写,发生在运行时,运行时动态绑定,调用子类重写的equals方法
System.out.println(
"objects o1 and o2 are equal"
);
}
if
(o3.equals(o4)){
// 重写,发生在运行时,运行时动态绑定,调用子类重写的equals方法
System.out.println(
"objects o3 and o4 are equal"
);
}
}
}
|
输出为:
MethodOverrideVsOverload equals method reached
objects o1 and o2 are equal
MethodOverrideVsOverload equals method reached
objects o3 and o4 are equal
上面的代码中,运行时equals方法正确的重写了Object中的相应方法。这是一个比较容易混淆的问题,面试的时候需要很详尽的解释相关的概念。
参考 : http://blog.youkuaiyun.com/haoel/article/details/4319793