如果将对象引用传递给方法,是否可以将对象设置为“只读”方法?
11 回答
严格来说不是。也就是说,不能改变对象的引用不能变成不能改变对象的引用。此外,除了使用约定之外,没有办法表示类型是不可变的或可变的。
确保某种形式的不变性的唯一功能是final
字段——一旦编写,它们就不能被修改。
也就是说,有一些方法可以设计类,以防止不需要的突变。这里有一些技巧:
防御性复制。传递对象的副本,这样如果它发生突变,它就不会破坏您的内部不变量。
使用访问修饰符和/或接口仅公开只读方法。您可以使用访问修饰符 (
public
/private
/protected
),可能与接口结合使用,以便只有某些方法对另一个对象可见。如果公开的方法本质上是只读的,那么您是安全的。默认情况下使您的对象不可变。对对象的任何操作实际上都会返回对象的副本。
另请注意,SDK 中的 API 有时具有返回对象不可变版本的方法,例如Collections.unmodifiableList
. 改变不可变列表的尝试将引发异常。这不会静态地强制不变性(在编译时使用静态类型系统),而是一种廉价且有效的方式来动态强制它(在运行时)。
已经有许多关于 Java 扩展的研究建议,以更好地控制别名和可访问性。例如,添加readonly
关键字。据我所知,它们都没有计划包含在 Java 的未来版本中。如果您有兴趣,可以看看这些指针:
- 为什么我们不应该在 Java 中添加“只读”(尚未) ——它列出并比较了大多数提案
- Checker 框架:Java 的自定义可插入类型——一种扩展类型系统的非侵入式方法,尤其是不可变类型。
Checker 框架非常有趣。在 Checker Framework 中,查看 Generic Universe Types checker、IGJ immutability checker 和 Javari immutability checker。该框架使用注释工作,因此它不是侵入性的。
不,不是没有装饰、合成、克隆等。
没有通用的机制。您需要编写特殊情况的代码来实现它,例如编写不可变的包装器(请参阅 参考资料Collections.unmodifiableList
)。
在大多数情况下,您可以通过克隆方法的第一个语句来实现类似的事情Object
,例如这个......
public void readOnlyMethod(Object test){
test = test.clone();
// other code here
}
因此,如果您调用readOnlyMethod()
并传入 any Object
,Object
则会获取 的克隆。克隆使用与方法的参数相同的名称,因此不存在意外更改原始Object
.
不,但是您可以在传递对象之前尝试克隆对象,因此该方法所做的任何更改都不会影响原始对象。
使其实现一个只有只读方法(没有 setter 方法)的接口,这给出了一个对象的副本(仅限道路副本)并返回接口的只读实例而不是返回对象本身的实例
您可以将对象的所有参数定义为,final
但这会使对象只读给所有人。
我相信您真正的问题是关于避免转义引用。
正如一些从类中提取接口并仅公开获取方法的答案中所指出的那样。它将防止意外修改,但它再次不是避免上述问题的万无一失的解决方案。
考虑下面的例子:
客户.java:
public class Customer implements CustomerReadOnly {
private String name;
private ArrayList<String> list;
public Customer(String name) {
this.name=name;
this.list = new ArrayList<>();
this.list.add("First");
this.list.add("Second");
}
@Override
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public ArrayList<String> getList() {
return list;
}
public void setList(ArrayList<String> list) {
this.list = list;
}
}
CustomerReadOnly.java:
public interface CustomerReadOnly {
String getName();
ArrayList<String> getList();
}
主.java:
public class Test {
public static void main(String[] args) {
CustomerReadOnly c1 = new Customer("John");
System.out.println("printing list of class before modification");
for(String s : c1.getList()) {
System.out.println(s);
}
ArrayList<String> list = c1.getList();
list.set(0, "Not first");
System.out.println("printing list created here");
for(String s : list) {
System.out.println(s);
}
System.out.println("printing list of class after modification");
for(String s : c1.getList()) {
System.out.println(s);
}
}
}
输出:
printing list of class before modification
First
Second
printing list created here
Not first
Second
printing list of class after modification
Not first
Second
因此,正如您所看到的,仅当您没有任何可变成员变量时,提取接口和仅公开 get 方法才有效。
如果您有一个集合作为成员变量,您不想从类中逃脱其引用,则可以Collections.unmodifiableList()
按照 ewernli 的回答中指出的那样使用。
有了这个,没有外部代码可以修改基础集合,并且您的数据是完全只读的。
但是当涉及到自定义对象时,我也只知道接口方法可以防止意外修改,但不确定避免引用转义的万无一失的方法。
取决于您希望在哪里执行规则。如果您正在协作处理一个项目,请使用final
注释告诉下一个人他们不打算修改此值。否则你不会简单地编写不接触对象的方法吗?
public static void main(String[] args) {
cantTouchThis("Cant touch this");
}
/**
*
* @param value - break it down
*/
public static void cantTouchThis(final String value) {
System.out.println("Value: " + value);
value = "Nah nah nah nah"; //Compile time error
}
因此,特别是对于这种方法,该值永远不会被写入,并且在编译时强制执行,从而使解决方案非常健壮。在此方法的范围之外,对象保持不变,而无需创建任何类型的包装器。
private boolean isExecuteWriteQueue = false;
public boolean isWriting(){
final boolean b = isExecuteWriteQueue;
return b;
}
扩展 ewernli 的答案...
如果您拥有这些类,则可以使用只读接口,以便使用对象只读引用的方法只能获取子对象的只读副本;而主类返回可写版本。
例子
public interface ReadOnlyA {
public ReadOnlyA getA();
}
public class A implements ReadOnlyA {
@Override
public A getA() {
return this;
}
public static void main(String[] cheese) {
ReadOnlyA test= new A();
ReadOnlyA b1 = test.getA();
A b2 = test.getA(); //compile error
}
}
如果您不拥有这些类,则可以扩展该类,覆盖设置器以引发错误或无操作,并使用单独的设置器。这将有效地使基类引用只读基类,但是这很容易导致混淆和难以理解的错误,因此请确保它有良好的文档记录。