
5.3 原理学习:Java的对象与类、接口实现与包机制
5.3.1 Java的对象与类
1. 对象与类的定义
(1)Java的对象。现实世界有很多对象,如猫、狗等,这些对象都有自己的状态和行为,如猫的状态有名字、品种和颜色等,猫的行为有叫、摇尾巴和跑等。
软件对象也有状态和行为。软件对象的状态就是属性,行为可以通过方法来实现。在软件开发中,方法可以改变对象内部的状态,对象之间的相互调用也是通过方法来完成的。
(2)Java的类。类可以看成创建对象的模板,一个类通常包含以下类型变量:
① 局部变量:在方法、构造函数或者语句块中定义的变量被称为局部变量,变量声明和初始化都是在方法中实现的,方法结束后,局部变量会被自动销毁。
② 成员变量:成员变量是定义在类中、方法外的变量。成员变量在创建对象时被实例化,成员变量可以被类中方法、构造函数和特定类的语句块访问。
③ 类变量:类变量在类中、方法外声明,必须声明为静态类型。
(3)定义构造函数。每个类都有构造函数。如果没有显式地为类定义构造函数,则Java编译器会为该类提供一个默认构造函数。在创建一个对象时,至少要调用一个构造函数。构造函数的名称必须与类名相同,一个类可以有多个构造函数。下面是一个构造函数的示例。

(4)创建对象。对象是根据类创建的,在Java中,可以使用关键字new来创建一个对象。创建对象需要以下三步:
① 声明:声明一个对象,包括对象名称和对象类型。
② 实例化:使用new创建一个对象。
③ 初始化:在使用new创建对象时,会调用构造函数来初始化对象。
下面是一个创建对象的示例。

(5)访问变量和方法。在Java中,可以通过已创建的对象来访问类中的变量和方法,如下所示。

(6)源文件的声明规则。如果要在一个源文件中定义多个类,并且还有import语句和package语句,则应遵守以下声明规则:
① 一个源文件中只能有一个public类。
② 一个源文件可以有多个非public类。
③ 源文件的名称应该和public类的名称保持一致。例如,若源文件中public类的名称是Dog,则源文件应该命名为Dog.java。
④ 如果一个类定义在某个包中,则package语句应该在源文件的首行。
⑤ 如果源文件包含import语句,则该语句应该放在package语句和类定义之间;如果没有package语句,则import语句应该放在源文件中最前面。
⑥ import语句和package语句在源文件中定义的所有类都有效。
(7)Java包。将功能相似或相关的类和接口放在同一个包中,可以方便类和接口的查找及使用,对类和接口进行分类。当开发Java程序时,可能会涉及很多类和接口,因此有必要对类和接口进行分类。
(8)import语句。import语句用来提供一个合理的目录,使得编译器可以找到某个类。例如,采用下面的语句可以载入Java安装目录“/java/io”下的所有类。

2. Java常用类介绍
(1)Java类库的结构。类库就是Java API,是系统提供的标准类的集合。Java类库中的类和接口大多封装在特定的包里,每个包具有自己的功能。表5.1列出了Java中的常用包,其中,包名后面带“.*”的表示其中包括一些相关的包,通过查阅Java技术文档可以了解更多的类库。
表5.1 Java中的常用包

除了java.lang,其他的包都需要用import语句导入之后才能使用。
(2)基本数据类型的类。java.lang不仅包含了Object类,java.lang.Object类是Java中整个类层次结构的根节点,还定义了基本数据类型的类,如Boolean、Character、Byte、Integer、Short、Long、Float和Double等,这些类支持基本数据类型的转换,如表5.2所示。
表5.2 基本数据类型的类

(3)String类和StringBuffer类。Java字符串是由String类和StringBuffer类来处理的。Java中的字符串属于String类,虽然可以用其他方法表示字符串,但使用String类作为字符串的标准格式,Java编译器可以把字符串转换成String对象。如果需要进行大量的字符串操作,就可以使用StringBuffer类或字符数组。
StringBuffer类与String类相似,它具有String类的很多功能。StringBuffer对象可以在缓冲区内被修改,如增加、替换字符或子字符串。在完成缓冲字符串数据操作后,可以通过StringBuffer.toString方法或String类的构造函数将字符串转换成标准字符串格式。
(4)System类。System类是一个特殊类,它是一个公共最终类,不能被继承,也不能被实例化,即不能创建System对象。System类功能强大,与Java运行时一起可以访问许多有用的系统功能。System类是保存静态方法和变量的集合,标准的输入、输出和Java运行时的错误输出都存储在变量in、out和err中。System类中所有的变量和方法都是静态的,使用时以System作为前缀,如“System.变量名”和“System.方法名”。
(5)Math类。Math类提供了用于基本数学运算的属性和方法。
(6)Vector类。当创建过大的数组时,会造成空间的浪费。Java中的java.util包提供了Vector类,通过该类可以根据需要创建动态数组。另外,Vector类还提供了一些有用的方法,如增加和删除元素的方法。
(7)Stack类。Stack类是Vector类的一个子类,用于实现后进先出的堆栈。Stack类定义了创建空堆栈的构造函数,不仅包括由Vector类定义的所有方法,还增加了几种它自己定义的方法。
(8)ArrayList类。ArrayList是一个容量能够动态变化的动态数组,它继承自AbstractList类,实现了List、RandomAccess、Cloneable、java.io.Serializable等接口。
ArrayList类适合随机访问元素,但在数组中插入和删除元素的效率较低。由于ArrayList类不是线程安全的,因此该类适合在单线程中使用,而在多线程中则使用Vector类或者CopyOnWriteArrayList类。
(9)HashMap类和HashSet类。HashMap类和HashSet类是Java集合框架(Collection Framework)中的两个重要成员,HashMap是Map接口的常用实现类,HashSet是Set接口的常用实现类。
3. 继承
继承允许创建不同等级的类,子类可以继承父类的特征和行为,从而使得子类的对象(实例)具有父类的实例域和方法。子类也可以从父类继承方法,使得子类与父类具有相同的行为。
Java可以通过关键字extends来声明一个类是从另外一个类继承而来的,格式如下:

(1)继承的特性。
① 子类拥有父类非private的属性和方法。
② 子类可以拥有自己的属性和方法,即子类可以对父类进行扩展。
③ 子类可以用自己的方式实现父类的方法。
④ Java的继承是单继承,但是可以多重继承。单继承是指一个子类只能继承一个父类;多重继承是指多层次的继承,如B类继承A类、C类继承B类,按照关系来说,B类是C类的父类,A类是B类的父类。
⑤ 继承可以提高类之间的耦合性,但耦合度高就会造成代码之间的联系变得紧密,使独立性变差。
(2)继承的关键字。Java可以通过关键字extends和implements来实现继承。在Java中,所有的类都继承自Object,如果一个类没有使用继承的两个关键字,则默认为继承自Object类。
① extends关键字。类的继承是单一继承,一个子类只能拥有一个父类,所以extends只能继承自一个类。
② implements关键字。使用implements关键字可以变相地使Java具有多继承的特性,适用于继承接口的情况,可以同时继承多个接口。
③ super与this关键字。通过super关键字可以访问父类成员,用来引用当前对象的父类;this关键字用于指向自己的引用。
④ final关键字。使用final关键字声明的类是不能被继承的,为最终类;使用final关键字修饰的类的方法不能被该类的子类覆写;定义为final的实例变量不能被修改;被声明为final类,其方法将会被自动声明为final,但实例变量并不是final。
(3)构造函数。子类不能继承父类的构造函数。如果父类的构造函数带有参数,则必须在子类的构造函数中显式地通过super关键字调用父类的构造函数并配以适当的参数列表;如果父类的构造函数没有参数,则在子类的构造函数中通过super关键字调用父类构造函数不是必需的,如果没有使用super关键字,系统会自动调用父类的无参构造函数。
4. 封装
封装是一种将抽象性函数接口的实现细节部分包装、隐藏起来的方法。适当的封装可以让代码变得更容易理解与维护。
(1)封装的优点。
① 良好的封装能够减少耦合。
② 可以对成员变量进行更精确的控制。
③ 可以隐藏实现细节。
(2)实现Java封装的步骤
① 通过修改属性的可见性来限制对属性的访问(一般限制为private)。例如:

② 对每个属性提供对外的公共方法,也就是创建一对赋/取值方法,用于对私有属性进行访问。例如:

5.3.2 Java的接口
1. 接口的定义
接口是一个抽象类型,是抽象方法的集合,接口通常用interface关键字来声明。一个类可以通过继承接口的方式来继承接口的抽象方法。
接口并不是类,编写接口的方式和类很相似,但是它们属于不同的概念。类描述的是对象的属性和方法,接口则包含类要实现的方法。除非实现接口的类是抽象类,否则该类要定义接口中的所有方法。接口无法被实例化,但是可以被实现。在类实现接口时,必须实现接口内所描述的所有方法,否则就必须声明为抽象类。另外,在Java中,接口类型可用来声明一个变量。
2. 接口的声明
interface关键字用来声明一个接口,格式如下:

接口具有以下特性:
(1)接口是隐式抽象的,当声明一个接口时,不必使用abstract关键字。
(2)接口中每一个方法也是隐式抽象的,在声明接口中的方法时同样也不必使用abstract关键字。
(3)接口中的方法都是公有的。
3. 接口的实现
当类实现接口时,类要实现接口中所有的方法,否则该类必须声明为抽象类。类使用implements关键字实现接口,在类声明中,implements关键字应放在class声明后面。实现一个接口的格式如下:

例如:

在覆写接口中声明的方法时,需要注意以下规则:
① 类在实现接口的方法时,不能抛出强制性异常,只能在接口中或者继承接口的抽象类中抛出该强制性异常。
② 类在覆写方法时要保持方法名的一致,并且应该保持相同或者相兼容的返回值类型。
③ 如果实现接口的类是抽象类,就没必要实现该接口的方法。
在实现接口时,也要注意以下规则:
① 一个类可以同时实现多个接口。
② 一个类只能继承一个类,但能实现多个接口。
③ 一个接口能继承另一个接口。
4. 接口的继承
一个接口能继承另一个接口,接口的继承使用extends关键字。下面的Animal接口被Bird和Fish接口继承:

Bird接口声明了1个方法,从Animal接口继承了2个方法,这样在实现Bird接口的类时需要实现3个方法。
5. 接口的多继承
类的多继承是不合法的,但接口允许多继承。在接口的多继承中,extends关键字只需要使用一次,在其后跟着继承接口即可。例如:

上述语句定义的子接口是合法的。与类不同的是,接口允许多继承。
6. 标记接口
最常用的继承接口是不包含任何方法的接口,不包含任何方法的接口称为标记接口。标记接口是一种不包含任何方法和属性的接口,仅仅表明它的类属于一个特定的类型。标记接口的作用是给某个对象打个“标”,使对象拥有某个或某些特权。例如,java.awt.event包中的MouseListener接口继承自java.util.EventListener接口,定义如下:

标记接口主要用于以下两种场合。
(1)建立一个公共的父接口:如EventListener接口,这是由几十个其他接口扩展的Java API,可以使用一个标记接口来建立一组接口的父接口。
(2)向一个类添加数据类型:实现标记接口的类不需要定义任何接口方法,但该类可通过多态性变成一个接口类型。