0

0

final, finally, finalize 三者有什么不同?

夜晨

夜晨

发布时间:2025-09-04 21:09:01

|

449人浏览过

|

来源于php中文网

原创

final用于定义不可变的变量、方法或类,保障不变性与安全性;finally确保异常处理中资源清理代码的执行;finalize是已被废弃的对象回收前清理方法,因不确定性与性能问题不推荐使用。

final, finally, finalize 三者有什么不同?

final
finally
finalize
这三个词,在Java(以及一些其他编程语言)的语境下,虽然长得像“三兄弟”,但它们各自扮演的角色和背后的设计哲学却截然不同。简单来说,
final
关乎不变性与限制
finally
确保代码的必然执行,而
finalize
则是一个过时且不推荐使用的资源清理机制

解决方案

这三个关键字或方法,尽管在拼写上仅一字之差,但在Java编程中却有着截然不同的用途和生命周期。理解它们的区别,是写出健壮、高效Java代码的基础。

final
关键字:
final
是一个修饰符,它用来声明一个实体是“最终的”,意味着它不能被改变、重写或继承。它的作用范围非常广:

  • 修饰变量: 当一个局部变量、实例变量或静态变量被
    final
    修饰时,它就成了一个常量,一旦被赋值,其值(对于基本类型)或引用(对于对象类型)就不能再改变。这意味着你不能给它重新赋值。对于对象引用,
    final
    只是保证引用本身不变,对象内部的状态依然可以改变(除非对象本身是不可变的)。
  • 修饰方法:
    final
    修饰的方法不能被子类重写(Override)。这通常用于确保某个方法的行为在继承体系中保持一致,或者为了实现某些设计模式(比如模板方法模式)。
  • 修饰类:
    final
    修饰的类不能被继承。这意味着它不能有子类。Java标准库中许多核心类,如
    String
    Integer
    等都是
    final
    类,这通常是为了安全、效率或设计上的完整性考虑。

finally
块:
finally
try-catch-finally
语句结构中的一个代码块。它的核心作用是保证其中的代码无论如何都会被执行,无论
try
块中是否发生异常,或者
try
块是否正常结束。

  • 目的: 主要用于资源清理工作,比如关闭文件流、数据库连接、网络连接等。这样可以避免资源泄漏,即使在程序执行过程中遇到意想不到的错误。
  • 执行时机:
    finally
    块会在
    try
    块和
    catch
    块执行之后,但在
    try
    语句(或
    catch
    语句)返回之前执行。即使
    try
    块中有
    return
    语句,
    finally
    块也会先执行。只有在JVM退出或遇到
    System.exit()
    等极端情况时,
    finally
    块才可能不会执行。

finalize()
方法:
finalize()
java.lang.Object
类中定义的一个方法。它的设计初衷是作为对象被垃圾回收器(Garbage Collector, GC)回收前执行的“遗言”。

  • 目的: 理论上,可以在
    finalize()
    方法中执行一些资源清理操作,比如关闭文件句柄、释放非Java内存资源等。
  • 执行时机: 它是由GC在检测到对象不再被引用时,但在真正销毁对象之前,异步调用的。
  • 问题: 现代Java编程中,
    finalize()
    方法被强烈不推荐使用。原因在于其执行时机不确定、性能开销大、可能导致资源泄漏、甚至可能复活对象等诸多问题。它就像一个不可靠的“后事处理员”,你永远不知道它什么时候来,甚至它可能根本不来。

为什么说
final
是Java中实现不变性(Immutability)的关键?

在我看来,

final
关键字在构建不可变对象(Immutability)方面扮演着一个基石性的角色,它不仅仅是防止变量被重新赋值那么简单,更是一种强大的设计契约。当我们说一个对象是不可变的,通常意味着它的状态在创建之后就不能再改变了。这对于编写并发程序、提高代码可读性和安全性至关重要。

final
变量是实现不可变性的核心。如果你有一个类,它的所有实例字段都被声明为
final
,并且这些字段本身也是不可变类型(例如
String
Integer
等),或者它们是可变类型但通过深拷贝(defensive copying)来确保外部无法修改内部状态,那么这个对象就具备了不可变性。比如:

public final class ImmutablePoint {
    private final int x;
    private final int y;

    public ImmutablePoint(int x, int y) {
        this.x = x;
        this.y = y;
    }

    public int getX() { return x; }
    public int getY() { return y; }

    // 没有setter方法
    // 也没有方法可以改变x或y的值
}

在这个

ImmutablePoint
例子中,
x
y
final
修饰,一旦构造函数赋值后,它们的值就不能再变了。同时,类本身被
final
修饰,防止被继承,进一步巩固了其不可变性。

不可变性带来的好处是巨大的:

  • 线程安全: 不可变对象天生就是线程安全的,因为它们的状态不会改变,多个线程可以同时访问而无需担心同步问题。这大大简化了并发编程
  • 可预测性: 一旦创建,对象的状态就固定了,这使得代码更容易理解和推理,减少了意外副作用的可能性。
  • 缓存: 不可变对象可以安全地被缓存,因为你不需要担心它们会在缓存之外被修改。
  • 哈希表键: 它们是作为
    HashMap
    HashSet
    的键的理想选择,因为它们的哈希码在整个生命周期中保持不变。

