带有 hashmap 的不可变 class 示例

Example of an immutable class with hashmap

我定义了以下 类 试图使 "IamImmutable.class" 不可变。但是当我在初始化 IamImmutable 后更改 TestingImmutability.class 中的 hashmap 值时,更改适用于 Hashmap。即使我们用 new HashMap(old) 实例化它,Hashmap 也会引用同一个对象。我需要使实例中的 Hashmap 不可变。我也尝试过迭代和复制值,但这不起作用。谁能建议如何进行?

package string;
import java.util.HashMap;
import java.util.Map.Entry;

public final class IamImmutable {
  private int i;
  private String s;
  private HashMap<String, String> h;

  public IamImmutable(int i, String s, HashMap<String, String> h) {
    this.i = i;
    this.s = s;

    this.h = new HashMap<String, String>();
    for (Entry<String, String> entry: h.entrySet()) {
      this.h.put((entry.getKey()), entry.getValue());
    }
  }

  public int getI() {
    return i;
  }

  public String getS() {
    return s;
  }

  public HashMap<String, String> getH() {
    return h;
  }
}

还有一个测试:

package string;

import java.util.HashMap;
import java.util.Map.Entry;

public class TestingImmutability {

  public static void main(String[] args) {
    int i = 6;
    String s = "!am@John";
    HashMap<String, String> h = new HashMap<String, String>();

    h.put("Info1", "!am@John");
    h.put("Inf02", "!amCrazy6");


    IamImmutable imm = new IamImmutable(i, s, h);

    h.put("Inf02", "!amCraxy7");

    System.out.println(imm.getS() + imm.getI());
    for (Entry<String, String> entry: h.entrySet())
      System.out.println(entry.getKey() + " --- " + entry.getValue());
  }
}

Expected output:

  !am@ John6
Inf02---!amCrazy6
Info1---!am@ John

Actual output:

  !am@ John6
Inf02---!amCraxy7
Info1---!am@ John

为了使您的 class 不可变,getH() 必须 return HashMap 的副本。否则,getH() 的任何调用者都可以修改 IamImmutable class 的 HashMap 成员的状态,这意味着它不是不可变的。

另一种方法是将 getH() 替换为访问内部 HashMap 而不公开它的方法。例如,您可以有一个方法 String[] keys() return 是 HashMap 的所有键和一个方法 String get(String key) return 是给定的值关键。

退房

<K,V> Map<K,V> java.util.Collections.unmodifiableMap(Map<? extends K,? extends V> m)

但请注意,这会在原始地图上创建一个只读视图。所以你可能想复制输入地图。

http://docs.oracle.com/javase/6/docs/api/java/util/Collections.html?is-external=true#unmodifiableMap%28java.util.Map%29

这应该有用……

public IamImmutable(int i,String s, Map<String,String> h) {
    this.i = i;
    this.s = s;

    Map<String,String> map = new HashMap<String,String>();
    map.putAll(h);
    this.h = Collections.unmodifiableMap(map);
}

您可能还需要将成员声明和获取方法更改为基础 Map 类型,而不是 HashMap

HashMap <MyKey, MyValue> unmodifiableMap = Collections.unmodifiableMap(modifiableMap); 

对不可变对象使用上面的代码。

您的测试是错误的,您检查的是 h 的内容,您传递给构造函数并随后修改的映射,而不是 imm.getH()。如果你检查正确的地图

for (Entry<String, String> entry : imm.getH().entrySet())
        System.out.println(entry.getKey() + " --- " + entry.getValue());

看起来还不错:

!am@John6
Info1 --- !am@John
Inf02 --- !amCrazy6

所以您的 IamImmutable 构造函数已经很好,以后对传递给构造函数的原始地图的任何更改都不会影响您在构造时制作的副本。也可以用你说的other HashMap constructor,可读性稍微好一点:

public IamImmutable(int i, String s, HashMap<String, String> h)
{
    this.i = i;
    this.s = s;
    this.h = new HashMap<String, String>(h);
}

那也行。


另一个问题是 getH() 将内部地图的引用传递给世界,如果世界更改该引用,事情就会出错。一个简单的解决方法是应用您已经在 getH() 的构造函数中使用的相同复制技巧:

public HashMap < String, String > getH() {
    return new HashMap<String,String>(h);
}

或者在返回之前装饰内部地图:

public Map<String, String> getH() {
    return Collections.unmodifiableMap(h);
}

考虑改用 ImmutableCollections in the guava library。他们完成了与此代码相同的工作,但更多地考虑了效率和易用性。构造时的完整副本和获取映射是笨拙的,如果我们知道它无论如何都不会被修改,那么标准的底层 HashMap 将进行毫无意义的修改检查。

首先你的变量声明是错误的。本质上应该声明为 final。为什么 final?这样就没有人可以编写这些变量的 setter 方法。此外,无论您是对 HashMap 进行浅拷贝还是深拷贝都没有关系。从不可变 class.

发送可变对象引用的 clone() 的基本规则

我已对您的 class 进行了细微修改。这是代码:

package string;
import java.util.HashMap;
import java.util.Map.Entry;

public final class IamImmutable {
    private final int i;
    private final String s;
    private HashMap<String, String> h;

    public IamImmutable(int i, String s, HashMap<String, String> h) {
        this.i = i;
        this.s = s;

        // It doesn't matter whether you make deep or shallow copy
        this.h = new HashMap<String, String>();
        for (Entry<String, String> entry : h.entrySet()) {
            this.h.put((entry.getKey()), entry.getValue());
        }
    }

    public int getI() {
        return i;
    }

    public String getS() {
        return s;
    }

    @SuppressWarnings("unchecked")
    public HashMap<String, String> getH() {
        // Here's the main change
        return (HashMap<String, String>) h.clone();
    }
}

而且你的测试class也有点不对。我通过本地和祖先更改对其进行了修改。这是测试 class:

package string;
import java.util.HashMap;

public class TestingImmutability {

    public static void main(String[] args) {
        int i = 6;
        String s = "!am@John";
        HashMap<String, String> h = new HashMap<String, String>();

        h.put("Info1", "!am@John");
        h.put("Inf02", "!amCrazy6");

        IamImmutable imm = new IamImmutable(i, s, h);
        System.out.println("Original values : " + imm.getI() + " :: " + imm.getS() + " :: " + imm.getH());

        h.put("Inf02", "!amCraxy7");
        System.out.println("After local changes : " + imm.getI() + " :: " + imm.getS() + " :: " + imm.getH());

        HashMap<String, String> hmTest = imm.getH();
        hmTest.put("Inf02", "!amCraxy7");
        System.out.println("After ancestral changes : " + imm.getI() + " :: " + imm.getS() + " :: " + imm.getH());

    }
}

HashMap 在 java 中实现了 Cloneable 接口,因此您可以简单地克隆地图和 return.