【问题标题】:Why do lambda and anonymous class have different implicitly added fields?为什么 lambda 和匿名类有不同的隐式添加字段?
【发布时间】:2020-06-15 21:44:53
【问题描述】:

众所周知,匿名内部类和捕获 lambda 在某些情况下都隐式添加了私有字段。对于实例字段匿名类,它是对外部类实例的引用,而对于本地和静态匿名实例则没有。但是本地实例 lambda 不同:它有一个捕获变量类型的字段。如果最终它们以相同的方式使用,为什么它们会有这种差异?为什么不能在本地匿名类的精确副本中构建本地 lambda?

UPD:

一个小样本

类来测试匿名类的字段

public class AnonymousExample {

    private Runnable instanceRunnable = new Runnable() {
        @Override
        public void run() { }
    };

    private static Runnable staticRunnable = new Runnable() {
        @Override
        public void run() { }
    };

    public static void main(String[] args) throws IllegalAccessException {
        Runnable localRunnable = new Runnable() {
            @Override
            public void run() { }
        };
        System.out.println("-- local anonymous class --");
        print(localRunnable.getClass(), localRunnable);

        System.out.println("\n-- instance anonymous class --");
        AnonymousExample em = new AnonymousExample();
        print(em.instanceRunnable.getClass(), em.instanceRunnable);

        System.out.println("\n-- static member anonymous class --");
        print(staticRunnable.getClass(), staticRunnable);
    }

    public static void print(Class<?> c, Object instance) throws IllegalAccessException {
        System.out.println("- constructors -");
        for (Constructor<?> constructor : c.getDeclaredConstructors()) {
            System.out.println(constructor.toGenericString());
        }
        System.out.println("- fields -");
        for (Field field : c.getDeclaredFields()) {
            System.out.printf(" field name: %s,%n field type: %s,%n field value: %s%n",
                    field.getName(),
                    field.getType(),
                    field.get(instance));
        }
    }

}

输出:

-- local anonymous class --
- constructors -
field.AnonymousExample$3()
- fields -

-- instance anonymous class --
- constructors -
field.AnonymousExample$1(field.AnonymousExample)
- fields -
 field name: this$0,
 field type: class field.AnonymousExample,
 field value: field.AnonymousExample@79fc0f2f

-- static member anonymous class --
- constructors -
field.AnonymousExample$2()
- fields -

类来测试捕获 lambda 的字段

    public class CapturingLambdaExample {

    private int x = 10;
    private static int y = 20;

    private Runnable instanceRunnable = () -> { System.out.println(x);};
    private static Runnable staticRunnable = () -> { System.out.println(y);};

    public static void main(String[] args) throws IllegalAccessException {
        int z = 20;//effectively final
        Runnable localRunnable = () -> { System.out.println(z);};
        System.out.println("-- local capturing lambda --");
        print(localRunnable.getClass());

        System.out.println("\n-- instance capturing lambda --");
        CapturingLambdaExample em = new CapturingLambdaExample();
        print(em.instanceRunnable.getClass());

        System.out.println("\n-- static capturing lambda --");
        print(staticRunnable.getClass());
    }

    public static void print(Class<?> c) throws IllegalAccessException {
        System.out.println("- constructors -");
        for (Constructor<?> constructor : c.getDeclaredConstructors()) {
            System.out.println(constructor.toGenericString());
        }
        System.out.println("- fields -");
        for (Field field : c.getDeclaredFields()) {
            System.out.printf(" field name: %s,%n field type: %s,%n ",
                    field.getName(),
                    field.getType());
        }
    }

}

输出:

-- local capturing lambda --
- constructors -
private field.CapturingLambdaExample$$Lambda$15/0x0000000840065440(int)
- fields -
 field name: arg$1,
 field type: int,

-- instance capturing lambda --
- constructors -
private field.CapturingLambdaExample$$Lambda$25/0x0000000840068840(field.CapturingLambdaExample)
- fields -
 field name: arg$1,
 field type: class field.CapturingLambdaExample,

-- static capturing lambda --
- constructors -
private field.CapturingLambdaExample$$Lambda$14/0x0000000840064c40()
- fields -

