Неизменяемый объект с переменной-членом ArrayList - почему эта переменная может быть изменена? - PullRequest
18 голосов
/ 26 мая 2011

У меня есть один класс с различными переменными-членами. Есть конструктор и есть методы-получатели, но нет методов-установщиков. На самом деле этот объект должен быть неизменным.

public class Example {
   private ArrayList<String> list; 
}

Теперь я заметил следующее: когда я получаю список переменных методом getter, я могу добавлять новые значения и так далее - я могу изменить ArrayList. Когда я в следующий раз вызываю get() для этой переменной, возвращается измененный ArrayList. Как это может быть? Я не установил это снова, я только работал над этим! С String такое поведение невозможно. Так в чем здесь разница?

Ответы [ 9 ]

42 голосов
/ 26 мая 2011

То, что ссылка на список неизменна, не означает, что список , к которому она относится , является неизменным.

Даже если list было сделано final, это было бы разрешено

// changing the object which list refers to
example.getList().add("stuff");

но это не разрешено:

// changing list
example.list = new ArrayList<String>();   // assuming list is public

Чтобы сделать список неизменным (исключите также первую строку), я предлагаю вам использовать Collections.unmodifiableList:

public class Example {
    final private ArrayList<String> list;

    Example(ArrayList<String> listArg) {
        list = Collections.unmodifiableList(listArg);
    }
}

(Обратите внимание, что это создает неизменяемое представление списка. Если кто-то удерживает исходную ссылку, тогда список все еще можно изменить с помощью этого.)


При использовании String такое поведение невозможно. Так в чем здесь разница?

Это потому, что String уже неизменен (неизменяем) так же, как список, если бы вы превратили его в неизменяемый список.

Сравнение:

              String data structure  | List data structure
           .-------------------------+------------------------------------.
Immutable  | String                  | Collection.unmodifiableList(...)   |
-----------+-------------------------+------------------------------------|
Mutable    | StringBuffer            | ArrayList                          |
           '-------------------------+------------------------------------'
18 голосов
/ 26 мая 2011

Вы возвращаете ссылку на list. И list не является неизменным.


Если вы не хотите, чтобы ваш список был изменен, верните его копию:

public List<String> get() {
    return new ArrayList<String>(this.list);
}

Или вы можете вернуть неизменяемый список :

public List<String> get() {
    return Collections.unmodifiableList(this.list);
}
3 голосов
/ 26 мая 2011

Ключ к пониманию того, что вы не изменяете строку - вы меняете , какая строка ссылается на список, содержащий .

Другими словами: если я вытащу доллар из вашего кошелька и заменю его на 10 центов, я не изменил ни доллар, ни 10 центов - я просто изменил содержимое вашего кошелька.

Если вы хотите просмотреть список только для чтения, посмотрите на Collections.unmodifiableList. Это не остановит список того, что упаковка не изменится, конечно, но остановит любого, у кого есть только ссылка на немодифицируемый список, изменение содержимого.

Список истинно неизменяемых можно найти в Guava ImmutableList class.

2 голосов
/ 12 января 2015

Collections.unmodifiableList () делает список неустранимым.Который снова создает новый окончательный список массивов и переопределяет методы add, remove, addall и clear для исключения unsupportedoperationexception.Это взлом класса коллекций.Но во время компиляции это не мешает вам добавлять и удалять вещи.Я бы предпочел пойти с клонированием этого списка.Что может помочь мне сохранить неизменность моего существующего объекта и не стоит создавать новый список.Прочесть разницу между клонированием и новым оператором (http://www.javatpoint.com/object-cloning). Также поможет избежать сбоя моего кода во время выполнения.

2 голосов
/ 26 мая 2011

Как и в других ответах, объект, который вы возвращаете из получателей, все еще изменчив.

Вы можете сделать список неизменным, украсив его с помощью класса Коллекции:

 list = Collections.unmodifiableList(list);

Если вы вернете это клиентам, они не смогут добавлять или удалять элементы. Тем не менее, они все еще могут вычеркивать элементы из списка - так что вы должны убедиться, что они тоже неизменны, если вы к этому стремитесь!

0 голосов
/ 14 ноября 2017

Другим решением является возврат копии массива, а не фактического списка массивов, как этот код

import java.util.ArrayList;
import java.util.List;

public final class ImmutableExample {

    private final List<String> strings = new ArrayList<String>();

    public ImmutableExample() {
        strings.add("strin 1");
        strings.add("string 2");
    }

    public List<String> getStrings() {
        List<String> newStrings = new ArrayList<String>();
        strings.forEach(s -> newStrings.add(s));
        return newStrings;
    }

    public static void main(String[] args) {
        // TODO Auto-generated method stub
        ImmutableExample im = new ImmutableExample();
        System.out.println(im.getStrings());
        im.getStrings().add("string 3");
        System.out.println(im.getStrings());

    }
}
0 голосов
/ 27 мая 2017

Чтобы получить действительно неизменный список, вам нужно будет сделать глубокие копии содержимого списка. UnmodifiableList только сделает список ссылок несколько неизменным. Теперь создание глубокой копии списка или массива будет не по размерам увеличиваться в памяти. Вы можете использовать сериализацию / десериализацию и сохранить глубокую копию массива / списка во временном файле. Сеттер не будет доступен, так как член varaible должен быть неизменным. Получатель должен сериализовать переменную-член в файл, а затем десализовать его, чтобы получить глубокую копию. Seraialization имеет врожденную природу проникновения в глубины дерева объектов. Это обеспечит полную неизменность при некоторых затратах на производительность.

package com.home.immutable.serial;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;

public final class ImmutableBySerial {

    private final int num;
    private final String str;
    private final TestObjSerial[] arr;

    ImmutableBySerial(int num, String str, TestObjSerial[] arr){
        this.num = num;
        this.str = str;
        this.arr = getDeepCloned(arr);
    }

    public int getNum(){
        return num;
    }

    public String getStr(){
        return str;
    }

    public TestObjSerial[] getArr(){
        return getDeepCloned(arr);
    }

    private TestObjSerial[] getDeepCloned(TestObjSerial[] arr){
        FileOutputStream fos = null;
        ObjectOutputStream oos = null;
        FileInputStream fis = null;
        ObjectInputStream ois = null;
        TestObjSerial[] clonedObj = null;
        try {
             fos = new FileOutputStream(new File("temp"));
             oos = new ObjectOutputStream(fos);
             oos.writeObject(arr);
             fis = new FileInputStream(new File("temp"));
             ois = new ObjectInputStream(fis);
             clonedObj = (TestObjSerial[])ois.readObject();

        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } finally {
            try {
                oos.close();
                fos.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return clonedObj;
    }
}
0 голосов
/ 26 мая 2011

Ссылка на список неизменна, но не на список. Если вы хотите, чтобы сам список был неизменным, рассмотрите возможность использования ImmutableList

0 голосов
/ 26 мая 2011

Поэтому вы не должны предоставлять метод получения для списка, если вы хотите защитить его от изменения.

Его объекты все еще остаются нетронутыми, так как у вас не было сеттеров. Но то, что вы делаете - это удаляете / добавляете новые / разные объекты, и это нормально.

...