Are all final variables captured by anonymous classes?

后端 未结 4 1243
北恋
北恋 2021-02-13 00:03

I thought I knew the answer to this, but I can\'t find any confirmation after an hour or so of searching.

In this code:

public class Outer {

    // othe         


        
相关标签:
4条回答
  • 2021-02-13 00:20

    Only obj1 is captured.

    Logically, the anonymous class is implemented as a normal class something like this:

    class Anonymous1 extends SomeCallbackClass {
        private final Outer _outer;
        private final SomeObject obj1;
        Anonymous1(Outer _outer, SomeObject obj1) {
            this._outer = _outer;
            this.obj1 = obj1;
        }
        @Override
        public void onEvent() {
             System.out.println(this.obj1.getName());
        }
    });
    

    Note that an anonymous class is always an inner class, so it will always maintain a reference to the outer class, even if it doesn't need it. I don't know if later versions of the compiler have optimized that away, but I don't think so. It is a potential cause of memory leaks.

    The use of it becomes:

    someManager.registerCallback(new Anonymous1(this, obj1));
    

    As you can see, the reference value of obj1 is copied (pass-by-value).

    There is technically no reason for obj1 to be final, whether declared final or effectively final (Java 8+), except that if it wasn't and you change the value, the copy wouldn't change, causing bugs because you expected the value to change, given that the copying is a hidden action. To prevent programmer confusion, they decided that obj1 must be final, so you can never become confused about that behavior.

    0 讨论(0)
  • 2021-02-13 00:22

    obj2 will be garbage collected since it has no reference to it. obj1 will not be garbage collected as long as the event is active since even if you created an anonymous class, you've created a direct reference to obj1.

    The only thing final does is that you can't re-define the value, it doesn't protect the object from the garbage collector

    0 讨论(0)
  • 2021-02-13 00:36

    The language spec has very little to say about how anonymous classes should capture variables from their enclosing scope.

    The only especially relevant section of the language spec that I can find is JLS Sec 8.1.3:

    Any local variable, formal parameter, or exception parameter used but not declared in an inner class must either be declared final or be effectively final (§4.12.4), or a compile-time error occurs where the use is attempted.)

    (Anonymous classes are inner classes)

    It does not specify anything about which variables the anonymous class should capture, or how that capturing should be implemented.

    I think it is reasonable to infer from this that implementations need not capture variables that aren't referenced in the inner class; but it doesn't say they can't.

    0 讨论(0)
  • 2021-02-13 00:37

    I was curious and surprised by your statement that much (why would compiler do such thing???), that I had to check it myself. So I made simple example like this

    public class test {
        private static Object holder;
    
        private void method1() {
            final Object obj1 = new Object();
            final Object obj2 = new Object();
            holder = new ActionListener() {
                @Override
                public void actionPerformed(ActionEvent e) {
                    System.out.println(obj1);
                }
            };
        }
    }
    

    And resulted with following bytecode for of method1

     private method1()V
       L0
        LINENUMBER 8 L0
        NEW java/lang/Object
        DUP
        INVOKESPECIAL java/lang/Object.<init> ()V
        ASTORE 1
       L1
        LINENUMBER 9 L1
        NEW java/lang/Object
        DUP
        INVOKESPECIAL java/lang/Object.<init> ()V
        ASTORE 2
       L2
        LINENUMBER 10 L2
        NEW test$1
        DUP
        ALOAD 0
        ALOAD 1
        INVOKESPECIAL test$1.<init> (Ltest;Ljava/lang/Object;)V
        PUTSTATIC test.holder : Ljava/lang/Object;
    

    Which means:

    • L0 - store first final with idx 1 (ASTORE 1)
    • L1 - store second final with idx 2(that one is not used in anon class) (ASTORE 2)
    • L2 - create new test$1 with argumets (ALOAD 0) this and obj1 (ALOAD 1)

    So I have no idea, how did you get to the conclusion that obj2 is passed to anonymous class instance, but it was simply wrong. IDK if it is compiler dependent, but as for what other has stated, it is not impossible.

    0 讨论(0)
提交回复
热议问题