Android移动应用开发实用教程
上QQ阅读APP看书,第一时间看更新

3.3 Java面向对象基础

Java是一种面向对象的语言(Object Oriented Programming Language)。Java语言具有三大特征:封装、继承和多态性。

封装是把过程和数据包围起来,对数据的访问只能通过已定义的界面。面向对象计算始于这个基本概念,即现实世界可以被描绘成一系列完全自治、封装的对象,这些对象通过一个受保护的接口访问其他对象。

继承是一种联结类的层次模型,并且允许和鼓励类的重用,它提供了一种明确表述共性的方法;对象的一个新类可以从现有的类中派生,这个过程称为类继承,新类继承了原始类的特性。派生类可以从它的基类那里继承方法和实例变量,并且类可以修改或增加新的方法,使之更适合特殊的需要。

多态性是指允许不同类的对象对同一消息做出响应。多态性语言具有灵活、抽象、行为共享、代码共享的优势,很好地解决了应用程序函数同名问题。

本节将介绍类、对象和接口等基本概念,围绕Java语言三大特征介绍Java的面向对象基础内容,为后面学习Android开发奠定基础。

3.3.1 类与对象

在面向对象技术中,将客观世界的一个事物作为一个对象看待,每个事物都有自己的行为和属性。从程序设计的角度来看,事物的属性可以用变量描述,行为可以用方法描述。类是定义属性和行为的模板,对象是类的实例,对象与类的关系就像变量和数据类型的关系一样。

1.类的声明

类(class)是既包括数据又包括作用于数据的一组操作的封装体。类的数据称为成员变量,类对数据的操作称为成员方法。成员变量反映类的状态和特征,成员方法反映类的行为和能力。类的成员变量和方法统称为类的成员。一个Java类由类的声明和类体两部分组成。

类的声明部分格式如下。

978-7-111-51177-9-Chapter03-27.jpg

●“[]”表示可选项,“<>”表示必选项,“{}”中为类体,类体也可是空的,即声明一个空类。

●一个类的声明中最少应该有calss+类名+类体。

●Classname必须为合法的Java标识符,不可为关键字。

●extends后面跟的Classname是继承的父类类名,implements后面跟的是类实现的接口。

2.类的主体

类的主体是指类声明后面的大括号里面的内容。它包括类的成员变量、成员方法等。

978-7-111-51177-9-Chapter03-28.jpg

●public、protected和private分别是修饰符。

●Classname必须为合法的Java标识符,不可为关键字。

●extends后面跟的Classname是继承的父类类名;implements后面跟的是类实现的接口。

下面用一段代码来讲解一个完整的类的声明和类的主体。

978-7-111-51177-9-Chapter03-29.jpg

●第01行代表类的声明。

●第0306行代表类的成员变量。

●第0712行是类的构造方法,第1315行是成员方法。

●第1620行是主方法,是程序的入口。

3.3.2 封装和继承

封装和继承都是面向对象的特征。继承是实现代码复用的重要手段,Java的继承具有单继承的特点,即只能继承自一个父类。每个子类只有一个直接父类,但是其父类又可以继承于另一个类,从而实现了子类可以间接继承多个父类,但其本质上划分仍然是一个父类和子类的关系,子类可以获得父类的全部属性和方法,并且可以扩展父类。封装性就是把类(对象)的属性和行为结合成一个独立的相同单位,并尽可能地隐蔽类(对象)的内部细节,对外形成一个边界,只保留有限的对外接口使之与外部发生联系。封装的特性使得类(对象)以外的部分不能随意存取类(对象)的内部数据(属性),保证了程序和数据不受外部干扰且不被误用。本节将详细介绍这两个重要概念。

1.封装

封装的实质是将数据进行隐藏,是指将对象的数据和操作数据方法相结合,通过方法将对象的数据和实现细节保护起来,只留下对外的接口,以便外界进行访问。系统的其他部分只有通过包裹在数据外面的被授权的操作(即方法)来访问对象。因此,封装实际上实现了对数据的隐藏。

在类的定义中设置访问对象属性和方法的权限,以限制该类对象属性和方法被访问的范围,该访问范围是由访问控制权限决定的,Java支持下面4种访问权限。

●public:公共访问权限,在任何场合都可以访问。

●protected:保护访问权限,在同包、同类或不同包的相同父类的子类之间访问。

●default或不使用任何权限修饰符:默认访问权限,在同包或同类之间访问。

●private:私有访问权限,只在同类下可以访问。

访问控制的对象有类、接口、类成员和方法,其中类和接口只能使用public和默认权限,类成员的属性和方法可以使用上面4种修饰符。

下面将分析一个对类成员封装的实例,如【例3-11】所示。

【例3-11】对类成员封装的主要代码如下。

978-7-111-51177-9-Chapter03-30.jpg

