一篇文章解决小白对于 final 的一切疑问!!!(巨详细且完整)

还是老样子,懂小编的人应该知道我的风格。

①我喜欢有针对性(带着问题)的学习一个新的知识,用最少的文字内容解决清楚,

②再用非常贴合的例子通俗易懂的解释让复杂的概念简单化,不再那么陌生(让人产生抵触感)。

③最后配上一串具体的代码案例让大家在实践中体会究竟如何使用。

final 是 Java 中的一个关键字,它不仅可以用于限制、也可以用于方法变量修改或扩展

使用 final 可以确保某些部分的代码在定义后不会被改变,这有助于增强代码的安全性和稳定性。下面我将分别解释 final 修饰类、方法和变量的情况,

首先final是什么?

final 这个词本身意味着“最终的”、“不可改变的”。

在编程中,当你把某个东西声明为 final,你就是在告诉编译器(或运行时环境)这个东西一旦被定义就不能再改变了

好处(解决了什么实际中遇到的问题?):这种特性可以用来保护数据的完整性防止意外或恶意的修改,同时也可以优化性能。

一,final 修饰类
含义: 当一个类被声明为 final 时,意味着这个类不能被其他类继承。也就是说,不能有子类从这个类派生出来。

为什么使用: 使用 final 修饰类的主要目的是为了防止类的扩展,确保类的行为和状态不被子类修改或覆盖。

这通常用于创建不可变对象或工具类,这些类不应该被外部修改或扩展,以保证其功能的稳定性和安全性。

举个栗子!!!

你要开发一款金融应用,其中有一个类叫做 MoneyTransferService,它负责处理用户的资金转账。由于涉及到金钱交易,安全性和准确性是至关重要的。你希望确保没有人能够通过继承这个类来改变其行为,例如绕过安全检查或者篡改转账逻辑。为了实现这一点,你可以将这个类声明为 final。

// 定义一个 final 类,防止任何类继承它
public final class MoneyTransferService {

    // 私有构造函数,防止实例化(如果需要单例模式)
    private MoneyTransferService() {
        // 可以在这里执行一些初始化操作
    }

    // 静态方法用于获取服务的唯一实例(单例模式)
    public static MoneyTransferService getInstance() {
        return SingletonHolder.INSTANCE;
    }

    // 执行转账的方法
    public void transferMoney(Account fromAccount, Account toAccount, double amount) {
        if (fromAccount.getBalance() < amount) {
            throw new InsufficientFundsException("Insufficient funds in the account.");
        }
        // 进行转账逻辑,包括安全检查等
        fromAccount.withdraw(amount);
        toAccount.deposit(amount);
        System.out.println("Transferred " + amount + " from " + fromAccount.getId() + " to " + toAccount.getId());
    }

    // 内部静态类实现单例模式
    private static class SingletonHolder {
        private static final MoneyTransferService INSTANCE = new MoneyTransferService();
    }

    // 自定义异常类,表示账户余额不足
    public static class InsufficientFundsException extends RuntimeException {
        public InsufficientFundsException(String message) {
            super(message);
        }
    }
}

// 定义一个简单的 Account 类
class Account {
    private String id;
    private double balance;

    public Account(String id, double initialBalance) {
        this.id = id;
        this.balance = initialBalance;
    }

    public String getId() {
        return id;
    }

    public double getBalance() {
        return balance;
    }

    public void deposit(double amount) {
        balance += amount;
    }

    public void withdraw(double amount) {
        balance -= amount;
    }
}

对这个简单的金融应用代码中需要额外注意的几个点做出解释:

①final 类 MoneyTransferService:通过将 MoneyTransferService 声明为 final,我们确保了它的行为和状态不会被子类修改。这增加了安全性,因为没有人可以通过继承来改变转账逻辑,从而避免潜在的安全漏洞。


