一、abstract和接口初认识
abstract class和interface是Java语言中对于抽象类定义进行支持的两种机制。abstract class和interface之间在对于抽象类定义的支持方面具有很大的相似性,甚至可以相互替换,因此很多开发者在进行抽象类定义时对于abstract class和interface的选择显得比较随意。其实,两者之间还是有很大的区别的,对于它们的选择甚至反映出对于问题领域本质的理解、对于设计意图的理解是否正确、合理。
abstract 关键字可以修饰类或方法。
抽象方法
Java中可以利用abstract关键字定义一些不包括方法体的方法,没有方法体就没有实现,具体实现交给子类,这样的方法称为 抽象方法
抽象类
有抽象方法的类就是抽象类
接口
一个类中如果所有方法都是abstract 方法,那么这个类我们可以利用 interface 定义成接口。
二、 抽象方法
什么是抽象方法
声明为 abstract 的方法就是抽象方法。
抽象方法的写法
abstract 返回值类型 方法名(参数列表);
抽象方法的特点
抽象方法只有声明,没有实现,抽象方法必须由子类进行重写实现
没有实现就是没有方法体(方法体就是方法后面的花括号),意味着这是一个没有完成的方法,抽象的。抽象方法只能由子类去重写实现abstract 关键字不能应用于 static、private 或 final 方法,因为这些方法不能被重写。
三、 抽象类
什么是抽象类
有抽象方法的类就是抽象类
抽象类的写法
public abstract class 类名{}
抽象类的特点
- 1、抽象类不能被实例化,实例化就必须实现全部方法,实现全部方法后这个类就不是抽象类。(实例化没意义),但可以有构造函数
- 2、抽象方法必须由子类进行重写实现
- 3、子类继承自抽象类,必须实现抽象类的全部抽象方法,这样的子类也叫具体类,具体类才可以被实例化(这个实现全部方法方法的子类不是抽象类,抽象类不能实例化)。如果没有实现全部抽象方法,那么这个子类必须也是抽象类。
- 4、一个类只要出现了abstract方法,那么这个类就必须是abstract类
- 5、抽象类中可以有非抽象方法,可以有变量
如果一个类中方法都是抽象方法,那么我们就可以把这个类定义成接口。
(接口是一种特殊的类,接口也是类)
代码示例
接下看通过简单的代码,看一下抽象类和抽象方法
AbsPerson
public abstract class AbsPerson {
public int number = 16;
{
System.out.println("抽象类的代码块");
}
public AbsPerson(){
System.out.println("抽象类的构造函数");
}
int maxAge = 200;
public abstract void say(String str);
public abstract void age();
public void absPersonNormal(){
System.out.println("抽象类的普通方法,或者叫 非抽象方法");
}
}
.
.
Student
public class Student extends AbsPerson{
{
System.out.println("学生类的代码块");
}
public Student() {
System.out.println("学生类的构造函数");
}
@Override
public void say(String str) {
System.out.println(str);
}
@Override
public void age() {
System.out.println("年龄18");
}
}
.
.
AbsPerson
// 没实现 AbsPerson 这个抽象类的所有方法,所以这个类也是抽象类
public abstract class Worker extends AbsPerson{
@Override
public void say(String str) {
// TODO Auto-generated method stub
}
}
.
.
TestClass
public class TestClass{
public static void main(String[] args) {
Student student = new Student();
student.say("day day up");// 子类(具体类)调用实现自抽象类的方法
student.age();// 子类(具体类)调用实现自抽象类的方法
student.absPersonNormal();// 子类(具体类)调用抽象类的 非抽象方法
System.out.println("子类调用抽象类的变量: "+student.number);
}
}
.
.
运行结果:
抽象类的代码块
抽象类的构造函数
学生类的代码块
学生类的构造函数
day day up
年龄18
抽象类的普通方法,或者叫 非抽象方法
子类调用抽象类的变量: 16
代码已经说得很清楚了。
四、接口
如果一个类中方法都是抽象方法,那么我们就可以把这个类定义成接口。
接口的出现让类不必受限于单一继承的束缚,可以灵活地继承一些共有的特性,间接实现类似多继承的目的。
接口里面只可能有两种东西:
- 1、抽象方法
- 2、全局静态常量
(接口中没有变量,默认都是用 public static final标识的,不过在interface中一般不定义属性成员,只定义抽象方法)
接口的特点:
1、接口的访问修饰符只能是public,或者不写* 2、interface中定义的方法和成员变量,默认为public访问权限,且仅能为public
(声明为其他访问修饰符会报错)3、接口中的没有变量,只有全局静态常量。
(看起来像常量,但是依然是静态全局常量)4、实现接口的非抽象类必须要实现该接口的所有方法。抽象类可以不用实现。
(接口中的方法不能是static、final或者private,也好理解,毕竟带了这些就不能被@Override了)5、不能使用new操作符实例化一个接口,但可以声明一个接口变量,该变量必须引用一个实现该接口的类的对象。通过这个做回调接口,这也开发中特别常见的。
6、可以使用 instanceof 检查一个对象是否实现了某个特定的接口。
例如:if(anObject instanceof Comparable){}。7、在java8中,接口里也可以定义默认方法:
注意点:
在实现多接口的时候一定要避免方法名的重复。
(多实现的时候,如果接口重名会比较麻烦,所以起名要有规范)
public interface java8{
//在接口里定义默认方法
default void test(){
System.out.println("java 新特性");
}
}
基本特点如上,下面通过示例代码大概看一下:
示例代码
IStuent
public interface IStuent{
int minAge = 9; // 默认会加上 public static final ,全局静态常量
void iStudentDohomeWord(); // 接口中的方法默认就是 abstract方法
void iStudentGoToSchool();
}
.
.
IOther
interface IOther {
void iOtherMethod();
}
.
.
AbsClass
// 抽象类实现接口,可以不复写接口中的 抽象方法
public abstract class AbsClass implements IStudent{
// 这里不复写任何抽象方法没问题
}
.
.
TestClass
public class TestClass implements IPerson,IStuent{
public static void main(String[] args) {
TestClass testClass = new TestClass();
testClass.iPersonEat();
testClass.iPersonSleep();
testClass.iStudentDohomeWord();
testClass.iStudentGoToSchool();
//minAge = 12; // 会报错,因为接口中的属性都是全局静态常量,不可以重新复制
System.out.println("访问接口中的全局静态常量 "+minAge);
// 判断一个类是否实现了某个接口
// 判断方式1: isAssignableFrom
boolean result1 = IPerson.class.isAssignableFrom(TestClass.class);
System.out.println("IPerson.class.isAssignableFrom ---- IPerson: "+result1);
boolean result2 = IOther.class.isAssignableFrom(TestClass.class);
System.out.println("IOther.class.isAssignableFrom ---- IOther: "+result2);
// 判断一个类是否实现了某个接口
// 判断方式2: instanceof
if(testClass instanceof IPerson){
System.out.println("testClass instanceof IPerson: true");
}else{
System.out.println("testClass instanceof IPerson: false");
}
if(testClass instanceof IOther){
System.out.println("testClass instanceof IOther: true");
}else{
System.out.println("testClass instanceof IOther: false");
}
}
@Override
public void iPersonEat() {
System.out.println("学生是人,会吃东西");
}
@Override
public void iPersonSleep() {
System.out.println("学生是人,会吃睡觉");
}
@Override
public void iStudentDohomeWord() {
System.out.println("做作业,学生要做这个");
}
@Override
public void iStudentGoToSchool() {
System.out.println("上学,学生要做这个");
}
}
// 这里不能写public访问修饰符,因为interface也是类,一个类Java文件中只能有一个public的类
interface IPerson{
void iPersonEat();
void iPersonSleep();
}
输出结果
学生是人,会吃东西
学生是人,会吃睡觉
做作业,学生要做这个
上学,学生要做这个
访问接口中的全局静态常量 9
IPerson.class.isAssignableFrom ---- IPerson: true
IOther.class.isAssignableFrom ---- IOther: false
testClass instanceof IPerson: true
testClass instanceof IOther: false
由上可知
1、interface和class写在同一个文件,因为class是public,所以inerface本你来只能写public,但是现在不能写了。
2、接口里面看起来像普通变量其实是全局静态常量,不可以重新赋值
3、抽象类可以不用全部实现接口中的抽象方法
4、具体类需要实现接口中的全部抽象方法才可以实例化
5、可以通过isAssignableFrom或者instanceof判断一个类是否实现了某个接口
五、回调接口
普通回调
我们现在通过简单代码演示一下点击一个按钮,触发一些功能的逻辑。
ButtomClass
public class ButtomClass {
private IBtnClick mBtnClick;
// 通过对方开放的方法,给调用一个回调接口
public void setOnClickListen(IBtnClick btnClick){
mBtnClick = btnClick;
}
// 假设是系统按钮内部的单击,不让外部调用
private void systemClick(){
System.out.println("系统内逻辑处理中,等待用户操作");
if(mBtnClick!=null){
mBtnClick.onClick();
}
}
// 假设是系统按钮内部的双击,不让外部调用
private void systemDoubleClick(){
System.out.println("系统内逻辑处理中,等待用户操作");
if(mBtnClick!=null){
mBtnClick.onDoubleClick();
}
}
// 假设是系统按钮内部的长按,不让外部调用
private void systemLongClick(){
System.out.println("系统内逻辑处理中,等待用户操作");
if(mBtnClick!=null){
mBtnClick.onLongClick();
}
}
//========= 以下是模拟用户行为 =========
// 模拟用户单击
public void userDoClick(){
systemClick();
}
// 模拟用户双击
public void userDoDoubleClick(){
systemDoubleClick();
}
// 模拟用户长按
public void userDoLongClick(){
systemLongClick();
}
}
.
.
IBtnClick
public interface IBtnClick {
void onClick();
void onLongClick();
void onDoubleClick();
}
.
.
TestClass
public class TestClass{
public static void main(String[] args) {
ButtomClass buttomClass = new ButtomClass();
buttomClass.setOnClickListen(new IBtnClick() {
@Override
public void onLongClick() {
System.out.println("外部回调,按钮 长按 长按");
}
@Override
public void onDoubleClick() {
System.out.println("外部回调,按钮 双击 双击");
}
@Override
public void onClick() {
System.out.println("外部回调,按钮 单击 单击");
}
});
buttomClass.userDoClick();
}
}
假设ButtomClass是一个系统的按钮控件。
这个按钮有单击,双击,长按三个事件,这些事件系统的内部处理肯定是对外隐藏的,开发者拿到这个控件的实例后,只需要通过 setOnClickListen 设置一个接口,复写对应的方法,然后写上我们点击之后需要的逻辑,即可将逻辑回调回系统控件ButtomClass。
像上面这么写,已经模拟完成了。
打印输出:
系统内逻辑处理中,等待用户操作
外部回调,按钮 单击 单击
.
.
这一切没什么问题,但是用的人可能觉得不爽,对一个按钮来说,最常见是单击,每次你都让我复写三个方法,我肯有可能 双击 和 长按 都是放空不写的,代码多余长了一些我不喜欢。
好,那现在我们就再优化一下代码
升级版回调
第一步,新增一个抽象类
起名为FreeClickListen,然后先把方法再这里先复写了。
相当于这是一个子接口
FreeClickListen
public abstract class FreeClickListen implements IBtnClick{
@Override
public void onClick() {
}
@Override
public void onLongClick() {
}
@Override
public void onDoubleClick() {
}
}
.
.
第二步,
TestClass 代码有小小的改动
public class TestClass{
public static void main(String[] args) {
ButtomClass buttomClass = new ButtomClass();
// buttomClass.setOnClickListen(new IBtnClick() {
// @Override
// public void onLongClick() {
// System.out.println("外部回调,按钮 长按 长按");
// }
//
// @Override
// public void onDoubleClick() {
// System.out.println("外部回调,按钮 双击 双击");
// }
//
// @Override
// public void onClick() {
// System.out.println("外部回调,按钮 单击 单击");
// }
// });
// 通过这种方式我们就可以不用必须复写三个方法了,也不会觉得有多余的很长的代码
buttomClass.setOnClickListen(new FreeClickListen() {
@Override
public void onClick() {
super.onClick();
System.out.println("外部回调,按钮 单击 单击 现在我可以只复写我需要的了");
}
});
buttomClass.userDoClick();
}
}
完成。
打印输出:
系统内逻辑处理中,等待用户操作
外部回调,按钮 单击 单击 现在我可以只复写我需要的了
这种方式很常见,比如系统给我们的接口需要复写很多个方法,通过这种方式,只需要多加一个抽象类,我们就可以在很多地方避免拖着长长的代码,而在需要比如 长按 这些功能的时候,我们只需要在
buttomClass.setOnClickListen(new FreeClickListen() {
@Override
public void onClick() {
super.onClick();
System.out.println("外部回调,按钮 单击 单击 现在我可以只复写我需要的了");
}
});
里面复写对应的方法即可。
通过这个可以举一反三,比如一些网络请求,我们可以通过类似的方式,调整一下,做统一处理,比如结果码的统一处理。
省缺设配/选择性复写
比如一个interface里面有10个接口,子类每次实现,只有2个方法是必须强制复写的,剩下都是可选项。
怎么做呢。其实就是根据上面的代码,哪一个抽象类去实现一个接口,然后抽象类里面只复写那些非强制的(就是8个非强制的),那么下次我们new 这个抽象类的时候,就必须强制复写那2个强制的了。另外的8个变成可选项了。
还是来个代码吧
接口
public interface IPostJsonStringCb {
void onSuccess(String str);
void onError(String str);
void onStart(Request<String, ? extends Request> str);
void onFinish();
}
.
抽象类
public abstract class AbsPostJsonStringCb implements IPostJsonStringCb{
// 抽象类里面复写的方法后面作为 非必选方法。
@Override
public void onError(String str) {
}
@Override
public void onStart(Request<String, ? extends Request> str) {
}
}
.
剩下onSuccess和onFinish就是需要强制实现的了
六、接口和抽象类选哪一个
1、基本都是接口
2、什么时候用抽象类?
- 需要定义子类的行为,有要为子类提供基础性功能时
- 做一个封装,比如适配器模式等
比较 | 抽象类 | 接口 |
---|---|---|
关键字 | abstract | interface |
定义 | 包括一个抽象方法 | 都是抽象方法和全局静态常量 |
组成 | 抽象方法,普通方法,变量,常量,构造 | 抽象方法,全局静态常量 |
权限 | 不能private | 只能是public |
使用 | 通过extents继承 | 通过implement实现 |
局限 | 抽象类只能单继承 | --- |
顺序 | 先继承后实现,多个接口都好隔开 | 先继承后实现,多个接口都好隔开 |
设计模式 | 模板模式等 | 工厂模式,代理模式等 |
结合 | 可以一起做个设配器模式 | 可以一起做个设配器模式 |
两者都是依靠多态性,通过子类进行实例化。