• 夯实Java基础(十)——抽象类和接口


    之前讲了Java的三大特征有封装、继承、多态,这样说其实我们少讲了一个,那就是抽象性,在平时的教程中认为只有三种特征,因为它们把抽象放到继承里了,认为抽象类是继承的一种,这也使抽象性是否是Java的一大特征具有争议。而Java语言中对抽象概念定义的机制就是抽象类和接口,正由于它们才赋予Java强大的面向对象的功能。他们两者之间对抽象概念的支持有很大的相似,但是也有区别。所以接下来我们就来学习它们两的使用和区别。(如果在平时面试时遇到问Java有几大特征,我们就说封装、继承、多态这三特征即可)

    1、抽象类

    在面向对象的概念中,我们知道所有的对象都是通过类来描绘的,但是反过来却不是这样。并不是所有的类都是用来描绘对象的,如果一个类中没有包含足够的信息来描绘一个具体的对象,这样的类就是抽象类。抽象类往往用来表征我们在对问题领域进行分析、设计中得出的抽象概念,是对一系列看上去不同,但是本质上相同的具体概念的抽象。比如我们在创建一个Animal对象时,我们只知道动物有吃喝拉撒睡等特征,但是却不知道它到底是哪一种动物,此时这个Animal类是比较抽象的,所以我们需要一个具体的类来描述该动物,如用狗、猫来对它进行特定的描述,我们才知道它是什么动物。

    在Java中用abstract关键字来修饰的类就是抽象类,当然这个关键字也可以用来修饰方法,表明该方法是抽象方法。

    我们在使用抽象类的时候需要注意以下几点:

    ①、抽象类不能实例化,必须要由继承它的子类来创建实例。

    ②、抽象方法只有方法的声明,没有方法体。

    ③、抽象类中的抽象方法必须要在子类中重写。

    ④、抽象类中既可以有抽象方法,也可以有普通方法,普通方法可不用重写。

    ⑤、只要包含一个抽象方法的类,该类必须要定义成抽象类。

    ⑥、abstract不能用来修饰属性、构造器等结构。

    ⑦、abstract不能与final并列修饰同一个类。

    ⑧、abstract不能与private、static、final或native并列修饰同一个方法。

    然后给出一个简单的示例:

     1 public abstract class Animal {
     2     //抽象方法
     3     public abstract void eat();
     4     //普通方法
     5     public void sleep(){
     6         System.out.println("动物需要睡觉...");
     7     }
     8 }
     9 
    10 class Cat extends Animal{
    11 
    12     @Override
    13     public void eat() {
    14         System.out.println("猫喜欢吃鱼...");
    15     }
    16 
    17     @Override
    18     public void sleep() {
    19         System.out.println("猫需要睡觉...");
    20     }
    21 }
    22 
    23 class Dog extends Animal {
    24 
    25     @Override
    26     public void eat() {
    27         System.out.println("狗喜欢吃骨头...");
    28     }
    29 
    30     @Override
    31     public void sleep() {
    32         System.out.println("狗需要睡觉...");
    33     }
    34 
    35     public static void main(String[] args) {
    36         Animal a1=new Dog();
    37         a1.eat();
    38         a1.sleep();
    39 
    40         Animal a2= new Cat();
    41         a2.eat();
    42         a2.sleep();
    43     }
    44 }

    程序运行结果:

    到这里我们可以看出,抽象类就是为了继承而存在的,如果你定义了一个抽象类,却不去继承它,那么这个抽象类就没有任何的意义了。

    2、接口

    接口的英文名称是 interface。接口是属于和类类型同等级别的结构,但是它不是类。却和类类型有着共同点,在接口中可以含有变量、方法。但是需要注意是,接口中的变量会被隐式地指定为public static final变量,并且只能是public static final变量,用private修饰会报编译错误。而接口中的方法会被隐式地指定为public abstract方法,且只能是public abstract方法,如果用其他关键字,如private、protected、static、 final等修饰都会导致报编译错误。也就是说接口中的变量全都是常量,方法都是抽象方法(JDK1.8以前)。从这里可以隐约看出接口和抽象类的区别,接口是一种极度抽象的类型,它比抽象类更加“抽象”。

    准确来说接口定义的是一种规范。 例如鸟和蝴蝶都是可以飞的动物,它们都符合 飞行 的规范,所以它们可以实现 飞 这个接口,对于任何其他可以飞行的动物都需要实现该接口。因为它们符合这种规范。

    我们在使用接口的时候同样需要注意以下几点:

    ①、接口使用interface关键字来定义,并且其子类要implement关键字来实现该接口。

    ②、接口和抽象类一样不能实例化,必须要由实现它的子类来创建实例。

    ③、接口中定义的成员变量都是全局常量,系统会在变量前面自动加上public static final,可以通过类命名直接访问:ImplementClass.name。

    ④、接口中的方法都是抽象方法,但是在JDK1.8以后,可以包含普通方法。

    ⑤、接口可以多实现,而抽象类只有单继承。

     1 public interface Fly {
     2     public void fly();
     3 }
     4 
     5 class Bird implements Fly{
     6 
     7     @Override
     8     public void fly() {
     9         System.out.println("鸟可以飞行...");
    10     }
    11 }
    12 
    13 class Butterfly implements Fly{
    14 
    15     @Override
    16     public void fly() {
    17         System.out.println("蝴蝶可以飞行...");
    18     }
    19 }
    20 
    21 class Test{
    22     public static void main(String[] args) {
    23         Fly bird = new Bird();
    24         bird.fly();
    25 
    26         Fly butterfly = new Butterfly();
    27         butterfly.fly();
    28     }
    29 }
    30 //运行结果:
    31 //鸟可以飞行...
    32 //蝴蝶可以飞行...

    接口在JDK1.8之前说里面的方法全都是抽象方法,但是在JDK1.8中,接口新添加了一些特性,我们可以在接口中添加静态方法和默认方法。当然从技术的角度来看,这完全合法的,只是看起来违反了接口作为一个抽象定义的理念。

    静态方法:使用static关键字来修饰。可以使用接口直接调用静态方法并且执行该方法体的内容。我们经常在相互一起使用的类中  使用静态方法。你可以在标准库中找到像Collection / Collections或者Path/Paths这样成对的接口和类。

    默认方法:默认方法必须要用default关键字来修饰。可以通过子类的实现类来调用。我们在已有的接口中提供新方法的同时,还保持了与旧版本代码的兼容性。比如Collection、Comparator等接口中提供了丰富的默认方法。

     1 public interface InterfaceTest {
     2     //静态方法
     3     public static void method1(){
     4         System.out.println("接口中静态方法...");
     5     }
     6 
     7     //默认方法
     8     public default void method2(){
     9         System.out.println("接口中默认方法...");
    10     }
    11 
    12     //默认方法
    13     default void method3(){
    14         System.out.println("接口中无修饰默认方法...");
    15     }
    16 }
    17 //创建一个父类
    18 class SuperClass{
    19     public void method2() {
    20         System.out.println("父类中默认方法method2...");
    21     }
    22 
    23     public void method3() {
    24         System.out.println("父类中默认方法method3...");
    25     }
    26 }
    27 //创建子类,继承SuperClass,实现InterfaceTest接口
    28 class SubClass extends SuperClass implements InterfaceTest{
    29     //重写一个方法method2
    30     @Override
    31     public void method2() {
    32         System.out.println("子类中默认方法method2...");
    33     }
    34 }
    35 
    36 class Test{
    37     public static void main(String[] args) {
    38         //调用接口中静态方法
    39         InterfaceTest.method1();
    40 
    41         InterfaceTest test=new SubClass();
    42         test.method2();
    43         test.method3();
    44     }
    45 }

    程序运行结果:

    通过运行我们可以得出以下几点:

    ①、接口中静态方法只能用接口方法来调用,而不能用实例调用。

    ②、接口中默认方法必须要用子类的实例来调用。

    ③、如果子类重写了接口中的默认方法,则调用的是子类中重写方法的内容。

    ④、如果子类(或实现类)继承的父类和实现的接口中声明了同名同参数的方法,那么子类在没有重写该方法的情况下,默认调用父类中的同名同参数的方法。-->类优先原则。

    ⑤、如果实现类实现了多个接口(没有继承),而多个接口中定义了同名同参数的默认方法,那么在实现类没有重写此方法的情况下,会报错。-->接口冲突。这就需要我们在实现类中重写此方法。

    ⑥、如果需要调用父类中方法或接口中默认方法,父类使用 super . 方法名,接口使用 接口名 . super . 方法名。

    这里讲的非常的细,其实我们只需要了解即可。

    3、抽象类和接口的区别

    以下参考链接:https://www.cnblogs.com/dolphin0520/p/3811437.html

    尽管抽象类和接口之间存在较大的相同点,甚至有时候还可以互换,但这样并不能弥补他们之间的差异之处。下面将从语法层次和设计层次两个方面对抽象类和接口进行阐述。

    1.语法层面上的区别

      1)抽象类可以提供成员方法的实现细节,而接口中只能存在public abstract 方法;

      2)抽象类中的成员变量可以是各种类型的,而接口中的成员变量只能是public static final类型的;

      3)接口中不能含有静态代码块以及静态方法,而抽象类可以有静态代码块和静态方法;

      4)一个类只能继承一个抽象类,而一个类却可以实现多个接口。

    2.设计层面上的区别

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

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

      下面看一个网上流传最广泛的例子:门和警报的例子:门都有open( )和close( )两个动作,此时我们可以定义通过抽象类和接口来定义这个抽象概念:

    1 abstract class Door {
    2     public abstract void open();
    3     public abstract void close();
    4 }

    或者:

    1 interface Door {
    2     public abstract void open();
    3     public abstract void close();
    4 }

    但是现在如果我们需要门具有报警alarm( )的功能,那么该如何实现?下面提供两种思路:

      1)将这三个功能都放在抽象类里面,但是这样一来所有继承于这个抽象类的子类都具备了报警功能,但是有的门并不一定具备报警功能;

      2)将这三个功能都放在接口里面,需要用到报警功能的类就需要实现这个接口中的open( )和close( ),也许这个类根本就不具备open( )和close( )这两个功能,比如火灾报警器。

      从这里可以看出, Door的open() 、close()和alarm()根本就属于两个不同范畴内的行为,open()和close()属于门本身固有的行为特性,而alarm()属于延伸的附加行为。因此最好的解决办法是单独将报警设计为一个接口,包含alarm()行为,Door设计为单独的一个抽象类,包含open和close两种行为。再设计一个报警门继承Door类和实现Alarm接口。

     1 interface Alram {
     2     void alarm();
     3 }
     4  
     5 abstract class Door {
     6     void open();
     7     void close();
     8 }
     9  
    10 class AlarmDoor extends Door implements Alarm {
    11     void oepn() {
    12       //....
    13     }
    14     void close() {
    15       //....
    16     }
    17     void alarm() {
    18       //....
    19     }
    20 }
  • 相关阅读:
    递归和this指向
    作用域,闭包
    三种存储方式
    事件流,冒泡,捕获,事件委托
    centos添加登陆成功提示消息
    centos7下安装oracle11g R2报错
    linux下从home分区扩展到根分区
    linux下搭建mongodb副本集
    linux服务下使用nginx之后数据导出超时
    linux下搭建git服务器
  • 原文地址:https://www.cnblogs.com/tanghaorong/p/11246043.html
Copyright © 2020-2023  润新知