java基础02—接口与抽象类的区别

接口与抽象类定义、语法区别

1. 关键字

  • 接口使用关键字interface定义。
  • 抽象类使用关键字abstract class定义。

2. 方法定义

  • 接口中的方法默认是publicabstract的,即使不显式声明。这意味着接口中的所有方法都是抽象方法,不能有方法体(除非是Java 8引入的默认方法default或静态方法static)。
    interface MyInterface {
        void method1(); // 默认public abstract
        
        default void method2() {
            // Java 8之后引入的默认方法
        }
        
        static void method3() {
            // Java 8之后引入的静态方法
        }
    }
    
  • 抽象类可以包含抽象方法和非抽象方法。抽象方法没有方法体,而非抽象方法可以有具体实现。
    abstract class MyAbstractClass {
        abstract void method1(); // 抽象方法,没有方法体
        
        void method2() {
            // 非抽象方法,有方法体
        }
    }
    

3. 变量

  • 接口中的变量默认是public static final的,这意味着它们是常量,必须初始化。
    interface MyInterface {
        int CONSTANT = 10; // 相当于 public static final int CONSTANT = 10;
    }
    
  • 抽象类中的变量可以是任何访问修饰符(private, protected, public),可以是静态的或非静态的,也可以是常量或变量。
    abstract class MyAbstractClass {
        protected int value;
        public static final int CONSTANT = 10;
    }
    

4. 继承/实现关系

  • 接口可以通过implements关键字被类实现,一个类可以实现多个接口。
    class MyClass implements MyInterface1, MyInterface2 {
        // 实现接口中的抽象方法
    }
    
  • 抽象类可以通过extends关键字被其他类继承,一个类只能继承一个抽象类(单继承),但可以实现多个接口。
    class MyClass extends MyAbstractClass {
        // 实现抽象类中的抽象方法
    }
    

5. 构造方法

  • 接口不能包含构造方法,因为它们不能被实例化。
  • 抽象类可以包含构造方法,尽管抽象类本身不能被实例化,但它的子类可以通过调用超类的构造方法来初始化超类的成员变量。
    abstract class MyAbstractClass {
        MyAbstractClass(int value) {
            this.value = value;
        }
    }
    

总结:Java接口和抽象类虽然都可以用于定义方法和行为的模板,但在设计和使用上有着不同的侧重点。

接口与抽象类使用场景区别

接口的使用场景

  1. 定义行为规范而不关心实现:

    • 接口适用于定义一组方法,任何实现类都可以实现这些方法,而接口本身不关心实现细节。接口提供了一种行为规范,使得不同的类能够共享相同的行为接口。
    • 例如:List, Set, Map等Java集合接口定义了集合的基本操作,不同的实现(如ArrayList, HashSet, HashMap)以不同的方式提供这些操作的实现。
  2. 实现多重继承:

    • Java不支持类的多重继承(一个类只能继承一个父类),但可以通过实现多个接口来实现多重继承的效果。通过接口,一个类可以从多个父接口继承行为。
    • 例如:一个类可以同时实现ComparableSerializable接口,从而具备比较和序列化的能力。
  3. 实现松耦合:

    • 接口有助于解耦系统中的组件,使得代码更易维护和扩展。通过接口,客户端代码依赖于接口而不是具体实现,便于替换实现。
    • 例如:使用依赖注入(Dependency Injection)时,可以通过接口注入具体的实现,使得系统更加灵活。
  4. 定义API或插件体系:

    • 当你需要为外部系统、插件、或库提供一套可扩展的API时,接口是首选。接口允许其他开发者按照规范提供自己的实现,从而扩展你的系统。
    • 例如:在框架开发中,常通过接口定义扩展点,如ServiceProvider接口,使得外部插件能够轻松接入。

抽象类的使用场景

  1. 为一组相关类提供共同行为和状态:

    • 当你有一组相关的类,它们共享一些共同行为和状态时,可以使用抽象类。抽象类允许你定义带有方法实现的类,同时提供抽象方法让子类实现。
    • 例如:动物类(Animal)可以作为一个抽象类,其中定义了动物的共同行为(如吃、睡),但具体的叫声或移动方式由子类(如Dog, Cat)决定。
  2. 部分实现共享逻辑:

    • 当你希望为一组子类提供部分实现时,可以使用抽象类。抽象类允许你在父类中定义公共的方法实现,避免子类重复代码。
    • 例如:模板方法模式(Template Method Pattern)通常使用抽象类定义算法的步骤,而子类可以覆盖特定的步骤实现。
  3. 继承代码的重用:

    • 当有一组类需要共享代码(字段或方法),而不仅仅是行为定义时,抽象类是更好的选择。抽象类可以定义成员变量和方法实现,子类可以直接继承这些成员和方法。
    • 例如:在设计文件处理系统时,可以有一个抽象类FileProcessor,包含处理文件的通用方法(如打开、关闭文件),具体的文件类型处理逻辑由子类实现。
  4. 限制类的使用:

    • 如果你希望一个类只能被继承而不能直接实例化,可以使用抽象类。抽象类本身不能被实例化,只能通过子类来实现和使用。
    • 例如:你可能希望某个核心类只能由框架内部继承使用,避免外部直接实例化。