②单例模式:MoneyTransferService 使用了单例模式,这意味着在整个应用程序中只会存在一个 MoneyTransferService 的实例。这样可以保证所有转账操作都是由同一个对象处理,进一步增强了系统的稳定性和一致性。


③转账方法 transferMoney:这个方法包含了转账的业务逻辑,包括安全检查(如余额是否充足)。由于 MoneyTransferService 是 final 的,所以这些逻辑是不可变的,确保了每次调用该方法时都遵循相同的规则。


④自定义异常 InsufficientFundsException:这是一个专门用来处理账户余额不足情况的异常类(具体情况还有很多其他非常规情况出现)。通过抛出这个异常,我们可以清晰地传达错误信息,并让调用者知道发生了什么问题。

二,final 修饰方法

含义: 当一个方法被声明为 final 时,意味着这个方法不能被子类重写(override)即使子类继承了这个方法,也不能改变它的实现。

为什么使用: 使用 final 修饰方法是为了锁定方法的实现防止子类改变父类的方法行为

这对于那些定义了关键业务逻辑或者安全敏感操作的方法特别有用,可以确保这些方法的执行方式不会被子类破坏。

举个栗子!!!

你正在开发一款在线支付系统

①其中有一个类叫做 PaymentProcessor,它负责处理用户的支付请求

②在这个类中,有一个方法 processPayment 用于验证支付信息并完成支付。

由于支付涉及到用户的资金安全,你希望确保这个方法的行为不会被任何子类修改,以避免潜在的安全风险或逻辑错误。

// 定义一个父类 PaymentProcessor
public class PaymentProcessor {

    // final 方法,防止子类重写
    public final void processPayment(double amount, String paymentMethod) {
        // 执行支付前的验证
        if (!validatePayment(amount, paymentMethod)) {
            throw new PaymentValidationException("Payment validation failed.");
        }

        // 模拟支付处理逻辑
        System.out.println("Processing payment of " + amount + " using " + paymentMethod);
        // 假设这里还有更多的支付处理逻辑...

        // 支付成功后更新数据库等操作
        updateTransactionStatus("Completed");
    }

    // 私有方法,用于验证支付信息
    private boolean validatePayment(double amount, String paymentMethod) {
        // 模拟支付验证逻辑
        return amount > 0 && (paymentMethod.equals("Credit Card") || paymentMethod.equals("PayPal"));
    }

    // 私有方法,用于更新交易状态
    private void updateTransactionStatus(String status) {
        // 模拟更新交易状态的逻辑
        System.out.println("Transaction status updated to: " + status);
    }

    // 自定义异常类,表示支付验证失败
    public static class PaymentValidationException extends RuntimeException {
        public PaymentValidationException(String message) {
            super(message);
        }
    }
}

// 定义一个子类 CreditCardPaymentProcessor,继承自 PaymentProcessor
public class CreditCardPaymentProcessor extends PaymentProcessor {

    // 尝试重写父类的 final 方法(这将导致编译错误)
    // @Override
    // public void processPayment(double amount, String paymentMethod) {
    //     // 子类无法重写 final 方法
    // }

    // 子类可以添加自己的方法
    public void applyDiscount(double discount) {
        System.out.println("Applying a discount of " + discount);
    }
}

对这个简单的支付系统代码中需要额外注意的几个点做出解释:

①final 方法 processPayment:通过将 processPayment 方法声明为 final,我们确保了它的行为不会被子类修改。这意味着所有继承 PaymentProcessor 的子类都必须使用相同的支付处理逻辑,从而保证了支付过程的安全性和一致性。


②私有方法 validatePayment 和 updateTransactionStatus:这两个方法是 private 的,因此它们只能在 PaymentProcessor 类内部调用。这样可以进一步保护支付验证和状态更新的逻辑不被外部访问或修改。


③自定义异常 PaymentValidationException:这是一个专门用来处理支付验证失败情况的异常类。通过抛出这个异常,我们可以清晰地传达错误信息,并让调用者知道发生了什么问题。


