我从Java开始,我正在学习setter,getters和encapsulation。我有一个非常简单的程序,两个类:
Container
有一个私人int数组(numArray
)与他的二传手和吸气剂。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更改private int数组,我知道这是因为getNumArray
返回对numArray
的引用,而不是数组本身。如果我对数组的单个元素感兴趣,我会使用索引值制作一个getter,但我希望整个数组用于totalArray
方法。
我如何防止numArray
被修改出他的班级?
您可以做的就是阻止人们更改阵列,只是在getter中提供它的副本。
public int[] getArray() {
return Arrays.copyOf(numArray, numArray.length);
}
这样,其他方法可以更改自己的数组副本,但是当他们再次调用getter时,它们会获得原始版本,不变。只有您提供的setNumArray()
才能实际修改内部数组。
否则,如果要完全阻止容器,则必须删除数组并使用不可变对象。 Some库提供不可变列表,或使用Collections.unmodifiableList。
如果要返回一个数组,可以克隆它:
public int[] getArray() {
return (int[]) numArray.clone();
}
在公共API中,您应该确保向调用者清楚地记录(实际上,无论哪种方式,如果他们获得的数组将改变类的状态 - 他们需要知道)。
通常,您会查看您尝试向班级的来电者提供的界面。
方法如:
void addItem(Object item);
Object getItem(int index);
int getSize();
是你在容器类中提供的东西。然后,他们代表呼叫者与私有阵列进行交互。
如果要在不允许更改的情况下返回整个数组,可以在getter中将数组复制到新数组中并返回副本。
或者,如果您使用Java集合类而不是原始数组,则它们提供了一个不可修改的XXX()方法(例如Collections.unmodifiableList(myList)
),它提供了一个围绕集合的只读包装器。
封装是隐藏实现的过程。集合是否将数据存储在数组中是一个实现细节;如果它是封装的,你可能希望能够将其更改为另一种存储类型。
事实上,您将状态(或派生状态)公开为getter和setter会破坏封装,并暗示您正在实现抽象数据类型而不是真正的面向对象类。例如,ArrayList
是一种数据类型,它不代表应用程序中任何行为的真正封装。这是否是您想要的取决于类型的使用方式和位置。
如果它只是一个容器数据类型,我倾向于使Container
实现Iterable<Integer>
用于外部交互,或者提供一个内部迭代器方法,如果它打算作为封装类传递访问者。如果它是抽象数据类型,请强烈考虑使用int[]
或List<Integer>
等内置函数。
还有另一种方法,它没有复制数组,但有其他缺点。我会将它用于非常大的数组:
private A[] items;
public List<A> getItems() {
return Collections.unmodifiableList(Arrays.asList(items));
}
如何在Java中封装数组
这个问题可能很清楚,但我想OOP的意思是将一个类设计为一个操纵这个数组并提取它自己的api的实体。
如果你坚持然后返回一个克隆数组/防御副本是简单的情况下的方法。
我想从我在这里看到的所有答案中提出一个不同的方法。当您考虑封装时,它也很方便,也可以考虑规则“不要向对象询问其数据,请求对象为您的数据操作”。
你没有给我使用numArray
,我打算假装你的目标是从数学(不是Java Vector)创建一个数字“Vector”,例如用途。
因此,您创建一个包含双精度数组的NumericVector类。您的NumericVector将使用multiplyByScalar(double scalar)
和addVector(NumericVector secondVector)等方法来添加元素。
你的内部数组是完全封装的 - 它永远不会逃脱。对它进行的任何操作都是通过这些“业务方法”在NumericVector类中完成的。操作后如何显示?让NumericVector覆盖NumericVector.toString()
以便正确打印出来,或者如果你有GUI,编写一个“Controller”类来将数据从Model(NumbericVector)传输到你的视图(GUI)。这可能需要一种从NumericVector流式传输元素的方法。
这也表明了一些要避免的事情:不要自动创建setter和getter,它们会破坏你的封装。你经常需要getter,但正如其他人所说,你应该让你的getter返回一个不可变的数组版本。也尽量让你的类不可变。我之前提到的那种方法numericVector.addVector(NumericVector secondVector)
可能不应该修改numericVector
但是返回一个新的NumericVector和解决方案。
这个(和OO一般)经常失败的情况是库 - 当你真的想要为你的数组添加一些功能但仍然把它作为通用数组时。在这种情况下,Java通常根本不会使用封装,它只是添加了一个可以对集合/数组执行操作的辅助方法/类(查看“Arrays”对象以获得一些很好的示例)。
一种记忆效率高的方法是......
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
的后备数组中设置正确的项目数,并减少创建的中间对象的数量。