【Java学习笔记系列】深入学习Java8中的接口和抽象类以及相关知识点

Java接口和抽象类的区别以及相关知识点总结


接口和抽象类,平时我们也都会使用,这里我就总结一下关于接口和抽象类的知识,和平时需要注意的地方。
要注意的是这里的总结是基于Java8的前提,没有特殊说明都是在Java8的情况下说明的。

  • 抽象类的概念?
  • 接口的概念?
  • 抽象类和接口的区别?
  • 扩展知识

抽象类的概念


抽象类是什么?
被abstract关键字所修饰的类就是抽象类,是一种可以含有抽象方法的类。在继承的层次结构中,每个新子类都使类变得越来越明确和具体。如果从一个子类追溯到父类,类就会变得更通用,更加不明确。类的设计就应该确保父类包含它的子类的共同特征。有时候一个类设计的非常抽象,以至于它都没有任何具体的实例。这样的类就称为抽象类。如一个动物类Animal,具有喊叫cry()的抽象方法,没有具体的实现,因为可以是任何动物,每种动物的叫声都不同,所以这个动物类就是一个抽象类,而实现了这个动物类Animal的所有抽象方法的子类狗类Dog,就能使用cry()喊出汪汪

抽象方法是什么?
既然都说到了抽象类,那么顺便说一下抽象方法,抽象方法被abstract关键字修饰的方法,就是没有具体实现的方法。只是一个方法声明,没有具体的内容,被abstract修饰且没有{}大括号的存在。被abstract修饰的方法不能是staticfinal

public abstract void cry();

接口的概念?


接口是什么?
接口是一种与类相似的结构,被Interface关键字所修饰,是一种接口集合的定义,一种方法规范的约定,它在许多方面与抽象类类似,接口的本质还是一个类,一种特殊的抽象类。但它的目的是指明相关或不相关的类的多个对象的共同行为,既给予实现该接口的类有一些共同的行为规范。但接口除了有这一个目的,还有一个很重要的特性是,允许程序员通过创建一个能够被向上转型为多种基态的类型,来实现某种类似多重继承变种的特性。

抽象类和接口的区别?


抽象类和接口之间的关系:
接口可以说是一种特殊的抽象类。抽象类是普通类和接口之间的一种中庸之道,尽管我们在构建某种具有未实现的方法的类时,你的第一想法可能是创建接口。但是抽象类仍然是用于此目的的一种重要方法。因为你不可能总是使用纯接口。

抽象类和接口在定义性质上的区别:
抽象类是一个类,其子类是用关键字extends来实现继承的,是继承的体系层次,有非常强的is-a的关系,这种关系一般来说符合里氏代换原则,比如子类Dog类继承抽象类Animal类,说明狗也是一种动物。而接口,则是被其他类用关键字implements实现下来的(extendsimplement如果没什么区别,何必整出两个不同的关键字呢是吧)。接口只是定义功能和行为规范,如果一个类实现了一个接口,那么这个类必须遵守这个接口的方法约定,但没有is-a的关系。比如把墙壁上的“小学生行为规范”想象成一个接口,那么一个小学生类如果实现了小学生行为准则这个接口,就相当于小学生必须遵守小学生行为规范这个约定,但小学生并不是一个“行为规范”,而是准守行为规范的“小学生”

相同点:

  • 抽象类和接口都可以有方法和属性
  • 抽象类和接口都可以有抽象方法和具体方法
  • 抽象类和接口都可以定义内部类
  • 抽象类和接口都不能实例化,new一个对象
  • 抽象类和接口都可以利用多态性原理让抽象类(接口)引用指向子类对象
  • 继承抽象类必须实现抽象类的全部抽象方法,接口同理,实现接口就必须实现接口的全部抽象方法,若继承(实现)抽象类(接口)的子类没有完全实现抽象类(接口)的抽象方法,那么该子类也只能是一个抽象类。

