匿名类实例中的最终参数存储在哪里?

12

我有以下静态工厂方法,可以将一个整数数组创建为列表视图:

public static List<Integer> newInstance(final int[] numbers) {
    return new AbstractList<Integer>() {

        @Override
        public Integer get(int index) {
            return numbers[index];
        }

        @Override
        public int size() {
            return numbers.length;
        }
    };
}


public static void main(String[] args) {
    int[] sequence = {10, 20, 30};
    List<Integer> list = ListFactory.newInstance(sequence);
    System.out.println("List is "+list);

}

在《Effective Java》一书中,Joshua Bloch提到:

该适配器允许将int数组视为Integer实例列表。

然而,我记得适配器使用组合,并且匿名列表实现的实例应该将int[]作为成员字段使用。

如果int[]输入参数不是匿名列表实现的成员字段,那么它究竟存储在哪里?

如果有人能够提供一些见解或一些链接以寻找更多信息,我会非常感激。

3个回答

7
您可以使用javac -d . -XD-printflat ListFactory.java命令来查看编译器如何理解内部类。实际上,在您的示例中有两个Java类。其中一个是ListFactory(请注意,numbers被传递给ListFactory$1的构造函数):
public class ListFactory {

    public ListFactory() {
        super();
    }

    public static List newInstance(final int[] numbers) {
        return new ListFactory$1(numbers);
    }
}

同时还有 AbstractList 的匿名实现的表示:

class ListFactory$1 extends AbstractList {
    /*synthetic*/ final int[] val$numbers;

    ListFactory$1(/*synthetic*/ final int[] val$numbers) {
        this.val$numbers = val$numbers;
        super();
    }

    @Override()
    public Integer get(int index) {
        return Integer.valueOf(val$numbers[index]);
    }

    @Override()
    public int size() {
        return val$numbers.length;
    }

    @Override()
    /*synthetic*/ public Object get(/*synthetic*/ int index) {
        return this.get(index);
    }
}

在编译时生成的方法和字段被标记为合成的,不可被程序员访问,但在运行时用于访问 int 数组。确实存在一个 val$numbers 字段,它持有对 int 数组的最终引用。
顺便提一下,你还可以注意到在 Integer get(int index) 方法中将 int 装箱为 Integer,并且为了符合原始(非泛型)List 接口,还生成了一个额外的 Object get(int index) 方法,该方法委托给类型安全的 Integer get(int index) 实现。

感谢您提到javac -d . -XD-printflat命令,在这种情况下非常有用! - Kewei Shang

3

它被存储在AbstractList的匿名类中,作为合成字段。您可以使用javap实用程序查看它:

final class q34290420.Test$1 extends java.util.AbstractList<java.lang.Integer> {
  final int[] val$numbers;   // here
  q34290420.Test$1(int[]);
  public java.lang.Integer get(int);
  public int size();
  public java.lang.Object get(int);
}

此外,你可以通过反射来发现它:
    Field[] fields = list.getClass().getDeclaredFields();
    System.out.println(fields[0].getName());
    System.out.println(fields[0].isSynthetic());

输出:

val$numbers
true

1
fields[0]并不总是有效。根据Class#getDeclaredFields()方法的javadoc:“返回的数组元素未排序,也没有特定的顺序。”尽管如此,还是要给你点赞,因为你回答了OP的问题。 - Kevin Cruijssen
@KevinCruijssen 感谢你的投票和评论。这只是一个例子,针对一个没有明确声明字段的特定情况。 - Andremoniy
@Andremoniy感谢您提到反射和合成字段的概念。 - Kewei Shang

2
这与问题相关:为什么只有 final 变量可以在匿名类中访问? Jon Skeet已经为上述问题提供了简明扼要的答案:
“当你创建一个匿名内部类的实例时,任何在该类内使用的变量都通过自动生成的构造函数进行值复制。这避免了编译器必须自动生成各种额外类型来保存‘局部变量’逻辑状态的情况。”
因此,在这种情况下,int[] numbers 被自动复制到从 AbstractList 扩展的匿名类作为一个合成字段。

感谢您提供Jon Skeet的答案链接,这与我的问题非常相关,并帮助我更好地理解了这个主题。 - Kewei Shang

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