运行结果如图3-10所示。

978-7-111-51177-9-Chapter03-31.jpg

图3-10 运行结果

【代码说明】

●第0830行代码定义了一个Student的类,私有成员变量,公有方法,此类是一个JavaBean。

●外面的类将无法直接访问Student类的成员变量,如果要访问,必须通过get和set方法来访问。如果第4、5行使用st.name和st.age(直接访问成员变量),将出现编译错误。

2.继承

Java继承是面向对象的最显著的一个特征。继承是从已有的类中派生出新的类,新的类能吸收已有类的数据属性和行为,并能扩展新的能力。Java继承是使用已存在的类作为基础建立新类的技术,新类的定义可以增加新的数据或新的功能,也可以用父类的功能,但不能选择性地继承父类。这种技术使得复用以前的代码非常容易,能够大大缩短开发周期,降低开发费用。其中父类又称为超类或基类,子类又称为派生类。父类是子类的一般化,子类是父类的特殊化(具体化)。一旦形成继承关系,子类将自动拥有父类所有的属性和方法。

(1)类的继承

类的继承使用extends关键字,具体语法格式如下。

978-7-111-51177-9-Chapter03-32.jpg

●修饰符:指访问权限,如public、abstract等。

●SubClassName:指子类的名称,为Java的合法标识符。

●SuperClassName:指父类的名称,是子类要继承的父类。

●extends:是继承的关键字。

Java的类继承只能实现单继承,即一个子类只能继承一个父类,而C++可以实现多继承,即一个子类可以继承多个父类。Java可以利用接口实现多继承,即一个类实现多个接口。

子类继承父类,将自动继承父类的所有属性、成员方法和构造方法,如【例3-12】所示。

【例3-12】子类继承父类的主要代码如下。

978-7-111-51177-9-Chapter03-33.jpg

978-7-111-51177-9-Chapter03-34.jpg

运行结果如图3-11所示。

978-7-111-51177-9-Chapter03-35.jpg

图3-11 运行结果

【代码说明】

●第1224行定义一个父类,含有成员变量、无参构造方法和成员方法。

●第07行代码实例化子类对象,将会先自动调用第1719行父类的无参构造方法,然后再执行自己的构造方法。

●第09、10行分别是子类调用父类的属性和方法。

(2)super关键词

super关键词一般用在子类中,用来表示父类的属性、成员方法或构造方法。

当super表示父类属性(成员变量)时,格式如下。

978-7-111-51177-9-Chapter03-36.jpg

当super表示父类的构造方法时,格式如下。

978-7-111-51177-9-Chapter03-37.jpg

当super表示父类的成员方法时,格式如下。

978-7-111-51177-9-Chapter03-38.jpg

super和this的区别:super表示子类引用父类的属性或构造方法,this表示引用当前类对象的属性或方法。

【例3-13】分别使用super调用父类的构造方法、成员方法和成员变量。

978-7-111-51177-9-Chapter03-39.jpg

运行结果如图3-12所示。

978-7-111-51177-9-Chapter03-40.jpg

图3-12 运行结果

【代码说明】

●第16行代码主函数实例化子类对象,将会先自动调用第2628行父类的无参构造方法,且super调用父类的构造方法一定要放在子类构造方法的第一行。

●第17行代码调用父类有参构造方法(第07行),输出语句第32行,然后输出第08行,最后调用第34行父类的fly()方法。

●第18行代码执行将调用testConstant()方法中的super.location,即使用super调用父类的变量。

(3)方法的重写

在类的继承中,子类要扩展和改造父类,就要改造父类的方法,Java中将子类改造父类的方法称为方法的重写(Overriding)。方法的重写具有以下几个特征。

●方法重写是子类和父类方法之间的联系,两者一定是继承关系。

●发生方法重写的两个方法返回值类型、方法名及参数列表必须完全一致(唯一不同的是方法体)。

●子类抛出的异常不能超过父类相应方法抛出的异常(子类异常不能大于父类异常)。

●子类方法的访问级别不能低于父类相应方法的访问级别(子类访问级别不能低于父类访问级别)。

【例3-14】方法重写的主要代码如下。

978-7-111-51177-9-Chapter03-41.jpg

978-7-111-51177-9-Chapter03-42.jpg

运行结果如图3-13所示。

978-7-111-51177-9-Chapter03-43.jpg

图3-13 运行结果

【代码说明】

●第2028行代码Jack子类继承Base父类,重写showName()方法,其方法名、返回值类型和参数完全一致,修饰符不同,父类使用protected,子类可以用public或者protected,也就是说子类修饰符不能比父类小。

●第2937行代码Mary子类继承Base父类,重写showName()方法。

3.3.3 多态性

