15

I know that this code:

Set<String> set = new HashSet<String>() {{
  add("test1");
  add("test2");
}};

is really:

Set<String> set = new HashSet<String>() {
  {//initializer
    add("test1");
    add("test2");
  }
};

The initializer block is being executed before the constructor block. In the above example, add("test1") is called before the constructor being executed. The constructor may be initializing many of the instance fields, so that this class would work. I am wondering why calling .add() before constructor would work? Is there any case that cause an issue?

user926958
  • 9,355
  • 7
  • 28
  • 33
  • 2
    This will probably fall under 'unspecified behaviour'... – 11684 Jan 18 '13 at 18:53
  • 2
    Interesting question. I don't really have an answer but I think you're making an incorrect assumption here. If you look at the constructor of HashSet it does this: `map = new HashMap();` and the add method does this: `return map.put(e, PRESENT)==null;`. If your assumption was correct, this would cause a NPE. – Daniel Kaplan Jan 18 '13 at 18:57
  • [It is certainly not wrong to think about whether this "pattern" is really worth the trouble](http://stackoverflow.com/q/924285/521799) – Lukas Eder Dec 17 '14 at 09:00

4 Answers4

17

There is a detail you left out that explains this.

First of all, let's review steps 3 through 5 of the initialization procedure (summarized):

3. the superclass constructor is called
4. the instance initializers are called
5. the body of the constructor is called

The detail that you've left out is that your expression is not simply creating a new instance of the HashSet class, it is in fact creating a new instance of an anonymous subclass of HashSet. (I believe this is specified in section 15.9.1.)

Since you did not declare a constructor, the default constructor is used. But before that, the constructor of the superclass HashSet has completed.

So, in summary, the HashSet constructor completes before your initializer block runs.

Samuel Edwin Ward
  • 6,526
  • 3
  • 34
  • 62
  • Thanks, Samuel. You exactly explained the piece that was missing. That block of code is not initializing HashSet, but an anonymous subclass of HashSet. – user926958 Jan 18 '13 at 19:13
  • @user926958, it's an easy detail to miss in this example. If you'd derived from an interface like `Set` or an abstract class, you would be required to add some method definitions which would make it more obvious a new class in being defined here. – Samuel Edwin Ward Jan 18 '13 at 19:20
6

This assumption is wrong:

The initializer block is being executed before the constructor block.

Because in this particular case, the initializer block is part of the constructor block.

The docs state clearly that

The Java compiler copies initializer blocks into every constructor. Therefore, this approach can be used to share a block of code between multiple constructors.

I think you are confusing with static initializers.

mprivat
  • 21,582
  • 4
  • 54
  • 64
  • Check out my comment on the original question. Wouldn't the block have to occur after every constructor instead of before it? Not that you're saying otherwise, I just want to understand better. – Daniel Kaplan Jan 18 '13 at 18:59
  • The order looks like to be initializer before constructor, at least I got it from here: http://stackoverflow.com/questions/2007666/in-what-order-do-static-initializer-blocks-in-java-run – user926958 Jan 18 '13 at 19:02
  • 1
    I don't mean to be rude but this is NOT what's happening; the initalizer block is called after super but before the rest of the constructor which would cause issue in most cases; he is doing in an inline extension of the HashMap class which is why this always works. – Andrew White Jan 18 '13 at 19:03
  • 1
    It is anonymous subclass, so the OP's initializer block will be converted to a class with a constructor with a body that looks like `super(); add("test1"); add("test2");` – 11684 Jan 18 '13 at 19:09
  • What do you think that code does?? It's is an anonymous subclass of HashSet. – mprivat Jan 18 '13 at 19:35
4

Instance initializers are executed just after the object is constructed. You are basically creating an inline extension of the HashSet and then "right after" it is created you are adding two items to it.

This is a common use pattern in mock objects for testing, such as in JMock, but also has other handy uses.

Hope this helps.

cjstehno
  • 13,468
  • 4
  • 44
  • 56
  • 2
    It is not executed after the object is constructed. It is executed during object construction, between the super class constructor and the constructor body. – Geoff Reedy Jan 18 '13 at 19:02
3

I consider this to be a bad practice because it creates pointless subclasses which can affect the memory usage and performance of the application. Anyway, the program is correct because the superclass constructor is called before the instance initializers. So when your initializer is run, the HashSet constructor has run so the call to add will work.

Geoff Reedy
  • 34,891
  • 3
  • 56
  • 79