Подтвердить что ты не робот

Неизменяемый объект с переменной члена ArrayList - почему эту переменную можно изменить?

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

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

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

4b9b3361

Ответ 1

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

Даже если 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 это поведение невозможно. Так в чем же разница?

Это потому, что a String уже неизменен (не поддаётся модификации), как и список, если вы превратили его в unmodifiableList.

Сравнение:

              String data structure  | List data structure
           .-------------------------+------------------------------------.
Immutable  | String                  | Collection.unmodifiableList(...)   |
-----------+-------------------------+------------------------------------|
Mutable    | StringBuffer            | ArrayList                          |
           '-------------------------+------------------------------------'

Ответ 2

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


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

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

Или вы можете вернуть немодифицируемый список:

public List<String> get() {
    return Collections.unmodifiableList(this.list);
}

Ответ 3

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

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

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

Для действительно неизменяемого списка посмотрите Guava ImmutableList.

Ответ 4

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

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

 list = Collections.unmodifiableList(list);

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

Ответ 5

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

Ответ 6

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

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

Ответ 7

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

Ответ 8

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

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

Ответ 9

Еще одно решение - вернуть копию массива не списка фактических массивов, как этот код

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

    }
}