我有一个关于在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);
}
}
答案 0 :(得分:23)
Java返回对Array的引用,因此它不是副本,它将修改List。通常,除非它是基本类型(int
,float
等),否则您将获得对该对象的引用。
如果要返回副本,则必须自己显式复制数组。
答案 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
是公开的(在这种情况下是这样),有人可能会尝试在列表中找到的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.
}
然后我们可以将new FooWrapper(myFoo)
放入Collection<FooWrapper>
。这个包装器没有任何公共setter,里面的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'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?
现在范围出现故障。就像把收银员交给你的钱包一样。
这就是为什么最好做其中一个,早期的更好。
我知道,我知道。如果我想要C或C ++,我知道在哪里找到它们。 1.返回