不同点:

  • 一个类只能继承一个抽象类,但可以实现多个接口
  • 抽象类中的成员变量跟正常类的修饰相同,而接口中成员变量隐式为public static final关键字修饰,只能是公开的常量。
  • 抽象类和接口都可以有抽象方法,但抽象类的抽象方法必须显式使用abstract修饰,且只能搭配publicprotected,接口的抽象方法隐式为public abstract方法。
  • 抽象类和接口都可以有具体的方法,抽象类的具体方法跟正常类相同定义,而接口的具体方法只能被staticdefault修饰,默认且只能是public的,不能是private,protected(Java9后接口具体方法可以被private所修饰,意义是给staticdefault方法调用,将重复代码抽象成一个内部调用的具体方法)
  • 抽象类可以定义代码块(包括静态代码块),接口中不允许定义代码块(包括静态代码块)
  • 抽象类中可以有构造方法,接口中不能有构造方法
  • 抽象类可以实现(implement)多个接口,可以单继承(extends)于实体类或抽象类。接口可以继承(extends)多个接口,不能实现(implement)接口,不能继承(extends)抽象类或实体类

扩展知识


什么场景该选择抽象类,什么时候该选择接口?

看到我们在抽象类和接口在定义性质上的区别所说的内容,抽象类是存在于继承体系中的,存在很强烈的is-a关系。而接口没有,只是一种行为规范的约定。

比如说有这么一个场景。一个几何图像Geometry抽象类,圆Circle子类,长方形Rectangle子类,三角形Triangle子类。圆、长方形、三角形类都是继承于几何图像类,都是几何图像的子类。我们可以说圆Circle、长方形Rectangle、三角形Triangle都是一种几何图形,这种关系是有现实意义的,是属于同一种现实领域。而此时我们有一种需求,需要定义一个方法来比较同种子类的对象的大小关系时,比如说两个圆Circle的面积、周长、半径是否相等,既是不是参数完全相同的圆。这时我们会想到一个办法,那就是可以在几何图形Geometry抽象类中定义一个抽象的比较方法compareTo()分别交给具体的子类去重写实现,这是可行的。

但此时又有一个场景,一个动物Animal抽象类、狗Dog子类,猫Cat子类,我也需要比较同种子类的的对象的大小关系,比如说两只狗的年龄谁大谁小,是否同岁。此时我们又以上一种的方式在抽象类Animal类中定义一个抽象方法交给子类重写去比较?似乎你也发现如果还有其他类型的类需要比较时,我们都需要在他们的父类中定义一个比较方法?这显然存在很多的重复步骤。所以接口就是一个很好的实现方式。我可以定义一个用于对象比较的接口Inteface Compare,在接口中定义一个抽象的比较方法compareTo(),分别让抽象类Geometry和动物类Animal去实现Compare接口。那么我们就不需要在每一个抽象类中都定义一个比较方法了。其实这就是Java语言中已经实现了的功能,那就是Java的Comparable接口.

当然以上只是一种选择抽象类和接口的使用场景,具体情况就要具体分析,有时候为了达到多重继承的目的,也会使用接口去体现并非接口含义的实现。因为他们的存在本身就是为了让程序员更好的达到自己的设计目的,即使是曲线救国路线,但是能达到了你的目的,也是可以行的。所以你想使用抽象类和接口都是可以的,但是你还是需要知道它们原本代表的含义,以免混淆。

为什么接口中的方法默认是public abstract?

Java7和以前
Java8以前,接口中方法模人物public abstract是有原因的,为什么public,因为接口中的方法要被实现类实现才有意义,所以必须public,为什么abstract,因为接口是一个高度抽象的,完全没有具体实现的抽象类。
Java8
但是在Java8中,这个完全没有具体实现的接口变了,可以由具体的方法了,既被staticdefault修饰的具体方法,但是抽象方法依然是默认为public abstract。意义是增强接口的功能,接口以前虽然是一套标准,只有声明,没有实现。但是慢慢的意识到有时候实现了接口的不同实现类,可能会存在一些必要的且重复的代码。所以这时候,我们就可以把这些重复方法抽出来,集成到它们的接口中,用default修饰,这样的方法就是接口的默认方法。static的可以使用,也说明现在接口可以含有主方法了(main方法)。
Java9
Java9中,又再一次加强了接口的功能,允许使用private去修饰具体的方法。意义是什么呢?我们来想一下这个场景。默认方法中可能有时候也存在大量的代码,有很多的逻辑段,那么当某段逻辑的代码很长很复杂时,整体方法的可读性就显得非常的不好,且可能该接口的其他默认方法也会用到这个逻辑代码段,所以这个时候我们就会想把这些复杂冗余的代码段抽出来,作为一个具体的方法。既提高性能,又让代码可读性更好。。但是在Java8中,具体方法只能是staticdefault。那我只能把复杂代码段作为另一个默认方法来实现。但这样就会出现另外一个问题,这个方法是不想暴露给实现类的,因为它与实现类无关。这时候private方法的好处就用上了。将这些代码段抽成一个private方法,在默认方法中调用private方法,且不会暴露给实现类。

