接口和抽象类的区别

很多常见的面试题都会出诸如抽象类和接口有什么区别,什么情况下会使用抽象类和什么情况你会使用接口这样的问题。本文我们将仔细讨论这些话题。

接口(interface)可以说成是抽象类的一种特例,接口中的所有方法都必须是抽象的。接口中的方法定义默认为public abstract类型,接口中的成员变量类型默认为public static final。另外,接口和抽象类在方法上有区别:    
1.抽象类可以有构造方法,接口中不能有构造方法。  
2.抽象类中可以包含非抽象的普通方法,接口中的所有方法必须都是抽象的,不能有非抽象的普通方法。
3.抽象类中可以有普通成员变量,接口中没有普通成员变量 
4. 抽象类中的抽象方法的访问类型可以是public,protected和默认类型
5. 抽象类中可以包含静态方法,接口中不能包含静态方法
6. 抽象类和接口中都可以包含静态成员变量,抽象类中的静态成员变量的访问类型可以任意,但接口中定义的变量只能是public static final类型,并且默认即为public static final类型
7. 一个类可以实现多个接口,但只能继承一个抽象类。
二者在应用方面也有一定的区别:
接口更多的是在系统架构设计方法发挥作用,主要用于定义模块之间的通信契约
而抽象类在代码实现方面发挥作用,可以实现代码的重用,例如,模板方法设计模式是抽象类的一个典型应用,假设某个项目的所有Servlet类都要用相同的方式进行权限判断、记录访问日志和处理异常,那么就可以定义一个抽象的基类,让所有的Servlet都继承这个抽象基类,在抽象基类的service方法中完成权限判断、记录访问日志和处理异常的代码,在各个子类中只是完成各自的业务逻辑代码。


1.抽象类可以有构造方法,接口中不能有构造方法。 

在抽象类中可以有构造方法,只是不能直接创建抽象类的实例对象,但实例化子类的时候,就会初始化父类,不管父类是不是抽象类都会调用父类的构造方法初始化一个类,先初始化父类。

在接口里写入构造方法时,编译器提示:Interfaces cannot have constructors

A. 构造方法用于初始化成员变量,但是接口成员变量是常量,无需修改。接口是一种规范,被调用时,主要关注的是里边的方法,而方法是不需要初始化的,

B. 类可以实现多个接口,若多个接口都有自己的构造器,则不好决定构造器链的调用次序

C. 构造器是属于类自己的,不能继承。因为是纯虚的,接口不需要构造器。


3.抽象类中可以有普通成员变量,接口中没有普通成员变量

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

首先明白一个原理,就是接口的存在意义。接口就是为了实现多继承的抽象类,是一种高度抽象的模板、标准或者说协议。规定了什么东西该是这样,如果你继承了我这接口,就必须这样。比如USB接口,就是小方口,两根电源线和两根数据线,不能多不能少。

1public

 既然是公共的模板或者协议,那么如果定义成private就没有意义了,因为所有继承了你这接口的类都不能用,并且接口中的方法是不能够被具体实现的,因此,接口内部中也没有任何方法可使用。因此,为了让所有实现了该接口的类能够使用,就必须是public的。接口中定义的所有东西就应该是对所有用户开放的东西。

 

2static

所谓static,顾名思义,它代表着静态,静止不动的,都知道JVM内存中除了程序计数器,堆内存,栈内存,还有一块不大的内存区,叫做静态存储区。这块区域就是用来存储所有静态的方法和变量的。我想,java一开始设计static的原因也许是不想和实例捆绑在一起。你想想,java的主类中一定会有个public static main()方法,那是程序的执行入口。当初我在想,为什么一定要有个static呢?如果没有static,那么就必须先实例化main方法所在类对象,可是在哪实例化呢?JVM这样就找不到入口,因为没有static就必须先实例化分配内存后,才有main方法。如此死循环。

有了static就不一样了,JVM加载类的时候,首先开始加载的就是静态的成员变量和方法。而main()方法就在里面,JVM不需要加载任何实例对象,就能开始执行了,因此main方法是必须static的。

好,罗嗦了。回到题目,如果接口中的成员变量是非静态的,那么每一个实现了该接口的类都会有这么一个变量。那么,因为接口是多继承的,那么如果另一个接口也是有同样这样一个变量呢,那你用哪一个?所以,因为是标准,所以我规定从一开始,这个东西只能有一份,只能放在静态存储区,如果第二个接口也想同命名这么一个变量,那么存储时候就会报错,因为我静态存储区已经有一份了。你改名吧。

 

 

3final

 想想,如果不是final的,那么意味着每一个实现了该接口的子类都可以去修改这个变量。我们开头说了,接口就是标准规范,也改也只能是制定该接口的架构师来改,如果某类随便改的话,那么其他也继承了该接口的类就会受到影响。牵一发而动全身!!因此,既然是标准,那么就不能改,方便管理。

 

最后归纳:

  • public是因为接口是标准,必须对外完全开放,自己藏着掖着没意义;
  • static是因为要确保该变量只有一份,避免重名;
  • final是因为接口的东西是大家共用的,不能随便修改,因此干脆不然你有修改的权限! 

至此,解释完毕。这次分析后,对接口的存在意义更深刻了。


