目录
一、抽象类基本概念
二、抽象类的使用
三、抽象类的相关规定(使用限制)
四、抽象类的应用——模板设计模式
含有抽象方法的类就是抽象类。普通类中可以含有构造方法,普通方法,static方法,常量和变量等内容,而抽象类就是在普通类的结构里增加了抽象方法。
抽象方法:我们知道所有的普通方法都会有一对“{}”,这个表示方法体,有方法体的方法一定可以被对象直接使用,而抽象方法就是没有方法体的方法。
且抽象方法必须用abstract修饰,而抽象方法所在的类必须用abstract声明,表示抽象类。
范例(定义一个抽象类):
abstract class Person{ //定义一个抽象类
public void fun1() { //普通方法
System.out.println("有方法体的普通方法");
}
public abstract void fun2();//抽象方法(1)没有方法体 (2)有abstract修饰
}
首先,如果直接实例化抽象类的对象
范例:
abstract class Person{ //定义一个抽象类
public void fun1() { //普通方法
System.out.println("有方法体的普通方法");
}
public abstract void fun2();//抽象方法(1)没有方法体 (2)有abstract修饰
}
public class TestDemo5 {
public static void main(String[]args){
Person student=new Person();
}
}
运行结果:
分析栗子可知:Person是抽象类,无法实例化,抽象类含有抽象方法,而抽象方法没有方法体,也就是没有具体实现,也就无法调用,因此抽象类不能直接产生实例化对象。
所以,小结一下:
抽象类的使用原则:
1、所有的抽象类必须有子类,使用关键字extends继承,一个子类只能继承一个抽象类;
2、抽象类不能直接实例化(不能直接new),需要依靠子类向上转型的方式来处理;
3、抽象方法必须为public或protected(如果是private则不能被子类继承,子类无法实现该方法),缺省情况下,默认为public;
4、子类(非抽象类)必须复写父类(抽象类)中全部的抽象方法(如果子类没有实现父类的抽象方法,则必须将子类也定义为abstract);
5、子类(抽象类),那么作为派生类的抽象类可以不实现父类的抽象方法。
范例(子类继承抽象类):
abstract class Person{ //定义一个抽象类
public void fun1() { //普通方法
System.out.println("有方法体的普通方法");
}
public abstract void fun2();//抽象方法(1)没有方法体 (2)有abstract修饰
}
class Human extends Person{ //非抽象类单继承父类
@Override
public void fun2() { //强制覆写父类的抽象方法
System.out.println("实现父类的抽象方法");
}
}
public class TestDemo5 {
public static void main(String[]args){
Person student=new Human();//向上转型
//student.fun1();
student.fun2();//调用被子类覆写过的抽象方法
}
}
运行结果:
通过以上的例子可以知道,子类继承抽象类和非抽象类的区别:
1、继承抽象类,子类有明确的方法覆写要求,继承普通类,可以有选择性的来决定是否需要重写;
2、普通类对象可以直接实例化,而抽象类对象必须通过向上转型才能得到。
1、抽象类中的构造方法
抽象类只是比普通类多了一些抽象方法而已,因此,抽象类也有构造方法,并且,子类对象实例化的时候,依然满足先执行父类构造,再执行子类构造的顺序。
范例:
abstract class Person{ //定义一个抽象类
public Person() {
System.out.println("父类Person的构造方法");
}
/* public void fun1() { //普通方法
System.out.println("有方法体的普通方法");
}*/
public abstract void fun2();//抽象方法(1),没有方法体(2),有abstract修饰
}
//单继承
class Human extends Person{ //Human是Person的一个子类,是个普通类
public Human() {
System.out.println("子类Human的构造方法");
}
@Override
public void fun2() { //强制要求覆写
System.out.println("实现父类的抽象方法");
}
}
public class TestDemo5 {
public static void main(String[]args){
Person student=new Human(); //向上转型
student.fun2();
}
}
运行结果:
2、fianal声明
抽象类不能用final声明,因为抽象类必须有子类,而final定义的类不能有子类。
3、抽象类能否用static声明
范例(外部抽象类用static):
static abstract class Person{ //定义一个抽象类
public abstract void fun3();
}
class Human extends Person{
@Override
public void fun3() {
System.out.println("===华丽的分割线===");
}
}
public class TestDemo5{
public static void main(String[]args){
Person student=new Human();
student.fun3();
}
}
运行结果:
范例(内部抽象类用static修饰):
abstract class Person{ //定义一个抽象类
static abstract class A{
public abstract void fun3();
}
}
class Human extends Person.A{
@Override
public void fun3() {
System.out.println("===华丽的分割线===");
}
}
public class TestDemo5{
public static void main(String[]args){
Person.A student=new Human();
student.fun3();
}
}
运行结果:
由此可知,外部的抽象类不允许用static修饰,而内部的抽象类可以用static修饰,使用static修饰的内部抽象类相当于一个外部抽象类,继承的时候使用“外部抽象类.内部抽象类”来表示类名称
4、思考:可以直接调用static修饰的方法吗?
任何时候,要执行类中static方法的时候,都可以在没有对象的情况下直接调用,抽象类也不例外。调用语法:类名.static修饰的方法
范例:
abstract class Person{ //定义一个抽象类
public static void fun4() {
System.out.println("===华丽的分割线===");
}
}
public class TestDemo5{
public static void main(String[]args){
Person.fun4();
}
}
5、有时候,抽象类中只需要一个特点的系统子类操作,所以可以忽略掉外部子类,此模式非正常模式,但是对一些封装性有一定好处,目的是隐藏用户不需要知道的子类
范例:
abstract class Person{ //定义一个抽象类
public abstract void fun5();
private static class B extends Person{ //内部抽象类子类
@Override
public void fun5() { //覆写抽象方法
System.out.println("======");
}
}
public static Person getInstance() {
return new B();
}
}
public class TestDemo5{
public static void main(String[]args){
//此时取得抽象类对象的时候完全不需要知道B这个子类
Person student=Person.getInstance();
student.fun5();
}
}
开闭原则:一个软件实体如类,模板,函数,应该对扩展开放,对修改关闭
定义一个操作的算法框架,而将一些步骤延迟到子类中。使得一个子类可以不改变一个算法的框架,即可重定义某些算法的特定步骤。
栗子:现有三类事务:
①学生:吃饭,学习,睡觉
②工人:吃饭,工作,睡觉
③婴儿:喝奶,睡觉
设计程序,实现三种不同事务的行为
类图:
(1)先定义一个抽象行为类,然后三类不同的人去实现它:
package com.WFG;
/**
* @Author WFG
* @Date 2019/4/19 16:34
*/
public abstract class Behavior {
protected abstract void eat();//吃饭
protected abstract void work();//工作
protected abstract void sleep();//休息
final void day(){
this.eat();
this.work();
this.sleep();
}
}
(2)定义学生类
package com.WFG;
/**
* @Author WFG
* @Date 2019/4/19 16:36
*/
public class Student extends Behavior {
@Override
protected void eat() {
System.out.println("学生吃饭");
}
@Override
protected void work() {
System.out.println("学生学习");
}
@Override
protected void sleep() {
System.out.println("学生休息");
}
}
(3)定义工人类
package com.WFG;
/**
* @Author WFG
* @Date 2019/4/19 16:40
*/
public class Woker extends Behavior {
@Override
protected void eat() {
System.out.println("工人吃饭");
}
@Override
protected void work() {
System.out.println("工人工作");
}
@Override
protected void sleep() {
System.out.println("工人休息");
}
}
(4)定义婴儿类
package com.WFG;
/**
* @Author WFG
* @Date 2019/4/19 16:43
*/
public class Baby extends Behavior{
@Override
protected void eat() {
System.out.println("婴儿喝奶");
}
@Override
protected void work() {
}
@Override
protected void sleep() {
System.out.println("婴儿休息");
}
}
(5)测试类:
package com.WFG;
/**
* @Author WFG
* @Date 2019/4/19 17:21
*/
public class TestDemo {
public static void main(String[]args){
Behavior student=new Student();
student.day();
Behavior woker=new Woker();
woker.day();
Behavior baby=new Baby();
baby.day();
}
}
(6)运行结果:
注意:所谓模板设计模式就是定义一个操作的算法框架,很明显,抽象类中的day方法就是这个算法的框架也就是模板。
范例:看一个通用的模板设计模式
public abstract class AbstractClass{ //定义抽象类(包含子类的行为抽象以及模板)
abstract void Behavior1(); //基本行为(抽象方法)
abstract void Behavior2(); //基本行为(抽象方法)
final void tempeteMethod() { //模板方法
this.Behavior1();
this.Behavior2();
}
}
public class ConcreteClass1 extends AbstractClass{
@Override
void Behavior1() {
//具体实现
}
@Override
void Behavior2() {
//具体实现
}
}
public class ConcreteClass2 extends AbstractClass{
@Override
void Behavior1() {
//具体实现
}
@Override
void Behavior2() {
//具体实现
}
}
在上面的day方法中,eat()、woke()、sleep()是子类必须实现的,而这三个方法的修改对应了不同的类,这个方法叫做基本方法,基本方法有三种:在抽象类中实现的基本方法叫做具体方法;在抽象类中没有实现,在子类中实现的基本方法叫做抽象方法;栗子所列出的几种方法都是抽象方法,还有一种钩子方法,如下:
钩子方法:模板模式的拓展
上述例子有个问题,人吃饭是默认进食,但吃饭与否的选择并没有实现
在原来设计的类图上增加一个方法:
isEat(),要不要吃饭,这就是钩子方法,修改抽象类:
package com.WFG;
/**
* @Author WFG
* @Date 2019/4/19 16:34
*/
public abstract class Behavior {
protected abstract void eat();//吃饭
protected abstract void work();//工作
protected abstract void sleep();//休息
final void day(){
if(this.isEat()) {
this.eat();
}
this.work();
this.sleep();
}
//钩子方法(默认做某事)子类视情况用不用覆盖它
protected boolean isEat() {
return true;
}
}
钩子方法是由抽象类实现的,子类可视情况重写:
(1)范例:学生永远不吃饭(好像不太恰当哈)
package com.WFG;
/**
* @Author WFG
* @Date 2019/4/19 16:36
*/
public class Student extends Behavior {
@Override
protected void eat() {
System.out.println("学生吃饭");
}
@Override
protected void work() {
System.out.println("学生学习");
}
@Override
protected void sleep() {
System.out.println("学生休息");
}
@Override
protected boolean isEat() {
return false;
}
}
(2范例:工人决定要吃饭
package com.WFG;
import java.util.Set;
/**
* @Author WFG
* @Date 2019/4/19 16:40
*/
public class Woker extends Behavior {
private boolean eatFlag=true;
@Override
protected void eat() {
System.out.println("工人吃饭");
}
@Override
protected void work() {
System.out.println("工人工作");
}
@Override
protected void sleep() {
System.out.println("工人休息");
}
@Override
protected boolean isEat() {
return this.eatFlag;
}
public void setEatFlag(boolean eatFlag) {
this.eatFlag =isEat();
}
}
测试代码:
public class TestDemo {
public static void main(String[]args){
Behavior student=new Student();
student.day();
Behavior woker=new Woker();
woker.day();
((Woker) woker).setEatFlag(true);
Behavior baby=new Baby();
baby.day();
}
}