为什么我可以在另一个包的子类中访问finalize()方法?

4
我是一名有用的助手,可以为您翻译文本。
我是Java的新手,第一次尝试学习Java。我的简单问题是关于java.lang.Object中的finalize()方法。为什么我只能在其他类中访问这个受保护的方法,而不能访问其他受保护的方法。我的导师告诉我,protected只在它的类、同一包和子类中有效。在这里我看到了这个解释。
有人能解释一下finalize()方法是否有特殊情况吗?我找到了一个不令人满意的答案,为什么finalize()是protected的在这里
我的代码如下:
//Creating Package Foo
package Foo;
class A
{
    protected finalize() 
    { 
        System.out.println("This is finalize method of Class A");
    }
}

// Creating Another Pacakage Foo1
package Foo1;
import Foo.*;
class B extends A
{
}
class C extends B
{
}
class D extends C
{
    public void foo() {
        C ob = new C();
        ob = null;
        System.gc(); // Why class A finalize() is getting call
    }
}

只有在 finalize() 方法中才会被调用,其他情况下不会被调用。 我问了我的导师,他拒绝回答并说你可能犯了错误,但是他没有回复我。请注意,我是 Java 的新手,也许我犯了一些大错误。

我不太明白你的问题是什么:你想知道为什么垃圾收集器可以调用受保护的finalize()方法吗? - Adrian Shum
@AdrianShum 是的,完全正确。 - Java_begins
6个回答

1
这个工作效果符合预期,我认为在Java中,finalize()方法并没有受到任何不同于其他方法的特殊待遇。可能有点不同的是,finalize()方法通常只会被JVM垃圾回收器本身调用,如JavaDoc所述:

当垃圾回收确定没有更多对该对象的引用时,由垃圾回收器在对象上调用。

此外,请注意Josh Bloch在Effective Java中强烈反对使用finalizers:

Finalizers是不可预测的、常常危险的,而且一般是不必要的。它们的使用可能会导致不稳定的行为、性能差和可移植性问题。Finalizers有一些有效的用途...但作为一个经验法则,你应该避免使用finalizers。

考虑以下类似于您的示例的示例:
一个拥有覆盖了finalize()方法的基类。
public abstract class BaseClass {
    @Override
    protected void finalize() throws Throwable {
        super.finalize();
        System.out.println("BaseClass finalisation occured");
    }
}

一个未覆盖finalize方法的子类:
public class SubClass extends BaseClass {
    public void foo() {
        System.out.println("SubClass Foo'd");
    }
}

还需要一个带有基本主方法以运行所有内容的驱动程序类:

public class Driver {
    public static void main(String[] args) {
        SubClass sc = new SubClass();
        sc.foo();
        sc = null;
        System.gc();        
    }
}

我们得到的输出如下:
SubClass Foo'd
BaseClass finalisation occured

Java方法查找的过程(非常简单的表述)是,在当前类中查找任何方法,如果没有,则向上遍历类层次结构,直到找到所需的方法。在上面的例子中,当在SubClass对象上调用foo()方法时,SubClass类包含方法定义,因此使用该实现,并且不会再往上遍历类层次结构。当调用finalize()方法(因为请求了System.gc()),该方法将首先在SubClass类中查找,但由于其没有包含finalize()的实现,所以会搜索其父类(BaseClass)。BaseClass包含finalize()的实现,因此使用该实现,并将一行打印到stdout。

现在考虑一个再次覆盖finalize()的子子类:

public class OverridenSubClass extends SubClass {
    @Override
    protected void finalize() throws Throwable {
        super.finalize();
        System.out.println("Overriden finalize called, which calls super's finalize first");
    }
}

以下是略作修改的Driver类:

public class Driver {

    public static void main(String[] args) {
        OverridenSubClass sc = new OverridenSubClass();
        sc.foo();
        System.out.println(sc.toString());
        sc = null;
        System.gc();
        System.exit(0);
    }
}

这会产生以下输出:
SubClass Foo'd
finalize.OverridenSubClass@7150bd4d
BaseClass finalisation occured
Overriden finalize called, which calls initial finalize first

希望这符合预期。需要注意的有:

  1. 我们没有在任何类中覆盖toString()方法,因此使用Object.toString()实现。
  2. 变量sc的类型并不决定所使用的方法实现 - 而是由sc引用的实际对象类型决定。

1
我的导师告诉我,protected只在其类、同一包中以及它的子类中具有作用域。
你误解了他。protected提供对所有这些作用域的访问,分别地。这些条件不必同时满足。
因此,您有从A继承并因此可以访问其受保护成员的类。
然而,这都是无关紧要的。您没有调用受保护的方法。您正在调用执行某些其他代码的系统方法,该代码最终调用finalize(),并且该代码通过JVM的技巧或者与Object相同的java.lang包的特性来访问,这将为它提供访问Object的受保护成员的权限。

