为啥 java.lang.Object 中的 finalize() 方法是“受保护的”?

Posted

技术标签:

【中文标题】为啥 java.lang.Object 中的 finalize() 方法是“受保护的”?【英文标题】:Why is the finalize() method in java.lang.Object "protected"?为什么 java.lang.Object 中的 finalize() 方法是“受保护的”? 【发布时间】:2011-01-18 11:41:45 【问题描述】:

出于好奇,

为什么finalize() 方法的访问修饰符被设为protected。为什么不能是public?有人可以解释一下这背后的任何具体原因吗?

另外,我知道finalize() 方法只被调用一次。如果我在内部程序中调用它两次,会发生什么?垃圾收集器会再次调用这个吗?

private void dummyCall() 
    try 
        finalize();
        finalize();
     catch (Throwable e) 
        e.printStackTrace();//NOT REACHES EXCEPTION
    

【问题讨论】:

为什么要编写一个方法来调用 finalize()。 finalize() 在 JVM 销毁实例时调用。你不应该调用它。不过,您可以覆盖它,以防您在销毁时需要特殊行为... 是的。我个人永远不会那样做。但是我出于好奇而问它,因为我读过 GC 如果它已经在一个对象上调用它就不会调用它。谁在跟踪这个 finalize() 方法是否被调用是我的疑问。不过,对于第二个问题,我还没有令人信服的解释。 真正有趣的问题是:为什么 finalize() 出现在 Java OO 层次结构的最顶层?这里有 200K LOC 项目,我们没有一次覆盖 finalize()。有些人甚至会说重写 finalize() 是一种代码味道,一些异端甚至会说 finalize() 在 OOA/OOD 级别不存在并且它存在于 Java 层次结构的顶部是一个(破碎的)Java 特质(并且与您的问题空间完全无关)。顺便说一句,这里有很多赞成的答案认为 finalize() 以某种方式被破坏/存在缺陷:) 只有 Java 专家才能接近 finalize()。 '真正有趣的问题是:为什么 finalize() 出现在 Java OO 层次结构的最顶层? Err,所以 GC 可以在任何对象上调用它? 【参考方案1】:

我用另一个问题回答你的问题:

为什么不应该保护finalize 方法?

一般来说,您应该尽量保持隐私。这就是封装的全部意义所在。否则,您可以制作一切 publicfinalize 不能是 private (因为派生类应该能够访问它才能覆盖它),所以它至少应该是 protected 但是为什么在不希望的时候给予更多的访问权限?


在仔细阅读了您的评论之后,我想我现在明白了您的主要观点。我认为你的意思是因为一切都来自java.lang.Object 并因此访问其protected 成员,所以它(或java.lang.Object 中的任何方法)不会有任何区别@ 987654331@ 而不是protected。就个人而言,我认为这是 Java 的设计缺陷。这在 C# 中确实是固定的。问题不在于为什么finalize 受到保护。没关系。真正的问题是您不应该能够通过基类类型的对象引用来调用基类中的受保护方法。 Eric Lippert 有一个 blog entry 讨论为什么允许这种对受保护成员的访问是一个坏主意,further elaborated on Stack Overflow in this question。

【讨论】:

“隐藏”尽可能多的信息是抽象的本质。 为什么不呢?我可以从另一个实例调用一个实例的 finalize() 方法,对吗?无论哪种情况,我的意图都是一样的。那么,为什么我不能这样做呢? 哦,这很奇怪。就我个人而言,我发现抽象方法显然不能是私有的,这是 java 的一个弱点。有人知道原因吗?是否与脆弱的基类问题有关? @Johannes:因为抽象方法必须被子类覆盖,你不能覆盖私有方法。因此,private abstract 在术语上是矛盾的。 @Johannes:你在说什么?这正是受保护方法的用途。 Private 用于不应该被覆盖的东西。【参考方案2】:

查看讨论它的this link。

基本上,它是private 最有意义,因为它只能由 JVM(垃圾收集器)调用。但为了允许子类调用父类finalize() 方法作为其finalize() 的一部分,它必须是protected

