【问题标题】:Access anonymous inner class variables访问匿名内部类变量
【发布时间】:2010-08-18 16:28:03
【问题描述】:

如何从外部类访问 i?

  HashSet<Integer> hs=new HashSet<Integer>(){
        int i=30;
    };

我可以这样做

int k=new HashSet<Integer>(){
    int i=30;
}.i;

但是如果我得到'i'那么我就无法得到hashset的实例。有没有办法同时得到两者?这个问题只是出于好奇。它没有太多实际应用。我只是想知道它是否可以的。

【问题讨论】:

    标签: java anonymous-class


    【解决方案1】:

    方案一:对象注册系统

    所展示的两个 sn-ps 本质上是在存储对刚刚创建的匿名类实例的引用或立即访问其自定义字段之间做出选择。做一个似乎会失去做另一个的能力。

    达成妥协的一种方法是拥有一个对象注册系统,并在您的匿名类中使用实例初始化程序 (JLS 8.6) 自行注册到此系统。

    这是一个简单的概念验证,使用Object[] 进行简单的注册系统:

        final Object[] registrar = { null };
        int k = new HashSet<Integer>(){
            { registrar[0] = this; }
            int i= 666;
        }.i;
        Set<Integer> set = (Set<Integer>) registrar[0];
    
        System.out.println(k); // 666
        System.out.println(set.getClass().isAnonymousClass()); // true
    

    本质上,我们使用 1 元素 Object[],并且在匿名类的实例初始化程序中,我们将 this“注册”到此数组。请注意,这只是一个概念验证;在生产代码中,您将使用比这更健壮和类型安全的对象注册系统。


    解决方案 2:元组

    如果您的匿名类可能有多个“字段”,则此方法非常有效。只需将它们全部打包到 tuple 中,确保在匿名类中包含对 this 的引用。

    这是一个简单的概念验证,使用 Object[] 作为元组:

        Object[] tuple = new HashSet<Integer>(){
            Object[] tuple = { this, 666, 13, "XXX", "OMG ABUZE" };
        }.tuple;
        Set<Integer> set = (Set<Integer>) tuple[0];
        set.add(null);
        System.out.println(set.getClass().isAnonymousClass()); // true
        System.out.println(Arrays.toString(tuple));
        // [[null], 666, 13, XXX, OMG ABUZE]
    

    在生产代码中,您将使用更面向对象和类型安全的CustomTuple 类。请注意,这是“我的方法如何返回 2 个值”问题的类似解决方案:只需返回 1 个值即可捕获所有这些信息。

    【讨论】:

    • 太棒了。我只是在玩 Java。我不会在生产代码中这样做。
    • @Emil:感谢您的接受,这让我修改了我的答案以使其达到标准。我现在认为这两种解决方案都非常好,而且非常地道。
    • +1 哇,您的回答总是令人惊叹。确实是“OMG ABUZE”:)) Mindsplosion。
    【解决方案2】:

    我猜是为了救援的反思:

    HashSet<Integer> hs = new HashSet<Integer>() {
        int i = 30;
    };
    int i = hs.getClass().getDeclaredField("i").getInt(hs);
    System.out.println(i);
    

    附:写完感觉有点脏。

    【讨论】:

    • 无需感到肮脏。我为你投了赞成票。你现在感觉如何?谢谢,我只是想知道是否有可能。如果有的话,我会接受你的回答没有其他人提供更好的。
    • 谢谢!只是这是对语言的滥用,你可能永远不应该在生产代码中写这样的东西。所以我想你可以把它作为一个不应该做的例子。
    • @Anderi:我首先接受了你的回答。但是 Poly 给出的答案比我预期的要疯狂得多。因为这是一个疯狂的问题。我认为疯狂的答案是最好的。
    【解决方案3】:

    仔细想想就明白了。

    表达式

    new HashSet<Integer>(){
        int i=30;
    }
    

    java.util.HashSet类型,而java.util.HashSet没有名为i的字段,所以

    new HashSet<Integer>(){
       int i=30;
    }.i
    

    不会编译。换句话说,尝试分解复合表达式,以便将new 的结果分配给变量x,然后调用x.i。你能给x 什么类型的代码来允许x.i 编译?没有这样的类型名称,它是一个匿名类。

    这是匿名类的“缺点”——它们只能有意义地覆盖正在扩展的类型中存在的成员。如果添加新成员,则只能通过反射访问。

    【讨论】:

      【解决方案4】:

      我不相信你可以。您不能将变量的类型声明为匿名类型1。您可以在方法中引入 named 类:

      import java.util.*;
      
      public class Test
      {
          public static void main(String[] args)
          {
              class Foo extends Hashtable
              {
                  int i = 30;
              }
      
              Foo f = new Foo();
              System.out.println(f.i);
          }
      }
      

      这太可怕了(真的,真的太可怕了),我认为我曾经在生产代码中看到过它 - 但它确实有效。


      1 C# 使用var 解决了这个问题 - 编译器使用初始化表达式来确定变量的类型。 Java 中的匿名类型与 C# 中的匿名类型有些不同,但如果 Java 有类似 var 的东西,我相信你的代码会起作用。

      【讨论】:

      • 我在用包含大量局部变量的非常长的方法重构旧代码时使用了这样的“局部类”。本地类允许您捕获这些(最终)变量,而无需将它们全部传递给嵌套的静态类构造函数。但除此之外,我从未见过或使用过它们。
      【解决方案5】:

      嗯,在我看来,总是有反射的方式,但对我来说似乎有点矫枉过正。

      当您将课程设置为匿名方式时,除了您的之外,我看不到任何其他方式。

      【讨论】:

        【解决方案6】:

        匿名内部类不是为此目的而设计的,所以据我所知,你不可能做到这一点。如果你愿意,你可以创建一个“普通”的内部类。

        【讨论】: