是否可以制作一个对象"只读"一种方法

时间:2012-05-10 12:11:09

标签: java object reference

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

11 个答案:

答案 0 :(得分:18)

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

确保某种形式的不变性的唯一功能是final字段 - 一旦编写,就无法修改。

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

  • Defensive Copying 的。传递一个对象的副本,这样如果它被突变,它就不会破坏你的内部不变量。

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

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

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

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

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

答案 1 :(得分:6)

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

答案 2 :(得分:6)

没有一般的机制。您需要编写特殊情况代码来实现它,比如编写一个不可变的包装器(参见Collections.unmodifiableList)。

答案 3 :(得分:4)

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

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

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

答案 4 :(得分:3)

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

答案 5 :(得分:2)

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

答案 6 :(得分:1)

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

答案 7 :(得分:1)

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

正如一些答案中指出的那样,它从类中提取一个Interface并仅公开get方法。这样可以防止意外修改,但又不是避免上述问题的万无一失的解决方案。

请考虑以下示例:

Customer.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();

}

Main.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方法才有效。

如果您有一个集合作为成员变量,并且不想从类中逃脱引用,则可以使用ewernli答案中指出的Collections.unmodifiableList()

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

但是对于自定义对象也可以做到这一点,我也只知道Interface方法,它可以防止意外修改,但不能确定避免引用转义的万无一失的方法。

答案 8 :(得分: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
} 

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

答案 9 :(得分:0)

private boolean isExecuteWriteQueue = false;
public boolean isWriting(){
    final boolean b = isExecuteWriteQueue;
    return b;
}

答案 10 :(得分:0)

扩展ewernli的answer ...

如果拥有这些类,则可以使用只读接口,以便使用对象的只读引用的方法只能获取子级的只读副本;而主类返回可写版本。

示例

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

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