接口和抽象类之间有什么区别?

自Java版本8起,抽象类和接口 的定义已经发展起来,了解二者之间的区别和交互相当重要。了解他们的主要差别将帮助用户最好地使用这些工具,以充分利用他们的所有潜力。java

抽象类

若是某个类知足如下条件,则将其视为抽象类:segmentfault

1,由abstract修饰符声明
2,没法实例化
3,能够声明抽象方法(即,使用abstract修饰符声明的其余方法)ide

此外,没有什么能够阻止抽象类实现其全部方法。一个抽象类不须要至少一个抽象方法,可是若是一个类包含一个抽象方法,则必须将其声明为abstract。函数

除了这些惟一标识符以外,抽象类还具备其余任何类的共同特征。它能够声明构造函数,具体方法,变量,常量,静态成员和嵌套类型。它还能够实现接口,扩展另外一个类,被其余类扩展等等。如下是抽象类的示例:工具

public abstract class vehicle {
  private String description;
  public abstract void accelerate () ;
  public abatract void decelerate ();
 //constructors and setters and getters methodz omitted
}

请注意,accelerate和decelerate方法被声明为抽象。这就定义了一个通用概念(行驶中的车辆),不能以具体方式(每一个车辆以不一样的方式行驶)来实现。学习

什么时候声明抽象类

声明至少一个抽象方法的抽象类也必须声明为abstract。Vehicle上面示例中的类是抽象的-其accelerate和decelerate方法没有定义,由于没有定义车辆身份的定义。想象一下,在不知道是在陆地仍是海上运行的状况下,解释了车辆的运动方式。在具体的子类,如Car,Ship,和Plane,扩展抽象定义。每一个子类都提供accelerate和decelerate方法的不一样实现(或说明)。ui

抽象类是基本的设计工具。声明抽象类有助于将开发直接引向该抽象类的子类的建立。这将节省时间和必须为子类实现的代码。能够将其插入抽象超类中。这样,子类– Car,Ship和Plane–中包含的对象就能够像车辆同样被使用和分类(请参见下面有关多态的部分)。url

没有理由扩展抽象类就没有理由。此外,必须记住抽象类表明的概念对于定义它们的上下文来讲太通用了。特别是,abstract修饰符强加了一个重要的设计约束:即便该类定义了其本身的全部方法,也没法实例化该类。必须经过利用继承和多态性的好处,用具体的类和子类来扩展它。spa

泛型对象

假设咱们要建立一个应用程序,使您能够创做和播放音乐。该应用程序必须定义虚拟乐器,即模拟真实乐器的软件。为此,咱们能够建立乐器的层次结构,经过该层次结构,咱们能够在下面进行报告:设计

public abatract class Instrument {  //抽象类
  private String name ;
  private String type ;
  public abstract void play(String note); 
  // 省略代码
}
public class Guitar extends Instrument {  
    private int numberofStrings;
    @0verride
    public void play(String note){  // 继承方法重写
       //吉他的实现方法
    }
    // 省略其他代码
}
public abstract class WindInstrument extends Instrument { /* 抽象类
                                                           *扩展
                                                           *I乐器类别 * /
     private String material;
     /* 方法 "play" 继承了抽象而且没有覆盖
      *通用的!* /
    // 省略代码
}
public class Flute extends WindInstrument { /* 具体类扩展
                                             *管乐器*/
    @Override
    public void play(String note) {
        // 长笛的方法实现.
    }
    //代码省略
}

在此示例中,咱们看到Instrument既是具体类Guitar(从新定义方法play)又是抽象类WindInstrument(不 覆盖play抽象方法)的超类。最后,具体类Flute扩展WindInstrument(已经扩展了Instrument)。这个逻辑说明吉他是一种乐器,若是长笛是管乐器,那么长笛也包含在整个类别的乐器中。咱们没法从两个抽象类实例化对象,而且此逻辑与它们的抽象是一致的。

考虑如何实现该类的play方法WindInstrument。现实世界中没有真正的通用管乐器。该类别的乐器仍然具备特定的名称,不管是谐波,小号,长笛和单簧管。在此示例中,管乐器过于通用,没法在咱们的程序中具体定义。这须要不一样的解决方案。

多态性

抽象类确实能够避免重复(请注意,某些变量是在抽象类中定义的),而且最重要的是,它能够利用多态性。例如,咱们的程序能够定义一个Performer类,该类可使用perform如下乐器演奏任何乐器的音符:

public class Perfomer {
    public void perform(Instrument instrument,String note) {
        instrument.play (note) ;
    }
}

多态性促进了与咱们软件的交互,由于咱们如今将始终使用相同的方法来演奏任何乐器。

接口定义

像类同样,接口是用Java语言定义的五种类型之一(其余三种是从版本14开始的枚举,注释和记录)。从Java版本8开始,接口定义已发生重大变化。实际上,在版本7以前,接口概念很是简单明了,由于全部方法都是隐式的公共和抽象方法。可是,今天的接口是:

1,使用interface关键字声明
2,没法实例化
3,可以扩展其余接口
4,一个类能够实现的多个接口之一
5,可以声明:
①公共抽象方法– 不须要使用publicandabstract修饰符,它们将由编译器隐式添加。
②公共默认方法,即用default修饰符标记的具体方法– 不须要使用public修饰符,该修饰符将由编译器隐式添加。
③具体的私有方法–只能由默认方法调用。
④公共或私有静态方法–编译器将隐式认为没有访问说明符的静态方法是公共的。
⑤静态和公共常量-它是不须要使用的public,final以及static改性剂和他们将编译器隐式添加。

在接口内不能声明其余任何内容。

其余高级属性还能够表征接口,例如具备隐式静态性质的嵌套类型声明功能。这些属性对大多数人都不感兴趣,由于它们仅在极少数状况下才有用。

查看如下接口示例:

public interface weighable {
    public static final String UNIT_OF_MEASURE = "kg”;
    public abstract double getWeight () ;
}

咱们能够等效地经过省略全部修饰符来重写它:

public interface weighable {
    String UNIT_OF_MEASURE = “kg”;
    double getWeight ( );
}

请记住,类不能使用extends关键字扩展接口,可是它们能够实现接口。实际上,该implements关键字的使用方式extends与产生相似的结果:继承已实现接口的成员。而后,咱们可使用提供的示例的接口,在一个Article类中实现该接口:

public class Item implementa weighable {
    private double weight ;
    private String description;
    public Item (String description,double weight) {
        setDescription(description) ;
        setWeight (weight) ;
    }
    public double getWeight ( {
        return weight ;
    }
}

接口是抽象类概念的演变

与抽象类相比,咱们能够强制子类更好地实现抽象方法(在接口中定义)。

在代码中,接口相似于缺乏内部实现的类。一个接口想用封装表示咱们所谓的公共接口, 即从外部可见的对象部分,它隐藏了其内部实现。这被视为对象的未实现部分。

宣言的差别

前面的示例是Java 8以前的接口的出色示例,可是接口名称已失去其原始含义。尽管老是能够经过仅声明抽象方法来使用接口,可是默认和静态方法的引入意味着就声明而言,接口如今几乎等同于抽象类。

若是咱们回顾过去使用interface关键字而不是 abstract class,同时认可接口修饰符一般是由编译器隐式推断的,则惟一重要的区别是接口没法声明实例变量和构造函数。

其他的遵循两个类似的概念:不可能实例化抽象类和接口,而且抽象类和接口所提供的共同优势是它们能够强制子类实现行为。继承抽象方法的类必须重写继承的方法,或者将其声明为抽象自己。在设计中,这些工具相似地支持抽象。

概念差别

概念上最重要但常常被忽略的差别之一。抽象类应定义一个过于笼统的抽象,没法在声明它的上下文中实例化。在Vehicle该类中找到了一个很好的例子:

public abstract class vehicle {
    private String description ;
    public abstract void accelerate() ;
    public abstract void decelerate() ;
}

所以,咱们能够将Vehicle类扩展到Plane上一个示例中的类,该示例将具备accelerate和decelerate方法的本身的重写实现:

public class Plane extends vehicle {
    @Override
    public void accelerate( {
        // 重写继承的方法
        // 省略实现
    }
    @Override
    public void decelerate( {
         // 重写继承的方法
         // 省略实现
    }
}

接口应抽象出多个类能够实现的行为,而且不该实例化行为。不该该有任何对象表明的行为和接口常用的形容词和行为的名称(Weighable,Comparable,Cloneable等)。对象应实现一种或多种行为。

例如,咱们能够引入一个Flying接口,该接口将由表示飞行物体的类实现。注意名称如何暗示行为而不是抽象对象:

public interface Flying {
    void land() ;
    void takeoff ();
}

每一个必须抽象飞行物体的概念的类(例如飞机,无人驾驶飞机或什至是鸟)都必须实现该Flying接口。这意味着咱们能够Plane按如下方式重写该类:

public clasz Plane extends vehicle implements Flying {
    @Override
    public void land() {
         //overrides the method of the Flying interface
    }
    @Override
    public void takeOff () {
         // overrides the method of the Flying interface
    }
    @Override
    public void accelerate() {
         // overrides the method of the vehicle abstract clasz
    }
    @Override
    public void decelerate() {
         //overrides the method of the vehicle abatract class
    }
}

image.png
而后,咱们能够建立多态参数以利用该Flying接口:

public class ControlTower {
    public void authorizeLanding (Flying v){
        v.land() ;
    }
    public void authorizeTakeOff(Flying v){
        v.takeOff ();
    }
}

这使咱们能够将已经由实现Flying接口的类已经建立的飞行对象传递给这些方法。顺序相反:Plane由抽象类定义的对象Vehicle具备经过Flying接口说明的加速和减速参数。

多重继承

类和接口之间最著名和最重要的区别在于继承。一次只能扩展一个类,可是能够实现任何数量的接口。Java 8中默认方法的引入使得必须指出,该功能的简化版本(多重继承)已最终引入该语言中。

咱们讨论简化是由于类只能从接口继承其功能部分(方法)。

除了接口能够声明的任何静态常量以外,它们都不能继承数据。可是,在其余语言中,定义了彻底的多重继承,这带来了复杂的规则来管理由其实现引发的问题。

甚至能够在Java 8以前实现多个接口,可是继承的抽象方法老是必须被重写。随着默认方法的出现,多重继承的含义与过去有所不一样。若是咱们考虑如下接口:

public interface Reader {
    default void read (Book book) {
        System. out. println("I'm reading: “ + book.getTitle() + ” by ”
              +book.getAuthor() :
    }
}
public interface Programmer {
    def ault void program(String language){
        System.out. println("I'm programming with” + language) ;
    } 
}

咱们能够建立如下实现两个接口并继承其方法的类:

public class WhosReading implements Reader,Programmer {

}

这是一个使用示例:

public clasz MultipleInheritanceTest {
    public static void main(String args[]) {
        var you = new WhozReading () :
        var javaForAliens = new Book ("Java for Aliens" , "Claudio De Sio Cesari" );
        you. read(javaFor&liens) ;
        you. program ("Java”);
    }
}

经过同时扩展一个类(是否抽象)和一个或多个接口来实现Java中的多个继承是不可能的。

继承适用性

另外一个不为人知的差别涉及继承的适用性。只有一个类能够扩展另外一个类,而其余任何类型的Java都不能作到这一点。相反,接口能够扩展其余接口,但不能扩展类(抽象或具体)。此外,接口能够经过类,枚举和记录来实现,所以也能够利用继承的默认方法。

在Java 14中做为功能预览引入的记录容许以最小的语法定义表示不可变数据的类。它们是不可扩展的,不能扩展类。这是由于在编译时,记录被转换为最终类,从而扩展了该类,所以没法扩展其余类。幸运的是,他们能够实现接口。例如,使用如下Designer界面:

public interface Designer {
    default void design(String tool) {
        System.out.println("I am designing software with“ + tool) :
    }
}

咱们能够Employee经过如下方式建立记录:

public record Employee (String name,int id) implements Designer { }

并将其与如下代码一块儿使用:

Employee serj = new Employee("serj", 10) ;
serj.design("UML”)∶

而后得到输出:

l am designing software with UML

枚举能够实现接口,但不能扩展类,由于在编译时它们被转换为扩展java.lang.Enum该类的类。

结论

随着源自Java 8的接口的发展,抽象类之间的技术差别有所减小。抽象类和接口都不能实例化,抽象类和接口均可以强制子类实现抽象方法。确保还记得其余更集中的区别:

1,除静态和公共常量外,接口没法声明数据。
2,抽象类应该抽象化过于通用而没法实例化的对象,而接口应该抽象出不一样对象能够实现的行为。
3,一次只能扩展一个类,可是能够实现多个接口。
4,类只能由其余类扩展,而接口也能够由枚举和记录实现。

更多Java基础学习能够加入个人十年Java学习园地