夯实基础(java学习)- 抽象类和接口

本文深入探讨面向对象设计中的抽象类、接口、抽象方法及其实现,对比接口与抽象类的区别,阐述面向接口编程的优势。并通过简单工厂模式和命令模式的实例,展示如何在实际编程中应用这些概念。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

抽象类

当编写一些类的时候,都会定义一些方法,在某些情况下,某些父类只是知道子类应该包含某些方法,但是无法准确知道子类方法的实现方式。如图形类的计算周长方法,圆形和矩形拥有不同的计算方法。

但是父类又必须定义一个这样的方法。此时我们可以使用方法来满足该要求。

抽象方法和抽象类

抽象方法和抽象类必须使用abstract修饰符来修饰,有抽象方法的类只能被定义为抽象类,抽象类里面可以没有抽象方法。

抽象方法和抽象类的定义规则如下。

  • 抽象类必须使用abstract修饰符来修饰,抽象方法也必须使用abstract修饰符修饰,抽象方法不能有方法体。
  • 抽象类不能被实例化,无法使用new关键字来调用抽象类的构造器创建抽象类的实例。即使抽象类里不包含抽象方法,这个抽象类也不能创建实例。
  • 抽象类可以包含成员变量、方法(普通方法和抽象方法都可以)、构造器、初始化块、内部类(接口、枚举)五种成分。抽象类的构造器泵用于创建实例,主要用于被子类调用。
  • 含有抽象方法的类只能被定义为抽象类。

抽象类的作用

抽象类不能实现实例,只能当成父类来继承。从语义的角度来说,抽象类是多个具体类抽象出来的父类,它具有更高层次的抽象。从多个具有想用特征的类中抽象出来一个抽象类,以这个类作为其子类的模板,从而避免了子类设计随意性。

模板模式在面对对象的软件中很常用,其原理很简单,实现也很简单。下面是使用模板模式的一些简单规则:

  • 抽象父类可以只定义需要使用的方法,把不能实现的部分抽象成抽象方法,留给其子类去实现。
  • 父类中可能包含需要调用其他方法的方法,这些被调方法既可以由父类实现,也可以由其子类实现。父类里提供的方法只是定义了一个通用的算法,其实现也许并不完全由自身实现,而必须依赖其子类的辅助。

接口

接口的概念

接口时从多个相似类当中抽象出来的规范,接口不提供任何实现。接口体现的时规范和实现分离的设计哲学。

让规范和实现相分离正是接口的好处之一,让软件系统面向接口耦合是一种松耦合的设计。

由于接口一种规范,因此接口的里不能包括构造器和初始化块定义。接口里的成员只能有:成员变量(只能是静态成员变量),接口,方法,内部类。

接口里定义的方法只能时抽象方法、类方法、默认方法或者私有方法,因此如果不是定义默认方法、类方法或者私有方法,系统将会自动为普通方法添加abstract关键词。

接口的继承

接口和类的继承不同,接口可以实现多继承,即一个接口可以有多个直接父类接口。和类继承相似,子接口扩展了某个父接口,将会获得父接口里定义的所有的抽象方法、变量。

一个接口继承多个父类接口的时候,多个父类接口排在extends关键词之后,多个父接口之间以英文逗号隔开。

使用接口

接口不可以用于创建实例,但是可以用来声明引用变量,所声明的引用变量必须是实现了接口的类。除此之外,接口的主要用途就是被实现类实现。归纳起来,接口主要有以下用途:

  • 定义变量,也可以用来强制类型转换
  • 调用接口中定义的常量
  • 被其他类实现

一个类只能继承单个类,但是一个类却可以实现多个接口,继承使用extends实现,实现则是使用implements关键词。当需要实现多个接口的时候,多个接口之间以英文逗号(,)隔开。一个类可以继承一个父类,并同时实现多个接口,implements部分必须放在extends的后面。

一个类实现了一个或者多个接口之后,这个类必须完全实现这些接口所定义的抽象方法。否则该类必须定义为抽象类。

一个类实现某个接口时,该类将会从接口中定义的常量、方法等,因此可以将实现接口看作一种特殊的继承。

实现接口方法时,必须使用public关键词,因为接口当中定义的方法都是public修饰,而实现的类必须要比public更大或者相等,所以必须使用public修饰