多态是指程序中定义的引用变量所指向的具体类型和通过该引用变量发出的方法调用在编程时并不确定,而是在程序运行期间才能确定,即一个引用变量到底会指向哪个类的实例对象,该引用变量发出的方法调用到底是哪个类中实现的方法,必须在程序运行期间才能决定。因为在程序运行时才能确定具体的类,这样,不用修改源程序代码,就可以让引用变量绑定到各种不同的类实现上,从而导致该引用调用的具体方法随之改变,即不修改程序代码就可以改变程序运行时所绑定的具体代码,让程序可以选择多个运行状态,这就是多态性。多态可分为运行时的多态(动态多态)和静态多态(编译时多态)。

1.运行时的多态

多态性的表现为:父类引用指向子类对象。

978-7-111-51177-9-Chapter03-44.jpg

其中Father是父类,Son是Father的子类,当使用多态性调用方法时,首先检查父类是否有该方法。如果没有该方法,则编译错误;如果有,再调用子类的该同名方法。如果用父类引用调用方法时,在父类中找不到该方法,这时需要进行向下的类型转换,将父类引用转换为子类引用。当父类对象需要调用子类方法时,需要将父类对象强制转换成子类对象,这种现象称为“向下造型”。如果不进行转换,可能会抛出异常,因此为安全起见,通常采用Instanceof进行类型测试,如【例3-15】所示。

【例3-15】用Instanceof进行类型测试的主要代码如下。

978-7-111-51177-9-Chapter03-45.jpg

978-7-111-51177-9-Chapter03-46.jpg

运行结果如图3-14所示。

978-7-111-51177-9-Chapter03-47.jpg

图3-14 运行结果

【代码说明】

●第2037行代码分别定义了3个子类继承Animal父类,重写eat()方法。

●第0409行代码分别用父类引用指向子类对象,调用子类的重写方法,new的是哪个子类对象,调用的即为哪个子类的重写方法。

●第4051行代码定义了一个用instanceof测试对象类型的方法,第1013行进行调用该测试方法,第12行代码已经明确传入对象为Sheep类对象,不需要强制转换,第13行代码中没有明确Animal是哪个子类对象,必须强制转换成Animal的某个子类对象。

2.静态多态

静态多态是指在编译阶段根据实参不同,静态判断具体调用哪个方法。在Java中,在一个类中定义多个同名方法,但参数个数或类型不同,这种现象称为方法的重载。因此方法重载就是一种静态多态。方法的重载具有以下几个特点。

●方法名相同。

●方法参数类型、参数个数和顺序至少有一项不相同。

●方法的返回类型可以不相同。

●方法的修饰符可以不相同。

重载一般指的是方法重载,方法重载既可以是构造方法重载,也可以是普通成员方法的重载。

【例3-16】为一个典型的Java方法重载示例。

978-7-111-51177-9-Chapter03-48.jpg

运行结果如图3-15所示。

978-7-111-51177-9-Chapter03-49.jpg

图3-15 运行结果

【代码说明】

●第1118行代码分别定义了两个重载的构造方法Flower(),一个方法有参数,另一个没有参数,这两个方法构成了重载。

●第03、06行代码分别调用了有参构造方法和无参构造方法。

●第1925行代码定义了两个重载的成员方法info(),一个有参数,一个无参数,构成了重载。这两个方法修饰符不同,一个为public,另一个为默认,返回值类型也不同,可见方法重载与返回值类型和修饰符无关,只与参数有关。

●第04、05行代码分别调用了无参数和有参数的成员方法。

虽然重载和重写只有一字之差,但两者有着显著的区别,它们之间的区别如表3-1所示。

表3-1 方法重载和方法重写的区别

978-7-111-51177-9-Chapter03-50.jpg

3.3.4 接口和抽象类

Java语言中,抽象类和接口是抽象定义的两种机制,正是由于这两种机制的存在,才赋予Java语言强大的面向对象能力。这两者在定义抽象事物方面有很多相似点,有时甚至可以相互替换,但它们两者又有着很大的区别。本节将重点围绕Java的这两个重要概念进行详细分析和说明。

1.抽象类

在面向对象的概念中,大家知道所有的对象都是通过类来描绘的,但是反过来却不是这样,即并不是所有的类都是用来描绘对象的。如果一个类中没有包含足够的信息来描绘一个具体的对象,这样的类就是抽象类。抽象类往往用来表征人们在对问题领域进行分析和设计中得出的抽象概念,是对一系列看上去不同但本质上相同的具体概念的抽象。

(1)抽象类的声明

Java中使用abstract修饰符来表示类为抽象类,抽象类的语法格式如下。

978-7-111-51177-9-Chapter03-51.jpg

●抽象类的修饰符只能是public或默认修饰符,如果为public修饰符,则文件名必须与抽象类名相同。

●abstract为抽象类的标识符。

