21

我有一个关于在 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”)?我想我已经在某些地方看到过这种情况,但我认为 getter 只是提供了该对象的副本。不是它的参考。如果它确实以这种方式工作(作为参考),那么它是否也可以工作(Test 类的 arraylist 对象是否也会因此而改变)?:

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

6 回答 6

28

Java 返回对数组的引用,因此它不会是副本,而是会修改列表。通常,除非它是原始类型(int,float等),否则您将获得对该对象的引用。

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

于 2013-06-04T21:31:54.933 回答
2

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

我在您的问题中看到了两个变体,test.getArray().remove(0)并且aVar.remove(0). 这些结果没有区别,它仍然只是一些类似指针的引用,它修改了原始引用。

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

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

然后我们可以将 anew FooWrapper(myFoo)放入 aCollection<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's methods to be implemented
}

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

现在您可以返回新的 custom List<Foo>。但同样,不是来自普通的吸气剂。让它像getUnmodifiableFooList()你的private ArrayList<FooImpl> fooList领域一样。

于 2013-06-12T13:38:46.280 回答
2

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

Java 所做的一切都是按值传递的。因此,无论何时您将对象引用变量作为参数传递或返回它的值,您都在传递或返回对象引用变量的值。

于 2013-06-04T21:39:20.660 回答
1

正如所指出的,您的 getter 不会修改列表,它会返回对列表的可修改引用。Findbugs 之类的工具会警告您...

public static List<String> getArray() {
            return Collections.unmodifiableList(array);
        }
于 2013-06-04T22:00:40.780 回答
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());
    }

}
于 2013-06-04T21:47:35.207 回答
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. 将集合作为可迭代对象返回,而不是作为它们本身。当然,有人可能会把它扔回去。
  5. 返回代理以访问无法转换为其类型的项目。

我知道我知道。如果我想要 C 或 C++,我知道在哪里可以找到它们。1.返回

于 2013-06-04T22:11:15.783 回答