④子类 CreditCardPaymentProcessor:尽管 CreditCardPaymentProcessor 继承了 PaymentProcessor,但它不能重写 processPayment 方法,因为该方法是 final 的。然而,子类仍然可以添加新的方法,如 applyDiscount,来扩展功能而不影响核心支付逻辑。

三,final 修饰变量


含义: 当一个变量被声明为 final 时,意味着这个变量只能被赋值一次,一旦赋值后就不能再改变。对于  “ 基本类型 ”,  这意味着它的值不能改变;

对于  “ 引用类型 ”,  这意味着引用不能指向另一个对象,但对象本身的内部状态是可以改变的(除非该对象本身也是不可变的)。

为什么使用: 使用 final 修饰变量主要是为了创建常量或确保某些字段的不可变性。这有助于提高代码的可读性和可维护性,同时也避免了因意外修改变量而导致的错误。此外,在多线程环境中,final 变量还可以帮助确保线程安全,因为它们的值不会发生变化。

举个栗子!!!

你正在开发一款天气预报应用,其中有一个类叫做 WeatherStation,它负责获取和显示当前的天气信息。在这个类中,有一个 final 变量 STATION_ID,用于存储气象站的唯一标识符。由于每个气象站的ID是固定的,不应该在运行时被修改,因此我们使用 final 来确保这一点。

public class WeatherStation {

    // final 基本类型变量,表示气象站的唯一标识符
    private final int STATION_ID;

    // final 引用类型变量,表示气象站的位置信息
    private final Location location;

    // 构造函数,初始化 final 变量
    public WeatherStation(int stationId, Location location) {
        this.STATION_ID = stationId;
        this.location = location;
    }

    // 获取气象站的 ID
    public int getStationId() {
        return STATION_ID;
    }

    // 获取气象站的位置信息
    public Location getLocation() {
        return location;
    }

    // 模拟获取当前天气的方法
    public void getCurrentWeather() {
        System.out.println("Fetching current weather for station ID: " + STATION_ID);
        // 假设这里有一些逻辑来获取并显示天气信息...
    }
}

// 定义一个简单的 Location 类
class Location {
    private String city;
    private String country;

    public Location(String city, String country) {
        this.city = city;
        this.country = country;
    }

    public String getCity() {
        return city;
    }

    public String getCountry() {
        return country;
    }

    // 允许修改位置信息(注意:这不是最佳实践,只是为了演示 final 引用类型的特性)
    public void setCity(String city) {
        this.city = city;
    }

    public void setCountry(String country) {
        this.country = country;
    }

    @Override
    public String toString() {
        return city + ", " + country;
    }
}

气象应用代码重要几个点解释:

①final  基本类型  变量 STATION_ID:这个变量是一个整数,表示气象站的唯一标识符。由于它是 final 的,一旦在构造函数中被赋值,就无法再修改。这确保了每个 WeatherStation 实例的 STATION_ID 是固定的,避免了意外修改带来的问题。


②final   引用类型  变量 location:这个变量是一个 Location 对象,表示气象站的位置信息。虽然 location 是 final 的,这意味着它不能指向另一个 Location 对象,但 Location 对象本身的内部状态(如城市和国家)是可以改变的。为了确保位置信息的不可变性,通常我们会将 Location 类也设计为不可变的(例如,移除 setCity 和 setCountry 方法)。


③构造函数:在构造函数中,我们对 final 变量进行了初始化。这是允许的,因为 final 变量可以在构造函数中赋值,但之后不能再改变


④获取方法:我们提供了 getStationId 和 getLocation 方法来访问 final 变量的值。这些方法返回的是变量的副本或不可变的视图,以防止外部代码修改这些值

ok,小编的分享到这里了。大家能看到这里也不容易,小编辛辛苦苦写到这里也费劲啦!!

能不能给小编点个关注,大家一起进步啊!!!

-------键盘敲烂,月薪过万!!!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值