面向对象技术
面向对象技术的引入带来了两个优点,常识思维向计算机的引入和有效的代码的重用。对象的引入让程序开发使用了常识思维,你可以将程序开发过程中使用的对象和你在日常生活中看到的对象联系起来,例如如果你想创建一个动物园的管理应用程序,动物园里有各种各样的动物,每种动物都有自己的不同的特点,例如狗和猫就有不同,但是他们也有相同点,例如每个动物都有名字,ID,都会叫等,你完全可以使用你的常识来使用Java建模动物园里的所有动物,使用常识来开发软件。同时面向对象的概念引入的另一个优点是有效的代码重用,通过继承,子类可以使用父类的某些方法,属性而不需要特别的声明。
基本概念
人和小张的区别
人是一个抽象的概念,小张是一个具体的人。小张拥有人的一切属性。但是“小张 != 人”。人和小张的区别就像模子和用模子铸造出来的东西一样。类,面向对象中的对象,都是人的概念,而不是小张的概念。
什么是对象树
考虑整个生物界,生物分为两类,动物和植物,在动物这个子种类中有可以分为哺乳类和非哺乳类动物,哺乳动物子类中又有灵掌类动物和其它动物,灵掌类动物子类中又有人和猴子等(这些概念都是人的概念而不是小张的概念)。这构成了如下的继承树:
什么是对象
这里我们讲的对象是指人的概念,对象是一种类型事物的抽象表示,在面向对象的世界里对象分为三种类型:数据对象,服务对象和两者的结合。
数据对象就是用来表示数据的对象,这类对象的典型特点是只有属性和属性的获取,设置方法。例如struts应用中的FormBean类。
服务对象专门用来向其它类提供服务,这类对象的主要特点是属性很少,或者没有,但是方确很多。例如struts应用中的Action类。
最常见的对象是两者的结合体,这类对象有一些内部属性,同时也有一些自己的方法,这类对象是最常见的对象类型。
访问控制
对象有属性,又有方法,那么就存在一个问题,究竟谁可以访问这些属性和方法?访问控制包括以下关键字:
序号 | 关键字 | 可访问范围 |
1 | Public | 本类内部,所有子类,同包内的所有其它类,不同包的其它类 |
2 | Protected | 本类内部,所有子类 |
3 | Private | 本类内部 |
4 | 默认 | 本类内部,同包内的所有类 |
继承机构与接口
继承
我们可以模仿上述的过程来建模我们的动物园应用,在阐述这些概念之前我们先谈一谈面向对象的基础。在我们开发程序的过程中我们经常需要对我们的业务建模,对于Java来说每一种类型的业务模型都可以用一个类来表示,比如我们要建模一个猴子对象,这个猴子有类型(猴子),会跑动等,那么我们可以这样建模一个猴子对象:
public class Monkey {
private String type = "Monkey";
/** Creates a new instance of Monkey */
public Monkey() {
}
public void run(){
System.out.println("Monkey run");
}
}
也可以建模一个人对象:
public class Man {
private String type = "Man";
/** Creates a new instance of Man */
public Man() {
}
public void run() {
System.out.println("Man run");
}
}
但是你现在会发现一些问题,第一,Man和Monkey都是生物,可是我们不能从上述代码中得到这样的信息。第二,Man和Monkey有重复的方法和属性,也许重复代码较小的时候这并不重要,但是你考虑整个动物园的所有动物的时候,这就不是一个小问题了。再回到我们一开始谈到的问题,面向对象的引入带给了我们两个优点,自然的面向对象的思维和代码重用,下面我们具体讲解Java在这方面的功能。
首先我们做一个Creature类来表示生物。
public class Creature {
protected String type;
/** Creates a new instance of Creature */
public Creature() {
}
}
然后修改刚才的Monkey和Man类的代码。
public class Monkey extends Creature {
/** Creates a new instance of Monkey */
public Monkey() {
}
public void run(){
System.out.println("Monkey run");
}
}
public class Man extends Creature {
/** Creates a new instance of Man */
public Man() {
}
public void run() {
System.out.println("Man run");
}
}
通过关键字extends我们可以从Creature类继承来Creature的属性和方法,也就是重用代码。同时我们可以使用如下的声明来创建Monkey或者Man对象。
Creature man = new Man();
Creature monkey = new Monkey();
这解决了上述的第一个问题,Creature和Monkey或者Man的关系。另一个问题是代码重用,现在你可以在Monkey或者Man的方法内部使用type属性,例如有如下代码:
public class Monkey extends Creature {
/** Creates a new instance of Monkey */
public Monkey() {
this.type = "Monkey";
}
public void run(){
System.out.println("Monkey run");
}
}
但是在Monkey内部并没有定义type属性。
其实extends关键字告诉编译器两点内容,第一,子类型是父类型的一个具体类型,即可以用父类型引用子类型的实例。第二,相当于将父类型,父类型的父类型依次递归下去的所有类型的代码全部拷贝到子类型的内部。关于第二点需要详细说明,访问控制并不影响父子类型间的代码拷贝。
对于方法的继承和属性的继承是一样的。
抽象类
抽象类是一类特殊的父类,特殊在他只是所有子类共同点的抽象,没有具体的业务实例,不需要生成具体的实例对象。抽象类通过添加关键字abstract来做到,例如如下代码:
public abstract class Animal {
protected int legs;
/** Creates a new instance of Animal */
public Animal() {
}
}
我们并不需要家里Animal对象,我们只可能建立Monkey或者Man对象,所以Animal的粗在只是为了给所有的动物的公共特性和方法提供一个存放的类,这就是抽象类产生的根源。上述关键字abstract说明Animal类是一个抽象类,并且不能使用Anima来创建对象,如下代码会产生变异错误:
Animal animal = new Animal();
抽象方法
抽象方法和抽象类具有相同的产生根源,有时又我们知道某一类的所有都有相同的方法,可是我们并不知道每一个具体子类到底如何具体操作,这个时候我们就可以使用抽象方法,抽象方法是用abstract来做标示,并且没有方法体。例如:
public abstract class Animal {
protected int legs;
/** Creates a new instance of Animal */
public Animal() {
}
public abstract void run();
}
如果一个类的定义中存在抽象方法,那么这个类一定是抽象类。反之,一个抽象类可能并不存在抽象方法。
从抽象类继承
从抽象类继承而来的子类,如果父抽象类有抽象方法,那么该子类必须实现父抽象类的所有抽象方法,这样子类才能成为一个非抽象类。如果子类没有完全实现父类的抽象方法,或者子类添加了抽象方法,那么子类必须也是抽象类。
接口
接口是一种契约,定义了一个服务单元所提供的所有服务的集合。接口声明和类的声明一样。例如:
public interface AnimalService {
/** */
public boolean isHealthy(Animal a);
}
如果某个类要显式的提供AnimalService所约束的所有方法,那么他可以这样做,第一,在类定义的时候声明对该接口的实现。第二,提供接口中声明的所有方法的实现。
public class AnimalServiceImpl implements AnimalService {
/** Creates a new instance of AnimalServiceImpl */
public AnimalServiceImpl() {
}
public boolean isHealthy(Animal a)
{
return true;
}
}
和类继承结构一样,你可以使用AnimalService来引用AnimalServiceImple的实例。例如:
AnimalService as = new AnimalServiceImpl();
继承与接口
许多人喜欢将继承和接口放在一起讨论,虽然我也这样做了,但是我主要是强调他们的区别。继承和接口的最大区别在于其有不同的逻辑意义。例如:
a 继承自 b :a是b类型的一个子类型,完全可以说a就是一个b,例如Monkey就是Animal。
a 实现 b :a 可以提供 b 所声明的服务。在契约b中定义的内容a都已经实现了。
多态与基于接口的编程
多态
有了继承就产生了一个问题,针对下述类定义:
public abstract class Creature {
protected String type;
/** Creates a new instance of Creature */
public Creature() {
}
}
public abstract class Animal extends Creature {
protected int legs;
/** Creates a new instance of Animal */
public Animal() {
}
public abstract void run();
}
public class Man extends Animal {
/** Creates a new instance of Man */
public Man() {
super.type = "Man";
super.legs = 2;
}
public void run() {
System.out.println("Man run");
}
}
public class Monkey extends Animal {
/** Creates a new instance of Monkey */
public Monkey() {
super.type = "Monkey";
super.legs = 4;
}
public void run(){
System.out.println("Monkey run");
}
}
我们实用下面的代码:
Animal man = new Man();
Animal monkey = new Monkey();
man.run();
monkey.run();
有如下的输出:
Man run
Monkey run
通过上述测试代码你可以看到虽然man和monkey声明的类型都是Animal但是在真正执行他们的方法时,man和monkey确能够找到自己真正的方法执行,这种具有相同类型的对象调用相同方法却有不同结果的现象叫多态。
基于接口的编成
接口和实现类有的关心与父子类的关系有些相像。针对如下定义:
public interface AnimalService {
public boolean isHealthy(Animal a);
}
public class AnimalServiceImpl1 implements AnimalService {
/** Creates a new instance of AnimalServiceImpl */
public AnimalServiceImpl1() {
}
public boolean isHealthy(Animal a)
{
return true;
}
}
public class AnimalServiceImpl2 implements AnimalService {
/** Creates a new instance of AnimalServiceImpl2 */
public AnimalServiceImpl2() {
}
public boolean isHealthy(Animal a){
return false;
}
}
虽然如下的代码与多态相似:
Animal a = new Monkey();
AnimalService s1 = new AnimalServiceImpl1();
AnimalService s2 = new AnimalServiceImpl2();
if(s1.isHealthy(a)) {
System.out.println("monkey is healthy in s1");
}
if(s2.isHealthy(a)) {
System.out.println("monkey is healthy in s2");
}
有如下输出:
monkey is healthy in s1
但是他们有着本质的不同,因为接口从来不会声明方法体。所以即使是相同的接口定义的不同实例对象调用相同签名的方法时,实际调用的方法肯定是不同的。