【问题讨论】:

  • 好吧,匿名类是实际的类,而 lambdas 的设计是为了在需要/需要时无需使用类即可实现它们。
  • 你确定本地匿名类不捕获this?
  • 您能否分享您所描述的场景示例和这些领域的证据?
  • @Savior 嗨,我添加了描述问题的简短示例
  • @markspace,我知道。它使这个问题变得更加有趣。为什么本地捕获 lambda 需要隐式添加字段而匿名类不需要?

标签: java lambda


【解决方案1】:

匿名类和 Lambda 确实有一些区别。

  1. 匿名类可以扩展另一个类,也可以有多个方法。所以关键字 this 和 super 需要分别用于访问它自己的成员和它的超类成员。此外,如果匿名类需要访问封闭类和封闭类的父成员,则必须使用限定的 this 和 super 关键字。示例:EnclosingClass.this.toString()

  2. 另一方面,Lambda 只能用一个方法实现一个接口。它没有其他成员,因此关键字 this 和 super 可以用来指代封闭类及其父类的成员。

引用Java Spec

与匿名类声明中出现的代码不同, 名称以及出现在 lambda 正文中的 this 和 super 关键字, 与引用声明的可访问性一起,是相同的 和周围的环境一样

示例代码:

public class EnclosingClass {

    public static void main(String[] args) {
        new EnclosingClass().start();
    }

    private void run(Runnable runnable) {
        runnable.run();
    }

    private void start() {
        System.out.println("--- Lambda ---");
        run(() -> {
            System.out.println(this.toString());
            System.out.println(super.toString()); // Use Object.toString
        });

        System.out.println("---Anonymous class---");
        run(new AnonymousSuperClass() {
            @Override
            public void run() {
                super.commonMethod();
                System.out.println(this.toString());
                System.out.println(super.toString());
                System.out.println(EnclosingClass.this.toString());
                System.out.println(EnclosingClass.super.toString()); // Use Object.toString
            }

            @Override
            public String toString() {
                return "Anonymous Class";
            }
        });
    }

    @Override
    public String toString() {
        return "Enclosing Class";
    }

    private static abstract class AnonymousSuperClass implements Runnable {
        protected void commonMethod() {
            System.out.println("Some common processing");
        }

        @Override
        public String toString() {
            return "Anonymous Super Class";
        }
    }
}

样本输出:

--- Lambda ---
Enclosing Class
EnclosingClass@eed1f14
---Anonymous class---
Some common processing
Anonymous Class
Anonymous Super Class
Enclosing Class
EnclosingClass@eed1f14

当匿名类不需要时,为什么本地捕获 lambda 需要隐式添加字段? 这不是真的。在您的示例中,匿名类没有使用封闭方法中的任何字段。

对于下面的sn-p,

private static void start() throws IllegalAccessException {
        int x = 5;
        System.out.println("---- Anonymous class ----");
        print(new Runnable() {

            @Override
            public void run() {
                System.out.println(x);
            }
        }.getClass());

        System.out.println("---- Lambda class ----");
        Runnable r = () -> System.out.println(x);
        print(r.getClass());
    }

这是输出:

---- Anonymous class ----
- constructors -
EnclosingClassV2$1(int)
- fields -
 field name: val$x,
 field type: int,
 ---- Lambda class ----
- constructors -
private EnclosingClassV2$$Lambda$1/455659002(int)
- fields -
 field name: arg$1,
 field type: int,

注意:

  1. 由于静态方法中存在匿名实例,因此对封闭实例的引用不可用。
  2. 匿名类的访问说明符是默认的,而 lambda 的访问说明符是私有的。我在与此相关的规范中看不到任何参考。我看不出为什么匿名类的访问说明符不能是私有的。可能是因为匿名类是内部类的扩展,同样的构造函数生成策略被复用了。

总结,生成lambda类遵循不同的策略,这样可以避免匿名类所涉及的额外的类生成复杂性。

【讨论】:

  • 谢谢。我忘了让匿名类捕获一些变量。在这种情况下,隐式添加的字段没有区别。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-10-14
  • 1970-01-01
  • 2018-01-02
  • 1970-01-01
  • 1970-01-01
  • 2014-12-06
相关资源
最近更新 更多