面向对象的三大基本特征:封装,继承和多态。
封装:就是将类的内部实现细节封装和数据封装起来,不对外暴露实,向外部提供接口,来操作内部封装的数据。
继承:为了提高代码的重用性,如果一个类A和类B是IS-A关系,则可以使用继承。
多态:指允许不同类的对象对同一消息做出响应。即同一消息可以根据发送对象的不同而采用多种不同的行为方式。(发送消息就是函数调用)。
作用:可以根据实现类的不同而实现不同的功能,以消除类型之间的耦合关系。
现实生活中的多态
比方说按下 F1 键这个动作,如果当前在 Flash 界面下弹出的就是 AS 3 的帮助文档;如果当前在 Word 下弹出的就是 Word 帮助;在 Windows 下弹出的就是 Windows 帮助和支持。同一个事件发生在不同的对象上会产生不同的结果。
下面来看这样一个例子
public class Person {
public void fun(){
System.out.println("person的fun()");
display();
}
public void display(){
System.out.print("person");
}
}
public class Man extends Person {
public void fun(int a){
System.out.println("Man的fun1(int a)");
display();
}
@Override
public void display() {
System.out.println("Man");
}
public static void main(String[] args){
Person p = new Man();
p.fun();
//p.fun(1); 编译错误,因为Person类中并没有fun(int)方法
}
}
Output:
person的fun()
Man
从这个例子可以看出,p首先调用person类的fun()方法,然后在方法fun()中调用的是Man类的display()方法。
这是为何呢?原因其实很简单,p首先调用fun()方法,而这个方法子类Man中并未重写fun()方法(需要注意的是Man中只是对fun()方法重载了,并未重写)然后在fun()方法中调用了display方法,发现子类Man重写了display方法于是调用Man中的display方法。
总结:
指向子类的父类引用由于向上转型了,它只能访问父类中拥有的方法和属性,而对于子类中存在而父类中不存在的方法,该引用是不能使用的,尽管是重载该方法。若子类重写了父类中的某些方法,在调用该些方法的时候,必定是使用子类中定义的这些方法(动态连接、动态调用)。
关于动态绑定的细节
对于面向对象而已,多态分为编译时多态和运行时多态。其中编辑时多态是静态的,主要是指方法的重载,它是根据参数列表的不同来区分不同的函数,通过编辑之后会变成两个不同的函数,在运行时谈不上多态。而运行时多态是动态的,它是通过动态绑定来实现的,也就是我们所说的多态性。
多态的实现
多态的实现条件
1.继承:在多态中必须存在有继承关系的子类和父类。
2.重写:子类对父类中某些方法进行重新定义,在调用这些方法时就会调用子类的方法。
3.父类引用指向子类对象。
多态的原则
Java中引用变量实际调用的方法不是由引用变量的类型决定的,而是有变量指向对象的实际类型决定的。
多态的实现方式
继承和接口
继承实现多态
public class Person {
public void display(){
System.out.print("person");
}
}
public class Man extends Person {
@Override
public void display() {
System.out.println("Man");
}
}
public class Woman extends Person {
@Override
public void display() {
System.out.println("Woman");
}
public static void main(String[] args){
Person[] persons = new Person[2];
persons[0] = new Man();
persons[1] = new Woman();
for (int i =0;i<persons.length;i++){
persons[i].display();
}
}
}
Output:
Man
Woman
从这个例子可以看出基于继承的实现机制主要表现在父类和继承该父类的一个或多个子类对某些方法的重写,多个子类对同一方法的重写可以表现出不同的行为。
接口实现多态
此处有两点不同:
1.因为接口方法都是抽象方法,所以具体的实现都是有子类去完成的。所以,在编码的时候,接口必须执行一个具体的实现类,调用实现类中的方法。
2.因为类的继承是但继承,而一个类可以实现多个接口,这就为编程带来了灵活性。
关于多态的经典的例子
class A
{
public String show(D obj)...{
return ("A and D");
}
public String show(A obj)...{
return ("A and A");
}
}
class B extends A
{
public String show(B obj)...{
return ("B and B");
}
public String show(A obj)...{
return ("B and A");
}
}
class C extends B{}
class D extends B{}
class E
{
public static void main(String [] args)
{
A a1 = new A();
A a2 = new B();
B b = new B();
C c = new C();
D d = new D();
System.out.println(a1.show(b)); //①
System.out.println(a1.show(c)); //②
System.out.println(a1.show(d)); //③
System.out.println(a2.show(b)); //④
System.out.println(a2.show(c)); //⑤
System.out.println(a2.show(d)); // ⑥
System.out.println(b.show(b)); //⑦
System.out.println(b.show(c)); //⑧
System.out.println(b.show(d)); //⑨
}
}
Output:
① A and A
② A and A
③ A and D
④ B and A
⑤ B and A
⑥ A and D
⑦ B and B
⑧ B and B
⑨ A and D
在分析这个之前,我们先来看一个原则:
其实在继承链中对象方法的调用存在一个优先级:this.show(O)、super.show(O)、this.show((super)O)、super.show((super)O)
下面我们来看一下上面这个例子的继承关系图
对于①、②、③来说很简单
但是对于下面的输出结果可能就有人搞不清楚了,下面我们来具体分析一下。
④、A类引用a2指向B类对象的实例,所以现在A中寻找,与实际参数类型匹配的方法,发现并没有,所以,就将b向上转型为A再去查找,发现public String show(A obj)方法,接着调用。
⑤、 A类引用a2指向B类对象的实例,所以现在A中寻找,与之匹配的参数,发现并没有,所以,就将b向上转型为A再去查找,但是在这里,B类中重写了public String show(A obj)所以调用B类对象中的。
⑥ 、直接在A类中查到了与之实际参数类型匹配的方法,然后直接调用。
⑦ 、B类引用b创建一个B类对象,直接查找B类中的方法,发现有与实际参数类型匹配的方法,直接调用即可。
⑧ 、B类引用b创建一个B类对象,直接查找B类中的方法,并未发现有与实际参数类型匹配的方法,直接去A类中寻找,也未找到,所以将实际参数类型C类类型转换为B类类型,在B类中找到了对应的方法,直接调用即可。
⑨、B类引用b创建一个B类对象,在B类中查找,并未找到,然后再去A类中查找。