Does modifying the result of a getter affect the object itself?

独自空忆成欢 提交于 2021-02-04 12:56:29

问题


I have a question about using getter methods in java. Suppose I had this class:

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

My question is:

Would the actual arraylist object be modified ("Test 1" removed from it)? I think I have seen this in places, but I thought that getters were simply providing a copy of that object. Not a reference to it. If it did work that way (as a reference), then would this work as well (Would the arraylist object of the class Test be altered by this as well)?:

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

回答1:


Java returns references to the Array, so it won't be a copy and it will modify the List. In general, unless its a primitive type (int,float,etc) you will be getting a reference to the object.

You have to explicitly copy the array yourself if you want a duplicate to be returned.




回答2:


The way I understand it, Object reference variables are little more than memory addresses of the objects themselves. So what is returned from getArray() is a reference variable to that ArrayList. An object may have many reference variables, but it is still the same object that gets modified.

Java does everything pass by value. So anytime you pass an object reference variable as a parameter or return it's value, you are passing or returning the value of the object reference variable.




回答3:


As others said, unless it's a primitive type, you get a reference to the object. It is similar to a pointer in C++, it allows you to access the object, but unlike C++ reference (pointer to the memory address of a variable) it doesn't allow you to replace it with another object. Only setter can do that.

I see two variants in your question, test.getArray().remove(0) and aVar.remove(0). There is no difference in the results of those, it's still just some pointer-like reference and it modifies the original.

You never get a clone by just calling a getter, so unless the object is immutable, you can modify the object that the getter gave you access to. For example, String is immutable, any basic Collection (including ArrayList) is mutable. You can call Collections.unmodifiable*(...) to make a collection unmodifiable. However, if the items of collection are mutable, they can still be changed.

In some cases, getting a clone is a good idea, in most cases it's not. A getter shouldn't clone anything at all, it shouldn't even modify data unless it initializes a possibly null collection or something like that. If you want an unmodifiable collection containing immutable objects, try to do it this way. In this example we have a class FooImpl that implements interface Foo, the reasons to be explained later.

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

As you see, Foo has no setter. If you create some ArrayList<Foo> and pass it from some getter as Collections.unmodifiableList(myArrayList), it almost seems you did it. But the work is not done yet. If the class FooImpl is public (which it is in this case), someone might try if that foo he found in the list is an instanceof FooImpl and then cast it as (FooImpl) foo making it mutable. However, we can wrap any Foo into a wrapper called FooWrapper. It implements Foo as well:

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

Then we can put a new FooWrapper(myFoo) into a Collection<FooWrapper>. This wrapper doesn't have any public setter and the foo inside is private. You cannot modify the underlying data. Now about that Foo interface. Both FooImpl and FooWrapper implement it, if any method doesn't intend to modify the data, it can ask for Foo on input. It doesn't matter which Foo you get.

So, if you want unmodifiable collection containing unmodifiable data, make a new Collection<Foo>, feed it with FooWrapper objects and then call Collections.unmodifiable*(theCollection). Or make a custom collection that wraps the whole collection of Foo, returning FooWrappers, for example this list:

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's methods to be implemented
}

With wrapped collection, you don't have to iterate through the original collection and make its clone with wrappers of data. This solution is much better when you don't read it whole, but it creates a new FooWrapper every time you call get() on it, unless the Foo on that index is already a FooWrapper. In a long running thread with millions of calls to get(), this could become an unnecessary benchmark for the garbage collector, making you use some inner array or map containing already existing FooWrappers.

Now you can return the new, custom List<Foo>. But again, not from a plain getter. Make it something like getUnmodifiableFooList() for your private ArrayList<FooImpl> fooList field.




回答4:


As pointed out, your getter does not modify the list, it returns an modifiable reference to the list. Tools like Findbugs will warn you about that... you may either live with that and trust the users of your class to not clobber your list, or use this to return an unmodifiable reference to your list:

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



回答5:


To answer your question, with a getter you get direct access to a variable. Run this code and you can see that the String in the ArrayList is removed. But don't use a static ArraList like in this example in your code.

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

}



回答6:


That a getter does not modify the object you call it upon is purely a matter of convention. It certainly does not change the target's identity, but it can change its internal state. Here's a useful example, if a bit sketchy:

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

So, calling Fibonacci.fibonacci(1000) may change the internal state of the target, but it's still the same target.

Now, here's a possible security violation:

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

The problem is that java.lang.Date is mutable. Someone can write code like:

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

Now range is out of order. It's like handing the cashier your wallet.

This is why it is best to do one of these, the earlier ones being preferable.

  1. Have as many objects as possible be immutable. This is why Joda-Time was written, and why dates will chnage yet again in Java 8.
  2. Make defensive copies of items one sets or gets.
  3. Return an immutable wrapper of an item.
  4. Return collections as iterables, not as themselves. Of course, someone might cast it back.
  5. Return a proxy to access the item, that can't be cast to its type.

I know, I know. if I want C or C++, I know where to find them. 1. Return



来源:https://stackoverflow.com/questions/16927810/does-modifying-the-result-of-a-getter-affect-the-object-itself

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!