【问题标题】:Access outer anonymous class from inner anonymous class从内部匿名类访问外部匿名类
【发布时间】:2017-05-09 08:07:10
【问题描述】:

我只是好奇。有没有办法访问另一个匿名类中的匿名类中的父级?

我让这个例子创建一个JTable 子类(匿名类)覆盖changeSelection 并在里面创建另一个匿名类。

MCVE:

public class Test{

    public static void main(String args []){

        JTable table = new JTable(){

            @Override
            public void changeSelection(
                final int row, final int column,
                final boolean toggle, final boolean extend) {

                SwingUtilities.invokeLater(new Runnable() {
                    @Override
                    public void run() {
                        super.changeSelection(row, column, toggle, extend); 
                        //more code here
                    }
                });
            }
        };

    }//end main

}//end test 

如何参考super.changeSelection(..)

【问题讨论】:

  • 根据经验,当我阅读“外部匿名类”时,我认为“听起来这值得重构”。匿名类很棒,但它们可能会被过度使用。如果您在其他类中有类,请考虑至少定义一个命名的内部类,如果不是适当的公共类。我愿意打赌,它会更易于使用且阅读更清晰。
  • 这类问题的解决方案通常不会比“我将创建一个命名的封闭类”更复杂。唯一的缺点是您的代码不会彼此相邻,但是对超出范围后立即被垃圾收集的类实例的单个引用不会带来性能劣势,可能允许您使用静态嵌套类作为封闭类,实际上可能使您的实现更简洁,更易于阅读。
  • @nachokk:下面有几个正确答案;如果你觉得有帮助,你应该接受其中之一。
  • @JasonC 我还在等待其他答案,感谢您的帮助,并进行投票。

标签: java anonymous-class


【解决方案1】:

不幸的是,您必须为外部匿名类命名:

public class Test{

    public static void main(String args []){

        class Foo extends JTable {

            @Override
            public void changeSelection(
                final int row, final int column,
                final boolean toggle, final boolean extend) {

                SwingUtilities.invokeLater(new Runnable() {
                    @Override
                    public void run() {
                        Foo.super.changeSelection(row, column, toggle, extend); 
                        //more code here
                    }
                });
            }
        };

        JTable table = new Foo();

    }//end main

}//end test 