所以,

final
不仅仅是一个语法糖,它是一种编程思想的体现,它帮助我们构建更健壮、更易于维护的系统。

finally
块在异常处理中扮演着怎样的角色,它与
try-with-resources
有何关联?

finally
块在Java的异常处理机制中,扮演着“无论如何都要完成任务”的角色。它的主要职责是确保在
try
块(无论是否发生异常)或
catch
块执行后,某些关键的清理代码能够被执行。这对于资源管理至关重要,比如关闭文件、数据库连接、网络套接字等,以防止资源泄漏。

Imagine By Magic Studio
Imagine By Magic Studio

AI图片生成器,用文字制作图片

下载

想象一下,如果你打开了一个文件,但在处理过程中抛出了异常,如果没有

finally
块来关闭文件,那么文件句柄就会一直被占用,最终可能导致系统资源耗尽。

一个典型的

finally
使用场景是这样的:

FileInputStream fis = null;
try {
    fis = new FileInputStream("file.txt");
    // 读取文件内容...
    // 假设这里可能抛出IOException
} catch (IOException e) {
    System.err.println("文件操作失败: " + e.getMessage());
} finally {
    if (fis != null) {
        try {
            fis.close(); // 确保文件流被关闭
        } catch (IOException e) {
            System.err.println("关闭文件流失败: " + e.getMessage());
        }
    }
}

这段代码虽然有效,但存在一些冗余,特别是关闭资源的逻辑需要嵌套

try-catch
,使得代码显得有些笨重。

这时候,Java 7 引入的

try-with-resources
语句就如同救星一般出现了。它是一种语法糖,专门用来简化那些实现了
java.lang.AutoCloseable
接口的资源的自动管理。它的核心思想是:只要在
try
关键字后面的括号中声明并初始化资源,这些资源就会在
try
块执行完毕后(无论正常结束还是异常退出)被自动关闭,无需显式地编写
finally
块。

使用

try-with-resources
,上面的例子可以被极大地简化和优化:

try (FileInputStream fis = new FileInputStream("file.txt")) {
    // 读取文件内容...
    // 假设这里可能抛出IOException
} catch (IOException e) {
    System.err.println("文件操作失败或关闭文件流失败: " + e.getMessage());
}

可以看到,

try-with-resources
极大地提升了代码的简洁性和可读性,同时确保了资源的正确释放。它在底层其实就是编译器帮我们自动生成了一个
finally
块来关闭资源。所以,可以说
try-with-resources
finally
块在特定场景下的一种更优雅、更安全的替代方案,它避免了手动管理资源的繁琐和潜在错误。在现代Java编程中,只要资源实现了
AutoCloseable
接口,都应该优先考虑使用
try-with-resources

Java的
finalize()
方法为什么被强烈不推荐使用,它有哪些潜在的问题?

finalize()
方法在Java的早期版本中,被设计为一种在对象被垃圾回收器(GC)销毁前执行清理操作的机制。听起来很美好,对吧?一个对象在“临终”前还能做点什么。但实践证明,这个方法带来的问题远多于其解决的问题,因此在现代Java编程中,它被强烈不推荐使用,甚至可以说,除非你对JVM和GC有极其深入的理解,并且有非常特殊的需求,否则永远不要碰它。

finalize()
方法的潜在问题主要体现在以下几个方面:

  1. 执行时机不确定性: 这是最致命的问题。
    finalize()
    方法的调用是由GC决定的,而GC的运行是异步的、非确定性的。你无法预测一个对象的
    finalize()
    方法何时会被调用,甚至不能保证它一定会被调用(比如程序在GC运行前就退出了)。这意味着你不能依赖它来释放关键资源,因为资源可能长时间得不到释放,导致泄漏。
  2. 性能开销: 拥有
    finalize()
    方法的对象,在垃圾回收过程中需要额外的处理。GC在回收这些对象时,需要将它们放入一个特殊的队列,等待一个单独的线程去执行
    finalize()
    方法。这会增加GC的负担,降低回收效率,影响应用程序的整体性能。
  3. 可能导致资源泄漏: 如果
    finalize()
    方法中出现异常,并且这个异常没有被捕获,那么这个异常会被忽略,但
    finalize()
    线程可能会终止,导致其他等待执行
    finalize()
    方法的对象永远得不到清理。
  4. 对象复活(Resurrection):
    finalize()
    方法中,你可以让一个对象重新被引用,从而阻止它被回收。这被称为“对象复活”,它使得对象的生命周期变得极其复杂和难以预测,是典型的反模式。
  5. 不确定性导致的调试困难: 由于其执行时机和行为的不可预测性,一旦出现与
    finalize()
    相关的问题,调试将变得异常困难。你很难重现问题,也难以追踪问题根源。
  6. 与现代资源管理机制的冲突: 现代Java已经有了更好的资源管理方式,比如前面提到的
    try-with-resources
    语句,以及更底层的
    java.lang.ref.Cleaner
    (用于更复杂的非Java内存资源清理场景)。这些机制提供了确定性、高效且安全的资源释放方式,完全取代了
    finalize()
    的作用。

