Java的继承、封装与多态
Java的继承、封装与多态
基本概念
- 面向对象OO(Object Oriented):把数据及对数据的操作方法放在一起,作为一个相互依存的整体,即对象。
- 对同类对象抽象出共性,即类。
比如人就是一个类,所有的人都有一些共同的属性,比如身高体重肺活量,还有一些特定的操作,即方法,比如吃饭,思考。但是人只是一个抽象的概念,所有具备人这个群体的属性和方法的对象都叫人,我们只能说张三这个实例化的人身高是多少,体重是多少,而不能说人这个类身高是多少。 - OOP面向对象编程:指软件开发的过程中,通过面向对象的思想进行表达和实现。
- 重构:指对程序做出一定的改变。
面向对象的特征:
封装,继承与多态。
封装
有时称为数据隐藏
将数据和和对数据的操作隐藏在一个类中,即隐藏对象的属性和实现细节,仅对外公开接口。
使用者不需要知道类的具体实现,只要知道怎么调用就可以了。
比如:
所有类的成员变量一般设置为private,如果需要访问和修改,通过getter和setter方法,优点:
1.可以改变内部实现,除了该类的方法外,不会影响其他代码。
private String firstName;
private String lastName;
public String getName(){
return firstName + "" + lastName;
}
2.setter方法可以执行错误检查,比如检查数据是否合乎规范,输入的age是否小于0,直接对域操作则没这种优势
3.可细微划分访问控制:可以只设置域访问器getter,不设置域修改器setter,让用户只能读取,而不能修改。而public既可以读取,也可以修改,可能造成数据不安全。
继承
是一种类的层次模型,从现有的类中通过extends关键字派生新类,现有的类称为父类,新类称为子类。
子类可以从它的父类那里继承方法和实例变量,并且可以重写父类的方法和增加新的方法。一般将通用的方法放到超类中,具有特殊用途的方法放到子类中。
特点:
- 不能多重继承。
- 只能继承非私有的成员变量和方法
- 当子类成员变量与父类成员变量重名时,子类覆盖父类。
- 定义为final的类不可继承,定义为final的方法不可以被重写。
多态
- 指允许不同类的对象对同一消息(即同一个方法的调用)做出响应。即同一个方法的调用,可以根据调用这个方法的对象的不同而实现不同的功能。
- 在Java核心技术第5章中定义:
实现多态的技术称为:动态绑定(dynamic binding),是指在运行期间判断所引用对象的实际类型,根据其实际的类型调用其相应的方法。 - 多态的作用:降低类型之间的耦合关系。
把不同的子类对象都当作父类来看,可以屏蔽不同子类对象之间的差异,写出通用的代码,做出通用的编程,以适应需求的不断变化。
例子
- 比如:
如果我们定义
Animal animal = new Cat(); //表示我定义了一个Animal类型的引用,指向新建的Cat类型的对象
或者
Cat cat = new Cat();
如果我要改为子类Dog类,那么前者只需要修改一行代码,不需要改实例名,后者要大量改动。
Animal animal = new Dog();
- 再比如:
你可以设计一个接口:DataBaseDao
然后,每个不同的数据库,你可以编写接口的实现类,比如:SQLServerDao, MySqlDao, DB2Dao等等
当用户使用你框架的时候,他就可以用多态,实现自己的数据库连接功能:
DaoBaseDao dao = new SQLServerDao(); //针对SQLServer的接口实现,这里dao就是多态
dao.connect();
- 再比如:
现实中,关于多态的例子不胜枚举。比方说按下 F1 键这个动作,如果当前在 Flash 界面下弹出的就是 AS 3 的帮助文档;如果当前在 Word 下弹出的就是 Word 帮助;在 Windows 下弹出的就是 Windows 帮助和支持。同一个事件发生在不同的对象上会产生不同的结果。
但注意:父类类型的引用可以调用父类中定义的所有属性和方法,而对于子类中定义而父类中没有的方法,它是无可奈何的;比如子类中增加的方法,父类引用无法调用。
多态存在的三个必要条件
一、要有继承;
二、要有重写;
三、父类引用指向子类对象。
多态的好处:
1.可替换性(substitutability)。多态对已存在代码具有可替换性。例如,多态对圆Circle类工作,对其他任何圆形几何体,如圆环,也同样工作。
2.可扩充性(extensibility)。多态对代码具有可扩充性。增加新的子类不影响已存在类的多态性、继承性,以及其他特性的运行和操作。实际上新加子类更容易获得多态功能。例如,在实现了圆锥、半圆锥以及半球体的多态基础上,很容易增添球体类的多态性。
3.接口性(interface-ability)。多态是超类通过方法签名,向子类提供了一个共同接口,由子类来完善或者覆盖它而实现的。
4.简化性(simplicity)。多态简化对应用软件的代码编写和修改过程,尤其在处理大量对象的运算和操作时,这个特点尤为突出和重要。
Java中多态的实现方式:
- 方法的重载(overload):重载是指同一个类中有多个同名的方法,但这些方法有不同的参数,因此在编译期就可以确定到底调用哪个方法,它是一种编译时多态。即一个类中的方法多态性。
- 方法的覆盖(即重写)(override):子类可以覆盖父类的方法,即同样的方法会在子类和父类中有不同的表现形式。在Java语言中,父类引用可以指向父类对象或者子类对象,接口引用可以指向其实现类的实例对象。 而程序调用的方法在运行期才动态绑定(绑定指的是将一个方法调用和一个方法主体连接到一起),即:在运行期间判断所引用对象的实际类型,根据其实际的类型调用其相应的方法。(注意此时父类引用只是指向子类对象,真正运行的还是子类对象,即优先从实际运行的对象的方法表中找对应的方法签名(方法名+参数列表称为方法签名),如果找不到,才回到相应的父类方法表中找调用方法的方法签名),由于运行时才能确定调用哪个方法,因此称方法覆盖的多态为运行时多态。
在《深入理解Java虚拟机》的P248页,
对方法的重载称为:静态分派(根据静态类型)
package org.fenixsoft.polymorphic;
/**
* 方法静态分派演示
* @author zzm
*/
public class StaticDispatch {
static abstract class Human {
}
static class Man extends Human {
}
static class Woman extends Human {
}
public void sayHello(Human guy) {
System.out.println("hello,guy!");
}
public void sayHello(Man guy) {
System.out.println("hello,gentleman!");
}
public void sayHello(Woman guy) {
System.out.println("hello,lady!");
}
public static void main(String[] args) {
Human man = new Man();
Human woman = new Woman();
StaticDispatch sr = new StaticDispatch();
sr.sayHello(man);
sr.sayHello(woman);
}
}
运行结果:
hello,guy!
hello,guy!
注意这里的静态类型指static类型,但其实不是static类型结果也一样,只不过将内部类写为static的可以实现和外部类相同的调用方式,比如:
public class StaticDispatch {
abstract class Human {
}
class Man extends Human {
}
class Woman extends Human {
}
public void sayHello(Human guy) {
System.out.println("hello,guy!");
}
public void sayHello(Man guy) {
System.out.println("hello,gentleman!");
}
public void sayHello(Woman guy) {
System.out.println("hello,lady!");
}
public static void main(String[] args) {
StaticDispatch staticDispatch = new StaticDispatch();
Human man = staticDispatch.new Man();
Human woman = staticDispatch.new Woman();
StaticDispatch sr = new StaticDispatch();
sr.sayHello(man);
sr.sayHello(woman);
}
}
代码中刻意地定义了两个静态类型相同但实际类型不同的变量,但虚拟机(准确地说是编译器)在重载时是通过参数的静态类型而不是实际类型作为判定依据的。并且静态类型是编译期可知的,因此,在编译阶段,Javac编译器会根据参数的静态类型决定使用哪个重载版本,所以选择了sayHello(Human)作为调用目标,并把这个方法的符号引用写到main()方法里的两条invokevirtual指令的参数中。
对方法的覆盖称为:动态分派(根据实际类型)
例子:
class Base {
public int i = 1;
public Base() {
g();
}
public void f() {
System.out.println("Base f");
}
public void g() {
System.out.println("Base g");
}
}
public class Derived extends Base {
public int i = 2;
public void f() {
System.out.println("Derived f");
}
public void g() {
System.out.println("Derived g");
}
public static void main(String args[]) {
Base b = new Derived();
b.f();
System.out.println(b.i);
}
}
输出:
Derived g
Derived f
1
上面的例子说明了:只有类中的方法才有多态的概念,类中成员变量没有多态的概念。类中成员变量的值取决于所定义变量的类型,这是在编译期间确定的。
面向接口编程
参考:http://www.cnblogs.com/leoo2sk/archive/2008/04/10/1146447.html
面向接口编程和面向对象编程是什么关系
首先,面向接口编程和面向对象编程并不是平级的,它并不是比面向对象编程更先进的一种独立的编程思想,而是附属于面向对象思想体系,属于其一部分。或者说,它是面向对象编程体系中的思想精髓之一。
接口的本质
- 接口是一组规则的集合,它规定了实现本接口的类或接口必须拥有的一组规则。体现了自然界“如果你是……则必须能……”的理念。
- 接口是在一定粒度视图上同类事物的抽象表示。比如我们说我和猴子不属于一类,分别继承Person接口和Monkey接口,但对动物学家,我们却继承同一接口:Animal接口。
面向接口编程的本质
在系统分析和架构中,分清层次和依赖关系,每个层次不是直接向其上层提供服务(即不是直接实例化在上层中),而是通过定义一组接口,仅向上层暴露其接口功能,上层对于下层仅仅是接口依赖,而不依赖具体类。
好处:
- 对系统灵活性大有好处。当下层需要改变时,只要接口及接口功能不变,则上层不用做任何修改。甚至可以在不改动上层代码时将下层整个替换掉。
- 不同部件或层次的开发人员可以并行开工,就像造硬盘的不用等造CPU的,也不用等造显示器的,只要接口一致,设计合理,完全可以并行进行开发,从而提高效率。
抽象类与接口
如果单从具体代码来看,对这两个概念很容易模糊,甚至觉得接口就是多余的,因为单从具体功能来看,除多重继承外(C#,Java中),抽象类似乎完全能取代接口。
区别
- 使用动机。使用抽象类是为了代码的复用(可以理解为代码和表示的共享机制),而使用接口的动机是为了实现多态性。
- 抽象类和它的子类之间应该是一般和具体的关系,而接口仅仅是它的子类应该实现的一组规则。