当前位置: 首页 > 知识库问答 >
问题:

lambdas中隐含的匿名类型

潘学民
2023-03-14

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

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
public class Person {
    Long id;
    String name, lastName;
    // getters, setters, hashCode, equals...
}

List<Person> people = ...;

并且我需要在某个存储库中存储Person实例的JSON表示,为此,我需要每个Person实例以及每个PersonID的JSON字符串:

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字符串。

为了避免这种情况,我看到许多人使用某种holder类,或pair,甚至tuple,或者仅仅使用abstractmap.simpleentry:

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

虽然这对于这个简单的示例来说已经足够好了,但它仍然需要一个泛型pair类的存在。如果我们需要在流中传播3个值,我想我们可以使用tuple3类等。使用数组也是一种选择,但是它不是类型安全的,除非所有的值都是相同的类型。

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

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);
Error: java: cannot find symbol
  symbol:   variable field
  location: variable anonymous of type java.lang.Object

这是意料之中的,因为object类中没有名为field的成员。

所以我想知道:

  • 这是在什么地方记录的吗,还是在JLS中有关于这一点的内容?
  • 这有什么限制(如果有的话)?
  • 这样编写代码实际上安全吗?
  • 有没有速记语法,或者这是我们所能做到的最好的语法?

共有1个答案

红朝
2023-03-14

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

诚然,在规范的深处,我可以忽略这样一个语句,但对我来说,关于匿名类型的唯一限制源于它们的匿名性质,即要求通过名称引用类型的每一种语言构造都不能直接使用该类型,因此您必须选择一个超类型,这看起来总是很自然的。

因此,如果表达式new Object(){String field;}的类型是包含字段“field”的匿名类型,那么不仅访问new Object(){String field;}.field可以工作,而且collections.singletonList(new Object(){String field;}).get(0).field也可以工作,除非有显式规则禁止,并且一致,否则lambda表达式同样适用。

从Java ;10开始,您可以使用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 d = new Object() {};  // d has the type of the anonymous class

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

var e = (CharSequence & Comparable<String>) "x";
                          // e has type CharSequence & Comparable<String>
 类似资料:
  • 问题内容: 在此问题中用户@Holger提供了一个答案,该答案显示了匿名类的不常见用法,我并不知道。 该答案使用流,但是此问题与流无关,因为这种匿名类型构造可以在其他上下文中使用,即: 令我惊讶的是,它编译并打印了预期的输出。 注意:我很清楚,自古以来,就可以构造一个匿名内部类并按如下方式使用其成员: 但是,这不是我要问的。我的情况有所不同,因为匿名类型是通过方法链传播的。 现在,我可以想象到此功

  • 问题内容: 自从Java8最近发布以来,它的全新lambda表达式看起来真的很酷,我想知道这是否意味着我们曾经习惯的Anonymous类的消亡。 我对此进行了一些研究,发现了一些很酷的示例,这些示例说明Lambda表达式将如何系统地替换这些类,例如Collection的sort方法,该方法用于获取Comparator的Anonymous实例来执行排序: 现在可以使用Lambdas完成: 并且看起来

  • 由于Java8最近发布了,它的全新lambda表达式看起来真的很酷,我想知道这是否意味着我们已经习惯的匿名类的消亡。 看起来出奇的简洁。所以我的问题是,有没有理由继续在Java8中使用那些类,而不是lambdas? 编辑 同样的问题但是相反的方向,使用Lambdas而不是匿名类有什么好处,既然Lambdas只能与单一方法接口一起使用,那么这个新特性只是一个只在很少情况下使用的快捷方式还是真的有用呢

  • 问题内容: 在javascript中,存在创建匿名函数并立即调用它的常见模式(通常称为自执行匿名函数或立即调用的函数表达式)。 使用Java 8 lambda,是否有标准方法可以复制此行为?有点像。 这个问题基本上提出了相同的问题,但是对于Java7。我正在明确地寻找使用lambda的构造。 问题答案: 也不是没有声明类型。由于Java是静态类型的语言,并且函数不是一等公民,因此编译器需要知道la

  • 可能重复: 什么是双大括号初始化在Java? 在查看一些遗留代码时,我遇到了一些非常令人困惑的问题: 在调试模式下运行代码后,我发现匿名块是在调用构造函数之后调用的。上面的功能和做的有什么不同: ?我会认为它们在功能上是等价的,并且会认为后一种方式是更好/更干净的编写代码的方式。

  • 输出:类名:包。名称在这里B 有人能告诉我为什么匿名类类型在getClass()方法中给出封闭类吗?这导致了问题的出现。对象C上的equals()始终失败。我的理解是,由于getClass提供了封闭类名,所以永远不会调用重写的equals?