lambdas中隐含的匿名类型 [英] Implied anonymous types inside lambdas

查看:122
本文介绍了lambdas中隐含的匿名类型的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

此问题中,用户@Holger提供了一个答案,显示了我不知道的匿名类的罕见用法。

In this question, user @Holger provided an answer that shows an uncommon usage of anonymous classes, which I wasn't aware of.

该答案使用流,但这个问题不是关于流,因为这个匿名类型构造可以在其他上下文中使用,即:

That answer uses streams, but this question is not about streams, since this anonymous type construction can be used in other contexts, i.e.:

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);

令我惊讶的是,这会编译并打印预期的输出。

To my surprise, this compiles and prints the expected output.

注意:我很清楚,自古以来,有可能构建一个匿名内部类并使用其成员如下:

Note: I'm well aware that, since ancient times, it is possible to construct an anonymous inner class and use its members as follows:

int result = new Object() { int incr(int i) {return i + 1; } }.incr(3);
System.out.println(result); // 4

但是,这不是我在这里要求的。我的情况不同,因为匿名类型通过可选方法链传播。

However, this is not what I'm asking here. My case is different, because the anonymous type is propagated through the Optional method chain.

现在,我可以想象这个功能的一个非常有用的用法......很多时候,我需要在<$上发布一些 map 操作c $ c>流管道同时还保留原始元素,即假设我有一个人员列表:

Now, I can imagine a very useful usage for this feature... Many times, I've needed to issue some map operation over a Stream pipeline while also preserving the original element, i.e. suppose I have a list of people:

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

List<Person> people = ...;

我需要存储我的 Person的JSON表示实例,我需要每个 Person 实例的JSON字符串,以及每个 Person id:

And that I need to store a JSON representation of my Person instances in some repository, for which I need the JSON string for every Person instance, as well as each Person id:

public static String toJson(Object obj) {
    String json = ...; // serialize obj with some JSON lib 
    return json;
}        

people.stream()
    .map(person -> toJson(person))
    .forEach(json -> repository.add(ID, json)); // where's the ID?

在这个例子中,我丢失了 Person.id 字段,因为我已经将每个人转换为相应的json字符串。

In this example, I have lost the Person.id field, since I've transformed every person to its corresponding json string.

为了避免这种情况,我看到很多人使用某种持有人类,或配对,甚至元组,或只是 AbstractMap.SimpleEntry

To circumvent this, I've seen many people use some sort of Holder class, or Pair, or even Tuple, or just AbstractMap.SimpleEntry:

people.stream()
    .map(p -> new Pair<Long, String>(p.getId(), toJson(p)))
    .forEach(pair -> repository.add(pair.getLeft(), pair.getRight()));

虽然这对于这个简单的例子已经足够了,但它仍然需要存在泛型配对类。如果我们需要通过流传播3个值,我想我们可以使用 Tuple3 类等。使用数组也是一个选项,但它不是类型安全的,除非所有的值都是相同的类型。

While this is good enough for this simple example, it still requires the existence of a generic Pair class. And if we need to propagate 3 values through the stream, I think we could use a Tuple3 class, etc. Using an array is also an option, however it's not type safe, unless all the values are of the same type.

因此,使用隐含的匿名类型,上面相同的代码可以重写如下:

So, using an implied anonymous type, the same code above could be rewritten as follows:

people.stream()
    .map(p -> new Object() { Long id = p.getId(); String json = toJson(p); })
    .forEach(it -> repository.add(it.id, it.json));

这很神奇!现在我们可以根据需要拥有尽可能多的字段,同时还保留类型安全性。

It is magic! Now we can have as many fields as desired, while also preserving type safety.

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

While testing this, I wasn't able to use the implied type in separate lines of code. If I modify my original code as follows:

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);

我收到编译错误:

Error: java: cannot find symbol
  symbol:   variable field
  location: variable anonymous of type java.lang.Object

这是可以预料的,因为<$ c $中没有名为字段的成员c>对象类。

And this is to be expected, because there's no member named field in the Object class.

所以我想知道:


  • 这是在某处记录的,还是JLS中有关于此的内容?

  • 这有什么限制,如果有的话?

  • 编写这样的代码实际上是安全吗?

  • 这是否有简写语法,或者这是我们能做的最好的事情?

  • Is this documented somewhere or is there something about this in the JLS?
  • What limitations does this have, if any?
  • Is it actually safe to write code like this?
  • Is there a shorthand syntax for this, or is this the best we can do?

