如何在Java中构建复杂的,层次结构的不可变数据结构?

时间:2010-09-18 18:14:26

标签: java

我正在为客户构建一个Java库,他们想要的一件事就是他们使用的一组特定标准的数据表示。我不想透露客户的利益,但如果他是炼金术士,他可能会想要以下内容:

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

我需要能够按名称访问各种数据元素,例如:

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

我还需要能够遍历属性,如:

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

我实际上已经能够创建这个数据结构了,我可以做我上面要求的所有事情 - 尽管我必须为迭代创建单独的数组,所以我真的看起来更像是:

  for (MagicalType attrib : Elements.Fire.MagicalAuxArray)

不幸的是,我无法满足我的最后要求:整个数据结构必须是不可变的。我已经反复尝试过,并在网上搜索示例,但到目前为止,我还没能以任何合理的方式完成此任务。请注意,最终的数据结构将非常大;我真的希望避免过于重复的解决方案或创建过多的公共符号。

我是一名非常有经验的程序员,对Java不太熟悉。任何人都可以建议我如何代表上述数据以满足我的所有要求吗?

4 个答案:

答案 0 :(得分:6)

立即浮现在脑海中的几种方式:

  • 不要为对象提供setter方法。用户只能通过构造函数创建对象,一旦创建,就无法修改。这也适用于其他状态修改方法。如果要在构造函数中避免使用非常大的参数列表,可以使用Builder pattern(在Joshua Bloch(第2版)中 Effective Java中描述
  • 返回收藏品时,请制作防御性副本。在这种情况下,请使用List而不是数组。这样您就可以return new ArrayList<MagicalType>(MagicalAuxList)代替return MagicalAuxList。这样,使用该类的人将无法修改该集合。一点需要注意。如果您的数组包含复杂对象,它们也必须是不可变的。
  • 对于不可变集合,您还可以尝试使用unmodifiableCollection静态方法(对于列表,集合等有类似的静态方法 - 使用适合您的方法)在您返回时转换您的集合它。这是防御性复制的替代方案。

答案 1 :(得分:1)

为什么使用数组?不可变的集合(例如来自Google Guava)是否会做得更好?

答案 2 :(得分:0)

您可以在公共API中使用Iterable。使用所有需要抑制的变异器来清除收藏。 (遗憾的是Iterator有一个remove()方法(?!),但那只是一个)

public final Iterable<MagicalType> magics;

for(MagicalType magic : magics) ...

答案 3 :(得分:0)

你可以尝试使用final,enums和unmodifiable maps的下面的代码。但是这不允许您按名称访问,因为您需要从地图中获取。你可以在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;
}