面向对象的三大特征:
- 封装:通过Java的类来实现数据和操作方法的封装,对外界可以将每一个Java类都视为一个黑箱,只需要调用该黑箱提供的方法即可完成你想要的操作。
- 继承:通过类的继承,便于将统一的功能集中在父类中,实现代码的重用和可维护性。
- 多态:通过重载、重写与覆盖,实现不同类的不同形态特征。
6.1 封装(encapsulation)——类
HelloWorld
public class HelloWorld {
private String world = "World";
public void say() {
System.out.println("Hello" + world + "!");
}
public static void main(String[] args) {
HelloWorld inst = new HelloWorld();
inst.say();
}
}
一个类的使用过程,包括封装类、生成实例、使用实例进行操作3个过程。
6.1.1 类的封装
封装(encapsulation)有如下的3个特点。
- 事物的内部实现细节隐藏起来。
- 对外提供一致的公共的接口——间接访问隐藏数据。
- 可维护性。
6.1.2 对象的生成
封装的类不是对象,要使用该封装类进行操作,必须先生成该类的一个实例——对象。
6.1.3 对象的使用
生成了对象后,就可以使用该对象来做具体的事情了。对象的使用包括引用对象的成员变量和方法,通过运算符可以实现对变量的访问和方法的调用,变量和方法可以通过设定一定的访问权限(在前文中5.2节已经讲过)
来允许或禁止其他对象对它的访问。比如上面的HelloWorld实例,我们先添加一个setWorld()方法,用来修改world变量的值:
public class HelloWorld {
private String world = "World";
public void setworld(String world) {
this.world = world;
}
public void say() {
System.out.println("Hello " + world + "!");
}
public static void main(String[] args) {
HelloWorld inst = new HelloWorld();
inst.setworld("China");
inst.say();
}
}
增加 getWorld()orld()方法
public class HelloWorld {
private String world = "World";
public void setworld(String world) {
this.world = world;
}
public String getWorld() {
return this.world;
}
public void say() {
System.out.println("Hello " + world + "!");
}
public static void main(String[] args) {
HelloWorld inst = new HelloWorld();
inst.setworld("China");
inst.say();
String world1 = new HelloWorld().world;
String world2 = new HelloWorld().getWorld();
System.out.println(world1 + world2);
}
}
6.3.3 接口与抽象类的区别
Java接口和Java抽象类代表的就是抽象类型,就是我们需要提出的抽象层的具体表现。OOP面向对象的编程,如果要提高程序的复用率,增加程序的可维护性、可扩展性,就必须是面向接口的编程、面向抽象的编程,正确地使用接口、抽象类这些有用的抽象类型作为你结构层次上的顶层。
Java接口和Java抽象类有太多相似的地方,又有太多特别的地方,究竟在什么地方才是它们的最佳位置呢?通过下面的比较你就能够发现。
Java接口和Java抽象类最大的一个区别,就在于Java抽象类可以提供某些方法的部分实现,而Java接口不可以,这就是Java抽象类唯一的优点,但这个优点非常有用。如果向一个抽象类里加入一个新的具体方时,那么它所有的子类一下子都得到了这个新方法。例如下面的抽象类添加了一个实现函数,那么子类就可以直接使用该方法了:
public abstract class ClassName1 {
public void func1() {}; //添加一个实现函数
}
而Java接口做不到这一点,如果向一个Java接口里加入一个新方法,所有实现这个接口的类就无法成功通过编译了,因为你必须让每一个类都再实现这个方法才行,这显然是Java接口的缺点。例如下例为接口类添加了一个接口函数,那么实现该接口的类必须实现该函数:
public interface ClassName2 {
public void func2(); //添加一个接口函数
}
(2)抽象类只能继承一个,而可以实现多个接口
一个抽象类的实现只能由这个抽象类的子类给出,也就是说,这个实现类处在抽象类所定义出的继承的等级结构中,而由于Java语言的单继承性,所以抽象类作为类型定义工具的效能大打折扣。在这一点上,Java接口的优势就出来了,任何一个实现了Java接口所规定的方法的类都可以具有这个接口的类型,而一个类可以实现任意多个Java接口,从而这个类就有了多种类型。
由此不难看出,Java接口是定义混合类型的理想工具,混合类表明一个类不仅仅具有某个主类型的行为,而且具有其他的次要行为。
结合(1)、(2)点中抽象类和Java接口的各自优势,最精典的设计模式就出来了:声明类型的工作仍然由Java接口承担,但是同时给出一个Java抽象类,且实现了这个接口,而其他同属于这个抽象类型的具体类可以选择实现这个Java接口,也可以选择继承这个抽象类,也就是说在层次结构中,Java接口在最上面,然后紧跟着抽象类,这就将两者的最大优点都能发挥到极至了。这个模式就是“默认适配模式”。
实现的代码如下所示:
这里先定义了一个接口类ClassName1,并为它添加了一个接口函数func1()。然后定义了一个抽象类ClassName2,添加了自己的实现函数func2()。最后可以定义具体的类ClassName3,它既继承了抽象类ClassName2,又实现了接口ClassName1,这样它就拥有了接口函数func1()和实现函数func2()。
接口在某些地方和抽象类有相似的地方,但是采用哪种方式来声明类主要参照以下两点。
● 如果要创建不带任何方法定义和成员变量的基类,那么就应该选择接口而不是抽象类。
● 如果知道某个类应该是基类,那么第一个选择的应该是让它成为一个接口,只有在必须要有方法定义和成员变量的时候,才应该选择抽象类。因为抽象类中允许存在一个或多个被具体实现的方法,只要方法没有被全部实现该类就仍是抽象类。
6.4 本课小结
6.4.1 总结本课的知识点
本节课以面向对象的三大特性——封装、继承与多态为主线,讲解了类的封装、抽象类与接口的具体使用方法,共包含13个知识点,如表6-1所示。
6.4.2 要掌握的关键点
对于本节课的内容,我们应该掌握如下的关键点:
1.Java面向对象的三大特性:封装、继承、多态。
2.封装有如下的3个特点:
● 将事物的内部实现细节隐藏起来。
● 对外提供一致的公共的接口——间接访问隐藏数据。
● 可维护性。
3.继承包括两个概念:超类和子类。
4.Java多态是通过方法重写和方法重载来实现的。
● 覆盖(override)——继承了父类的同名无参函数。
● 重载(overload)——继承了父类的同名有参函数。
● 重写(overwrite)——当前类的同名方法。
6.4.3 课后上机作业
抽象类与接口的概念虽然有些抽象,但一旦你能够运用它来编写自己的一个实现,就会发现它是如此简单,只不过是一个特殊的类。重要的是,我们应该如何把握建立抽象类与接口的时机。
通过6.3.3节的讲解,我们已经了解到,接口通常只用于定义接口的方法,在仅仅需要暴露外部接口函数时使用;而如果需要在接口中实现某些操作函数,就需要定义抽象类了。在实际的运用中,我们通常将接口作为顶层的类,用以暴露外部接口,然后编写一个抽象类来实现共同的函数。待实现的具体类既要继承抽象类,又要实现接口,这样就可以充分利用抽象类与接口的优点了。
为了演练这种开发模式,我们制定了一个实现需求,让读者来实现如下一个开发案例(120分钟)。
1.实现目标:实现一个简单的计算器,能够计算两个数的加、减、乘、除。
2.实现思路:由于加、减、乘、除的运算都很相似,都是对两个数值进行表达式运算,因此我们可以为这4种运算设计一个接口ICalculator,并设计接口函数calculate(String expression),输入表达式可以返回计算结果,再为每一个运算编写一个实现类,来实现计算接口函数即可。
由于对于用户输入的字符串表达式expression还需要进行分析,例如对于3+2、3-2等,需要将它们拆分为3和2,这对4种运算来说都需要进行这种操作。而这种操作又是一个实际的实现,因此我们可以将提取数值的操作放在一个抽象类AbstractCalculator中来进行提取,不同的实现类继承该抽象类,即可直接调用该函数来进行数值的提取了。
3.实现步骤:
(1)编写接口类ICalculator,添加计算的统一接口calculate(Stringexpression)。
(2)编写抽象类AbstractCalculator,添加根据计算表达式提取计算数值的函数实现split(String expression, String deliString),deliString为分隔符。
(3)编写实现类,均实现接口ICalculator,并继承AbstractCalculator:
● 加法Plus。
● 减法Minus。
● 乘法Multiply。
● 除法Divide。
● 默认Default:用于在输入不是以上4种运算表达式时的调用。
(4)编写测试类Test:根据用户输入的表达式,分别调用加法、减法、乘法和除法4种运算类,执行计算。
这些类之间的关系如图6-4所示。
6.4.4 上机作业参考样例
根据以上的4个实现步骤,下面是作者根据以上要求做的一个参考类代码。
(1)计算器接口类
package com.calculater;
/**
*
* @author tjjingpan
*计算器接口类
*/
public interface ICalculator {
/**
* 计算表达式接口
* @param expression 待计算的表达式
* @return 计算结果
*/
public int caculate(String expression);
}
(2)计算器抽象类
package com.calculater;
/**
*
* @author tjjingpan
*
*/
public abstract class AbstractCalculator {
/**
* 根据计算表达式,提取待计算的数值
* @param expression 计算表达式
* @param deliString 分隔符
* @return 待计算数值的数组
*/
public int[] split(String expression,String deliString) {
String array[] = expression.split(deliString); //拆分字符串
int arrayInt[] = new int[2]; //创建数值数组
arrayInt[0] = Integer.parseInt(array[0]); //第一个数值
arrayInt[01] = Integer.parseInt(array[1]);//第二个数组
return arrayInt;//返回数组
}
}
(3)计算器实现类:加、减、乘、除,以及默认计算类
● 加法计算类:
package com.calculater.impl;
import com.calculater.AbstractCalculator;
import com.calculater.ICalculator;
/**
*
* @author tjjingpan
*加法计算类
*/
public class Plus extends AbstractCalculator implements ICalculator {
@Override
public int caculate(String expression) {
int arrayInt[] = split(expression,"\\+");//拆分加法表达式
return arrayInt[0] + arrayInt[1]; //计算加法结果
}
}
● 减法计算类:
package com.calculater.impl;
import com.calculater.AbstractCalculator;
import com.calculater.ICalculator;
/**
*
* @author tjjingpan
*减法计算类
*/
public class Minus extends AbstractCalculator implements ICalculator {
@Override
public int caculate(String expression) {
int arrayInt[] = split(expression,"-");//拆分减法表达式
return arrayInt[0] - arrayInt[1]; //计算减法结果
}
}
● 乘法计算类:
package com.calculater.impl;
import com.calculater.AbstractCalculator;
import com.calculater.ICalculator;
/**
*
* @author tjjingpan
*乘法计算类
*/
public class Multiply extends AbstractCalculator implements ICalculator{
@Override
public int caculate(String expression) {
int arrayInt[] = split(expression,"\\*");//拆分乘法表达式
return arrayInt[0] * arrayInt[1]; //计算乘法结果
}
}
● 除法计算类:
package com.calculater.impl;
import com.calculater.AbstractCalculator;
import com.calculater.ICalculator;
/**
*
* @author tjjingpan
*除法计算类
*/
public class Devide extends AbstractCalculator implements ICalculator {
@Override
public int caculate(String expression) {
int arrayInt[] = split(expression,"/");//拆分除法表达式
return arrayInt[0]/arrayInt[1]; //计算除法结果
}
}
● 默认计算类:
package com.calculater.impl;
import com.calculater.AbstractCalculator;
import com.calculater.ICalculator;
/**
*
* @author tjjingpan
*默认计算类
*/
public class Default extends AbstractCalculator implements ICalculator {
@Override
public int caculate(String expression) {
return 0;
}
}
(4)测试类
package com.calculater;
import com.calculater.ICalculator;
import com.calculater.impl.Default;
import com.calculater.impl.Devide;
import com.calculater.impl.Minus;
import com.calculater.impl.Multiply;
import com.calculater.impl.Plus;
public class Test {
public static void main(String[] args) {
while(true) {
System.out.println("准备输入:");
String expression = System.console().readLine();
//初始化实例
ICalculator calculator;
if(expression.indexOf("+") !=-1) {
calculator = new Plus();//调用加法计算器
}else if(expression.indexOf("-") !=-1){
calculator = new Minus();//调用减法类
}else if(expression.indexOf("*") !=-1){
calculator = new Multiply();//调用乘法类
}else if (expression.indexOf("/")!= -1) {
calculator = new Devide();//调用除法类
}else {
calculator = new Default();//调用默认类
}
//开始运算
int value = calculator.caculate(expression);
System.out.println("=" + value);
}
}
}
运行该类后的测试计算如图6-5所示。