【讨论】:

  • 所以没有办法 :( ?
  • @nachokk:嗯,问题是,为了使用合格的super,你需要有你想要的超级类的名称。当然,匿名类是没有名字的。
  • @nachokk 不,没有办法,因为正如 newacct 所说,该类是匿名的。此处发布的答案是执行此操作的唯一方法。你可以像我发布的那样做一些事情,你可以命名封闭类,你可以用反射做黑客。
  • 你在我的回答中也问过这个问题(那些 cmets 已经回答了)。如果您接受此问题的其中一个答案,然后再提出一个新答案,您可能会得到更好的结果。您原帖中的问题已得到解答。
  • @nachokk:我相信,如果将两个匿名类都替换为 lambda,它也不会起作用,因为 thissuper 从不引用 lambda 实例;而是指最接近的封闭非 lambda 范围。但是,我相信如果您将外部匿名类保留为匿名类,并将内部匿名类更改为 lambda,那么它只适用于 super。 (未测试)
【解决方案2】:

在您的上下文中,“super”当然是指 Runnable 基础,而不是 JTable 基础。如您所知,在内部类中使用“super”是指该内部类的超类,而不是其封闭类的超类(无论它是否匿名)。由于要调用 JTable 基础的方法,因此必须在 JTable 子类方法之一的上下文中使用“super”。

您可以在您的 JTable 子类中创建一个新方法,例如jTableBaseChangeSelection(),它调用您打算调用的 JTable 的 changeSelection()。然后你从 Runnable 子类调用它:

public static void main(String args []){

    JTable table = new JTable(){

        // calls JTable's changeSelection, for use by the Runnable inner
        // class below, which needs access to the base JTable method.
        private void jTableBaseChangeSelection (int row, int column, boolean toggle, boolean extend) {
            super.changeSelection(row, column, toggle, extend);
        }

        @Override
        public void changeSelection(
            final int row, final int column,
            final boolean toggle, final boolean extend) {

            SwingUtilities.invokeLater(new Runnable() {
                @Override
                public void run() {
                    // call JTable base changeSelection, since we don't have access
                    // to the JTable base class at this point.
                    jTableBaseChangeSelection(row, column, toggle, extend); 
                    //more code here
                }
            });
        }
    };

}//end main

请注意,此答案试图保留您对匿名封闭类的原始设计。这样做肯定是有原因的(是的,在某些情况下,快速组合一些代码一个正当理由)。有一些孤立的情况发生这种情况——没有造成伤害;但是,如果您发现自己经常遇到这种情况,您可能仍希望重新考虑您的设计。

【讨论】:

  • 我不确定您指的是什么。方法superChangeSelection 是JTable 子类的方法。 super 在这种情况下正是指它应该指的东西。你能澄清一下你的意思吗?
  • 我也不清楚否决票,因为答案正是 OP 所要求的。如果它是让您不快的任意名称,我当然可以重命名该方法。我可以把它重命名为jimGarrisonChangeSelection,也许?
  • Jason C 是正确的:Jason 的代码中使用的“超级”是 JTable,它是匿名 JTable 的超类。但也许上面的描述段落可能更清楚,因为 Runnable 基础没有 changeSelection 方法,这就是 OP 的代码不起作用的原因。
  • 原始代码的编写方式有些误导,因为super 只能引用Runnable 实例的超类。所以除非您编辑答案,否则不会让我收回反对票(为什么?)。
  • 相信 Jim Garrison 的问题是该名称在从 Runnable 子类调用的上下文中似乎不合适。我有点同意。但是,由于该方法是私有的,并且开发人员知道发生了什么,因此代码中的解释性注释足以使代码清晰。我将进行编辑以添加评论,并将方法更改为较少误导的方法。
【解决方案3】:

我认为您可以创建外部引用并在匿名内部使用它。至少在 JDK 1.7 和 JDK 1.8 上这对我有用。

public class Test{

        public static void main(String args []){

            class Foo extends JTable {

                @Override
                public void changeSelection(
                    final int row, final int column,
                    final boolean toggle, final boolean extend) {
                    final Foo outer = this; // reference to itself

                    SwingUtilities.invokeLater(new Runnable() {
                        @Override
                        public void run() {
                            outer.changeSelection(row, column, toggle, extend); 
                            //more code here
                        }
                    });
                }
            };

            JTable table = new Foo();

        }//end main

 }//end test 

【讨论】:

    【解决方案4】:

    我认为下面的代码可以满足技术要求。也就是说,如果有更简单的选择,我不建议走这条路。

    我相信你会希望你的 run 方法做一些比 print "Hello World!" 更有趣的事情。在无限循环中,但这对于概念验证来说似乎没问题。

    package test;
    
    import java.lang.reflect.InvocationTargetException;
    import java.lang.reflect.Method;
    
    import javax.swing.JTable;
    import javax.swing.SwingUtilities;
    
    public class GetOuterAnonymousClass {
    
        public static void main(String args []){
    
            JTable table = new JTable(){
    
                @Override
                public void changeSelection(
                    final int row, final int column,
                    final boolean toggle, final boolean extend) {
                    Runnable runnable = new Runnable() {
                        private Object caller;
                        public void setCaller(Object caller){
                            this.caller = caller;
                        }
    
                        @Override
                        public void run() {
                            System.out.println("Hello World!");
                            try {
                                Class clazz = this.getClass().getEnclosingClass();
                                Method method = clazz.getDeclaredMethod("changeSelection", new Class[]{Integer.TYPE, Integer.TYPE, Boolean.TYPE, Boolean.TYPE});
                                method.invoke(caller, 1, 1, true, true);
                            } catch (SecurityException e) {
                                e.printStackTrace();
                            } catch (NoSuchMethodException e) {
                                e.printStackTrace();
                            } catch (IllegalArgumentException e) {
                                e.printStackTrace();
                            } catch (IllegalAccessException e) {
                                e.printStackTrace();
                            } catch (InvocationTargetException e) {
                                e.printStackTrace();
                            }
                        }
                    };
                    Method method;
                    try {
                        method = runnable.getClass().getDeclaredMethod("setCaller", new Class[]{Object.class});
                        method.invoke(runnable, this);
                    } catch (SecurityException e1) {
                        e1.printStackTrace();
                    } catch (NoSuchMethodException e1) {
                        e1.printStackTrace();
                    } catch (IllegalArgumentException e) {
                        e.printStackTrace();
                    } catch (IllegalAccessException e) {
                        e.printStackTrace();
                    } catch (InvocationTargetException e) {
                        e.printStackTrace();
                    }
                    SwingUtilities.invokeLater(runnable);
                }
            };
            table.changeSelection(1, 1, true, true);
    
        }
    
    }
    

    【讨论】:

    • 对于不需要大量代码的东西来说,这当然是很多代码。在任何情况下,您都可以直接使用 getClass().getEnclosureClass() 来获取封闭类。
    • 非常感谢您的提示!使用 getEnclosureClass() 我能够删除初始化块。再次感谢!