28

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.


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 = ...;

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?

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

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

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

I get a compilation error:

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

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

So I would like to know:

  • 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?
shmosel
  • 49,289
  • 6
  • 73
  • 138
fps
  • 33,623
  • 8
  • 55
  • 110
  • 2
    See [here](http://stackoverflow.com/questions/32959680/invoking-a-method-of-an-anonymous-class) for the corresponding effect for methods. Here, generic type inference exposes the anonymous type, where you can discover the fields. In your last example, you've specifically used `java.lang.Object`, which doesn't have such fields. – Sotirios Delimanolis May 15 '17 at 19:31
  • 2
    Though this won't compile on Eclipse :\ – Sotirios Delimanolis May 15 '17 at 19:32
  • 3
    @SotiriosDelimanolis that;s actually great, but i don't think they are quite *the same*. When calling a method directly you sort of `chain` this - the type is there, you just can't express it. In lambda creation the type will be *passed* from stage to stage. I find that differrent – Eugene May 15 '17 at 19:34
  • @SotiriosDelimanolis Thanks for the link. I'm well aware of the `new Object() { int incr(int i) { return i + 1; } }.incr(n);` construction. I was amazed to find out that the anonymous type's members are propagated through chained method calls, such as in a stream or optional. – fps May 15 '17 at 19:37
  • 3
    As far as I know, there is no place saying that this should work, it’s actually the other way round; there is no place saying that an expression’s type should fall back to the base class, if it is anonymous. So the reason why `new Object() { void foo() {} }.foo()` works, is the same why the other examples involving lambdas work. But as said, “as far as I know”, a single counter-example pointing to a place in the JLS saying “no no” could prove me wrong. – Holger May 15 '17 at 19:46
  • @Eugene I find this very handy too. I wish I knew about this, when I used to complain about not having implicit support for tuples in Java. However I would like to know if it's safe to use it before letting this leak into some code base. – fps May 15 '17 at 20:18
  • @FedericoPeraltaSchaffner exactly. but then it does not compile with eclipse, so it this is major drawback for me; as we all use eclipse at my workplace. – Eugene May 15 '17 at 20:19
  • @Eugene Maybe it's a bug of eclipse. I remember another eclipse bug regarding type inference that made me provide explicit generic type parameters for methods when it wasn't actually needed. – fps May 15 '17 at 20:23
  • 4
    @SotiriosDelimanolis @Eugene It compiles for me in Neon. It even shows the inferred type for `of`: `
    Optional
    java.util.Optional.of(Main(){} value)`
    – Jorn Vernee May 15 '17 at 20:57
  • Doesn't work in Oxygen, at least not consistently. – shmosel Mar 07 '18 at 17:54

3 Answers3

15

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.

Holger
  • 285,553
  • 42
  • 434
  • 765
  • Thanks for your answer, Holger. Yes, I've seen the captured values you mention. They are `val$p` for the lambda parameter and `this$0` for the enclosing class. – fps May 16 '17 at 12:40
  • So, in short, you are saying that anonymous types are possible everywhere, as long as they are represented by a generic type variable. Is this correct? – fps May 16 '17 at 18:25
  • 4
    As long as the usage doesn’t require to refer to them by name. Generic type variables help inferring them, but it requires a construct like a lambda expression which allows to create (parameter) variables without specifying their type name. A method reference of the `Type::name` form doesn’t allow to refer to members of the anonymous type as it requires the name, however, `new Object() { void foo() {} }::foo` would work. – Holger May 17 '17 at 08:19
4

Absolutely not an answer, but more of 0.02$.

That is possible because lambdas give you a variable that is inferred by the compiler; it is inferred from the context. That is why it is only possible for types that are inferred, not for types we can declare.

The compiler can deduce the type as being anonymous, it's just that it can't express it so that we could use it by name. So the information is there, but due to the language restrictions we can't get to it.

It's like saying :

 Stream<TypeICanUseButTypeICantName> // Stream<YouKnowWho>?

It does not work in your last example because you have obviously told the compiler the type to be : Optional<Object> optional thus breaking anonymous type inference.

These anonymous types are now (java-10 wise) available in a much simpler way too:

    var x = new Object() {
        int y;
        int z;
    };

    int test = x.y; 

Since var x is inferred by the compiler, int test = x.y; will work also

Eugene
  • 117,005
  • 15
  • 201
  • 306
  • 4
    C++ has `auto` which can infer a type, even an anonymous type of a lambda. [Project Amber](http://openjdk.java.net/jeps/286) is proposing to add a similar keyword to Java. Perhaps in the future we could have something like `var = Optional.of(AnonymousType)`, where `var` would be inferred to `Optional`. – Jorn Vernee May 15 '17 at 21:07
  • Type inference is not exclusive to lambdas. See Holger's example with `singletonList()`. – shmosel Mar 07 '18 at 17:51
  • 2
    @JornVernee I guess the future is present now – Eugene Mar 07 '18 at 18:56
  • @Eugene The future is still in early access ;) – Jorn Vernee Mar 07 '18 at 19:00
  • @shmosel I seriously don't know how to put it... what I was referring to was `map(anonymous -> anonymous.field)`, the `anonymous` will be inferred by the compiler, and you can't even declare something like this outside lambda expression, i.e. a reference to a anonymous inner class – Eugene Mar 07 '18 at 19:01
  • Declare, perhaps not. But it's possible to infer an anonymous type in other circumstances. – shmosel Mar 07 '18 at 19:11
  • @shmosel of course. this has been long possible before `new Thread(){}.start();` if that was your point – Eugene Mar 07 '18 at 19:13
  • `start()` is defined on `Thread` already, so I'm not sure what your point is there. All I'm saying is that the type can be inferred through generics, with or without lambdas. Again, see Holger's example. – shmosel Mar 07 '18 at 20:05
  • @shmosel I stand corrected, thank you. I will revise this – Eugene Mar 07 '18 at 20:10
  • @shmosel I did edit, thank you for coming back to this one, your input made perfect sense! – Eugene Mar 22 '18 at 10:09
1

Is this documented somewhere or is there something about this in the JLS?

I think it is not a special case in anonymous class that need to introduced into JLS . as you have mentioned in your question you can access anonymous class members directly, e.g: incr(3).

First, let's look at a local class example instead, this will represent why chain with the anonymous class could access its members. for example:

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

    Foo it = new Foo();

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

As we can see, a local class members can be accessed out of its scope even if its members is private.

As @Holger has mentioned above in his answer, the compiler will create an inner class like as EnclosingClass${digit} for each anonymous class. so Object{...} has it own type that derived from Object. due to the chain methods return it own type EnclosingClass${digit} rather than the type which is derived from Object. this is why you chain the anonymous class instance could works fine.

@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;
}

Due to we can't reference anonymous class directly, so when we break the chain methods into two lines we actually reference the type Object which is derived from.

AND the rest question @Holger has answered.

Edit

we can conclude that this construction is possible as long as the anonymous type is represented by a generic type variable?

I'm sorry I can't find the JLS reference again since my English is bad. but I can tell you that it does. you can using javap command to see the details. for example:

public class Main {

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

    <T> T chain(T it) {
        return it;
    }
}

and you can see that checkcast instruction has invoked below:

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
holi-java
  • 29,655
  • 7
  • 72
  • 83
  • But in your `chain` example you are just returning the received argument, while i.e. the signature for `Stream.map` is ` Stream map(Function super T, ? extends R> mapper)`. I mean, you are not returning the argument, but a new stream instead, with a function being applied in between. Anyways, I understand your point, which is that this is common behavior. – fps May 16 '17 at 18:02
  • @FedericoPeraltaSchaffner Hi, it's the same thing with `Stream` since the type of `T` is the anonymous class rather than the `Object` class. – holi-java May 16 '17 at 18:17
  • So maybe we can conclude that this construction is possible as long as the anonymous type is represented by a generic type variable? – fps May 16 '17 at 18:22