9

我从 Java 开始,正在学习 setter、getter 和封装。我有一个非常简单的程序,两个类:

  • Container有一个私有的 int 数组 ( numArray) 和他的 setter & getter。

  • Main创建一个Container对象并在totalArray方法中使用它。


public class Container {
    private int numArray[]= {0,0,0};
    public int[] getNumArray() {
        return numArray;
    }
    public void setNumArray(int index, int value){
        numArray[index] = value;
    }    
}

public class Main {
    public static void main(String[] args) {
        Container conte = new Container();
        System.out.println(totalArray(conte.getNumArray()));
        conte.getNumArray()[2]++;
        System.out.println(totalArray(conte.getNumArray()));
    }
    private static int totalArray (int v[]){
        int total=0;
        for (int conta =0; conta<v.length;conta++){
            total+=v[conta];
        }
        return total;
    }
}

问题:我可以通过 getter 更改私有 int 数组,我知道这是因为getNumArray返回对 的引用numArray,而不是数组本身。如果我对数组的单个元素感兴趣,我会创建一个带有索引值的 getter,但我想要该totalArray方法的整个数组。

我怎样才能防止numArray被修改出他的班级?

4

8 回答 8

10

为了防止人们更改您的数组,您所能做的就是在 getter 中提供它的副本。

public int[] getArray() {
    return Arrays.copyOf(numArray, numArray.length);
}

这样,其他方法可以更改自己的数组副本,但是当它们再次调用 getter 时,它们会得到原始版本,保持不变。只有setNumArray()您提供的才能实际修改您的内部数组。

否则,如果要完全阻塞容器,则必须删除数组并使用不可变对象。一些库提供不可变列表,或使用Collections.unmodifiableList

于 2010-01-05T15:10:44.450 回答
6

如果你想返回一个数组,你可以克隆它:

  public int[] getArray() {
       return (int[]) numArray.clone();
  }

在公共 API 中,您应该确保向调用者清楚地记录这一点(无论哪种方式,如果他们得到一个会改变类状态的数组 - 他们需要知道)。

于 2010-01-05T15:11:01.383 回答
3

通常,您会查看您尝试提供给类的调用者的接口。

方法如:

void addItem(Object item);
Object getItem(int index);
int getSize();

是您将在容器类中提供的那种东西。然后,它们代表调用者与私有数组交互。

如果您想在不允许更改的情况下返回整个数组,您可以在 getter 中将数组复制到一个新数组中并返回副本。

或者,如果您使用 Java 集合类而不是原始数组,它们会提供一个 unmodifiableXXX() 方法(例如Collections.unmodifiableList(myList)),该方法为集合提供只读包装。

于 2010-01-05T15:08:00.830 回答
1

封装是隐藏实现的过程。集合是否将其数据存储在数组中是一个实现细节;如果它被封装,您可能希望能够将其更改为另一种存储类型。

您将状态(或派生状态)公开为 getter 和 setter 的事实打破了封装,并暗示您正在实现抽象数据类型而不是真正的面向对象的类。例如,anArrayList是一种数据类型,它不代表应用程序中行为的任何真正封装。这是否是您想要的取决于该类型的使用方式和位置。

如果它只是一个容器数据类型,我倾向于为外部交互Container实现实现,或者如果它打算作为一个封装类,我会提供一个内部迭代器方法,你可以将访问者传递给它。Iterable<Integer>如果它是抽象数据类型,强烈考虑使用内置类型,例如int[]or List<Integer>

于 2010-01-05T16:00:50.587 回答
0

还有另一种方法,它不会复制数组,但有其他缺点。我会将它用于非常大的数组:

private A[] items;

public List<A> getItems() {
    return Collections.unmodifiableList(Arrays.asList(items));
}
于 2012-06-16T08:56:08.663 回答
0

我想提出一种与我在这里看到的所有答案不同的方法。当您考虑封装时,还可以考虑“不要向对象询问其数据,而是要求对象为您操作其数据”这一规则,这很方便。

您没有给我使用 for numArray,我将假装您的目标是出于示例目的从数学(不是 Java 向量)创建一个数字“向量”。

因此,您创建了一个包含双精度数组的 NumericVector 类。您的 NumericVector 将具有像multiplyByScalar(double scalar)addVector(NumericVector secondVector) 这样的方法来添加元素。

您的内部数组是完全封装的 - 它永远不会逃脱。对其进行的任何操作都是通过这些“业务方法”在您的 NumericVector 类中完成的。操作后如何显示?覆盖 NumericVectorNumericVector.toString()以便正确打印,或者如果您有 GUI,请编写“控制器”类以将数据从模型 (NumbericVector) 传输到视图 (GUI)。这可能需要一种从 NumericVector 流式传输元素的方法。

这也表明了一些要避免的事情:不要自动创建 setter 和 getter,它们会破坏您的封装。您经常需要 getter,但正如其他人所说,您应该让您的 getter 返回数组的不可变版本。尽可能让你的类不可变。我之前提到的那个方法numericVector.addVector(NumericVector secondVector)可能不应该修改numericVector,而是返回一个新的 NumericVector 和解决方案。

这个(以及一般的 OO)经常失败的情况是库 - 当您真的想向您的数组添加一些功能但仍将其保留为通用数组时。在这种情况下,Java 通常根本不关心封装,它只是添加了一个辅助方法/类,可以对集合/数组做一些事情(查看“Arrays”对象以获得一堆很好的示例)。

于 2017-10-13T23:49:36.507 回答
0

如何在Java中封装一个数组

这个问题可能已经很清楚了,但是我想OOP的重点是将一个类设计为一个操纵这个数组并提取它自己的 api的实体。

如果您坚持然后返回阵列克隆/防御性副本简单情况下的方法。

于 2017-10-14T00:23:27.303 回答
0

一种内存有效的方法是......

package com.eric.stackoverflow.example;

import java.util.List;

import com.google.common.collect.ImmutableList;

public class Container {
    private int numArray[] = {0, 0, 0};
    public int[] getNumArray() {
        return ImmutableList.copyOf(numArray);
    }
    public void setNumArray(int index, int value) {
        numArray[index] = value;
    }    
}

这允许ImmutableList在 的后备数组中设置正确的项目数,List并减少创建的中间对象的数量。

于 2017-10-14T03:43:20.993 回答