(Edit - 只是一般的警告 - 通常不鼓励使用 finalize() 方法,因为无法确保它会被调用。虽然这并不意味着你'永远不会有机会使用它 - 这很少见。)

【讨论】:

【参考方案3】:

为什么finalize()方法的访问 修饰符被设置为受保护的。为什么 不能公开吗?

它不是公开的,因为它不应该被 JVM 以外的任何人调用。但是,它必须受到保护,以便它可以被需要为其定义行为的子类覆盖。

如果我在我的程序中调用它两次, 内部发生了什么?

你可以随心所欲地调用它,毕竟它只是一个方法。然而,很像public static void main(String [] args),它对JVM有特殊的意义

垃圾收集器会调用这个吗 再来一次?

是的

【讨论】:

但是我仍然在调用它(这意味着我可以简单地调用 finalize() 方法,当我想要在我的对象中时,尽管它没有任何逻辑)?在那种情况下,他们应该以这样的方式设计它,我不应该能够调用它(从程序员的角度来看)。 重点是,你不应该自己调用finalize。曾经。 jvm 可以在垃圾收集时清理(如果需要),以防在应用程序失败的情况下需要释放外部/本机资源。它受到保护,因此您的子类可以在需要时提供它的实现。 .finalize() 不能替代 .close/.dispose 或其他常见的资源释放约定 @nos: 当你覆盖它时你应该调用super.finalize()【参考方案4】:

它不是public(或默认访问),因为它应该在对象被垃圾回收时在内部由 JVM 调用——它 不是 应该被其他任何东西调用。而且它不是private,因为它是要被覆盖的,你不能覆盖私有方法。

如果我在我的程序中调用它两次, 内部发生了什么?将 垃圾收集器会调用它 再来一次?

可能是的,但很难想象这样的场景会有什么意义——finalize() 的重点是在对象被垃圾收集时进行清理。而且它甚至没有那么好,所以它确实是你应该完全避免而不是尝试的东西。

【讨论】:

【参考方案5】:

finalize() 仅被 JVM 用于在收集对象时清理资源。一个类定义在集合上应该采取什么行动是合理的,为此它可能需要访问 super.finalize()。外部进程调用 finalize() 没有任何意义,因为外部进程无法控制何时收集对象。

【讨论】:

也就是说,我的第二个问题的答案是什么。如果我调用 finalize() 两次,会发生什么? 如果finalize方法实现得当,调用两次也无妨。 Object.finalize 默认情况下什么都不做,nada,zip 除非您已经覆盖它或从一个确实覆盖它的类派生。如果您确实对覆盖它的东西调用 finalize,您可能会在您应该之前释放它的资源,并且对该对象的后续操作可能会以各种方式失败。 '如果我调用 finalize() 两次,会发生什么?为什么要这么做?你甚至不应该调用它一次。这个问题没有出现。【参考方案6】:

另外,我知道 finalize() 方法只调用一次。如果我打电话 它在我的程序中两次,在内部 发生了什么事?

您可能在对 C++ ~destructors 的印象下问这个问题。在 java 中的 finalize() 方法没有做任何魔法(比如清除内存)。它应该由垃圾收集器调用。但反之则不然。

我建议您阅读 Joshua Bloch 的“Effective Java”中的相应章节。它说使用终结器是一种不好的做法,可能会导致性能和其他问题,并且只有几种情况应该使用它们。本章从下一个单词开始:

终结器是不可预测的,通常 危险,而且通常是不必要的。

【讨论】:

【参考方案7】: finalize 仅由 gc 调用,因此不需要公共访问权限 finalize 保证只被 gc 调用一次,你自己调用它会破坏这个保证,因为 gc 不会知道它。 任何覆盖类都可以公开 finalize,我认为由于上述原因这很糟糕 finalize 不应包含太多代码,因为 finalize 抛出的任何异常都可能杀死 gc 的终结器线程。

反对 finalize()

管理本机资源或任何需要调用 dispose() 或 close() 的资源可能会导致难以发现错误,因为它们只会在 jvm 内存不足时释放,您应该手动释放资源。 Finalize 只能用于调试资源泄漏或手动管理资源工作量太大的情况。 finalize 将在 gc 的附加线程中调用,可能会导致资源锁定等问题。 参考类如 WeakReference 和 ReferenceQueue 是处理清理的另一种(相当复杂的)方法,并且可能与本机资源的 finalize() 有相同的问题。

当心以上语句中的错误,我有点累了:-)

【讨论】:

我怀疑 Java 的设计者对 Finalize 的实用性过于乐观了;如果不是这样,类似于 AutoCloseable 的东西就会成为 Java 1.0 的一部分。如果该语言提供了一种方法来区分封装资源所有权的引用和仅识别其他人拥有的资源的引用,我认为 99% 的资源清理可以确定性地自动进行,因为 99% 的需要清理的实体将,在他们生命中的任何时候,都只有一个主人。并不总是同一个所有者,但绝不是零,也绝不是两个。 虽然在少数情况下实体需要清理但没有明确的所有者是有意义的,但大多数封装可变状态的对象应该具有明确定义的所有权(无论它们是否需要清理)大多数需要清理的对象都封装了可变状态。很难知道对对象的最后一个引用何时超出范围;知道对象的唯一所有者何时使用它很容易。后一点是应该清理对象的时间,不管可能存在哪些其他引用【参考方案8】:

finalize() 只被调用一次的部分仅适用于来自 GC 的调用。您可以将对象想象为具有隐藏标志“finalize() 已被 GC 调用”,并且 GC 检查该标志以了解如何处理该对象。您自己手动拨打finalize() 不会对旗帜产生任何影响。

在最终确定时,请阅读 Hans Boehm(以垃圾收集方面的工作而闻名)的 this article。这是关于最终确定的大开眼界;特别是,Boehm 解释了为什么最终确定必须是异步的。一个推论是,虽然定稿是一种强大的工具,但它很少是特定工作的正确工具。

【讨论】:

【参考方案9】:

我认为finalize受到保护的原因可能是它被JDK中的某些类覆盖,而那些被覆盖的方法被JVM调用。

【讨论】:

以上是关于为啥 java.lang.Object 中的 finalize() 方法是“受保护的”?的主要内容,如果未能解决你的问题,请参考以下文章

为啥 Java 中的每个对象都隐式扩展 java.lang.Object 类?

为啥 Java.lang.Object 不实现 Serializable 接口? [复制]

为啥 java.lang.Object 不是抽象的? [复制]

java.lang.Object

java.lang.Object

java.lang.Object 中的“shadow$_klass_”和“shadow$_monitor_”变量是啥?