Warning: file_get_contents(/data/phpspider/zhask/data//catemap/9/java/315.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181

Warning: file_get_contents(/data/phpspider/zhask/data//catemap/4/oop/2.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Java 共享可变状态有什么不对?_Java_Oop - Fatal编程技术网

Java 共享可变状态有什么不对?

Java 共享可变状态有什么不对?,java,oop,Java,Oop,在Java并发实践第3章中,作者建议不要共享可变状态。此外,他还补充说,下面的代码并不是一个分享州的好方法 class UnsafeStates { private String[] states = new String[] { "AK", "AL" }; public String[] getStates() { return states; } } 书中: 以这种方式发布状态是有问题的,因为任何调用方都可以修改其内容。在本例

在Java并发实践第3章中,作者建议不要共享可变状态。此外,他还补充说,下面的代码并不是一个分享州的好方法

class UnsafeStates {

    private String[] states = new String[] {
      "AK", "AL"
    };

    public String[] getStates() {
        return states;
    }
}
书中:

以这种方式发布状态是有问题的,因为任何调用方都可以修改其内容。在本例中,states数组已超出其预期范围,因为原本应该是私有状态的内容实际上已公开


我这里的问题是:我们经常使用getter和setter来访问类级别的私有可变变量。如果不是正确的方式,那么分享国家的正确方式是什么?封装
状态的正确方法是什么?

对于基本类型、
int
float
等,使用这样的简单getter不允许调用方设置其值:

someObj.getSomeInt() = 10; // error!
但是,对于数组,您可以从外部更改其内容,这可能是不需要的,具体取决于以下情况:

someObj.getSomeArray()[0] = newValue; // perfectly fine
这可能会导致字段被代码的其他部分意外更改的问题,从而导致难以跟踪错误

您可以做的是返回数组的副本:

public String[] getStates() {
    return Arrays.copyOf(states, states.length);
}

这样,即使调用者更改了返回数组的内容,对象所持有的数组也不会受到影响。

根据您拥有的内容,有人可以通过getter本身更改您的
private
数组的内容:

public static void main(String[] args) {
  UnsafeStates us = new UnsafeStates();
  us.getStates()[0] = "VT";

  System.out.println(Arrays.toString(us.getStates());
}
输出:

[VT, AR]
如果要封装状态并使其无法更改,则最好创建枚举:

public enum SafeStates {
  AR,
  AL
}

创建枚举有两个优点。它允许人们使用精确的阀门。它们不能被修改,它很容易测试,并且可以很容易地对其执行switch语句。使用枚举的唯一缺点是必须提前知道值。也就是说,你为它编码。无法在运行时创建。

这个问题似乎是针对并发性提出的

首先,当然,有可能修改通过简单的getter获得的非原始对象;正如其他人所指出的,即使使用单线程程序,这也是一种风险。避免这种情况的方法是返回数组的副本或集合的不可修改实例:例如,请参见

但是,对于使用并发的程序,即使getter的调用方不尝试修改返回的对象,也有返回实际对象(即,不是副本)的风险。由于并发执行,对象可能会在“他看它的时候”更改,通常这种缺乏同步的情况会导致程序出现故障

很难将原始的
getStates
示例转化为对我的观点的令人信服的说明,但是想象一个getter返回一个
Map
。在拥有对象内部,可以实现正确的同步。但是,仅返回对
Map
的引用的
getTheMap
方法会邀请调用方调用
Map
方法(即使只是
Map.get


基本上有两种方法可以避免这个问题:(1)返回深度副本;在这种情况下,一个不可修改的包装器是不够的,它应该是一个深度副本,否则我们只会在下一层遇到同样的问题,或者(2)不要返回未传递的引用;相反,扩展方法库,以提供正确的内部同步,从而准确地提供可支持的内容。

getter和setter适用于原语。在本例中,您将获得一个数组,因此您可以在不使用getter或setter的情况下更改其内容。仅使用getter仍然允许使用者修改数组。也许作者建议在本例中返回数组的副本/克隆。在公开复杂对象时,讨论会更加深入,通常会引用Demeter定律,并建议对象应显式公开其完整的操作集,而不是公开内部对象(如您的示例中),内部对象本身具有修改其状态的操作。