抽象类和接口的区别

3,351 阅读7分钟

详细的整理了接口和抽象类的区别。

抽象类和接口的区别

当我们遇到这样一个面试提问的时候首先应该考虑的是:

  • 什么是抽象类,作用是什么
  • 什么接口,作用是什么

抽象类及其作用

抽象类在我们实际开发当中扮演了一个什么样的角色?当我们在开发或者设计一些功能和属性大部分差不多的Activity或者是class的时候,为了避免大量重复的工作,最好的做法就是抽取一个公共的基类,这样做的目的即可以减少重复的代码,又让代码变得简洁,简单。

因此抽象类就是用于抽取,捕捉子类通用共性的一种类。只能用于作为父类,提供给子类继承并且不能被实例化,作为被用来创建继承层级的一种模板。也是多态特性的一种重要表现形式。

下面举个简单的例子帮助理解

class Demo3Abstract {
    public static void main(String[] args) {
        Zi z = new Zi();
        z.print();                  //直接调用子类中的print方法
        z.method();                 //也可以拿到从父类继承的method方法
      
      Fu f= new Zi();               //父类指向子类对象。
      f.print();                    //编译看左边,运行看右边。
     System.out.println(f.i);       //out i0
    
      
      Zi zx=(Zi)f;                  //向下转型
      zx.print1111();               //才能拿到子类特有的方法。
     System.out.println(zx.i);      // out 20
    }
}
abstract class Fu {
  int i=10;
    public abstract void print();   //抽象方法必须有子类重写后使用
    public void method() {          //非抽象方法子类可以直接继承用
        System.out.println("Hello World!");
    }
}
class Zi extends Fu {
  int i=20;
    public void print() {
        System.out.println("Zi");
    }
  public void print1111(){
       System.out.println("Zi111");
  }
}

上述例子说明了,抽象类最重要的特点之一,就是可以Zi类拿到Fu提供的method方法,又拥有了自己的特性(print方法)。这样就大大减少了代码的冗余。但是有有一点需要注意的是当我们面向接口编程的时候,当重写了父类中的抽象方法的时候,编译时,找的父类的方法体,倒是实际上在运行过程中,使用的是子类的实现方法。这是一种动态绑定实现机制,也是Java语言的重要基石。

多态的弊端在于,当采用面向接口编程的过程中,当需要到子类的特性的时候,就必须向下转型,这样才能拿到子类的特性(特有的方法)。这其实也很好理解,因为在内存的存储中,Zi类继承Fu,在Zi类的内存中,有一部分是保存有Fu类的相关数据的地址值的,所以我们在才能对代码实现复用,但是面向接口编程的时候,是从Fu类的内存中去找值的,所以f.1zx.i值完全是不一样,当我们想要拿到子类值的时候,只能向下转型才能拿到20,否则就只能拿到父类的成员变量里面的10。换句话说,只有父类和子类二者重写方法之间,存在动态绑定的过程,当时其他方面(比如说成员变量),是不存在动态绑定的,他们是有各自存有的区域。这点需要特别注意。

抽象类的一些特性

  • 抽象类不能被实例化,但可以有构造函数
  • 抽象方法必须由子类进行重写
  • 只要包含一个抽象方法的类,就必须定义为抽象类,不管是否还包含其他方法
  • 抽象类中可以包含具体的方法,也可以不包含抽象方法
  • 抽象类可以包含普通成员变量,其访问类型可以任意
  • 抽象类也可以包含静态成员变量,其访问类型可以任意
  • 子类中的抽象方法不能与父类的抽象方法同名
  • abstract不能与private、static、final或native并列修饰同一个方法

接口的及其作用

接口是抽象方法的集合。如果一个类实现了某个接口,那么它就继承了这个接口的抽象方法。这就像契约模式,如果实现了这个接口,那么就必须确保使用这些方法。接口只是一种形式,接口自身不能做任何事情

下面同样是用一个例子来说明

class Demo1Interface {
    public static void main(String[] args) {
        Inter i = new Demo();
        i.print();
    }
}
interface Inter {
    public abstract void print();
     public static final int num = 10;              //接口中所有的变量都是常量
     public abstract void print11();                //接口中所有的方法都是抽象的
    /*public void method() {                //错误: 接口方法不能带有主体
        System.out.println("aaa");          //接口中所有的方法都是抽象的
    }*/
}
class Demo implements Inter {
    public void print() {
        System.out.println("print");
    }
}

正如上面提到的一样,接口之中的方法,所有都是抽象方法并且修饰符只能是public,因为接口本身就是提供的一种规范和约束。当你实现一个接口的时候就必须实现里面所有的方法。这里需要注意的仍然是上述的多态的弊端,再次就不再赘述了。并且不能定义变量,只能定义常量。

接口的一些特性

  • 接口中不能有构造方法。
  • 接口的所有方法自动被声明为public,而且只能为public,如果使用protectedprivate,会导致编译错误。
  • 接口可以定义”成员变量”,而且会自动转为public final static,即常量,而且必须被显式初始化。
  • 接口中的所有方法都是抽象方法,不能包含实现的方法,也不能包含静态方法
  • 实现接口的非抽象类必须实现接口的所有方法,而抽象类不需要
  • 不能使用new来实现化接口,但可以声明一个接口变量,它必须引用一个实现该接口的类的对象,可以使用instanceOf来判断一个类是否实现了某个接口,如if (object instanceOf ClassName){doSth()};
  • 在实现多接口的时候一定要注意方法名的重复

抽象类和接口的区别

有了上述的知识储备,我想我们终于可以来回答一下这二者之间的区别了。

参数 抽象类 接口
默认的方法实现 它可以有默认的方法实现 接口完全是抽象的。它根本不存在方法的实现
关键字 子类使用extends关键字来继承抽象类。如果子类不是抽象类的话,它需要提供抽象类中所有声明的方法的实现。 子类使用关键字implements来实现接口。它需要提供接口中所有声明的方法的实现
构造器 抽象类可以有构造器 接口不能有构造器
与正常Java类的区别 除了你不能实例化抽象类之外,它和普通Java类没有任何区别 接口是完全不同的类型
访问修饰符 抽象方法可以有publicprotecteddefault这些修饰符 接口方法默认修饰符是public。你不可以使用其它修饰符。
main方法 抽象方法可以有main方法并且我们可以运行它 接口没有main方法,因此我们不能运行它。
多继承 抽象类只可以继承一个类和实现多个接口 接口和接口之间是可以多继承或者单继承多实现的。
速度 它比接口速度要快 接口是稍微有点慢的,因为它需要时间去寻找在类中实现的方法。
添加新方法 如果你往抽象类中添加新的方法,你可以给它提供默认的实现。因此你不需要改变你现在的代码。 如果你往接口中添加方法,那么你必须改变实现该接口的类。
设计理念 is-a的关系,体现的是一种关系的延续 like-a体现的是一种功能的扩展关系

具体使用的场景

  • 如果你拥有一些方法并且想让它们中的一些有默认实现,那么使用抽象类吧。
  • 如果你想实现多重继承,那么你必须使用接口。由于Java不支持多继承,子类不能够继承多个类,但可以实现多个接口。因此你就可以使用接口来解决它。
  • 如果基本功能在不断改变,那么就需要使用抽象类。如果不断改变基本功能并且使用接口,那么就需要改变所有实现了该接口的类。
  • 多用组合,少用继承。

参考资料