Java抽象类和接口的区别

向东       2024/11/29       技术       共4378字       103

相同点

  • 抽象类和接口都是对事物的抽象,都用于隐藏实现细节、定义接口、解耦系统
  • 抽象类和接口都不能被实例化,都需要通过字类(实现类)来创建实例
  • 抽象类和接口都包含抽象方法,且字类都需要去实现这些抽象方法。接口的抽象方法是隐式的,不用写关键字abstract
  • 抽象类和接口都可以被扩展,抽象类可以被子类继承(extends),接口可以被实现类实现(implement
  • 抽象类和接口都是多态的一种实现,也就是说子类(实现类)在创建对象的时候,可以用父类(接口)的类型进行接收

区别

语法层面的区别

  • 关键词:

    • 抽象类用abstract关键词定义
    • 接口用interface关键词定义
  • 继承与实现

    • 抽象类可以继承另一个抽象类或普通类,一个类只能继承一个抽象类(单继承)
    • 接口可以继承多个接口,一个类可以实现多个接口(多继承)
  • 方法

    • 抽象类可以包含抽象方法(无方法体)、普通方法(有方法体)、方法可以是任意访问符号
    • jdk1.8之前,接口中只能有抽象方法(无方法体,可以不写abstract关键词),1.8之后可以有带方法体的default方法和static方法
  • 字段(成员变量)

    • 抽象类可以定义是实例变量和静态变量,变量可以用任意权限修饰符修饰(privateprotectedpublic
    • 接口中只能定义常量,而且默认给你添加了public static final修饰符,且必须进行初始化
  • 构造器

    • 抽象类可以有构造器,用于被字类调用以初始化字段
    • 接口不能有构造器,也不可以直接创建对象
  • 设计目的

    • 抽象类表达的是is a的关系,用于定义通用的特性(属性+行为),并提供部分实现(比如Animal定义共有字段name,共有方法eat())。
    • 接口表达的是has a的关系,用于定义行为能力的扩展(比如Flyable表示飞行能力,Swimmable表示游泳能力)。

设计层面的区别

抽象类是对一种事物的抽象,即对类的抽象,继承抽象类的子类和抽象类本身是一种is a的关系。而接口是对行为的抽象,实现类与接口属于是hash a的关系。抽象类是对整个类的整体抽象,包括属性和行为,但是接口是一种对类的局部抽象,只对行为进行抽象。

举个例子,飞机和鸟是不同的事物,他们都有一个共同的行为:会飞。所以你就可以把飞机和鸟分别定义一个抽象类:AirplaneBird。把他们都会的能力定义一个接口:Fly。

战斗飞机和民用飞机都属于飞机这一种事物,所以这俩字类可以继承Airplane抽象类,然后去实现Fly接口。如果一个类继承了某个抽象类,则子类必定是抽象类的种类,而接口实现则是有没有、具备不具备的关系,比如鸟是否能飞(或者是否具备飞行这个特点),能飞行则可以实现这个接口,不能飞行就不实现这个接口。

接口是对类的某种行为的一种抽象,接口和类之间并没有很强的关联关系,举个例子来说,所有的类都可以实现 Serializable 接口,从而具有序列化的功能,但不能说所有的类和 Serializable 之间是 is-a 的关系。

抽象类作为很多子类的父类,它是一种模板式设计。而接口是一种行为规范,它是一种辐射式设计。什么是模板式设计?最简单例子,大家都用过 ppt 里面的模板,如果用模板 A 设计了 ppt B 和 ppt C,ppt B 和 ppt C 公共的部分就是模板 A 了,如果它们的公共部分需要改动,则只需要改动模板 A 就可以了,不需要重新对 ppt B 和 ppt C 进行改动。

辐射式设计,比如某个电梯都装了某种报警器,一旦要更新报警器,就必须全部更新。也就是说对于抽象类,如果需要添加新的方法,可以直接在抽象类中添加具体的实现,子类可以不进行变更;而对于接口则不行,如果接口进行了变更,则所有实现这个接口的类都必须进行相应的改动。

抽象类的使用场景

第一种场景:代码复用

当我们希望一些通用的功能被多个子类复用的时候,就可以使用抽象类。比如说,AbstractPlayer 抽象类中有一个普通的方法 sleep(),表明所有运动员都需要休息,那么这个方法就可以被子类复用。

abstract class AbstractPlayer {
    public void sleep() {
        System.out.println("运动员也要休息而不是挑战极限");
    }
}

子类 BasketballPlayer 继承了 AbstractPlayer 类:

class BasketballPlayer extends AbstractPlayer {
}

也就拥有了 sleep() 方法。BasketballPlayer 的对象可以直接调用父类的 sleep() 方法:

BasketballPlayer basketballPlayer = new BasketballPlayer();
basketballPlayer.sleep();

子类 FootballPlayer 继承了 AbstractPlayer 类:

class FootballPlayer extends AbstractPlayer {
}

也拥有了 sleep() 方法,FootballPlayer 的对象也可以直接调用父类的 sleep() 方法:

FootballPlayer footballPlayer = new FootballPlayer();
footballPlayer.sleep();

这样是不是就实现了代码的复用呢?

第二种场景:接口扩展

当我们需要在抽象类中定义好 API,然后在子类中扩展实现的时候就可以使用抽象类。比如说,AbstractPlayer 抽象类中定义了一个抽象方法 play(),表明所有运动员都可以从事某项运动,但需要对应子类去扩展实现,表明篮球运动员打篮球,足球运动员踢足球。

abstract class AbstractPlayer {
    abstract void play();
}

BasketballPlayer 继承了 AbstractPlayer 类,扩展实现了自己的 play() 方法。

public class BasketballPlayer extends AbstractPlayer {
    @Override
    void play() {
        System.out.println("我是张伯伦,我篮球场上得过 100 分,");
    }
}

FootballPlayer 继承了 AbstractPlayer 类,扩展实现了自己的 play() 方法。

public class FootballPlayer extends AbstractPlayer {
    @Override
    void play() {
        System.out.println("我是C罗,我能接住任意高度的头球");
    }
}

代码示例

加入现在有一个1.txt文本文件,里面每一行都是一个英文单词,现在需要使用大写和小写两种模式进行内容读取和输出。

  • 1.txt文件中的内容

    hello
    word
  • 首先定义抽象类AbstractReader.java,并在里面定义属性、普通方法、抽象方法

    package com.cryptogram.abstractDemo;
    
    import java.io.BufferedReader;
    import java.io.FileReader;
    import java.io.IOException;
    
    /**
    * 定义一个抽象类
    * 用于定义内容读取的公共方法和规范
    */
    abstract public class AbstractReader {
    
      /**
       * 实例属性,可用任意权限字符修饰
       */
      public String filePath;
    
      /**
       * 构造方法传入文件路径
       * 字类需要写构造方法,并用super传递filePath参数
       * @param filePath 待读取文件路径
       */
      public AbstractReader(String filePath){
          this.filePath = filePath;
      }
    
      /**
       * 普通方法,含方法体
       * 内容解析
       */
      public void parse(){
          try (BufferedReader br = new BufferedReader(new FileReader(filePath))) {
              String line;
              while ((line = br.readLine()) != null) { // 逐行读取
                  // printPattern需要由字类扩展定义
                  System.out.println(printPattern(line));
              }
          } catch (IOException e) {
              e.printStackTrace();
          }
      }
    
      /**
       * 抽象方法
       * 定义输出模式:大写输出还是小写输出
       * @param str 待输出的原内容
       * @return 格式化后的内容
       */
      public abstract String printPattern(String str);
    }
    
  • 定义大写输出的类UpperReader.java,并继承抽象类

    package com.cryptogram.abstractDemo;
    
    public class UpperReader extends AbstractReader {
      public UpperReader(String filePath){
          super(filePath);
      }
    
      @Override
      public String printPattern(String str){
          return str.toUpperCase();
      }
    }
  • 定义小写输出的类LowerReader.java,并继承抽象类

    package com.cryptogram.abstractDemo;
    
    public class LowerReader extends AbstractReader {
    
      public LowerReader(String filePath){
          super(filePath);
      }
    
      @Override
      public String printPattern(String str){
          return str.toLowerCase();
      }
    }
    
  • 实际应用

    package com.cryptogram.abstractDemo;
    
    public class App {
      public static void main(String[] args) {
          String filePath = "E:\\Projects\\04-LearningProjects\\LearningDemos\\src\\main\\java\\com\\mengxiangdong\\cryptogram\\abstractDemo\\1.txt";
          AbstractReader lowerReader = new LowerReader(filePath);
          lowerReader.parse();
          System.out.println("=========");
          AbstractReader upperReader = new UpperReader(filePath);
          upperReader.parse();
      }
    }
    
  • 输出

    hello
    word
    =========
    HELLO
    WORD

除非注明,向东的笔记本文章均为原创,本文地址 https://www.mengxiangdong.com/jishu/187.html,转载请以链接形式注明出处。

作者: 简介:

《Java抽象类和接口的区别》留言数:0

发表留言