为什么接口中的成员变量非得是public static final的呢?

接口的存在意义之一就是一个为了实现多继承的特殊抽象类,成员变量为什么必须是public static final?

public:
public意味着公开,这样实现接口的类才能够使用到该公共变量,否则实现类无法使用的变量将是毫无意义
static:
static意味着静态,静态变量意味着属于接口定义的一部分,只要实现该接口的所有类都共用同一个静态变量。为什么不能有实例变量,这个我个人的理解是觉得为了避免冲突和更好的体现接口是更为更高度抽象的。如果一个实现类实现了多个接口,而多个接口中都含有同名的实例变量,那么这个冲突怎么解决?实例变量不像方法,如果冲突了,可以重写解决。那么唯一的解决方法只能去除同名冲突变量。而这样又会造成另一些混乱,如这个接口的冲突变量已经别其他实现类使用过了,可能存在牵连非常广的情况,所以为了避免这种冲突,接口成员变量只能是静态的。
final:
final主要是为了配合static关键字,让变量成为一个静态的常量。 当然还有一个问题就是,接口是一套规范,是一种标准。如果一个静态变量不是final的,那么意味着每一个实现了该接口的子类都可以去修改这个变量。并且其他继承了该接口的类就会受到影响,牵一发而动全身!!我是一个是标准,岂能随意被别人修改?还有就是接口的作用就是一个标准出口,作用在于「出」,入口修改出口的东西,就不符合规矩了。

接口为什么不能有构造方法,而抽象类却可以有?

说白了就是接口只是一套标准出口,是一套规范。而抽象类是生存在继承体系中的,是继续体系中的一个父类角色,任然是一个类。继承父类的子类在实例化的时候,是首先要调用父类的构造函数的。所以抽象类可以有构造方法。而接口不存在这个特性,所以不需要构造方法,并且强制要求不能有构造方法。

接口为什么只能继承接口,不能实现接口?

这是要我们就要区别extends关键字和Inplement关键字的区别的,一个代表继承,一个代表实现。
接口中能声明的具体方法有限,而如果一个接口去实现另一个接口。那么这个接口就要实现那个接口的全部抽象方法。
这明显是不可取的。所以不能实现

接口为什么不能继承抽象类和实体类

不可以,因为接口中只能出现3种成员

  • 公共的静态常量(public final static )
  • 公共的抽象方法(public abstract )和default方法和static方法
  • 内部类(class)

而一个类中,就算什么都不写,也必须带一个构造方法,在extends时就会被接口继承,但很明显构造方法不在上面三项之列,而且如果类中有一般的方法和成员变量,也会被接口全部继承,而这些更不能出现在接口中,所以接口不允许继承一个类

接口的向上转型与继承体系的一致
//接口一
public interface Eating {
    public void eat();
}
//接口二
public interface Running {
    public void run();
}
//实现类
public class Animal implements Eating,Running {

    @Override
    public void run() {
        System.out.println("Animal runing");

    }

    @Override
    public void eat() {
        System.out.println("Animal eating");


    }
    public static void main(String[] args) {
        Eating animal1= new Animal();
        animal1.eat();                        //Animal eating
        Running animal2 = new Running();
        animal2.run()                         //Animal running

    }

}

跟继承体系中一致,将一个接口的变量指向一个实现类的对象(如父类变量指向子类对象),该变量只能调用实现类重写该接口的方法,而不能调用实现类重写其他接口的方法。

多重继承时的一些问题(接口方法与抽象类方法冲突,接口与接口方法冲突)

Java中允许多重继承,但是多重继承的体现是单继承和实现多个接口的方式。

