Изменяет ли результат воздействия геттер на сам объект?

У меня вопрос об использовании методов getter в java. Предположим, что у меня был этот класс:

class Test {
    private ArrayList<String> array = new ArrayList<String>();

    public ArrayList getArray() {
        return this.array;
    }

    public void initArray() {
        array.add("Test 1");
        array.add("Test 2");
    }
}

class Start {
    public static void main(String args[]) {
        initArray();
        getArray().remove(0);
    }
} 

Мой вопрос:

Будет ли изменен фактический объект arraylist ( "Тест 1" удален из него)? Я думаю, что видел это в разных местах, но я думал, что геттеры просто предоставляют копию этого объекта. Не ссылка на него. Если бы это сработало так (как ссылка), то это тоже сработало бы (не изменил бы и объект arraylist класса Test)?:

class Start {
    public static void main(String args[]) {
        initArray();
        ArrayList aVar = getArray();
        aVar.remove(0);
    }
} 

Ответы

Ответ 1

Java возвращает ссылки на массив, поэтому он не будет копией, и он изменит список. В общем случае, если его примитивный тип (int, float и т.д.), Вы получите ссылку на объект.

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

Ответ 2

То, как я это понимаю, переменные ссылки объекта - это немного больше, чем адреса памяти самих объектов. Итак, то, что возвращается из getArray(), является ссылочной переменной для этого ArrayList. Объект может иметь много ссылочных переменных, но он остается тем же самым объектом, который изменяется.

Java делает все по значению. Поэтому в любое время, когда вы передаете ссылочную переменную объекта в качестве параметра или возвращаете его значение, вы передаете или возвращаете значение ссылочной переменной объекта.

Ответ 3

Как говорили другие, если это не примитивный тип, вы получаете ссылку на объект. Он похож на указатель на С++, он позволяет вам получить доступ к объекту, но в отличие от ссылки С++ (указатель на адрес памяти переменной) он не позволяет вам заменить его на другой объект. Только сеттер может это сделать.

Я вижу два варианта вашего вопроса: test.getArray().remove(0) и aVar.remove(0). Нет никакой разницы в результатах этих, это все еще только ссылка на указатель, и она изменяет оригинал.

Вы никогда не получаете клон, просто называя геттер, поэтому, если объект не является неизменным, вы можете изменить объект, к которому предоставил вам доступ. Например, String является неизменным, любая базовая Collection (включая ArrayList) изменчива. Вы можете вызвать Collections.unmodifiable*(...), чтобы сделать сбор немодифицированным. Однако, если элементы коллекции изменяемы, они все равно могут быть изменены.

В некоторых случаях получение клона - хорошая идея, в большинстве случаев это не так. Геттер не должен вообще клонировать, он даже не должен модифицировать данные, если он не инициализирует нулевую коллекцию или что-то в этом роде. Если вы хотите немодифицируемую коллекцию, содержащую неизменяемые объекты, попробуйте сделать это таким образом. В этом примере у нас есть класс FooImpl, который реализует интерфейс Foo, причины, которые будут объяснены позже.

public interface Foo {
    int getBar();
}

public class FooImpl Foo {
    private int bar;
    @Override
    public int getBar() {
        return bar;
    }
    public void setBar(int newValue) {
        this.bar = newValue;
    }
}

Как вы видите, у Foo нет сеттера. Если вы создаете некоторый ArrayList<Foo> и передаете его из некоторого getter как Collections.unmodifiableList(myArrayList), похоже, что вы это сделали. Но работа еще не завершена. Если класс FooImpl является общедоступным (что в этом случае), кто-то может попробовать, если этот Foo, который он нашел в списке, является instanceof FooImpl, а затем отбрасывает его как (FooImpl) foo, делая его изменяемым. Однако мы можем обернуть любой Foo в оболочку с именем FooWrapper. Он также реализует Foo:

public class FooWrapper implements Foo {
    private Foo foo;
    public FooWrapper(Foo foo) {
        this.foo = foo;
    }
    public int getBar() {
        return foo.getBar();
    }
    // No setter included.
}

Тогда мы можем положить a new FooWrapper(myFoo) в Collection<FooWrapper>. У этой обертки нет публичного сеттера, а внутри foo является закрытым. Вы не можете изменять базовые данные. Теперь об этом интерфейсе Foo. Оба FooImpl и FooWrapper реализуют его, если какой-либо метод не намерен изменять данные, он может запрашивать Foo на входе. Неважно, какой Foo вы получите.

