2006/10/29 | [java]java类、抽象类、接口、继承和对象解析
类别(编程开发) | 评论(1) | 阅读(207) | 发表于 02:13

这不是什么教材,笔者有时会在论坛上瞧瞧,看到不少初学者问到很多问题,这些问题是java程序员应该

懂得的,而一般书上不会讲到或者一笔带过的知识。因此斗胆涂鸦一篇文章,把想说的在这里一口气说完

。这也是本人第一次写技术性的文章,文笔不畅之外,还请各位见谅。


首先讲清楚类和对象的区别。


类是广泛的概念,表示一个有共同性质的群体,而对象指的是具体的一个实实在在的东西。例如,“人”

是一个类,它可以表示地球上所有的人;而“张三”、“李四”、“爱因斯坦”等则是一个个的对象,或

者说它们是“人”这个类的一个个实例。在 Java 中,我们可以定义类,然后创建类的对象。


例如:
// 声明一个类“Human”
class Human{
private String name;
public String getName(){
return name;
}
public void setName(String value){
this.name = value;
}
//......
}


创建一个类:
Human human = new Human();

 

 

其次,很多人对对象和对象的引用认识模糊
引用是程序操作对象的句柄,相当于C和C++中的指针。
前面说了,对象是一个实实在在的东西,比如前面的代码:
Human human = new Human();
程序执行到这里之后,java虚拟机将会在内存中创建一个 Human 对象,并将这个对象的引用赋给 human

变量。这里有两步,首先是创建 Human 对象,然后把创建的对象的引用赋给 human 变量。
如果声明了一个对象的引用,但没有将对象赋值给它,则这个引用指向了空的对象,或者说引用了不存在

的对象。这时如果想通过这个引用访问对象,则会抛出空指针异常,例如:
Human human;
//......
human.setName("张三");

 

 

下面重点谈一谈类、抽象类、接口和继承之间的关系
不少细心的初学者在论坛上问类似这样的问题:
1、接口不实现方法,但我却在程序中可以调用接口的方法,这是为什么?比如 java.sql 包中的

Connection、Statement、ResultSet 等都是接口,怎么可以调用 它们的方法呢?
2、抽象类不能实例化,但是jdk中却有很多抽象类的对象,这是为什么?比如 System.in 是一个

InputStream 类型对象,但 InputStream 是抽象类,怎么可以得到它的对象呢?


不管怎么样,大家应该明白一点:不管是抽象类中的抽象方法,还是接口中定义的方法,都是需要被调用

的,否则这些方法定义出来就没有意义了。


可能有很多书上没有提到,或者提到了而读者没有注意到这一点:
一个子类如果继承了它的基类,则表示这个类也是其基类的一种类型,这个子类的一个对象是子类类型,

并且同时也是其基类的一个对象,它也具有基其类的类型;一个类如果实现了一个接口,则表示这个类的

一个对象也是这个接口的一个对象。


可能这样说不太好懂,又是子类、基类、类型、接口什么的,容易搞混。其实举个现实的例子你就会觉得

其实很简单:
如果“人”是一个基类,则“男人”是“人”的一个子类。如果“张三”是一个“男人”,也就是说“张

三”是“男人”的一个对象,那么显然“张三”也是“人”这个基类的一个对象。


明白了这一点,就容易理解为什么我们可以得到抽象类的对象了:原来我们得到的抽象类的对象其实是它

的已经实现了抽象方法的子类或子孙类的一个对象,但我们拿它当它的抽象类的基类来用。比如“人”这

个类,每个人都会“悲伤”,男人悲伤的时候抽烟、喝酒,女人悲伤的时候哭泣、流泪。由于不同的子类

在“悲伤”时所进行的动作不一样,因此这个动作(方法)在基类中不好实现,但基类中又需要有这个方法

,因此,“人”这个类就可以定义一个抽象方法“悲伤”,由其子类“男人”和“女人”来实现“悲伤”

这个方法。但是调用者只把男人和女人的对象当作其基类“人”的一个对象,调用它的“悲伤”方法。
读者可以去体验一下 jdk 的抽象类 java.lang.Process :
Runtime runtime = Rumtime.getRuntime();
Process process = rumtime.exec("notepad.exe");
Class cls = process.getClass();
System.out.println(cls.getName());
这时会打印出 process 类的名字,如果在 Windows 下它会是一个类似于 *Win32* 的名字,它是

Process 的一个子类。因为 process 类用于管理打开的进程,而在不同的操作系统上都有不同的实现,

