3.2 抽象类与接口
3.2.1 抽象类的定义与使用
抽象类 = 普通类 + 抽象方法(只声明但是没有实现的方法,这类方法要用abstract关键字定义),为了和普通的类区分,抽象类也需要用abstract关键字来定义。
abstract class Person{ private String name ; // 属性 public String getName(){ // 普通方法 return this.name; } public void setName(String name){ this.name = name ; } // {}为方法体,所有抽象方法上不包含方法体 public abstract void getPersonInfo() ; //抽象方法
} |
由于抽象类中包含了没有具体实现的抽象方法,这种抽象类是不能直接产生实例化对象的。为了使得这些类能够产生对象,需要有一个类继承抽象类,同时子类必须覆写抽象类的所有抽象方法。
abstract class Person{ private String name ; // 属性 public String getName(){ // 普通方法 return this.name; } public void setName(String name){ this.name = name ; } // {}为方法体,所有抽象方法上不包含方法体 public abstract void getPersonInfo() ; //抽象方法
}
class Student extends Person{ public void getPersonInfo(){ System.out.println("I am a student"); } }
public class Test{ public static void main(String[] args) { Person per = new Student() ; //实例化子类,向上转型 per.getPersonInfo() ; //被子类所覆写的方法 } } |
使用抽象类的注意事项
1、抽象类必须要有子类,所以抽象类不能够用final修饰;
2、抽象类的子类需要覆写父类的抽象方法,所以abstract和private不同同时修饰方法或类。
3、抽象类也分为外部抽象类和内部抽象类。只有内部抽象类可以使用static修饰,但实际上使用很少。
关于抽象类的叙述正确的是?( ) |
正确答案: A 抽象类不可以实例化 抽象类就是一种特殊的接口 抽象类的方法都是抽象方法 抽象类的导出类一定不是抽象类 |
3.2.2 抽象类的实际应用——模板设计模式
模板设计模式是抽象类的一个实际应用场景,它满足开闭原则(OCP原则),即一个软件实体如类、模块和函数应该对扩展开放,对修改关闭。
这里举一个例子来说明模板设计模式是如何作用的,它与抽象类的关系是什么。
我们泡速溶咖啡的一般步骤是烧水->倒咖啡粉->倒热水->加辅料(糖、牛奶等)
泡花茶的一般步骤是烧水->倒茶叶->倒热水->加辅料(柠檬片、枸杞等)
如果把这两个行为用代码来表示的话,如下所示:
泡速溶咖啡 | 泡花茶 |
class Coffee { void 步骤() { 烧水(); 倒咖啡粉(); 倒热水(); 加辅料(); } public void烧水() { System.out.println("烧水"); }
public void倒咖啡粉() { System.out.println("倒咖啡粉"); }
public void 倒热水() { System.out.println(" 倒热水"); }
public void加辅料() { System.out.println("加糖和牛奶"); } } | class Tea { void 步骤() { 烧水(); 倒茶叶(); 倒热水(); 加辅料(); } public void烧水() { System.out.println("烧水"); }
public void倒茶叶() { System.out.println("倒茶叶"); }
public void倒热水() { System.out.println("倒热水"); }
public void加辅料() { System.out.println("加柠檬和枸杞"); } } |
注意:Java支持UTF-8的编码方式,所以方法名、变量等均可使用中文,这里为了方便理解使用中文,实际编程中请避免使用。 |
建立完Coffee和Tea两个类之后,可以发现几个问题:
1、两个类中烧水、倒热水的方法完全一致(代码重复);
2、倒咖啡和倒茶叶的动作只有倒入的内容存在区别、加辅料的操作也只有加入的东西存在区别。
针对于这两个问题,我们可以采取的思路是:
1、方法完全一致的内容,可以放在一个父类中,子类继承时可以完全继承父类的方法。
2、方法相似的内容,可以变成抽象方法放入抽象类中,子类继承时覆写成需要的方法即可。
具体的代码实现如下:
abstract class 提神饮品 { final void 步骤() { 烧水(); 倒主料(); 倒热水(); 加辅料(); }
abstract void倒主料(); abstract void加辅料();
void烧水() { System.out.println("烧水"); }
void倒热水() { System.out.println("倒热水"); } }
class Tea extends提神饮品{ void倒主料() { System.out.println("倒茶叶"); } void 加辅料() { System.out.println("放柠檬和枸杞"); } }
class Coffee extends提神饮品{ void倒主料() { System.out.println("倒咖啡粉"); } void 加辅料() { System.out.println("放糖和牛奶"); } } |
这个就是模板设计模式,包含了实际的“模板方法”。
模板方法定义了一个算法的步骤,并且允许子类为一个或者多个步骤提供具体实现。
比较上面两个实现咖啡和花茶的算法,模板方法的作用是什么呢?
不好的茶或咖啡实现 | 模板方法提供的提神饮品 |
Coffee或Tea主导一切,控制算法 | 由超类主导一切,它拥有算法,并且保护这个算法 |
Coffee与Tea之间存在重复代码 | 有超类的存在,因此可以将代码复用最大化 |
对于算法所做的代码改变,需要打开各个子类修改很多地方 | 算法只存在一个地方,容易修改 |
弹性差,新种类的饮料加入需要做很多工作 | 弹性高,新饮料的加入只需要实现自己的冲泡和加料方法即可 |
算法的知识和它的实现分散在许多类中 | 超类专注于算法本身,而由子类提供完整的实现。 |
但是上面模板设计模式还有一些小问题,如果有人不希望加入辅料如何处理?这个时候可以引入钩子方法。钩子方法是一类默认方法,子类可以根据需要来选择性覆写此方法。
3.2.3 接口的定义与使用
模板设计模式强化了父类与子类之间的关系,但是由于Java中存在单继承的限制,如果要避免这个问题就需要使用接口。在开发中,在一个操作既可以使用抽象类又可以使用接口的时候,优先考虑接口。
接口是抽象方法和全局常量的集合,在Java中接口使用interface关键字定义。
interface IMessage{ public static final String MSG = "I am a biter" ; // 全局常量 public abstract void print() ; // 抽象方法 } |
为了区分接口和类,建议所有接口的命名统一在开头追加I
子类如果想要实现接口,那么久必须使用implements关键字来实现接口,一个子类可以实现多个接口,但是对于接口的子类(非抽象类)必须覆写接口中的全部抽象方法。随后可以使用子类的向上转型通过实例化子类来得到接口的实例化对象。
interface IMessage{ public static final String MSG = "I am a biter" ; // 全局常量 public abstract void print() ; // 抽象方法 } interface INews { public abstract String getNews() ; } class MessageImpl implements IMessage,INews { public void print() { System.out.println(IMessage.MSG) ; } public String getNews(){ return IMessage.MSG ; // 访问常量都建议加上类名称 } }
public class Test{ public static void main(String[] args) { IMessage m = new MessageImpl() ; //子类向上转型,为父接口实例化对象 m.print() ; // 调用被子类覆写过的方法 INews n = (INews) m ; System.out.println(n.getNews()) ; } } |
接口使用的注意事项:
1、接口只能使用public权限,不论是属性还是方法。由于这种特性,在接口定义的时候可以简化权限关键字和static/final/abstract等关键字;
2、当一个子类即需要实现接口又需要继承抽象类时,请先使用extends继承一个抽象类,而后使用implements实现多个接口;
interface IMessage { public void print() ; } abstract class News { // 抽象类中方法前面的abstract不能省略,否则就是普通方法 public abstract void getNews() ; }
class MessageImpl extends News implements IMessage { public void print() { System.out.println("I am a biter") ; } public void getNews() { System.out.println("I am News") ; } } public class Test{ public static void main(String[] args) { IMessage m = new MessageImpl() ; m.print() ; // MessageImpl是抽象类和接口的共同子类 News news = (News) m ; news.getNews() ; } } |
3、一个抽象类可以使用implements实现多个接口,但是接口不能继承抽象类;
4、一个接口可以使用extends继承多个父接口,例如interface C extends A,B ;
5、接口可以定义一系列的内部结构,包括:内部普通类,内部普通类,内部接口。其中,使用static定义的内部接口就相当于一个外部接口。
接口的应用
定义操作标准、表示能力、暴露远程服务方法(在分布式开发中)
抽象类与接口的区别
抽象类 | 接口 |
普通类+抽象方法 | 抽象方法+全局变量 |
各种权限均可 | public |
子类继承抽象类用extends关键字 | implements关键字实现接口 |
一个抽象类可以实现若干接口 | 接口不能继承抽象类,但可以extends关键字继承多个父接口 |
一个子类只能继承一个抽象类 | 一个子类可以实现多个接口 |