接口与抽象类有什么区别?


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

抽象类 如果某个类满足以下条件,则将其视为抽象类:

由abstract修饰符声明 无法实例化 可以声明抽象方法(即,使用abstract修饰符声明的其他方法) 此外,没有什么可以阻止抽象类实现其所有方法。一个抽象类不需要至少一个抽象方法,但是如果一个类包含一个抽象方法,则必须将其声明为abstract。

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

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

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

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

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

如果没有扩展抽象类的意图,就没有理由声明它。此外,必须记住抽象类代表的概念对于定义它们的上下文来说太通用了。特别是,abstract修饰符强加了一个重要的设计约束:即使该类定义了其自己的所有方法,也无法实例化该类。必须通过利用继承和多态性的好处,用具体的类和子类来扩展它。

泛型对象 假设我们要创建一个应用程序,使您可以创作和播放音乐。该应用程序必须定义虚拟乐器,即模拟真实乐器的软件。为此,我们可以创建乐器的层次结构,通过该层次结构,我们可以在下面进行报告:

public abstract class Instrument{ // Abstract Class
    private String name;
    private String type;
    public abstract void play(String note); /*Each instrument sounds different!
                                            *Impossible to define this
                                            *method!*/
    //Rest of code omitted

}

public class Guitar extends Instrument {// Concrete class
    private int numberOfStrings;

    @Override
    public void play(String note){// Override of the inherited method
        //Method implementation for a guitar
    }
    // Rest of code omitted

}

public abstract class WindInstrument extends Instrument { /* Abstract class
                                                            * extending the
                                                            * Instrument class*/
    private String material;

    /* Method "play" inherited abstract and not overridden because it is too
     * generic! */

    //Rest of code omitted

}

public class Flute extends WindInstrument { /* Concrete classe extending 
                                             *WindInstrument */

    @Override
    public void play(String note){
        // Method implementation for a flute.
    }
    //Rest of code omitted
}

在此示例中,我们看到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);
    }
    //Rest of code omitted
}

多态性促进了与我们软件的交互,因为我们现在将始终使用相同的方法来演奏任何乐器。

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

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

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

查看以下接口示例:

public interface Weighable{
    String UNIT_OF_MEASURE ="kg";
    double getWeight();
}

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

public class Item implements Weighable{
    private double weight;
    private String description;

    public Item(String description,double weight){
        setDescription(description);
        setWeight(weight);
    }

    public double getWeight(){
        return weight;
    }
    //Rest of code omitted
}

接口是抽象类概念的演变。

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

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

Difference in the Declaration 前面的示例是Java 8之前的接口的出色示例,但是接口名称已失去其原始含义。尽管总是可以通过仅声明抽象方法来使用接口,但是默认和静态方法的引入意味着就声明而言,接口现在几乎等同于抽象类。

如果我们回顾过去使用interface关键字而不是 abstract class,同时承认接口修饰符通常是由编译器隐式推断的,则唯一重要的区别是接口无法声明实例变量和构造函数。

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

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

public abstract class Vehicle{
    private String description;

    public abstract void accelerate();
    public abstract void decelerate();

    // constructors and setters and getters methods omitted
}

因此,我们可以将Vehicle类扩展到Plane上一个示例中的类,该示例将具有acceleratedecelerate方法的自己的重写实现:

public class Plane extends Vehicle{
    @Override
    public void accelerate(){
        // override of the inherited method
        //implementation omitted
    }
}

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

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

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

每个必须抽象飞行对象(例如飞机,无人机甚至鸟)的类都必须实现该Flying接口。这意味着我们可以Plane按以下方式重写该类:

public class 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 class
    }
    @Override
    public void decelerate
        //overrides the method of the Vehicle abstract class
        }
}

1614342853476.png

然后,我们可以创建多态参数以利用该Flying接口:

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

    public void authorzeTakeOff(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{
    defaule void program(String language){
        System.out.println("I'm programming with" + language);
    }
}

我们可以创建以下实现两个接口并继承其方法的类:

public class WhosReading implements Reader, Programmer{

}

这是一个使用示例:

public class MultipleInheritanceTest{
    public static void main(String args[]){
        var you = new WhosReading();
        var javaForAliens = new Book("java for Aliens","Claudio De Sio Cesari");
        you.read(javaForAliens);
        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");

然后获得输出:

I am designing software with UML

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

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

  • 除静态和公共常量外,接口无法声明数据。
  • 抽象类应该抽象化过于通用而无法实例化的对象,而接口应该抽象出不同对象可以实现的行为。
  • 一次只能扩展一个类,但是可以实现多个接口。
  • 类只能由其他类扩展,而接口也可以由枚举和记录实现。


原文链接:https://codingdict.com/