Java lambdas中的隐含匿名类型

Java lambdas中的隐含匿名类型,java,lambda,java-8,language-lawyer,anonymous-types,Java,Lambda,Java 8,Language Lawyer,Anonymous Types,在中,user@Holger提供了一个匿名类的不常见用法,我不知道 该答案使用流,但此问题与流无关,因为此匿名类型构造可用于其他上下文,即: String s = "Digging into Java's intricacies"; Optional.of(new Object() { String field = s; }) .map(anonymous -> anonymous.field) // anonymous implied type .ifPresent(

在中,user@Holger提供了一个匿名类的不常见用法,我不知道

该答案使用流,但此问题与流无关,因为此匿名类型构造可用于其他上下文,即:

String s = "Digging into Java's intricacies";

Optional.of(new Object() { String field = s; })
    .map(anonymous -> anonymous.field) // anonymous implied type 
    .ifPresent(System.out::println);
令我惊讶的是,它编译并打印了预期的输出


注意:我很清楚,自古以来,构建匿名内部类并按如下方式使用其成员是可能的:

int result = new Object() { int incr(int i) {return i + 1; } }.incr(3);
System.out.println(result); // 4
people.stream()
    .map(p -> new Object() { Long id = p.getId(); String json = toJson(p); })
    .forEach(it -> repository.add(it.id, it.json));
String s = "Digging into Java's intricacies";

Optional<Object> optional = Optional.of(new Object() { String field = s; });

optional.map(anonymous -> anonymous.field)
    .ifPresent(System.out::println);
然而,这不是我在这里要问的。我的案例不同,因为匿名类型是通过
Optional
方法链传播的


现在,我可以想象这个功能的一个非常有用的用法。。。很多时候,我需要在
管道上执行一些
映射
操作,同时保留原始元素,即假设我有一个人员列表:

public class Person {
    Long id;
    String name, lastName;
    // getters, setters, hashCode, equals...
}

List<Person> people = ...;
在本例中,我丢失了
Person.id
字段,因为我已将每个人转换为其对应的json字符串

为了避免这种情况,我看到许多人使用某种
Holder
类,或
Pair
,甚至
Tuple
,或只是
抽象映射。SimpleEntry

people.stream()
    .map(p -> new Pair<Long, String>(p.getId(), toJson(p)))
    .forEach(pair -> repository.add(pair.getLeft(), pair.getRight()));
这是魔术!现在,我们可以拥有所需的任意多个字段,同时还保留了类型安全性

在测试时,我无法在单独的代码行中使用隐含类型。如果我修改我的原始代码如下:

int result = new Object() { int incr(int i) {return i + 1; } }.incr(3);
System.out.println(result); // 4
people.stream()
    .map(p -> new Object() { Long id = p.getId(); String json = toJson(p); })
    .forEach(it -> repository.add(it.id, it.json));
String s = "Digging into Java's intricacies";

Optional<Object> optional = Optional.of(new Object() { String field = s; });

optional.map(anonymous -> anonymous.field)
    .ifPresent(System.out::println);
这是意料之中的,因为
对象
类中没有名为
字段
的成员

所以我想知道:

  • 是否在某个地方对此进行了记录,或者JLS中是否有相关内容
  • 这有什么限制,如果有的话
  • 这样写代码真的安全吗
  • 有没有简写的语法,或者这是我们能做的最好的

绝对不是答案,而是更多的
0.02$

这是可能的,因为lambdas给您一个由编译器推断的变量;它是根据上下文推断出来的。这就是为什么它只能用于推断的类型,而不能用于我们可以声明的类型

编译器可以
推断出
类型是匿名的,只是它不能表达它,所以我们可以按名称使用它。所以信息就在那里,但由于语言的限制,我们无法获取

就像说:

 Stream<TypeICanUseButTypeICantName> // Stream<YouKnowWho>?

由于
var x
是由编译器推断的,
int test=x.y也会起作用