我的建议是:忘掉

finalize()
方法吧。 如果你需要清理资源,请使用
try-with-resources
。如果资源不属于
AutoCloseable
范畴,那么请提供一个显式的
close()
方法,并在使用完毕后手动调用它。这才是确保资源被及时、正确释放的可靠途径。
finalize()
就像一个被遗弃的旧工具,虽然还在那里,但已经无人问津,因为它带来的麻烦远超其价值。

热门AI工具

更多
DeepSeek
DeepSeek

幻方量化公司旗下的开源大模型平台

豆包大模型
豆包大模型

字节跳动自主研发的一系列大型语言模型

WorkBuddy
WorkBuddy

腾讯云推出的AI原生桌面智能体工作台

腾讯元宝
腾讯元宝

腾讯混元平台推出的AI助手

文心一言
文心一言

文心一言是百度开发的AI聊天机器人,通过对话可以生成各种形式的内容。

讯飞写作
讯飞写作

基于讯飞星火大模型的AI写作工具,可以快速生成新闻稿件、品宣文案、工作总结、心得体会等各种文文稿

即梦AI
即梦AI

一站式AI创作平台,免费AI图片和视频生成。

ChatGPT
ChatGPT

最最强大的AI聊天机器人程序,ChatGPT不单是聊天机器人,还能进行撰写邮件、视频脚本、文案、翻译、代码等任务。

相关专题

更多
string转int
string转int

在编程中,我们经常会遇到需要将字符串(str)转换为整数(int)的情况。这可能是因为我们需要对字符串进行数值计算,或者需要将用户输入的字符串转换为整数进行处理。php中文网给大家带来了相关的教程以及文章,欢迎大家前来学习阅读。

1030

2023.08.02

java基础知识汇总
java基础知识汇总

java基础知识有Java的历史和特点、Java的开发环境、Java的基本数据类型、变量和常量、运算符和表达式、控制语句、数组和字符串等等知识点。想要知道更多关于java基础知识的朋友,请阅读本专题下面的的有关文章,欢迎大家来php中文网学习。

1567

2023.10.24

java基础知识汇总
java基础知识汇总

java基础知识有Java的历史和特点、Java的开发环境、Java的基本数据类型、变量和常量、运算符和表达式、控制语句、数组和字符串等等知识点。想要知道更多关于java基础知识的朋友,请阅读本专题下面的的有关文章,欢迎大家来php中文网学习。

1567

2023.10.24

硬盘接口类型介绍
硬盘接口类型介绍

硬盘接口类型有IDE、SATA、SCSI、Fibre Channel、USB、eSATA、mSATA、PCIe等等。详细介绍:1、IDE接口是一种并行接口,主要用于连接硬盘和光驱等设备,它主要有两种类型:ATA和ATAPI,IDE接口已经逐渐被SATA接口;2、SATA接口是一种串行接口,相较于IDE接口,它具有更高的传输速度、更低的功耗和更小的体积;3、SCSI接口等等。

1926

2023.10.19

PHP接口编写教程
PHP接口编写教程

本专题整合了PHP接口编写教程,阅读专题下面的文章了解更多详细内容。

656

2025.10.17

php8.4实现接口限流的教程
php8.4实现接口限流的教程

PHP8.4本身不内置限流功能,需借助Redis(令牌桶)或Swoole(漏桶)实现;文件锁因I/O瓶颈、无跨机共享、秒级精度等缺陷不适用高并发场景。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

2397

2025.12.29

java接口相关教程
java接口相关教程

本专题整合了java接口相关内容,阅读专题下面的文章了解更多详细内容。

47

2026.01.19

线程和进程的区别
线程和进程的区别

线程和进程的区别:线程是进程的一部分,用于实现并发和并行操作,而线程共享进程的资源,通信更方便快捷,切换开销较小。本专题为大家提供线程和进程区别相关的各种文章、以及下载和课程。

765

2023.08.10

C# ASP.NET Core微服务架构与API网关实践
C# ASP.NET Core微服务架构与API网关实践

本专题围绕 C# 在现代后端架构中的微服务实践展开,系统讲解基于 ASP.NET Core 构建可扩展服务体系的核心方法。内容涵盖服务拆分策略、RESTful API 设计、服务间通信、API 网关统一入口管理以及服务治理机制。通过真实项目案例,帮助开发者掌握构建高可用微服务系统的关键技术,提高系统的可扩展性与维护效率。

76

2026.03.11

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
Kotlin 教程
Kotlin 教程

共23课时 | 4.4万人学习

C# 教程
C# 教程

共94课时 | 11.2万人学习

Java 教程
Java 教程

共578课时 | 81.3万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号