2015-04-20 2 views
3

Я определил следующие классы, пытающиеся сделать «IamImmutable.class» неизменным. Но когда я изменяю значения hashmap в TestingImmutability.class после инициализации IamImmutable, изменения применимы к 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 = "[email protected]"; 
    HashMap<String, String> h = new HashMap<String, String>(); 

    h.put("Info1", "[email protected]"); 
    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()); 
    } 
} 

Ожидаемый результат:

[email protected] John6 
Inf02---!amCrazy6 
[email protected] John 

Фактический выход:

[email protected] John6 
Inf02---!amCraxy7 
[email protected] John 
+0

см. Http://stackoverflow.com/questions/9043254/how-to-get-a-immutable-collection-from-java-hashmap – slipperyseal

ответ

3

Ваш тест не соответствует действительности, вы проверяете содержимое h, карту, которую вы передали конструктору, а затем модифицировали вместо imm.getH(). Если необходимо проверить правильность карты

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

вещей выглядят просто отлично:

[email protected] 
Info1 --- [email protected] 
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 будет выполнять проверки изменений, которые бессмысленны, если мы знаем, что он не будет изменен в любом случае.

+1

Все хорошие советы. (Я лично использую Guava immutables lot, и подойду к ним.) Я также предлагаю сначала заменить конкретный, изменяемый «HashMap» на более общий интерфейс «Карта» всюду, кроме фактических вызовов конструктора. Это облегчило бы увидеть, как альтернативы, подобные этому, могут быть удалены, и сделать это проще сделать позже, как только вы написали код против него. –

2

Для того, чтобы ваш класс был неизменным, getH() должен вернуть копию HashMap. В противном случае любой вызывающий абонент getH() может изменить состояние члена HashMap вашего класса IamImmutable, что означает, что он не является неизменным.

Альтернативой было бы заменить getH() методами, которые обращаются к этому внутреннему HashMap, не подвергая его воздействию. Например, вы можете иметь метод String[] keys(), который возвращает все ключи HashMap и метод String get(String key), который возвращает значение для заданного ключа.

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

+0

Если он изменит это, тест все равно будет терпеть неудачу, потому что тест неверен , – flup

+0

Спасибо .. это может работать, но @flup ответ в моем контексте – Atom

+0

, конечно, лучшим решением было бы исправить тест и использовать unmodifiableMap, потому что копирование карты и возвращение того же модифицируемого экземпляра не делает карту неизменной. это означает, что это не повлияет на исходную карту. что прекрасно, если это все, что требуется (но его нельзя назвать неизменяемым). – slipperyseal

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

использования над кодом для неизменяемого объекта.

0

Прежде всего ваши объявления переменных неверны. Он должен быть объявлен как final. Почему final? Чтобы никто не мог писать методы настройки этих переменных. Кроме того, неважно, сделаете ли вы мелкую или глубокую копию своего 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 = "[email protected]"; 
     HashMap<String, String> h = new HashMap<String, String>(); 

     h.put("Info1", "[email protected]"); 
     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()); 

    } 
} 

Надеюсь, это поможет.

Cheers.

Смежные вопросы