Java抽象类和接口


一、抽象类

🍑1、抽象类的概念

抽象: 所谓抽象就是抽出像的部分。是从多个事物中将共性的,本质的内容抽取出来。
抽象方法: 多个对象都具备相同的功能,但是功能具体内容有所不同,那么在抽取过程中,只抽取了功能定义,并未抽取功能主体,那么只有功能声明,没有功能主体的 方法称为抽象方法。
抽象类: 包含抽象方法的类就是抽象类。

🍑2、抽象类的定义和使用

其实抽象类的语法并不难理解,无非是增加了abstract关键字,多了一些限制:

abstract class Person {
    private String name;
    private int age;

    //抽象方法
    abstract public void doSport();//抽象方法,只声明不实现
    //普通方法
    public void eat() {
        System.out.println(name+"吃饭!");
    }
    public void sleep() {
        System.out.println(name+"睡觉!");
    }

    //构造器
    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }
}
class Student extends Person {

    //必须帮助抽象父类实现构造
    public Student(String name, int age) {
        super(name, age);
    }
    //必须重写抽象方法
    @Override
    public void doSport() {
        System.out.println("唱,跳,rap,打篮球!");
    }
}
//抽象类的使用
public class Test {
    public static void main(String[] args) {
        Student student=new Student("路人甲",18);
        Person person=student;//通过接收子类引用达到使用目的
        person.doSport();
        person.eat();
        person.sleep();
    }

🍑3、抽象类的特性

通过上面的例子我们大致了解了抽象类的定义和使用,下面就总结一下抽象类的特性:

  1. 抽象类不能直接实例化对象。抽象类的存在就是被继承的。
  2. 抽象方法不能被privatefinalstatic修饰,因为抽象方法要被子类重写,需要满足重写条件。
  3. 抽象类必须被继承,并且继承后子类要重写父类中的抽象方法,除非子类也是abstract修饰的抽象类。
  4. 抽象类中不一定包含抽象方法,但是有抽象方法的类一定是抽象类。
  5. 抽象类中可以有构造方法,供子类创建对象时,初始化父类的成员变量。

🍑4、抽象类的作用

抽象类本身不能被实例化,要想使用,只能创建该抽象类的子类。然后让子类重写抽象类中的抽象方法。抽象类不能实例化,这让人看上去只是相比普通父类类增加了一个不必要的限制。其实不然:

  1. 使用抽象方法,而非空方法体,子类就知道他必须要实现该方法,而不可能忽略。
  2. 使用抽象类,类的使用者创建对象的时候,就知道他必须要使用某个具体子类,而不可能误用不完整的父类。

引入抽象方法和抽象类,是Java提供的一种语法工具,对于一些类和方法,引导使用者正确使用它们,减少被误用。无论是写程序,还是平时做任何别的事情的时候,每个人都可能会犯错,减少错误不能只依赖人的优秀素质,还需要一些机制,使得一个普通人都容易把事情做对,而难以把事情做错。抽象类就是Java提供的这样一种机制。这种机制也可以配合编译器在编译期间让我们提早发现错误,防患于未然!


二、接口

🍑1、接口的概念

接口: 接口就是一种规范,(就像人间的法律一样),每个实现类都需要遵守。在Java中,接口可以看成是多个类的公共规范,是一种引用数据类型。

接口这个概念在生活中并不陌生,电子世界中一个常见的接口就是USB接口。电脑往往有多个USB接口,可以插各种USB设备,可以是键盘、鼠标、U盘、摄像头、手机等等。

接口声明了一组能力,但它自己并没有实现这个能力,它只是一个约定,它涉及交互两方对象,一方需要实现这个接口,另一方使用这个接口,但双方对象并不直接互相依赖,它们只是通过接口间接交互。

🍑2、为什么使用接口

我刚接触到接口的时,对它的表述以及相关的功能都感觉非常模棱两可,不能理解接口存在的意义,不知道什么场景下使用接口,以及为何存在接口。经过我的一番折腾,我在《Java编程的逻辑》一书中找到了如下观点:

之前我们一直在说,程序主要就是数据以及对数据的操作,而为了方便操作数据,高级语言引入了数据类型的概念,Java定义了八种基本数据类型,而类相当于是自定义数据类型,通过类的组合和继承可以表示和操作各种事物或者说对象。

但,这种只是将对象看做属于某种数据类型,并按该类型进行操作,在一些情况下,并不能反映对象以及对对象操作的本质。

为什么这么说呢?很多时候,我们实际上关心的,并不是对象的类型,而是对象的能力,只要能提供这个能力,类型并不重要。我们来看一些生活中的例子。

要拍个照片,很多时候,只要能拍出符合需求的照片就行,至于是用手机拍,还是用Pad拍,或者是用单反相机拍,并不重要,关心的是对象是否有拍出照片的能力,而并不关心对象到底是什么类型,手机、Pad或单反相机都可以。

要计算一组数字,只要能计算出正确结果即可,至于是由人心算,用算盘算,用计算器算,用电脑软件算,并不重要,关心的是对象是否有计算的能力,而并不关心对象到底是算盘还是计算器。

要将冷水加热,只要能得到热水即可,至于是用电磁炉加热,用燃气灶加热,还是用电热水壶,并不重要,重要的是对象是否有加热水的能力,而并不关心对象到底是什么类型。

在这些情况中,类型并不重要,重要的是能力。那如何表示能力呢?
Java使用接口这个概念来表示能力。

所以说接口并不依赖于类,它唯一注重的是能力或者说是功能。就比如fly-飞飞行的能力不依赖于任何一个Bird类或者说Plane类,无论是鸟还是飞机,它们都具有飞行的能力,因此我们可以将这个不依赖于类的功能抽象出来,化为一个通用的接口。

🍑3、定义接口

接口的定义格式与定义类的格式基本相同,接口的关键字是interface

public interface IUsb {
 	String func = "这是一个USB接口";//变量量默认以public static final修饰
    void openDevice();  //方法默认以public abstract修饰
    void closeDevice(); //通常省略不写
}

注:

  1. 创建接口时, 接口的命名一般以大写字母 I 开头。
  2. 阿里编码规范中约定, 接口中的方法和属性不要加任何修饰符号, 保持代码的简洁性。

🍑4、实现接口

类可以实现接口,表示类的对象具有接口所表示的能力。Java中使用implements这个关键字表示实现接口,前面是类名,后面是接口名。

interface IUsb {

    String func = "这是一个USB接口";//变量量默认以public static final修饰
    void openDevice();  //方法默认以public abstract修饰
    void closeDevice(); //通常省略不写
}

public class KeyBoard implements IUsb {

    @Override
    public void openDevice() {
        System.out.println("链接键盘!");
    }

    @Override
    public void closeDevice() {
        System.out.println("断开链接!");
    }
}

Java中一个子类只能继承一个父类,即类和类之间是单继承的且不支持多继承,但是一个类可以实现多个接口,中间用,隔开。

interface IUsb {
    void openDevice();  //方法默认以public abstract修饰
    void closeDevice(); //通常省略不写
}

interface IGlow {
    void openLamp();
    void closeLamp();
}

interface IAdjust {
    void turnUp();
    void turnDown();
}


public class KeyBoard implements IUsb , IGlow , IAdjust {

    //……
}

上面的keyBoard类实现了三个接口IUsb、IGlow、IAdjust,表示一个带有USB接口并且可以发光和调节亮度的键盘。

🍑5、使用接口

与类不同,接口不能new,不能直接创建一个接口对象,对象只能通过类来创建。但可以声明接口类型的变量,通过接收引用来使用接口:

interface IFly {
    void fly();
}

class Bird implements IFly{
    private String name;

    public Bird(String name) {
        this.name = name;
    }

    @Override
    public void fly() {
        System.out.println(name+"用翅膀飞!");
    }
}

class Plane implements IFly{
    private String type;

    public Plane(String type) {
        this.type = type;
    }

    @Override
    public void fly() {
        System.out.println(type+"用机翼飞!");
    }
}


//测试
public class Test {
    public static void main(String[] args) {
    	//创建接口变量,接收引用
        IFly fly1=new Bird("啄木鸟");
        IFly fly2=new Plane("战斗机");
        fly1.fly();
        fly2.fly();
    }
}

通过这个例子,我们可以简单体会到接口的强大威力,不同于抽象类需要依赖于类型,只要实现了相关接口,不管是什么类都可以使用接口,这就是接口强大的地方。

可以说,针对接口而非具体类型进行编程,是计算机程序的一种重要思维方式同一套代码可以处理多种不同类型的对象,只要这些对象都有相同的能力,更重要的是降低了耦合,提高了灵活性,使用接口的代码依赖的是接口本身,而非实现接口的具体类型,程序可以根据情况替换接口的实现,而不影响接口使用者。

🍑6、接口继承

在Java中,类和类之间是单继承的,一个类可以实现多个接口,接口与接口之间可以多继承。即:用接口可以达到多继承的目的。

interface IGlow {
    void openLamp();
    void closeLamp();
}

interface IAdjust {
    void turnUp();
    void turnDown();
}

interface IAuto extends IGlow,IAdjust {
    
}

public class KeyBoard implements IAuto {
    //……
}

上述IAuto接口同时继承了IGlowIAdjust接口,表示即可发光又可以调节亮度。此时KeyBoard实现IAuto接口相当于实现了两个接口,此时和多实现接口达到的效果相同。

注:接口间的继承相当于把多个接口合并在一起.

🍑7、接口的特性

了解了以上接口的语法,最后我们在来总结一下接口的特性:

  1. 接口类型是一种引用类型,但是不能直接new接口的对象
  2. 接口中每一个方法都是public的抽象方法, 即接口中的方法会被隐式的指定为 public abstract(只能是 public abstract,其他修饰符都会报错)
  3. 接口中可以含有变量,但是接口中的变量会被隐式的指定为 public static final 变量
  4. 接口中的方法是不能在接口中实现的,只能由实现接口的类来实现
  5. 重写接口中方法时,不能使用默认的访问权限,只能使用public(满足重写规则)
  6. 接口中不能有代码块构造方法

🍑8、再谈instanceof

在类中instanceof可以判断对象是否是类的实例,接口也可以使用instanceof关键字,用来判断一个对象是否实现了某接口:

public class Test {
    public static void main(String[] args) {
        IFly fly1=new Bird("啄木鸟");
        IFly fly2=new Plane("战斗机");
        fly1.fly();
        fly2.fly();
        //判断对象fly1是否实现了IFly接口
        if(fly1 instanceof IFly) {
            System.out.println("Yes");
        }
        //判断对象fly1是否是Bird的实例
        if(fly1 instanceof Bird) {
            System.out.println("Yes");
        }

    }
}


三、抽象类和接口

Java中抽象类和接口都是非常重要的部分,同时面试也会经常问到,下面就详细总结一下Java中的抽象类和接口:

语法层面的区别:

核心区别: 抽象类中可以包含普通方法和普通字段, 这样的普通方法和字段可以被子类直接使用(不必重写), 而接口中不能包含普通方法, 子类必须重写所有的抽象方法。

参数抽象类接口
声明抽象类使用abstract关键字声明接口使用interface关键字声明
实现子类使用extends关键字来继承抽象类。如果子类不是抽象类的话,它需要提供抽象类中所有声明方法的实现子类使用implements关键字来实现接口。他需要提供接口中所有声明方法的实现
构造器抽象类可以有构造器接口不能有构造器
访问修饰符抽象类中的方法可以是任意访问修饰符接口方法默认修饰符是public。并且不允许定义为private或protected
多继承一个类最多只能继承一个抽象类一个类可以实现多个接口
字段声明抽象类的字段声明可以是任意的接口的字段默认都是static和final

不管是抽象类还是普通类,我们都是在类型的基础上实现对对象相关的操作,而接口是将对象的某一种能力抽离出来,是针对对象的某一能力进行的操作。但是它们在设计的本质上又有什么区别呢?通过一番检索我在贴吧上找到了如下设计层面的区别,大家可了解一下👇

设计层面的区别:

(1)抽象类是对一种事物的抽象,即对类抽象,而接口是对行为的抽象。抽象类是对整个类整体进行抽象,包括属性、行为,但是接口却是对类局部(行为)进行抽象。

例子: 举个简单的例子,飞机和鸟是不同类的事物,但是它们都有一个共性,就是都会飞。那么在设计的时候,可以将飞机设计为一个类Plane,将鸟设计为一个类Bird,但是不能将飞行 这个特性也设计为类,因此它只是一个行为特性,并不是对一类事物的抽象描述。此时可以将 飞行设计为一个接口Fly,包含方法fly(),然后Airplane和Bird分别根据自己的需要实现Fly这个接口。从这里可以看出,继承是一个"是不是"的关系,而 接口实现则是"有没有"的关系。如果一个类继承了某个抽象类,则子类必定是抽象类的种类,而接口实现则是有没有、具备不具备的关系,比如鸟是否能飞(或者是否具备飞行这个特点),能飞行则可以实现这个接口,不能飞行就不实现这个接口。

(2)设计层面不同,抽象类作为很多子类的父类,它是一种模板式设计。而接口是一种行为规范,它是一种辐射式设计

例子: 最简单例子,大家都用过ppt里面的模板,如果用模板A设计了ppt B和ppt C,ppt B和pptC公共的部分就是模板A了,如果它们的公共部分需要改动,则只需要改动模板A就可以了,不需要重新对pptB和pptC进行改动。而辐射式设计,比如某个电梯都装了某种报警器,一旦要更新报警器,就必须全部更新。也就是说对于抽象类,如果需要添加新的方法,可以直接在抽象类中添加具体的实现,子类可以不进行变更;而对于接口则不行,如果接口进行了变更,则所有实现这个接口的类都必须进行相应的改动。


小结

本篇文章我们只谈了两件事:抽象类-接口。抽象类和接口是Java中两个非常重要的概念,它们各有独特的优势,共同特点是都很抽象。本章主要探讨的是它们的语法,想要真正的深入理解它们的关系和功能,还需要我们多加练习和应用。Just coding!

  • 48
    点赞
  • 102
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 27
    评论
评论 27
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

不摸鱼的程序员

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值