前几天我们整理了继承,继承中提到过抽象类,今天我们就来说一下抽象类以及利用抽象类可以做哪些事。
类继承的主要作用在于扩充已有类的功能,但是在对于之前的继承操作而言会发现,子类可以任意决定是否要覆写一个方法,此时父类不能强制对子类进行约定,即强制性子类必须覆写某些方法;这时候我们很少进行对某个类的继承,或者说在开发中,我们很少继承一个已经完善功能的类,这时候我们就要进行继承抽象类。
1.抽象类基本概念
抽象类主要作用为对之类中覆写方法进行约定,在抽象类中可以去定义一些抽象方法以实现这样的约定,抽象方法指的是使用abstract关键字定义的并且没有提供方法体的方法,而抽象方法所在的类必须为抽象类,抽象类必须使用abstract关键字来定义。
抽象类简单来说就是在普通类的基础上追加定义抽象方法的类,我们简单举一个例子:
abstract class Message{//定义抽象类
private String type;//消息类型
public abstract String getConnectInfo();//抽象方法
public void setType(String type) {//普通方法
this.type = type;
}
public String getType() {//普通方法
return this.type;
}
}
于是我们试着new一个Message对象:
public class first {
public static void main( String args[] ){
Message msg = new Message();
System.out.println(msg.getConnectInfo());
}
}
结果发现出错了:
Exception in thread "main" java.lang.Error: Unresolved compilation problem:
Cannot instantiate the type Message
我们要知道,当一个抽象类定义完成之后,它不是完整的,要想使用抽象类,必须依据下列几点:
- 抽象类必须提供子类,子类使用extends继承一个抽象类
- 抽象类的子类一定不是抽象类,在不是抽象类的前提下,要覆写抽象类的全部抽象方法
- 抽象类的对象实例化可以利用对象多态性通过子类向上转型的方式来完成
在刚才的例子中,我们来实现一下。首先我们添加一个子类来继承Message类:
class DatabaseMessage extends Message{
}
这个时候我们如果直接编译会报错,当然使用工具的同学可能不会发现报错信息,但会有错误提示:
而当我们在子类中覆写抽象方法时,就没有报错了:
class DatabaseMessage extends Message{
public String getConnectInfo() {
return "方法覆写";
}
}
同时,我们来实例化一个对象,看能不能不管成功的向上转型实例化出来:
public class first {
public static void main( String args[] ){
Message msg = new DatabaseMessage();
msg.setType("Type消息");
System.out.println(msg.getType());
System.out.println(msg.getConnectInfo());
}
}
我们想要new一个Messaga对象,来实现他的抽象方法,利用DatabaseMassage类来实现父类中的抽象方法,同时对象本身属于Message类,可以使用类中的普通方法:
public class first {
public static void main( String args[] ){
Message msg = new DatabaseMessage();
msg.setType("Type消息");
System.out.println(msg.getType());
System.out.println(msg.getConnectInfo());
}
}
上述代码编译后运行结果为:
Type消息
方法覆写
从整体上来说,抽象类只是比普通类增加了抽象方法以及对子类的强制性覆写要求,其他使用过程和传统的类继承是完全相同的。对于抽象类使用的几点意见:
- 抽象类使用很大程度上因为抽象类无法直接实例化。
- 抽象类之中主要目的是进行过渡操作使用,所以当你使用抽象类开发的时候,往往都是在设计中需要解决类继承时所带来的代码重复的问题。
2.抽象类的相关说明
抽象类是一个重要的面向对象的设计结构,对于抽象类使用的时候要注意以下几点:
- 在定义抽象类的时候不能使用final关键字来定义,因为抽象类必须要有子类,但是final关键字不能有子类。
- 抽象类是作为一个普通类的加强版出现的(抽象类的组成就是在普通类的基础上扩展而来的,只是追加了抽象方法),那么普通类之中可以定义属性和方法,而抽象类基于普通类也就可以提供构造方法,并且子类也一定会按照子类对象的实例化原则进行父类构造调用。
我们首先在Message中添加一段代码,这段代码用来定义一个构造方法:
abstract class Message{//定义抽象类
private String type;//消息类型
public Message(String type) {//添加的代码
this.type = type;
}
public abstract String getConnectInfo();//抽象方法
public void setType(String type) {//普通方法
this.type = type;
}
public String getType() {//普通方法
return this.type;
}
}
然后我们运行程序,结果如下:
Exception in thread "main" java.lang.Error: Unresolved compilation problem:
Implicit super constructor Message() is undefined for default constructor. Must define an explicit constructor
这时候我们需要在子类中添加一段代码,由于父类的构造方法没有无参构造,所以子类必须明确的调用无参构造:
class DatabaseMessage extends Message{
public DatabaseMessage(String str) {//添加的代码
super(str);
}
public String getConnectInfo() {
return "方法覆写";
}
}
这时候我们就可以直接在实例化的时候传递参数了:
public class first {
public static void main( String args[] ){
Message msg = new DatabaseMessage("Type消息");
System.out.println(msg.getType());
System.out.println(msg.getConnectInfo());
}
}
因为我们通过DatabaseMessage类实例化了一个Message类的对象,而传递过去的参数str就到了父类的构造方法中,这样就能显示出来了,运行上述代码,结果如下:
Type消息
方法覆写
我们还要注意:
- 抽象类中允许没有抽象方法,但是即便没有抽象方法也不能直接通过new关键字来实例化对象,要获取对象只能通过子类。
- 抽象类中可以提供有static方法,并且该方法不受到抽象类对象的局限。
我们将刚才的代码改一下:
abstract class Message{//定义抽象类
public abstract String getInfo();//抽象方法
public static Message getInstance() {
return new DatabaseMessage();
}
}
class DatabaseMessage extends Message{
public String getInfo() {
return "方法覆写";
}
}
public class first {
public static void main( String args[] ){
Message msg = Message.getInstance();
System.out.println(msg.getInfo());
}
}
我们在Message类中定义一个抽象方法,在DatabaseMessage类中覆写它,然后再定义一个static关键字的方法getInfo(),用来实例化DatabaseMessage类的对象,那么编译之后运行结果为:
方法覆写
所以我们就可以知道,static方法永远不受结构的影响,永远都可以使用类名称调用。
3.模板设计模式
抽象来本质上就属于一个加强型的类,对于类已经很清楚了,可以描述一切有关的现实事物,但是通过分析可以发现,抽象类的定义应该是比我们普通的类更高一级的定义。对于抽象类的实际应用来研究一下:
假如说我们现在要描述有三类事物:
- 机器人:不休息,补充能量和工作
- 人类:需要休息,吃饭和努力的工作
- 猪:天天休息,吃饭,不需要工作
如果我们要实现描述这三类事物,首先要明确这三类事物的形为都是一致的:吃、睡、工作,所以我们首先定义一个抽象类,再通过子类将行为具体实现:
abstract class Action{
public static final int EAT = 1;
public static final int SLEEP = 5;
public static final int WORK = 10;
public void command(int code) {
switch(code) {
case EAT:{
this.eat();
break;
}
case SLEEP:{
this.sleep();
break;
}
case WORK:{
this.work();
break;
}
case EAT + SLEEP :{
this.eat();
this.sleep();
break;
}
case EAT + WORK :{
this.eat();
this.work();
break;
}
case SLEEP + WORK :{
this.sleep();
this.work();
break;
}
case EAT + SLEEP + WORK :{
this.eat();
this.sleep();
this.work();
break;
}
}
}
public abstract void eat();
public abstract void sleep();
public abstract void work();
}
在上面这段代码中,我们首先定义了一个抽象类,以及所有的抽象方法;同时为了避免识别错误,我们分别用1、5、10来代替吃饭、睡觉、工作,这样我们就可以开始编写子类了:
class Robot extends Action{
public void eat() {
System.out.println("充电来补充能量!");
}
public void sleep() {
System.out.println("不需要休息!");
}
public void work() {
System.out.println("严谨有序的工作!");
}
}
class Reson extends Action{
public void eat() {
System.out.println("吃饭来补充能量!");
}
public void sleep() {
System.out.println("在床上睡觉来休息!");
}
public void work() {
System.out.println("努力的工作!");
}
}
class Pig extends Action{
public void eat() {
System.out.println("吃东西来补充能量!");
}
public void sleep() {
System.out.println("经常在休息!");
}
public void work() {
System.out.println("不需要工作!");
}
}
这三个子类分别对应了机器人、人类、猪的行为,需要注意的是,如果没有动作,那么方法中可以不写任何内容,例如机器人不需要休息,那我们将Robot类中的sleep()方法空着也是完全没有问题的!
接下来我们来实例化三个对象并根据不同的子类产生不同的行为:
public class first {
public static void main( String args[] ){
Action robotact = new Robot();
Action resonact = new Reson();
Action pigact = new Pig();
System.out.println("**********机器人行为**********");
robotact.command(Action.EAT + Action.SLEEP + Action.WORK);
System.out.println("**********人类行为**********");
resonact.command(Action.EAT + Action.WORK);
System.out.println("**********猪的行为**********");
pigact.command(Action.SLEEP);
}
}
可以看到我们展示了三种不同的行为状态:
**********机器人行为**********
充电来补充能量!
不需要休息!
严谨有序的工作!
**********人类行为**********
吃饭来补充能量!
努力的工作!
**********猪的行为**********
经常在休息!
这样就可以看到所有的对象的行为了;当然,如果在子类中自己写一个新的方法来实现吃、睡、工作等行为,由于父类并不知道有这样的方法存在,所以传递参数给父类就没有任何反馈。
抽象类的好处:
- 对子类方法的统一管理。
- 自身可以提供一些普通方法,并且可以调用抽象方法(这些抽象方法必须在有子类提供实现的时候才会生效)。
这就是一种模板设计的模式,要想实现某些功能,就按照父类要求写方法,父类就会按照标准调用,如果不规范写方法,那父类就会报错。
这篇文章整理了抽象类以及抽象类可以实现的模板设计模式,这是一种非常规范的,实用的设计模式,虽然在实际开发中规范书写代码比较困难,但大家还是要掌握,我们下次见👋