23

如果将对象引用传递给方法,是否可以将对象设置为“只读”方法?

4

11 回答 11

24

严格来说不是。也就是说,不能改变对象的引用不能变成不能改变对象的引用。此外,除了使用约定之外,没有办法表示类型是不可变的或可变的。

确保某种形式的不变性的唯一功能是final字段——一旦编写,它们就不能被修改。

也就是说,有一些方法可以设计类,以防止不需要的突变。这里有一些技巧:

  • 防御性复制。传递对象的副本,这样如果它发生突变,它就不会破坏您的内部不变量。

  • 使用访问修饰符和/或接口仅公开只读方法。您可以使用访问修饰符 ( public/ private/ protected),可能与接口结合使用,以便只有某些方法对另一个对象可见。如果公开的方法本质上是只读的,那么您是安全的。

  • 默认情况下使您的对象不可变。对对象的任何操作实际上都会返回对象的副本。

另请注意,SDK 中的 API 有时具有返回对象不可变版本的方法,例如Collections.unmodifiableList. 改变不可变列表的尝试将引发异常。这不会静态地强制不变性(在编译时使用静态类型系统),而是一种廉价且有效的方式来动态强制它(在运行时)。

已经有许多关于 Java 扩展的研究建议,以更好地控制别名和可访问性。例如,添加readonly关键字。据我所知,它们都没有计划包含在 Java 的未来版本中。如果您有兴趣,可以看看这些指针:

Checker 框架非常有趣。在 Checker Framework 中,查看 Generic Universe Types checker、IGJ immutability checker 和 Javari immutability checker。该框架使用注释工作,因此它不是侵入性的。

于 2012-05-10T12:22:26.550 回答
7

不,不是没有装饰、合成、克隆等。

于 2012-05-10T12:13:23.313 回答
7

没有通用的机制。您需要编写特殊情况的代码来实现它,例如编写不可变的包装器(请参阅 参考资料Collections.unmodifiableList)。

于 2012-05-10T12:13:23.933 回答
4

在大多数情况下,您可以通过克隆方法的第一个语句来实现类似的事情Object,例如这个......

public void readOnlyMethod(Object test){
    test = test.clone();
    // other code here
}

因此,如果您调用readOnlyMethod()并传入 any ObjectObject则会获取 的克隆。克隆使用与方法的参数相同的名称,因此不存在意外更改原始Object.

于 2012-05-10T12:14:11.193 回答
3

不,但是您可以在传递对象之前尝试克隆对象,因此该方法所做的任何更改都不会影响原始对象。

于 2012-05-10T12:13:03.990 回答
3

使其实现一个只有只读方法(没有 setter 方法)的接口,这给出了一个对象的副本(仅限道路副本)并返回接口的只读实例而不是返回对象本身的实例

于 2017-10-02T06:34:19.610 回答
1

您可以将对象的所有参数定义为,final但这会使对象只读给所有人。

于 2012-05-10T12:13:43.663 回答
1

我相信您真正的问题是关于避免转义引用。

正如一些从类中提取接口并仅公开获取方法的答案中所指出的那样。它将防止意外修改,但它再次不是避免上述问题的万无一失的解决方案。

考虑下面的例子:

客户.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 的回答中指出的那样使用。

有了这个,没有外部代码可以修改基础集合,并且您的数据是完全只读的。

但是当涉及到自定义对象时,我也只知道接口方法可以防止意外修改,但不确定避免引用转义的万无一失的方法。

于 2019-06-12T11:54:14.443 回答
0

取决于您希望在哪里执行规则。如果您正在协作处理一个项目,请使用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
} 

因此,特别是对于这种方法,该值永远不会被写入,并且在编译时强制执行,从而使解决方案非常健壮。在此方法的范围之外,对象保持不变,而无需创建任何类型的包装器。

于 2012-05-15T18:10:51.490 回答
0
private boolean isExecuteWriteQueue = false;
public boolean isWriting(){
    final boolean b = isExecuteWriteQueue;
    return b;
}
于 2014-10-02T05:11:54.690 回答
0

扩展 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
    }
}

如果您不拥有这些类,则可以扩展该类,覆盖设置器以引发错误或无操作,并使用单独的设置器。这将有效地使基类引用只读基类,但是这很容易导致混淆和难以理解的错误,因此请确保它有良好的文档记录。

于 2018-11-21T18:15:25.737 回答