vw-register
vw-register

Reputation: 224

How to build a complex, hierarchic immutable data structure in Java?

I'm building a Java library for a customer, and one of the things they want is a data representation of a particular set of standards they work with. I don't want to reveal my customer's interests, but if he were an alchemist, he might want the following:

Elements
  Fire
    Name="Fire"
    Physical
      Temperature=451
      Color="Orange"
    Magical
      Domain="Strength"
  Water
    Name="Water"
    Physical
      Color="Blue"
  Earth
    Name="Earth"
    Magical
      Domain="Stability"
      Ordinality=1

I need to be able to access various data elements by name, such as:

  Elements.Earth.Name
  Elements.Water.Physical.Color

I also need to be able to iterate through attributes, as:

  for (MagicalType attrib : Elements.Fire.Magical)
  {
    ...
  }

I have actually been able to create this data structure, and I can do everything I've asked for above -- though I had to create separate arrays for the iteration, so really what I do looks more like:

  for (MagicalType attrib : Elements.Fire.MagicalAuxArray)

Unfortunately I haven't been able to meet my last requirement: the entire data structure must be immutable. I have tried repeatedly, and scoured the web looking for examples, but so far I haven't been able to accomplish this in any reasonable manner. Note that the final data structure will be quite large; I'm really hoping to avoid a solution that is too repetitious or creates too many public symbols.

I am a very experienced programmer, less experienced with Java. Can anyone suggest how I might represent the above data to meet all my requirements?

Upvotes: 6

Views: 367

Answers (4)

Vivin Paliath
Vivin Paliath

Reputation: 95558

A few ways that come to mind immediately:

  • Don't provide setter methods for your object. You users can only create the object via a constructor and once created, it cannot be modified. This goes for other state-modification methods as well. If you want to avoid a very large parameter-list in your constructor, you can use the Builder pattern (described in Effective Java by Joshua Bloch (2nd Ed))
  • When returning collections, make defensive copies. In this case use a List instead of an array. That way you can do return new ArrayList<MagicalType>(MagicalAuxList) instead of return MagicalAuxList. This way people who use the class won't be able to modify the collection. One caveat here. If your array contains complex objects, they must be immutable as well.
  • For immutable collections, you can also try using the unmodifiableCollection static method (there are similar static-methods for lists, sets, etc. - use whichever one is appropriate for you) to convert your collection when you return it. This is an alternative to defensive copying.

Upvotes: 6

Ray Tayek
Ray Tayek

Reputation: 10003

you could try the code below that uses final, enums and unmodifiable maps. but that does not let you access by name since you need to do a get from the map. you could probably do that in groovy.

import java.util.*;

enum Color {
    red, green, blue;
}

class Physical {
    Physical(final Double temperature, final Color color) {
        this.temperature = temperature;
        this.color = color;
        final Map<String, Object> map=new LinkedHashMap<String, Object>();
        map.put("temperature", temperature);
        map.put("color", color);
        this.map=Collections.unmodifiableMap(map);
    }

    final Double temperature;
    final Color color;
    final Map<String, Object> map;
}

class Magical {
    Magical(final String domain, final Integer ordinality) {
        this.domain = domain;
        this.ordinality = ordinality;
        final Map<String, Object> map=new LinkedHashMap<String, Object>();
        map.put("domain", domain);
        map.put("ordinality", ordinality);
        this.map=Collections.unmodifiableMap(map);
    }

    final String domain;
    final Integer ordinality;
    final Map<String, Object> map;
}

public enum Elements {
    earth("Earth", new Magical("Stability", 1), null), air("Air", null, null), fire("Fire", new Magical("Strength", null), new Physical(451., Color.red)), water(
            "Water", null, new Physical(null, Color.blue));
    Elements(final String name, final Magical magical, final Physical physical) {
        this.name = name;
        this.magical = magical;
        this.physical = physical;
    }

    public static void main(String[] arguments) {
        System.out.println(Elements.earth.name);
        System.out.println(Elements.water.physical.color);
        for (Map.Entry<String, Object> entry : Elements.water.physical.map.entrySet())
            System.out.println(entry.getKey() + '=' + entry.getValue());
        for (Map.Entry<String, Object> entry : Elements.earth.magical.map.entrySet())
            System.out.println(entry.getKey() + '=' + entry.getValue());
    }

    final String name;
    final Magical magical;
    final Physical physical;
}

Upvotes: 0

irreputable
irreputable

Reputation: 45443

You can use Iterable in your public API. Cleaner than Collections with all the mutators that you have to suppress. (unfortunately Iterator has a remove() method(?!) but that's just one)

public final Iterable<MagicalType> magics;

for(MagicalType magic : magics) ...

Upvotes: 1

Pascal Thivent
Pascal Thivent

Reputation: 570505

Why do you use arrays? Wouldn't immutable collections (e.g. from Google Guava) do a better job?

Upvotes: 1

Related Questions