因此它把方法定义为 Process 的抽象方法,而具体的操作只能由对应在不同操作系统下的子实现。


下面来谈接口,我们知道接口只定义了一些方法,而没有实现这些方法。而其实,接口是一个规范,它规

定了实现这个接口所要做的事情,或者说规定了实现接口的类必须具备的能力(也就是方法)。
那么我们可以这样对比:
某种类型的驾驶执照,规定了拿到这个驾照的人必须能够“开小汽车”和“开公共汽车”。那么我们认为

这个驾照是一个接口,它规定了实现它的类所必须有的能力。
我们可以定义一个类 Driver,继承自 Human,然后实现“驾照持有者”这个接口:
public interface DriverHolder{
public void driverCar();
public void driverBus();
}
public class Driver extends Human implements DriverHolder{
public void driverCar(){
// ......
}
public void driverBus(){
// ......
}
}


这样一来,一个“Driver”对象,它同时也是一个 DrivreHolder 对象。即一个司机(Driver)同时是一个

驾照执持有者对象。在程序中我们可以这样:
DriverHolder driverholder = new Driver();
driverholder.driverCar();


这样我们就解释了为什么“接口没有实现方法,却可以得到接口类的对象”的问题。


但是这样一来,肯定有人会问:为什么要定义一个接口呢,为什么不直接把这个方法定义到 Driver 类中

去,然后 Driver driver = new Driver(); 一样可以调用它的 driverCar(); 和 driverBus() 方法,这

样做岂不是方便得多?


这是因为java是单继承的,它只能继承于一个类,这样它的类型就只限于其基类或者基类的基类。但是

java可以实现多个接口,这样它就可以有很多个接口的类型。就象一个人,它继承自“脊椎动物”这个类

,而“脊椎动物”又继承自“动物”这个类,因此“张三”是个人,他是一个“脊椎动物”,当然他也是

一个“动物”。但他可以继承很多个接口,比如拿驾驶执照之后,他就是“驾照持有者”类型,他也可以

拿英语六级证书,这样他就是一个六级证书持有者等等。


明白这一点之后,我们来看一看 java 的事件机制。
java.awt.Button 类有一个 addActionListener(ActionListener l);方法。这个方法传入的是一个接口

类型:ActionListerner,在实际中,我们需要实现 ActionListener 接口,并且把实现这个接口的类的

对象引用作为参数传入。这样,Button对象就得到了一个 ActionListener 对象,它知道这个

ActionListener 对象有一个 actionPerformed 方法,或者说它有处理 Action 事件的能力,当 Action

事件发生时,它就可以调用这个对象的 actionPerformed 方法。


比如一般我们会这样做:
public class TestButton extends Frame implements ActionListener{
private Button btn1 = new Button();
//......

public TestButton(){
btn.addActionListener(this);
this.add(btn);
}

public void actionPerformed(ActionEvent e){

}
}


现在我们假设 ActionListener 不是接口,而是一个类。那么我们只能继承 ActionListener 类,并且重

写 actionPerformed 方法。但是java是单继承的,如果类继承了 ActionListener 类,那么它就不能继

承其它的类(Frame 类)了,而不从 Frame 类继承的话,又怎么创建窗体,怎么把 Button 放到窗体中去

呢?


其实接口不完全是为了解决 java 的单继承问题,它在某种程度上可以达到调用和实现细节的分离。
比如说,中国的民用电规范是一个接口:平均电压220V、50Hz、Sin 交流电,水力发电厂、火力发电厂、

核电厂,还有小型的柴油发电机如果按这个规范发电,则表示它们实现了这个民用电源的接口;冰箱、电

视、洗衣机等家用电器使用这些电源,表示它们在调用这个接口。在这里,家用电器不管电从哪里来,只

要它符合民用电源的规范就好,电源也不管它发的电用于什么工作,只管提供电源。


再回过头来看看 Button 的事件机制。要知道,Button 要保证所有的 action 事件发生时,程序员都可

以在他的代码中处理它,图书馆管理系统、收银系统、进销存等等等等等等。而接口就可以做到这一点:

找一个类实现 ActionListener 接口,并且让 Button 得到这个类的对象的引用( 调用

addActionListener 方法),从而当Action事件发生时,button 创建一个包含了事件信息的对象

(ActionEvent),然后调用这个接口对象的方法,到底怎么处理这次事件,这就是实现接口的类的事情了

。在这里,Button 丝毫不了解 actionPerformed 方法中到底干了什么事情,也不应该知道。Button 与

具体的业务逻辑完全分离开了,它可以应用到所有的场合。

0

评论Comments