这种用法在JLS中没有提到,但是,当然,编程语言提供的列举所有可能性的规范并不起作用。相反,您必须应用关于类型的正式规则,并且它们对匿名类型没有例外,换句话说,规范在任何时候都没有说,对于匿名类,表达式的类型必须回退到命名的超类型

诚然,我本可以在规范的深处忽略这样一条语句,但在我看来,关于匿名类型的唯一限制源于其匿名性质是很自然的,即每个需要按名称引用类型的语言构造都不能直接使用该类型,所以你必须选择一个超类型

因此,如果表达式
newobject(){String field;}
的类型是包含字段“
field
”的匿名类型,那么不仅访问
newobject(){String field;}.field
有效,而且
Collections.singletonList(newobject(){String field;}.get(0).field
,除非明确的规则一致禁止,否则lambda表达式也是如此

从Java10开始,您可以使用
var
声明从初始值设定项推断其类型的局部变量。这样,您现在可以声明任意局部变量,而不仅仅是lambda参数,具有匿名类的类型。例如,以下工作

var obj = new Object() { int i = 42; String s = "blah"; };
obj.i += 10;
System.out.println(obj.s);
同样,我们可以让你的问题成为一个例子:

var optional = Optional.of(new Object() { String field = s; });
optional.map(anonymous -> anonymous.field).ifPresent(System.out::println);
在这种情况下,我们可以参考一个类似的例子,表明这不是疏忽,而是有意的行为:

另一个暗示变量可能具有不可指示类型的一般可能性:

var e=(字符序列和可比)“x”;
//e具有CharSequence和Compariable类型

也就是说,我必须提醒大家不要过度使用该功能。除了可读性问题(您自己称之为“不常见用法”),在使用它的每个地方,您都在创建一个独特的新类(与“双括号初始化”相比)。它不像实际的元组类型或其他编程语言中的未命名类型那样平等地对待同一组成员的所有出现

另外,像
newobject(){String field=s;}
这样创建的实例消耗的内存是所需内存的两倍,因为它不仅包含声明的字段,还包含用于初始化字段的捕获值。在
newobject(){Long id=p.getId();String json=toJson(p);}
示例中,由于捕获了
p
,您需要为存储三个引用而不是两个引用付费。在非静态上下文中,匿名内部类也总是捕获周围的
this

是否在某个地方对此进行了记录,或者JLS中是否有相关内容

我认为这不是匿名类中需要引入JLS的特殊情况。正如您在问题中提到的,您可以直接访问匿名类成员,例如:
incr(3)

首先,让我们看一个本地类示例,这将说明为什么带有匿名类的链可以访问其成员。例如:

@Test
void localClass() throws Throwable {
    class Foo {
        private String foo = "bar";
    }

    Foo it = new Foo();

    assertThat(it.foo, equalTo("bar"));
}
public class Main {

    void test() {
        int count = chain(new Object() { int count = 1; }).count;
    }

    <T> T chain(T it) {
        return it;
    }
}
正如我们所看到的,即使本地类的成员是私有的,也可以在其范围外访问其成员

As@Hol
@Test
void localClass() throws Throwable {
    class Foo {
        private String foo = "bar";
    }

    Foo it = new Foo();

    assertThat(it.foo, equalTo("bar"));
}
@Test
void chainingAnonymousClassInstance() throws Throwable {
    String foo = chain(new Object() { String foo = "bar"; }).foo;

    assertThat(foo,equalTo("bar"));
}

private <T> T chain(T instance) {
    return instance;
}
public class Main {

    void test() {
        int count = chain(new Object() { int count = 1; }).count;
    }

    <T> T chain(T it) {
        return it;
    }
}
void test();
descriptor: ()V
     0: aload_0
     1: new           #2      // class Main$1
     4: dup
     5: aload_0
     6: invokespecial #3     // Method Main$1."<init>":(LMain;)V
     9: invokevirtual #4    // Method chain:(Ljava/lang/Object;)Ljava/lang/Object;
    12: checkcast     #2    // class Main$1
    15: getfield      #5    // Field Main$1.count:I
    18: istore_1
    19: return