【问题标题】:Does modifying the result of a getter affect the object itself?修改 getter 的结果会影响对象本身吗?
【发布时间】:2013-06-04 21:29:30
【问题描述】:

我有一个关于在 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);
    }
} 

【问题讨论】:

  • Java 从不复制对象。
  • 在这个例子中,getter 在哪里修改对象?
  • 这似乎很容易验证。实际上,在您的示例中仅传递了引用,因此修改会影响原始数组。
  • 问得好,但你为什么不能自己测试一下呢?
  • @raina77ow 方法返回'this.array',OP想知道是否可以通过调用getter方法修改对象,然后修改它返回的对象。

标签: java arraylist getter-setter


【解决方案1】:

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

如果你想返回一个副本,你必须自己显式复制数组。

【讨论】:

  • 太棒了。感谢你的回答。所以第二段代码也会修改原始对象,对吗?
  • 返回 Collections.unmodifiableCollection(...) 怎么样?
  • 我的答案仍然有效,它正在返回一个参考。在这种情况下,尽管您返回的 unmodifiableCollection 将是“只读的”并且不可修改。 docs.oracle.com/javase/7/docs/api/java/util/…
【解决方案2】:

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

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

【讨论】:

    【解决方案3】:

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

    我在您的问题中看到了两个变体,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&lt;Foo&gt; 并从某个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&lt;FooWrapper&gt;。这个包装器没有任何公共设置器,里面的 foo 是私有的。您不能修改基础数据。现在关于Foo 接口。 FooImplFooWrapper 都实现了它,如果任何方法不打算修改数据,它可以在输入时要求Foo。不管你得到哪个Foo

    因此,如果您想要包含不可修改数据的不可修改集合,请创建一个新的 Collection&lt;Foo&gt;,用 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&lt;Foo&gt;。但同样,不是来自普通的吸气剂。为您的 private ArrayList&lt;FooImpl&gt; fooList 字段设置类似于 getUnmodifiableFooList() 的内容。

    【讨论】:

      【解决方案4】:

      正如所指出的,您的 getter 不会修改列表,它会返回对列表的 modifiable 引用。 Findbugs 之类的工具会警告您...您可以接受它并相信您班级的用户不会破坏您的列表,或者使用它来返回对您的列表的不可修改的引用:

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

      【讨论】:

      • 那么在这种情况下,这会被认为是按引用传递还是按值传递?当您说“您的 getter 不会修改列表”但“它返回对列表的可修改引用”时,这是否意味着对 getArray() 的引用所做的更改确实会影响测试课?
      • 我怀疑它仍然是参考。我非常怀疑当您尝试修改返回时会出现运行时错误而不是编译时错误。因此,与 C++ 相比,Java 对 const 的处理很糟糕!
      【解决方案5】:

      要回答您的问题,您可以使用 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());
          }
      
      }
      

      【讨论】:

        【解决方案6】:

        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.返回

        【讨论】:

          猜你喜欢
          • 2016-11-11
          • 1970-01-01
          • 2021-02-18
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多