Итак, если вы хотите немодифицируемую коллекцию, содержащую немодифицируемые данные, создайте новый Collection<Foo>, подайте его с объектами FooWrapper, а затем вызовите Collections.unmodifiable*(theCollection). Или создайте собственную коллекцию, которая обертывает всю коллекцию Foo, возвращая FooWrappers, например, этот список:

public MyUnmodifiableArrayList implements List<Foo> {
    ArrayList<Foo> innerList;
    public get(int index) {
        Foo result = innerList.get(index);
        if (!(result instanceof FooWrapper)) {
            return new FooWrapper(result);
        }
        return result; // already wrapped
    }
    // ... some more List interface methods to be implemented
}

С завернутой коллекцией вам не нужно выполнять итерацию по исходной коллекции и делать ее клон с оберткой данных. Это решение намного лучше, если вы не читаете его целиком, но он создает новый FooWrapper каждый раз, когда вы вызываете get() на нем, если Foo на этом индексе уже не является FooWrapper. В длинном потоке с миллионами звонков на get() это может стать ненужным эталоном для сборщика мусора, заставив вас использовать некоторый внутренний массив или карту, содержащую уже существующие FooWrappers.

Теперь вы можете вернуть новый, настраиваемый List<Foo>. Но опять же, не из простого геттера. Сделайте это как getUnmodifiableFooList() для вашего поля private ArrayList<FooImpl> fooList.

Ответ 4

Чтобы ответить на ваш вопрос, с помощью getter вы получите прямой доступ к переменной. Запустите этот код, и вы увидите, что String в ArrayList удаляется. Но не используйте статический ArraList, как в этом примере, в вашем коде.

public class Test {

        private static ArrayList<String> array = new ArrayList<String>();

        public static ArrayList<String> getArray() {
            return array;
        }

        public static void initArray() {
            array.add("Test 1");
            array.add("Test 2");
        }

    public static void main(String[] args) {
            initArray();
            ArrayList aVar = getArray();
            aVar.remove(0);
            System.out.println(aVar.size());
    }

}

Ответ 5

Как указано, ваш получатель не изменяет список, он возвращает изменяемую ссылку на список. Такие инструменты, как Findbugs, будут предупреждать вас об этом... вы можете жить с этим и доверять пользователям своего класса, чтобы не сбивать ваш список, или использовать его для возврата неизменяемой ссылки на ваш список:

public static List<String> getArray() {
            return Collections.unmodifiableList(array);
        }

Ответ 6

То, что геттер не изменяет объект, на который вы его называете, является чисто условным. Это, безусловно, не изменяет целевую идентичность, но может изменить свое внутреннее состояние. Вот полезный пример, если немного отрывочно:

public class Fibonacci {
    private static ConcurrentMap<Integer, BigInteger> cache =
        new ConcurrentHashMap<>();

    public BigInteger fibonacci(int i) {
        if (cache.containsKey(i)) {
            return cache.get(i);
        } else {
            BigInteger fib = compute(i); // not included here.
            cache.putIfAbsent(i, fib);
            return fib;
    }
}

Таким образом, вызов Fibonacci.fibonacci(1000) может изменить внутреннее состояние цели, но он все тот же целевой.

Теперь возможно возможное нарушение безопасности:

public class DateRange {
    private Date start;
    private Date end;
    public DateRange(final Date start, final Date end) {
        if (start.after(end)) {
            throw new IllegalArgumentException("Range out of order");
        }
        this.start = start;
        this.end = end;
    }
    public Date getStart() {
        return start;
    }
    // similar for setStart, getEnd, setEnd.
}

Проблема заключается в том, что java.lang.Date является изменяемым. Кто-то может написать код вроде:

DateRange range = new DateRange(today, tomorrow);
// In another routine.
Date start = range.getStart();
start.setYear(2088);  // Deprecated, I know.  So?

Теперь диапазон неисправен. Это похоже на раздачу кассира вашего кошелька.

Вот почему лучше всего сделать один из них, причем более ранние из них предпочтительнее.

  • Как можно больше объектов быть неизменными. Вот почему Joda-Time была написана и почему даты снова будут отображаться на Java 8.
  • Сделать защитные копии предметов наборы или получить.
  • Возвращает неизменяемую оболочку элемента.
  • Возвращает коллекции как итеративные, а не как сами. Конечно, кто-то может отбросить его.
  • Возвращает прокси для доступа к элементу, который не может быть передан его типу.

Я знаю, я знаю. если я хочу C или С++, я знаю, где их найти. 1. Верните