JAVA精灵 第六章 我们的族谱

2016-10-15  本文已影响0人  sandlk

中国是一个姓氏繁多的国家,而且每个姓氏都有很多有趣的故事,成为了中国文化的重要元素。除了我们常见的陈、李、黄、何、张等姓氏以外,还有很多有趣的姓氏。在十二生肖当中,用作姓氏的就有九个,包括马、牛、羊、狗、猪、鸡、虎、龙、蛇。前段时间,有一位姓“鸡”的网友在网上为儿子征名,让网友脑洞大开,纷纷献言献策,结果,没有一个是好听的——“鸡蛋”、“鸡毛”、“鸡公”,更有人说“鸡屁股”。唉,看来,这位爸爸也只能是“潮州人拉二胡”——自己顾自己了!中国有句俗语——“行不改名坐不改姓”,虽说有些姓氏可能比较奇怪,但中国人却极少有人会改自己的姓氏的,因为这是祖宗留下的东西,是自己的“根”。谈到姓氏,我们很自然会联想到“族谱”,这是一本手册,从炎黄始祖开始,按照世代、时间的顺序,将本宗族的所有人一一囊括其中,就如同一棵大树,不断开枝散叶,繁衍生息。在Java世界,我们也有属于自己的族谱,其中最为知名的一本族谱,就是Java API。

我们以平时用得非常多的一个类——ArrayList为例,来看一下这本族谱是怎么编排的,从中我们又能看出些什么东西?

Paste_Image.png

我们看到,ArrayList这个家伙的完整路径是“java.util.ArrayList”,这里的“java.util”是包名,我们可以将其看作是人类族谱当中的地区名称,就如同“广东省广州市”之类,然后他的父亲是“java.util.AbstractList”(称为ArrayList的基类或父类),他的爷爷是“java.util.AbstractCollection”,再往上一就是他的祖先“java.lang.Object”,从包名的差异我们可以判定,这个家伙的祖先可能位于黄河之滨而不是在广州。注意这个“java.lang.Object”是所有类的祖先,它的地位就相当于炎黄始祖,任何子类都会流淌着他的血脉。此外,我们看到ArrayList自己也有三个儿子,分别为“AttributeList”、“RoleList”和“RoleUnresolvedList”,如果我们点击“AttributeList”,也可以看到类似的一棵树。

这种树状的关系就是我们第二章所提到的“继承”关系,我们在类定义当中也清晰看到“extends”关键字。

Paste_Image.png

此外,我们在这里还看到这棵树下面有一行内容——“所有已实现的接口”。所谓的“接口”(interface),我们可以将其简单理解为“一组功能的集合”,不过,它只是一种定义(或规范),具体如何实现则是要由类自己来确定,所以我们这里写的是“实现”(implements)接口。其实,这个概念跟我们日常生活中的“接口”也是类似的。例如,我们的插线板(排插)上可能有两个孔的接口、也有三个孔的接口,但无论是什么牌子的排插,遵循同一个标准(如新国标)的排插,其同一类接口中各个孔的排列、间距、宽度等都是相同的,这样我们的插头才能够任意适配不同的插线板。而插线板内部的实现则可能是各不相同的,但这个并不是接口所要关心的问题。

我们来看一个简单的“接口”:

package test.code1;
import java.util.Date;
public interface Alarm{
    public void ring();
    public void setAlarmTime(Date time);
}

这里我们定义了一个“Alarm”闹铃的接口,可以看到,它跟“类”的定义非常类似,只是关键字换成了interface。不过,我们要留意的是,接口当中的方法是都以“;”结尾的,并没有方法体({ … })部分。我们可以将这种方法称为“抽象方法”,因为它只是一个抽象的概念,只是定义了一种规范、一种功能需求而没有实际的内容。

接下来,我们用上一章的Clock类来实现这个“Alarm”接口,以便让这个钟具备闹铃的功能。

package test.code1;
import java.util.Date;
public class Clock implements Alarm{
    
    public static void main(String[] args){
        System.out.println("Hello Kitty");
    } 
    
    private Date alarmTime;

    public void ring(){
        System.out.println("懒虫,快起床!");
    }

    public void setAlarmTime(Date time){
        this.alarmTime=time;
        System.out.println("闹铃时间:"+time);
    }
}

我们看到,实现接口其实就是对接口中定义的方法增加方法体(这个过程也可以称为“实现方法”),使它有血有肉,具有实际的功能,也就是将“抽象方法”进行实体化。由于实现接口只是要求我们将每个抽象方法都变成实体方法,至于这个实体方法如何去写,接口本身并不关心,例如,你可以只写一对大括号{ }然后里面什么都不写(就如同插线板上只有一个三孔接口,里面却是空的),这样也是可以的,不过,你必须保证你的类当中不再有未实现的抽象方法,除非你的类也定义成一个“抽象类”(abstract class)。

我们使用EditPlus先对Alarm.java进行编译(注意Alarm.java跟Clock.java都是放在JavaTest目录下),接着对Clock.java进行编译。不过,在编译的时候看到如下提示:

Paste_Image.png

这里我们在EditPlus中执行的Dos命令是这样子的:

javac -d D:\JavaTest D:\JavaTest\Clock.java

这里我们指定了输出目录(D:\JavaTest)以及要编译的文件(D:\JavaTest\Clock.java),对于内置的类,例如java.util.Date,javac命令是可以在程序安装目录当中找到对应的类的,但对于我们自己的类或接口(Alarm),我们需要明确告诉它该从哪里去查找,否则它会找不到。我们可以用选项“cp”(classpath,类路径)来指定查找类的位置。将上述命令修改以下:

javac -d D:\JavaTest -cp D:\JavaTest D:\JavaTest\Clock.java

Paste_Image.png

需要留意的是,我们这里指定的查找目录是D:\JavaTest而不是Alarm.class实际存放的目录D:\JavaTest\test\code1,这是因为,Clock.java以及Alarm.java都有下面这一行:

package test.code1;

这一行跟告知了这两个类的位置(相对于D:\JavaTest),所以javac能够自动去对应的目录中查找。

需要注意的是,我们在编译时必须先编译Alarm.java,再编译Clock.java。这就有一个问题,有时候我们可能连续写多个类和接口,但每次都要按顺序逐个编译,能否有更简便的方法?答案是有的,我们可以使用通配符(*)。再次改一下EditPlus,将原来的“文件路径”改成“文件目录”下的所有java文件:

Paste_Image.png

这样,我们每次执行javac的时候,它都会自动按照引用的先后顺序来完成批量编译。

另外,我们还要记住java当中的一条非常重要的规矩:一个类只能继承一个直接的父类,但可以实现多个接口。这其实也是很容易理解的,人只能有一个亲生父亲,但却可以练就一身本领(实现多个不同的接口)。不过,这个父亲并不包括java.lang.Object类,因为默认所有类都会“隐式”继承这个类,并不需要通过extends来继承。例如我们前面的ArrayList它继承自java.util.AbstractList,并实现了6个接口,分别为Serializable、Cloneable、Iterable<E>、Collection<E>、List<E>、RandomAccess。

[原创作品,未经授权请勿转载]

上一篇下一篇

猜你喜欢

热点阅读