0

finalize 在实例被垃圾收集器开始销毁时调用,因为它不再可用(没有其他对象对该实例有引用)。

内置垃圾回收器是特殊的,它可以调用方法,而不管其访问修饰符如何。

关于访问修饰符的标准规则仅适用于您的Java类。

编辑

子类可能需要覆盖finalize以释放系统资源,即使它们位于不同的包中也是如此。如果finalize是私有方法,则这将是不可能的。另一方面,不希望从外部调用该方法,因为通常会导致不一致的状态(例如在实例仍在使用时释放或销毁系统资源),因此public不是该方法的选项。

以下简单示例可能会使其更加清晰。

package foo;

public class A {

    public void _public() {}

    void _default() {}

    private void _private() {}

    @Override
    protected void finalize() throws Throwable {
        freeSomeResources();  // implementation not shown but it should be clear
    }

}

package bar;

import foo.A;

public class B extends A {

    @Override
    public void _public() {}        // fine

    @Override
    void _default() {}              // compile error -> can't access from other package

    @Override
    private void _private() {}      // compile error -> private method


    @Override                       // fine - subclass may override
    protected void finalize() throws Throwable {
        super.finalize();
    }

}

1
我理解finalize()方法是在对象无法使用时由GC调用的。但是,如果它不适用,为什么没有关于finalize()的规定,为什么它是protected?您能否解释得更详细一些呢? 我的导师告诉我,protected对其他子类是不可用的。 - Java_begins
稍作澄清:系统会保留一个对象列表,这些对象覆盖了 finalize 方法,但尚未被认为已经被废弃。只要对象在列表中,就不会被销毁,但是如果该列表是宇宙中唯一的强引用,则该对象将被识别为废弃,并从上述列表中删除,然后添加到需要清理的事物列表中。完成清理后,通常在宇宙中不会再有对该对象的引用,它将停止存在。 - supercat

0
在OpenJDK中,finalize()是从本地代码中调用的,使用JNI的GetMethodID()。请参见java.lang.ref.Finalizer顶部的注释:
/* A native method that invokes an arbitrary object's finalize method is
   required since the finalize method is protected
 */
static native void invokeFinalizeMethod(Object o) throws Throwable;

而在./jdk/src/share/native/java/lang/ref/Finalizer.c中的本地代码中,对finalize()的实际调用如下:

JNIEXPORT void JNICALL 
Java_java_lang_ref_Finalizer_invokeFinalizeMethod(JNIEnv *env, jclass clazz,
                                                  jobject ob)
{   
    jclass cls;
    jmethodID mid;

    cls = (*env)->GetObjectClass(env, ob);
    if (cls == NULL) return;
    mid = (*env)->GetMethodID(env, cls, "finalize", "()V");
    if (mid == NULL) return;
    (*env)->CallVoidMethod(env, ob, mid);
}   

0
我不太明白你的问题:你想知道为什么垃圾收集器可以调用受保护的finalize()吗?-Adrian Shum
@AdrianShum 是的,确切地说。
如果这是您的问题,那么我将尝试对其进行讨论。
首先,finalize()是旨在由系统垃圾收集器调用的特殊方法。如果它是被处理方式不同的特殊情况,那么这并不会让我感到惊讶,并且可访问性会被简单忽略。
但是,这并不意味着它被特殊处理。我不知道为什么你得出结论认为它不应该发生。
尽管您看到该方法被保护,但实际上finalize()方法是在Object中声明的。这意味着,如果有人有权限访问Object#finalize(),则可以调用它。
只是举个正常方法的例子。
假设有一个名为foo的包:
package foo;
public class Foo {
    protected void foo() {
        System.out.println("foo");
    }
}

我们在同一个包中有一个FooRunner,它可以访问Foo#foo,因为它们在同一个包中。

package foo;
public class FooRunner {
    public void runFoo(Foo foo) {
        foo.foo();
    }
}

然后,即使我有另一个包bar,其中有一个扩展FooBar类,并覆盖foo()方法,FooRunner仍然可以接受一个Bar实例并访问其foo()

package bar;
public class Bar extends Foo {
    @Override
    public void foo() {
        System.out.println("bar");
    }
}

// this is still valid
fooRunner.runFoo(new Bar());

虽然Bar在另一个包中定义,但是在foo包中的FooRunner仍然可以通过将其视为Foo来间接运行foo()方法。


0

我认为它正在按预期工作。进入D类并进行实例化后,就没有更多要执行的内容了,因此finalize()方法被调用。 而你的问题的答案是:

A ^ | B ^ | C ^ | D 所以D显然继承了A的属性,因此protected finalize方法对D可访问,因此它被调用。

希望这解决了你的疑问。


那么除了finalize()之外,为什么其他任何受保护的成员或方法都没有被调用? - Java_begins

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接