总结:当你需要定义一组不依赖于具体实现的行为,或者需要让类实现多个行为时,选择接口。
当你有一组相关类需要共享代码和状态,或者你希望部分实现行为时,选择抽象类。
通常,如果仅仅是为了定义行为规范而不涉及实现细节,优先考虑接口;而当涉及到共享代码、状态、或部分实现时,抽象类是更好的选择。

通过接口和抽象类模拟实现短信发送

需求描述

  • 设计一个短信发送系统,支持多种短信通道(如阿里云、腾讯云)。
  • 用户调用一个统一的发送接口,服务端决定用哪个通道发送短信。
  • 不在代码中使用大量的if-else语句来判断使用哪个通道,而是通过面向对象的设计来实现这一功能。

1. 定义接口

定义发送短信的行为规范,所有的短信发送通道都必须实现这个接口。

public interface SmsSender {
    void sendSms(String message, String recipient);
}

2. 抽象类实现接口

实现SmsSender接口,并提供一个log方法,供所有子类共享。sendSms方法在抽象类中被定义为抽象方法,由具体的子类实现。

public abstract class AbstractSmsSender implements SmsSender {
    
    // 公共方法,可以在子类中使用
    protected void log(String message) {
        System.out.println("Log: " + message);
    }
    
    // 定义发送短信的模板方法,由子类具体实现
    @Override
    public abstract void sendSms(String message, String recipient);
}

3. 子类实现具体通道

继承自AbstractSmsSender,并实现各自的短信发送逻辑。通过继承抽象类,它们共享了日志记录的功能。

public class AliyunSmsSender extends AbstractSmsSender {

    @Override
    public void sendSms(String message, String recipient) {
        // 调用公共方法
        log("Sending SMS via Aliyun: " + message + " to " + recipient);
        
        // 阿里云的具体实现逻辑
        System.out.println("Aliyun SMS sent to " + recipient + ": " + message);
    }
}

public class TencentSmsSender extends AbstractSmsSender {

    @Override
    public void sendSms(String message, String recipient) {
        // 调用公共方法
        log("Sending SMS via Tencent: " + message + " to " + recipient);
        
        // 腾讯云的具体实现逻辑
        System.out.println("Tencent SMS sent to " + recipient + ": " + message);
    }
}

4. 工厂类(使用策略模式)

通过Map维护不同短信发送器的实例,并根据提供的通道名称返回相应的SmsSender实现。这样避免了if-else语句,增加了系统的扩展性。

import java.util.HashMap;
import java.util.Map;

public class SmsSenderFactory {

    // 使用Map来存储不同的短信发送器实例
    private static final Map<String, SmsSender> smsSenderMap = new HashMap<>();

    static {
        // 注册不同的短信发送实现类
        smsSenderMap.put("aliyun", new AliyunSmsSender());
        smsSenderMap.put("tencent", new TencentSmsSender());
        // 其他短信发送通道可以在这里注册
    }

    // 根据提供的通道名称返回相应的短信发送器
    public static SmsSender getSmsSender(String provider) {
        SmsSender sender = smsSenderMap.get(provider.toLowerCase());
        if (sender == null) {
            throw new IllegalArgumentException("Unknown provider: " + provider);
        }
        return sender;
    }
}

5. 客户端代码

负责调用短信发送功能。客户端只需要提供通道名称(如“aliyun”或“tencent”),其余工作由服务端负责。

@Service
public class SmsService {
	@Autowired
    private SmsSender smsSender;

    public SmsService(String provider) {
        this.smsSender = SmsSenderFactory.getSmsSender(provider);
    }

    public void sendSms(String message, String recipient) {
        smsSender.sendSms(message, recipient);
    }

    public static void main(String[] args) {
        SmsService service = new SmsService("aliyun");
        service.sendSms("Hello, Aliyun!", "1234567890");

        service = new SmsService("tencent");
        service.sendSms("Hello, Tencent!", "0987654321");
    }
}

注:上述内容参考自网络和ChatGPT

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值