Friday, March 28, 2008

Initializers in Java

I'm working on slides about language features I'ld like to see in Java. One of them are initializers.
IMHO the lack of this features is the main reason why everybody uses XML. It's just ugly to construct object graphs in an imperative way. Usually such code looks like:

Customer c = new Customer();
c.setName("foobar");
c.setId(4711);

Address address = new Address();
address.setStreet("Schauenburgerstr. 116");
address.setZip("24118");
address.setCity("Kiel");

c.setAddress(address);

Order o1 = new Order();
o1.setArticleId(0815);
o1.setAmount(2);

c.addOrder(o1);

Would be much better if we could specify such a data structure like this:

Customer c = new Customer {
name = "foobar",
id = 4711,
address = new Address {
street = "Schauenburgerstr. 116",
zip = "24118",
city = "Kiel",
},
orders = [new Order {
articleId = 0815;
amount = 2;
}]
};
Actually Java already supports this in some way (not ideal but IMHO better than doing it in a procedural manner). The following code is working Java code:
Customer c = new Customer() {{
name = "foobar";
id = 4711;
address = new Address {{
street = "Schauenburgerstr. 116";
zip = "24118";
city = "Kiel";
}};
addOrder(new Order {{
articleId = 0815;
amount = 2;
}});
}};
The code creates anonymous subclasses with initializers in it. However, as there are no such thing as properties for now you may want to replace the field access by getters and setters. Note that you don't have to make the fields public. So with a combination of getters and protected fields you get immutable types.
Btw.: the same mechanism forks for collections of course:

List l = new ArrayList() {{
add(new Order() {{
setName("stuff");
}});
add(new Order() {{
setName("foo");
setAmount(34);
}});
}};

I'ld still prefer "real" initializers and "real" collection literals, but all in all I think this seems to be a pretty useful idiom.

7 comments:

  1. Hi Sven,

    the problem with the "poor man's" approach proposed by you is the need to load the anonymous classes, in order to instantiate the data. All strings will be interned, thus straning the already sparse PermGen. Same actually applies to initializers too.

    Given the application of the pattern proposed by you in generated code, the point with PermGen can become quite urgent quite fast. As you probably know ;)

    Cheers,
    Boris

    ReplyDelete
  2. I often need to construct test data, and as I usually don't have to care about memory or performance issues in unit tests, this pattern may be useful here.
    Anyway, you're right it's the "poor man's" approach and we definitely should have a cleaner concept for this built into the language.

    ReplyDelete
  3. And what about:

    Customer c = new Customer(
    "foobar",
    4711,
    new Address(
    "Schauenburgerstr. 116",
    "24118",
    "Kiel"),
    new Order[] {new Order(815, 2)});

    Just define the constructors...

    ReplyDelete
  4. There are different problems/limitations when using constructors. First of all you'll have to define constructors for all kinds of initialization data. Second constructor calls like Address("foo","bar","stuff") are hard to read, because there are just three strings and it is no clear what the values shall be assigned to.
    Finally try something like this:

    Customer c = new Customer() {{
    final Customer temp = this;
    name = "foobar";
    id = 4711;
    address = new Address() {{
    street = "Schauenburgerstr. 116";
    zip = "24118";
    city = temp.getName();
    }};
    for(final String orderName : orderNames) {
    addOrder(new Order() {{
    name = orderName;
    }});
    }
    }};

    ReplyDelete
  5. The problem you state for invoking constructors with positional parameters is really the same problem in all invocations, so it would seem a little odd to provide only a solution for the constructor case. Another more important issue to consider is that fields are often private or protected because the underlying framework code really wants the setter methods to be called by the clients. So a mechanism that relies on public access to fields is unlikely to be widely accepted. Things also get trickier if you have to initialize a list or worse yet, if you need to initialize references that might be bidirectional...

    ReplyDelete
  6. Factory methods are your friends...

    For collections, you can define some nice factories that take varargs:

    [wow - tight window for edits...]

    public <T> ArrayList<T> createArrayList(T... elements) {
      ArrayList<T> list = new ArrayList<T>();
      for (T item : elements) {
        list.add(item);
      }
      return list;
    }

    then use
      List<String> strings =
        createArrayList("Scott", "Steve", "Mike");

    For objects that do not have constructors, define factory methods for them as well:

    public Address createAddress(String street, String zip, String city) {
      // does the obvious
    }

    Granted, when there are many fields to set it's not as readable, but it's nicer than filling permgen...

    ReplyDelete