Java基础——抽象abstract和接口

一、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就是需要强制实现的了

image.png

六、接口和抽象类选哪一个

1、基本都是接口
2、什么时候用抽象类?

  • 需要定义子类的行为,有要为子类提供基础性功能时
  • 做一个封装,比如适配器模式等
比较 抽象类 接口
关键字 abstract interface
定义 包括一个抽象方法 都是抽象方法和全局静态常量
组成 抽象方法,普通方法,变量,常量,构造 抽象方法,全局静态常量
权限 不能private 只能是public
使用 通过extents继承 通过implement实现
局限 抽象类只能单继承 ---
顺序 先继承后实现,多个接口都好隔开 先继承后实现,多个接口都好隔开
设计模式 模板模式等 工厂模式,代理模式等
结合 可以一起做个设配器模式 可以一起做个设配器模式

两者都是依靠多态性,通过子类进行实例化。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,761评论 5 460
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,953评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,998评论 0 320
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,248评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,130评论 4 356
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,145评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,550评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,236评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,510评论 1 291
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,601评论 2 310
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,376评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,247评论 3 313
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,613评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,911评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,191评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,532评论 2 342
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,739评论 2 335

推荐阅读更多精彩内容

  • 1. Java基础部分 基础部分的顺序:基本语法,类相关的语法,内部类的语法,继承相关的语法,异常的语法,线程的语...
    子非鱼_t_阅读 31,505评论 18 399
  • 你很清楚的知道什么时候用抽象类,什么时候用接口么?p.s. 多文字预警! 1 抽象类和接口简介 1.1 抽象类 ...
    Sharember阅读 2,324评论 9 55
  • 1、一个".java"源文件中是否可以包括多个类(不是内部类)?有什么限制?答:可以有多个类,但只能有一个publ...
    岳小川阅读 904评论 0 2
  • 这个系列面试题主要目的是帮助你拿轻松到offer,同时还能开个好价钱。只要能够搞明白这个系列的绝大多数题目,在面试...
    独念白阅读 331评论 0 3
  • 面向对象主要针对面向过程。 面向过程的基本单元是函数。 什么是对象:EVERYTHING IS OBJECT(万物...
    sinpi阅读 1,036评论 0 4