5. 抽象类中可以包含静态方法,接口中不能包含静态方法

抽象方法是不能使用static进行修饰,有static的方法是不能override的,所以这样定义接口才有意义。

Java 8 对接口做了进一步的增强。

public interface JDK8Interface {  
  
    // static修饰符定义静态方法  
    static void staticMethod() {  
        System.out.println("接口中的静态方法");  
    }  
  
    // default修饰符定义默认方法  
    default void defaultMethod() {  
        System.out.println("接口中的默认方法");  
    }  
}

接口的实现

public class JDK8InterfaceImpl implements JDK8Interface {  
    //实现接口后,因为默认方法不是抽象方法,所以可以不重写,但是如果开发需要,也可以重写  
}  

静态方法,只能通过接口名调用,不可以通过实现类的类名或者实现类的对象调用。default方法,只能通过接口实现类的对象来调用。

public class Main {  
    public static void main(String[] args) {  
        // static方法必须通过接口类调用  
        JDK8Interface.staticMethod();  
  
        //default方法必须通过实现类的对象调用  
        new JDK8InterfaceImpl().defaultMethod();  
    }  
}  

a 在接口中可以添加使用 default 关键字修饰的非抽象方法。即:默认方法(或扩展方法)

b. 接口里可以声明静态方法,并且可以实现。

我们都知道在Java语言的接口中只能定义方法名,而不能包含方法的具体实现代码。接口中定义的方法必须在接口的非抽象子类中实现。下面就是关于接口的一个例子:

public interface SimpleInterface {
  public void doSomeWork();
}
 
class SimpleInterfaceImpl implements SimpleInterface{
  @Override
  public void doSomeWork() {
    System.out.println("Do Some Work implementation in the class");
  }
 
  public static void main(String[] args) {
    SimpleInterfaceImpl simpObj = new SimpleInterfaceImpl();
    simpObj.doSomeWork();
  }
}

那么,如果我们在SimpleInterface里面添加一个新方法,会怎样呢?

public interface SimpleInterface {
  public void doSomeWork();
  public void doSomeOtherWork();
}

如果我们尝试编译上面的这段代码,会得到如下结果:

$javac .\SimpleInterface.java
.\SimpleInterface.java:18: error: SimpleInterfaceImpl is not abstract and does not 
override abstract method doSomeOtherWork() in SimpleInterface
class SimpleInterfaceImpl implements SimpleInterface{
^
1 error

因为接口有这个语法限制,所以要直接改变/扩展接口内的方法变得非常困难。我们在尝试强化Java 8 Collections API,让其支持lambda表达式的时候,就面临了这样的挑战。为了克服这个困难,Java 8中引入了一个新的概念,叫做default方法,也可以称为Defender方法,或者虚拟扩展方法(Virtual extension methods)。

Default方法是指,在接口内部包含了一些默认的方法实现(也就是接口中可以包含方法体,这打破了Java之前版本对接口的语法限制),从而使得接口在进行扩展的时候,不会破坏与接口相关的实现类代码。接下来,让我们看一个例子:

public interface SimpleInterface {
  public void doSomeWork();
 
  //A default method in the interface created using "default" keyword
  //使用default关键字创在interface中直接创建一个default方法,该方法包含了具体的实现代码
  default public void doSomeOtherWork(){
    System.out.println("DoSomeOtherWork implementation in the interface");
  }
}
 
class SimpleInterfaceImpl implements SimpleInterface{
  @Override
  public void doSomeWork() {
    System.out.println("Do Some Work implementation in the class");
  }
  /*
   * Not required to override to provide an implementation 
   * for doSomeOtherWork.
   * 在SimpleInterfaceImpl里,不需要再去实现接口中定义的doSomeOtherWork方法
   */
 
  public static void main(String[] args) {
    SimpleInterfaceImpl simpObj = new SimpleInterfaceImpl();
    simpObj.doSomeWork();
    simpObj.doSomeOtherWork();
  }
}

该程序的输出是:

Do Some Work implementation in the class
DoSomeOtherWork implementation in the interface

现在大家问得比较多的一个问题是:如果一个类实现了两个接口(可以看做是“多继承”),这两个接口又同时都包含了一个名字相同的default方法,那么会发生什么情况? 在这样的情况下,编译器会报错。让我用例子来解释一下:

public interface InterfaceWithDefaultMethod {
  public void someMethod();
  default public void someOtherMethod(){
    System.out.println("Default method implementation in the interface");
  }
}
public interface InterfaceWithAnotherDefMethod {
  default public void someOtherMethod(){
    System.out.println("Default method implementation in the interface");
  }
}
然后我们定义一个类,同时实现以上两个接口:
public class DefaultMethodSample implements
  InterfaceWithDefaultMethod, InterfaceWithAnotherDefMethod{
 
  @Override
  public void someMethod(){
    System.out.println("Some method implementation in the class");
  }
  public static void main(String[] args) {
    DefaultMethodSample def1 = new DefaultMethodSample();
    def1.someMethod();
    def1.someOtherMethod();
  }  
}
如果编译以上的代码,会得到一个编译器错误,如下所示。因为编译器不知道应该在两个同名的default方法中选择哪一个,因此产生了二义性。



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值