接口不能显式的继承任何类,但所有的接口类型的引用变量都可以直接赋给object类型的引用变量,这是向上转型。

接口和抽象类

接口和抽象类很像,他们都具有如下的特征:

  • 接口和抽象类都不能实例化,他们都位于继承树的顶端,用于被其他类实现或者继承。
  • 接口和抽象类都可以包含抽象方法,实现接口或者继承抽象类的普通子类都必须实现这些方法。

但是接口和抽象类的差别非常大,这种差比主要体现在二者的设计目的之上:

接口作为系统与外界交互的窗口,主要体现的是一种规范。对于接口的实现者而言,接口规定了实现者必须向外提供哪些服务,对于接口的调用者来说,接口主要规定了调用者可以使用哪些服务。当在一个程序当中使用接口时,接口是多个模块之间的耦合标准;当在多个应用程序之间使用接口的时候,接口是多个程序之间的通信标准。

从某种程度上来看,接口类似于整个系统中“总纲”,它制定了系统各模块之间应当遵循的标准,因此一个系统当中的接口不应当经常改变,一旦改变其影响应当是辐射性的。

抽象类则不一样,抽象类作为系统当中多个类的共同法国父类,它所体现的是一种模版式设计。抽象类作为多个类的共同抽象父类。可以被当作系统实现过程中的中间产品,这个中间产品已经实现了系统的部分功能(那些已经提供实现的方法),但是这个产品依旧不能当作最终产品。必须要有更进一步的完善,这种完善可能有几种不同方式。

除此之外抽象类还和接口有以下不同:

  • 接口里面只能包括抽象方法,静态方法,默认方法和私有方法,不能为普通方法提供方法实现。
  • 接口里只能定义静态常量,不能定义普通成员变量;抽象类里则即可以定义普通成员变量,也可以定义静态常量
  • 接口里不包含构造器;抽象类里可以包含构造器,抽象类里的构造器并不是用于创建对象,而是用于其子类调用这些构造器完成初始化。
  • 接口里不能包含初始化块;但是抽象类里完全可以包含初始化块。
  • 一个类最多只能有一个直接父类,包括抽象类;但一个类可以直接实现多个接口,通过实现多个接口可以弥补java但继承的不足。

面向接口编程

简单工厂模式

有一个场景:假设程序当中有个computer类需要组合一个输出设备,现在有两个选择:直接让computer类组合一个printer,或者让computer类组合一个Output。

如果是第一种方法,直接在computer类中定义printer类的话,不同厂家的printer可能会有不同的实现方法,也需要computer作出相应的调整或者适配。如果某一厂家的printer出现了问题,那么将会影响到所有computer类,修复起来将会十分复杂。

而如果是第二种方法,在computer类中定义output接口,那么它将不需要知道这个类当中的实现方法,直接调用接口所定义的方法即可。

以下定义一个接口

package com.eight.factory;

public interface Output {
    public void out();    
}

下面定义实现它的类和computer类以及测试类

package com.eight.factory.imp;

import com.eight.factory.Output;

public class HpPrinter implements Output{
    @Override
    public void out() {
        System.out.println("HP Printer is ready");
    }   
}
package com.eight.factory.imp;

import com.eight.factory.Output;

public class Computer {
    private Output printer;
    public Computer(Output printer){
        this.printer = printer;
    }
    public void Output(){
        this.printer.out();
    }
}
import com.eight.factory.Output;
import com.eight.factory.imp.Computer;
import com.eight.factory.imp.HpPrinter;

public class App {
    public static void main(String[] args) throws Exception {
        Output HP = new HpPrinter();
        Computer computer =new Computer(HP);
        computer.Output();
    }
}

输出结果如下

HP Printer is ready

如果不想要使用HP的printer只需要在代码中将HP的变量指定为其他的类型即可。

命令模式

假设如下场景,某个方法需要某种行为,但是这个行为的具体实现方法无法确定,必须等到执行方法时才能确定。具体一点:假设有个方法需要遍历某个数组的数组元素,但无法确定在遍历数组元素的时候如何处理这些元素,需要在调用这个方法的时候指定某种行为。

这个要求有点奇怪,这个方法不仅需要普通的数据可以变化,甚至方法体也要变化,难道把方法体作为参数传输进去?

可以考虑一个command接口来定义一个方法,用这个方法来封装这个行为。

代码已在本地实现,近期更新至博客

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值