假如存在这么一个情况,一个类继承于一个抽象类和实现一个接口,抽象类中和接口有一个同名的方法,这会产生什么样的效果呢?(如果是实现两个接口,两个接口也有同样的方法,情况又是?)重写、重载、实现令人不愉快的搅和在一起。
这里分很多种情况:

  • 抽象类和接口方法冲突

    1. 一个是具体方法一个是抽象方法
      • 参数列表不一致
        属于不同方法,算重载,重载方法与返回值无关,不冲突
      • 参数列表一致,返回值相同
        抽象类为具体方法,接口为抽象方法时,接口的抽象方法不需要重写了,因为子类已经从抽象类中继承了同一个方法的实现,反之,接口为具体方法,抽象类为抽象方法,则要重写该方法
      • 参数列表一致,返回值不同
        因为返回值是无法区分重载,所以当返回值不同时,重写的方法的返回值与其中具体方法的返回值不同,会导致冲突,而该冲突是无法解决的,必须删去一方,或修改返回值至相同
    2. 存在同名抽象方法

      • 两抽象方法参数列表一致,返回值相同 void eat()
        这里不会出现问题,因为都是抽象方法,且签名和返回值都相同,且以抽象类的方法为准
      • 两抽象方法参数列表一致,返回值不相同boolean run(), String run()
        这种冲突无法解决,会编译报错,必须删除抽象类或接口中的冲突抽象方法,或修改返回值达到一致,进入上一种情况
      • 两抽象方法参数列表不一致,返回值相同void run(Object o) , void run(String s)
        这里不会出现问题,相当于方法重载
      • 两抽象方法参数列表不一致,返回值不相同boolean run(Object o) , String run(String s)
        这里不会出现问题,相当于方法重载,重载与返回值无关
    3. 存在同名具体方法(接口为default方法)

      • 具体方法的内容相同void eat(){}
        • 两具体方法参数列表一致,返回值相同 void eat(){}
          这里不会出现问题,因为内容相同且签名和返回值都相同,属于同一方法,且以抽象类的方法为准,隐藏接口该方法的实现
        • 两具体方法参数列表一致,返回值不相同boolean run(), String run()
          这种冲突无法解决,会编译报错,必须删除抽象类或接口中的冲突抽象方法,或修改返回值达到一致,进入上一种情况
        • 两具体方法参数列表不一致,返回值相同void run(Object o) , void run(String s)
          这里不会出现问题,相当于方法重载
        • 两具体方法参数列表不一致,返回值不相同boolean run(Object o) , String run(String s)
          这里不会出现问题,相当于方法重载,重载与返回值无关
      • 具体方法的内容不相同void eat(){..a..},void eat(){..b..}
        只要内容不一致,参照上面情况,是同一方法的,以抽象类的方法为正式方法,既隐藏接口的实现。
  • 接口和接口冲突
    几乎所有情况都与抽象类与接口冲突的情况一致。但是唯独不同的时,两接口具有相同的具体方法,但内容的实现不同,这会产生冲突,不会像抽象类与接口的冲突,会隐藏接口的实现,以抽象类的方法为准。此时就需要重写两个接口共有的同一方法,不然是无法通过的。

//接口1
public interface Movement1 {
    default void show(){
        System.out.println("I am Movement1");
    }
}
//接口2
public interface Movement2 {

    default void show(){
        System.out.println("I am Movement2");
    }
}
//实现类
public class Demo implements Movement1,Movement2{

    /*
    @Override
    public void show() {
        Movement1.super.show();
    }
    */
    //重写实现第一个接口方法或则重写实现第二个接口方法
    /*
    @Override
    public void show() {
        Movement2.super.show();
    }
    */
    //或则完全重写,抛弃原有方法
    @Override
    public void show() {
        System.out.println("I am Demo");
    }
}

总结:
所以当多重继承的时候,尽量避免在不同的接口或类中使用相同的方法名。另外我们对冲突总结一下,大致就是

  • 类和接口产生同一方法冲突时,类的优先级更高,以类为准,隐藏接口的实现。
  • 接口和接口产生同一方法冲突时,需要重新冲突的方法来避免冲突
  • 其他的情况大致就可以分成重载方法时不冲突非重载时因为返回值不一致而导致冲突的问题了。


参考资料:
《Java编程思想》、《Java语言程序设计》

[CSDN] - 接口和抽象类的异同——加入Java8的特性。
[百度知道] - 接口为什么不能有构造器
[新浪微博] - 为什么接口中的成员变量非得是public static final?
[百度知道] - java中接口可不可以继承一般类,为什么?

在此感谢参考的网站和博客的作者,非常感谢!!!

猜你喜欢

转载自blog.csdn.net/snailmann/article/details/80372269
今日推荐