修改getter的结果会影响对象本身吗?

时间:2013-06-04 21:29:31

标签: java arraylist getter-setter

我有一个关于在java中使用getter方法的问题。 假设我有这门课:

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对象(从中删除“Test 1”)?我想我已经在某些地方看过这个,但我认为吸气剂只是提供了该物体的副本。不是对它的引用。如果它确实以这种方式工作(作为参考),那么它是否也可以工作(类Test的arraylist对象也会被这个改变)?:

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

6 个答案:

答案 0 :(得分:23)

Java返回对Array的引用,因此它不是副本,它将修改List。通常,除非它是基本类型(intfloat等),否则您将获得对该对象的引用。

如果要返回副本,则必须自己显式复制数组。

答案 1 :(得分:2)

我理解它的方式,对象引用变量只不过是对象本身的内存地址。所以从getArray()返回的是该ArrayList的引用变量。对象可能有许多引用变量,但它仍然是被修改的同一对象。

Java使一切都按值传递。因此,只要将对象引用变量作为参数传递或返回它的值,就会传递或返回对象引用变量的值。

答案 2 :(得分:2)

正如其他人所说,除非是原始类型,否则你会得到对象的引用。它类似于C ++中的指针,它允许您访问该对象,但与C ++引用(指向变量的内存地址的指针)不同,它不允许您将其替换为另一个对象。只有setter可以做到这一点。

我在您的问题中看到了两个变种test.getArray().remove(0)aVar.remove(0)。它们的结果没有区别,它仍然只是一些类似指针的参考,它会修改原文。

您永远不会通过调用getter来获取克隆,因此除非该对象是不可变的,否则您可以修改getter为您提供访问权限的对象。例如,String是不可变的,任何基本Collection(包括ArrayList)都是可变的。您可以调用Collections.unmodifiable*(...)使集合无法修改。但是,如果收集的项目是可变的,它们仍然可以更改。

在某些情况下,获得克隆是一个好主意,在大多数情况下,它不是。 getter不应该克隆任何东西,它甚至不应该修改数据,除非它初始化一个可能为null的集合或类似的东西。如果您想要一个包含不可变对象的不可修改的集合,请尝试这样做。在这个例子中,我们有一个实现接口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是公开的(在这种情况下是这样),有人可能会尝试在列表中找到的fooinstanceof 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.
}

然后我们可以将new FooWrapper(myFoo)放入Collection<FooWrapper>。这个包装器没有任何公共setter,里面的foo是私有的。您无法修改基础数据。现在关于Foo接口。 FooImplFooWrapper都实现了它,如果任何方法不打算修改数据,它可以在输入时请求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's methods to be implemented
}

使用包装集合,您不必遍历原始集合并使用数据包装进行克隆。当你不完整地阅读它时,这个解决方案要好得多,但是每次你在它上面调用get()时它会创建一个新的FooWrapper,除非该索引上的Foo已经是FooWrapper。在一个长时间运行的线程中,有数百万次调用get(),这可能成为垃圾收集器的一个不必要的基准,使你使用一些包含现有FooWrappers的内部数组或映射。

现在您可以返回新的自定义List<Foo>。但同样,不是来自普通的吸气鬼。为getUnmodifiableFooList()字段设置private ArrayList<FooImpl> fooList

答案 3 :(得分:1)

正如所指出的,你的getter不会修改列表,它会返回一个可修改的对列表的引用。像Findbugs这样的工具会警告你......你可能要么接受它并信任你的类用户不要破坏你的列表,要么使用它来返回你的列表的不可修改的引用:

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

答案 4 :(得分:0)

要回答您的问题,使用getter可以直接访问变量。 运行此代码,您可以看到ArrayList中的String已被删除。但是不要在代码中使用像这个例子中的静态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 :(得分:0)

getter不会修改你调用它的对象纯粹是一个常规问题。它当然不会改变目标的身份,但它可以改变其内部状态。这是一个有用的例子,如果有点粗略:

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?

现在范围出现故障。就像把收银员交给你的钱包一样。

这就是为什么最好做其中一个,早期的更好。

  1. 尽可能多的对象是不可变的。这就是Joda-Time的原因,以及为什么日期会再次出现在Java 8中。
  2. 制作一套或一件物品的防御性副本。
  3. 返回项目的不可变包装。
  4. 将集合作为iterables返回,而不是作为自己。当然,有人可能会把它丢回来。
  5. 返回代理以访问该项目,该代理无法转换为其类型。
  6. 我知道,我知道。如果我想要C或C ++,我知道在哪里找到它们。 1.返回