推荐答案

JLS中没有提到这种用法,当然,规范并没有提到通过列举编程语言提供的所有可能性来工作。相反,你必须应用关于类型的形式规则,它们对匿名类型没有例外,换句话说,规范在任何时候都没有说,表达式的类型必须回退到指定的超类型匿名类的情况。

This kind of usage has not been mentioned in the JLS, but, of course, the specification doesn’t work by enumerating all possibilities, the programming language offers. Instead, you have to apply the formal rules regarding types and they make no exceptions for anonymous types, in other words, the specification doesn’t say at any point, that the type of an expression has to fall back to the named super type in the case of anonymous classes.

当然,我可能在规范的深处忽略了这样的陈述,但对我来说,它总是看起来很自然匿名类型源于其匿名性质,即每个需要通过名称​​引用类型的语言构造,都不能直接使用该类型,因此您必须选择一个超类型。

Granted, I could have overlooked such a statement in the depths of the specification, but to me, it always looked natural that the only restriction regarding anonymous types stems from their anonymous nature, i.e. every language construct requiring referring to the type by name, can’t work with the type directly, so you have to pick a supertype.

因此,如果表达式的类型 new Object(){String field; } 是包含字段 field 的匿名类型,而不仅仅是访问 new Object(){String field; } .field 将起作用,但 Collections.singletonList(new Object(){String field;})。get(0).field ,除非显式规则禁止并且一致,否则同样适用于lambda表达式。

So if the type of the expression new Object() { String field; } is the anonymous type containing the field "field", not only the access new Object() { String field; }.field will work, but also Collections.singletonList(new Object() { String field; }).get(0).field, unless an explicit rule forbids it and consistently, the same applies to lambda expressions.

从Java 10开始,您可以使用 var 声明从初始化程序推断出类型的局部变量。这样,您现在可以声明任意局部变量,而不仅仅是lambda参数,具有匿名类的类型。例如,以下作品

Starting with Java 10, you can use var to declare local variables whose type is inferred from the initializer. That way, you can now declare arbitrary local variables, not only lambda parameters, having the type of an anonymous class. E.g., the following works

var obj = new Object() { int i = 42; String s = "blah"; };
obj.i += 10;
System.out.println(obj.s);

同样,我们可以举例说明您的问题:

Likewise, we can make the example of your question work:

var optional = Optional.of(new Object() { String field = s; });
optional.map(anonymous -> anonymous.field).ifPresent(System.out::println);

在这种情况下,我们可以参考规范显示了一个类似的例子,表明这不是一种疏忽而是预期的行为:

In this case, we can refer to the specification showing a similar example indicating that this is not an oversight but intended behavior:


var d = new Object() {};  // d has the type of the anonymous class


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

and another one hinting at the general possibility that a variable may have a non-denotable type:


var e = (CharSequence & Comparable<String>) "x";
                          // e has type CharSequence & Comparable<String>







那说,我必须警告过度使用该功能。除了可读性问题(你自己称之为不常见的用法),在你使用它的每个地方,你都在创建一个独特的新类(与双支撑初始化相比)。它不像实际的元组类型或未命名类型的其他编程语言那样会同等地处理同一组成员的所有出现。


That said, I have to warn about overusing the feature. Besides the readability concerns (you called it yourself an "uncommon usage"), each place where you use it, you are creating a distinct new class (compare to the "double brace initialization"). It’s not like an actual tuple type or unnamed type of other programming languages that would treat all occurrences of the same set of members equally.

此外,创建的实例如 new Object(){String field = s; } 根据需要消耗两倍的内存,因为它不仅包含声明的字段,还包含用于初始化字段的捕获值。在 new Object(){Long id = p.getId(); String json = toJson(p); } 例如,您支付了三个引用而不是两个引用的存储空间,因为已经捕获了 p 。在非静态上下文中,匿名内部类也总是捕获周围的 this

Also, instances created like new Object() { String field = s; } consume twice as much memory as needed, as it will not only contain the declared fields, but also the captured values used to initialize the fields. In the new Object() { Long id = p.getId(); String json = toJson(p); } example, you pay for the storage of three references instead of two, as p has been captured. In a non-static context, anonymous inner class also always capture the surrounding this.

这篇关于lambdas中隐含的匿名类型的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