4 抽象思想
作者:来自ArimaMisaki创作
[TOC]
4.1 抽象类
4.1.1 概述
说明:在Java中用abstract
关键字来表示修饰抽象的事物,修饰类时该类变为抽象类
,修饰方法时该方法变为抽象方法
;抽象类如同一张不完美的设计图,一般使用时我们让抽象类作为父类让其他子类来继承;在抽象类中,我们定义了子类共有的行为,但这些行为是抽象的,详细干了什么在子类中去定义。
提示:
- 抽象方法只有方法签名,没有方法体
- 一个类中如果定义了抽象方法,那么该类必须定义为抽象类
- 一个子类继承了抽象父类,则需要重写抽象父类中的抽象方法
4.1.2 特点
说明:由于一个类中定义了抽象方法则其必须是抽象类的特性,故我们可以总结:抽象方法不一定有抽象类,但抽象类一定有抽象方法。
我们前面还提到,子类必须重写抽象父类的抽象方法,若不重写,则子类也为抽象类。
抽象类虽然得到了抽象能力,但却失去了生成对象的能力;换而言之,抽象类不能创建对象。
4.1.3 抽象类作为形参
说明:
- 方法的形参是抽象类名,但由于抽象类无法创建对象,故实际上需要的是该抽象类的子类对象
- 方法的返回值时抽象类名,其实返回的是该抽象类的子类对象
4.1.4 模板方法模式
说明:模板方法模式是23种设计模式之一,其属于行为型模式
;当一个系统中出现同一个功能多地方使用,而该功能大部分代码一样,少数部分不同时,采用模板方法模式可以提高代码复用性。
本质:模板方法模式实现的方法是将功能定义为一个所谓的模板方法放于抽象类中,模板方法只定义通用且能确定的代码,而不确定的功能定义成抽象方法让具体子类去实现。
案例:在下面的代码中,我们在代码中实现模板方法模式;现有两类学生分别为中学生和小学生,他们都需要写《我的爸爸》这篇作文,在这篇作文中,标题、第一段和最后一段都必须一样,但正文内容可以不同。
提示:模板方法建议使用final修饰,这样可以避免模板方法被使用者重写。
package Class4.Abstract;
// 模板方法
public abstract class Student {
public final void write(){
// 标题
System.out.println("\t\t\t\t 《我的爸爸》");
// 第一段
System.out.println("你的爸爸是啥样");
// 正文
System.out.println(writeMain());
// 最后一段
System.out.println("谢谢爸爸");
}
public abstract String writeMain();
}
package Class4.Abstract;
public class StudentMiddle extends Student{
@Override
public String writeMain() {
return "我的爸爸很有钱";
}
}
package Class4.Abstract;
public class StudentChild extends Student{
@Override
public String writeMain() {
return "我的爸爸很厉害";
}
}
package Class4.Abstract;
public class Demo {
public static void main(String[] args) {
StudentMiddle studentMiddle = new StudentMiddle();
studentMiddle.write();
}
}
4.2 接口
4.2.1 接口概述
说明:
- 接口是一种规范
- 由于接口一般需要公开给别人使用,故里面的
属性
和抽象方法
一般自带public
和static
- 由于接口中必须是常量而非变量,故接口中的
属性
自带final
- 接口一般只是提供规范,故接口中的方法自带
abstract
格式:
public interface 接口名{
//常量
//抽象方法
}
4.2.2 接口的基本使用
说明:接口是用来被类实现的,实现接口的类称为实现类;我们可以简单地把接口看做是一个父类,而实现了接口的类看做是一个子类。
提示:
- 一个类可以实现一个接口,也可以
实现
多个接口;在规范层面上,这恰恰是和抽象类所体现的不一样的地方 - 一个类实现了接口,则必须重写完接口中的全部抽象方法,否则这个类需要定义为抽象类。这是因为接口是用于规范行为的,好比插座,你要把插头插入一个三角插座,那你的三角插头只能插入其中的两个孔是不允许的
格式:
修饰符 class 实现类 implements 接口1,接口2,...{
}
4.3.3 接口和接口的关系
反思:
- 类和类的关系:单继承
- 类和接口的关系:多实现
- 接口和接口的关系:多继承,一个接口可以同时继承多个接口
说明:接口之所以能够多继承,是因为一个类如果实现多个接口会造成代码不优美;采用一个接口合并多个接口可以便于子类实现。
4.3.4 JDK8后的接口
引入:如果一个项目在后期的版本中需要在某个接口中加入新的抽象方法,则实现了该接口的类必须一一实现这些新的抽象方法
说明:在JDK8最新的改动中,Java允许在接口中直接定义带有方法体的方法,但这样的代价是接口失去了纯洁性;在接口中直接定义带有方法体的方法也许遵守一定的规则,大致可分为默认方法
、静态方法
和私有方法
。
默认方法:
- 类似之前写的普通实例方法,但必须要用default修饰
- 默认会public修饰,需要用接口的实现类的对象来调用
静态方法:
- 默认会public修饰,必须static修饰
- 接口的静态方法必须用本身的接口名来调用
私有方法:
- 必须使用private修饰,从JDK1.9后才有
- 只能在本类中被其他的默认方法或者私有方法访问
提示:JDK8后新增的3种方法在开发中很少使用,通常是Java源码中才有所涉及
4.3.5 接口的注意事项
说明:
- 接口不能创建对象
- 一个类实现多个接口,多个接口中有同样的静态方法不冲突
- 一个类继承了父类,同时又实现了接口;若父类中和接口中有同名方法,则默认用父类的;从简单理解的角度来看,我们在写代码时先写extend,而后才写implement,extend离我们比较近,故方法优先采取父类的
- 一个类实现了多个接口,多个接口中存在同名的
默认方法
不会造成冲突,这个类重写该方法即可 - 一个类继承多个接口是没有问题的,如果多个接口中存在规范冲突则不能多继承
4.3.6 抽象类和接口的区别
成员区别:
- 抽象类:变量、常量、有构造方法、有抽象方法还有非抽象方法
- 接口:常量和抽象方法
关系区别:
- 类与类:单继承
- 类和接口:多实现
- 接口和接口:多继承
设计理念:
- 抽象类:对类抽象,其中抽象的是属性和行为
- 接口:对行为抽象,主要是行为
4.3.7 接口多态
引入:虽然我们平时总是说接口可以看做是一个类,但实际上它并非一个类,故它不能使用new来实例化一个对象
说明:接口虽然不能被实例化,但可以被声明。我们可以采取一种多态的思想来看待一个问题,既然我们说接口可以看做父类,实现类可以看做子类,父类和子类之间存在多态关系,即父类 对象名=new 子类
,那我们也可以存在接口名 接口声明=new 实现类
这样的形式,如果想要使用实现类的成员属性和方法,也可以采取向下转型
的方式。
package Class4.Interface;
public interface Action {
void eat();
}
package Class4.Interface;
public class Person implements Action{
@Override
public void eat() {
System.out.println("人会吃饭");
}
}
package Class4.Interface;
public class Demo {
public static void main(String[] args) {
// 1.向上转型
Action action = new Person();
// 2.向下转型
Person person = (Person) action;
// 3.调取方法
person.eat();
}
}
4.3.8 接口作为形参
说明:
- 方法的形参是接口名,但接口无法实现对象,故实际上需要的是该接口的实现类对象
- 方法的返回值时接口名,其实返回的是该接口的实现类对象
4.3.9 接口回调
引入:在JavaScript中,我们常常使用到回调函数。实际上,接口回调和回调函数是同样的道理,本质上都是叫个人帮自己做任务,完成任务后通知自己任务做完。
说明:接口回调
和向上转型
是设计模式的解耦核心,可以说几乎所有的模式都是建立在这两者的应用之上的。我们简单讲述一下接口回调是如何实现的:一个A类被接口Aip实现了,还有一个没有接口实现但里面有Aip接口声明的B类;A类就是任务发出者,B类是任务执行者;B类做完任务后通过Aip接口通知A任务做完了。
应用:和JavaScript的作用一致,接口回调常用于异步任务;在下面的例子中,我们使用接口回调创建一个同步任务,如果需要保证效率,可以将老师发布任务的代码片段放在一个新线程中,开启异步任务。
理解:我们现在来实现一个老师通知学生做作业的任务。
创建一个接口,接口用于学生作业完成后通知老师作业做好了。
package Class4.InterfaceCallback; public interface NoticeInterface { void notice(); }
创建一个学生
package Class4.InterfaceCallback; public class Student { public void doPractice(NoticeInterface noticeInterface){ System.out.println("学生做练习中..."); System.out.println("学生做好了,准备告诉老师"); // 调用接口告诉老师作业做好 noticeInterface.notice(); } }
创建一个老师类
package Class4.InterfaceCallback; public class Teacher implements NoticeInterface{ Student student = new Student(); public Teacher(Student student) { this.student = student; } // 1.老师叫学生做作业 public void doEvent(){ System.out.println("老师让学生做作业"); // 2.老师实现了接口,这里可以利用接口多态传入老师对象即可 student.doPractice(this); } // 3.用于接收学生发来的消息 @Override public void notice() { System.out.println("学生作业已做完"); } }
创建一个测试类
package Class4.InterfaceCallback; public class Demo { public static void main(String[] args) { Teacher teacher = new Teacher(new Student()); teacher.doEvent(); } }