Implied anonymous types inside lambdas

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.

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.

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.

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.

Leave a Comment