带有hashmap的不可变类的示例

时间:2015-04-20 06:32:17

标签: java immutability

我已经定义了以下类,试图使“IamImmutable.class”不可变。但是当我在初始化IamImmutable后更改TestingImmutability.class中的hashmap值时,更改适用于Hashmap。即使我们用新的HashMap(旧)实例化它,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());
  }
}
  

预期产出:

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

实际输出:

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

6 个答案:

答案 0 :(得分:3)

结帐

<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

答案 1 :(得分:3)

您的测试错误,您正在检查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将进行修改检查毫无意义。

答案 2 :(得分:2)

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

另一种方法是使用访问内部getH()的方法替换HashMap而不暴露它。例如,您可以使用方法String[] keys()返回HashMap的所有键以及返回给定键值的方法String get(String key)

答案 3 :(得分:2)

首先,你的变量声明是错误的。应该基本上声明为final。为什么final?这样任何人都无法编写那些变量的setter方法。此外,您是否执行HashMap的浅或深拷贝并不重要。从不可变类中发送clone()可变对象引用的基本规则。

我修改了你的课程,只做了一些小改动。这是代码:

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();
    }
}

你的测试课也有点不对劲。我用局部和祖先的变化修改了它。这是测试类:

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());

    }
}

我希望这会有所帮助。

干杯。

答案 4 :(得分:0)

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

将上述代码用于不可变对象。

答案 5 :(得分:0)

HashMap在Java中实现了Cloneable接口,因此您可以简单地克隆地图并返回。