●ClassName为抽象类的类名。

●抽象类类体也可以为空。

【例3-17】本例的主要代码如下。

978-7-111-51177-9-Chapter03-52.jpg

978-7-111-51177-9-Chapter03-53.jpg

运行结果如图3-16所示。

978-7-111-51177-9-Chapter03-54.jpg

图3-16 运行结果

【代码说明】

●抽象类不能直接实例化对象的类,即抽象类不能使用new运算符去创建对象。

●抽象类中既可以有抽象方法,也可以没有抽象方法,如第09行代码定义了一个抽象类,这个抽象类里面既有非抽象方法(如第1416行代码),又有抽象方法(如第17行代码)。

●第0406行代码由于定义的是Dog类的对象(子类对象),因此调用的move()方法为子类Dog的重写方法。

(2)抽象方法

抽象方法声明的语法格式如下。

978-7-111-51177-9-Chapter03-55.jpg

●抽象方法修饰符一般为public或者默认的,不能为私有的或者静态的。

●抽象方法返回值为空void。

●抽象方法只有声明部分,而没有实现部分。

【例3-18】抽象方法示例的主要代码如下。

978-7-111-51177-9-Chapter03-56.jpg

运行结果如图3-17所示。

978-7-111-51177-9-Chapter03-57.jpg

图3-17 运行结果

【代码说明】

●抽象类中不一定包含抽象方法,但是包含抽象方法的类必须说明为抽象类。

●抽象类一般包括一个或几个抽象方法。

●抽象方法需要用abstract修饰符进行修饰,抽象方法只有方法的声明部分,没有具体的方法实现部分。如第1214行代码,抽象类Car有一个startUp()抽象方法,不含任何方法体。

●抽象类的子类必须重写父类的抽象方法,才能实例化子类,否则子类也是一个抽象类,如第15行代码抽象类Audi继承抽象类Car,那么Audi类中就没有实现Car类的抽象方法,而Audi_A6和Audi_A8继承Audi类,就重写了抽象类的抽象方法startUp()。

●抽象方法不能用final和static修饰。

2.接口

在Java中,要像C++那样实现类的多重继承,必须实现接口。接口是一种特殊的抽象类,接口中的方法全部是抽象方法(但其前的abstract可以省略),所以抽象类中的抽象方法不能用的访问修饰符在这里也不能用。

(1)接口的声明

接口的声明语法格式如下。

978-7-111-51177-9-Chapter03-58.jpg

●修饰符:只能为public和默认修饰符。

●abstract关键字是可选项,这里可以省略。

●InterfaceName为接口名,命名要符合标识符规则。

●extends super_interface为继承的父接口。接口和类为同一层次的,可以相互继承,继承多个父接口时,父接口之间使用逗号隔开。定义一个接口的代码如下。

978-7-111-51177-9-Chapter03-59.jpg

●接口中定义的变量默认都为public、static或final类型,因此第03行代码合法。

●接口中定义的方法默认都是public或abstract类型,因此第04行代码合法,第05行代码显示声明接口下的方法为public和abstract类型也是合法的。

●接口不能包含构造方法,因此第06行不合法,将出现编译错误。

(2)接口的实现

接口和接口之间可以继承,而类和接口之间是implements关系,即类实现接口。

【例3-19】一个类实现接口的示例。

978-7-111-51177-9-Chapter03-60.jpg

运行结果如图3-18所示。

978-7-111-51177-9-Chapter03-61.jpg

图3-18 运行结果

【代码说明】

●第1924行代码定义了两个接口,两个接口的方法默认为public和abstract类型。

●第2534行代码定义两个类实现这两个接口,同时定义一个类Broccoli实现两个接口,即一个类可以实现多个接口,同样一个接口也可以继承多个接口。

●第03、04行代码定义两个接口对象指向实现这个接口的类,第05行代码接口对象调用第12行的eat()方法,进行传参,将Chicken对象传入,再调用Chicken类下的howToEat()方法,返回"Chicken",最后打印出Chicken。

●第0607行代码调用对象和传参与前面第04、05行代码的调用过程一样,因此最后输出Duck。

(3)抽象类和接口的区别

abstract class和interface是Java语言中对于抽象类定义进行支持的两种机制,正是由于这两种机制的存在,才赋予了Java强大的面向对象能力。abstract class和interface之间在对于抽象类定义的支持方面具有很大的相似性,甚至可以相互替换,因此很多开发者在进行抽象类定义时,对于abstract class和interface的选择显得比较随意。

其实,两者之间还是有很大区别的,对于它们的选择甚至反映出对于问题领域本质的理解、对于设计意图的理解是否正确、合理。表3-2列出了两者的主要区别。

表3-2 抽象类和接口的区别

978-7-111-51177-9-Chapter03-62.jpg