如何编写一个不可变类

2021-06-16  本文已影响0人  G__yuan

我们一般写的实体类如下:

public class Location {
    private double x ;

    private double y;

    public Location(double x, double y) {
        this.x = x;
        this.y = y;
    }
    public double getX() {
        return x;
    }
    public void setX(double x) {
        this.x = x;
    }
    public double getY() {
        return y;
    }
    public void setY(double y) {
        this.y = y;
    }
    public void setXY(double x, double y){
        this.x =x;
        this.y = y
    }
}

接着我们写操作该实体类的业务代码

public class CarLocationTracker {

    private Map<String,Location> locationMap = new HashMap<>();

    public void updateCarLocation(String carCode,double x,double y){
        Location location = locationMap.get(carCode);
        location.setXY(x,y);
    }
    public Location get(String carCode){
        return locationMap.get(carCode);
    }
}

通过这业务代码我们可以通过方法updateCarLocation()来改变位置信息,通过get()方法来获取位置信息。但是在改变Location的位置信息是,setXY方法是线程不安全的。我们来通过下图分析为啥线程不安全。


image.png

如图所示,这个location信息的初始值是x = 1.0,y = 1.0;这时候线程1调用updateCarLocation()方法来更新位置信息为x = 2.0,y = 2.0,在更新的过程中线程1只更新了x = 2.0,还没来的急更新 y呢,结果线程2来读取这个location信息了,此时就读取到x = 2.0,y = 1.0,这分明不是想要的结果么,所以说这线程不安全。

接下来,利用不可变来改造成线程安全的

那怎么改呢,所谓的不可变类就是,一个对象一创建就不再改变。对于咋们这案例来说,就是location类一创建它的x 和 y就不能改变了,那这x和y值不能改变,是不是突然就想到了java中的关键字final来修饰这两个字断,来保证这两个字段的值不可变。

private final double x ;
private final double y;

接着想,x和y的值都不能改变,那还有setXY()方法和set方法干啥,干掉它。
接着想,如果这个类被继承了,那还是一个不可变类吗?看下面代码:

public class SubLocation extends Location {

    public SubLocation(double x, double y) {
        super(x, y);
    }
    @Override
    public double getX() {
        return super.getX() +1;
    }
}

如上代码所示,这样有人继承了location类,然后重写了getX()方法。比如说本来location对象的x值为1,但是这个子类却返回了1+1=2。这显然不符合不可变对象的行为,因为它的子类可以改变它的行为,为了避免这样的问题,我们将改类改造成不可继承的类。最终的Location类如下:

public final class Location {
    private final double x ;
    private final double y;

    public Location(double x, double y) {
        this.x = x;
        this.y = y;
    }
    public double getX() {
        return x;
    }
    public double getY() {
        return y;
    }
}

接下来我们改造业务代码:

public class CarLocationTracker {

    private Map<String,Location> locationMap = new HashMap<>();

    public void updateCarLocation(String carCode,Location newLocation){
        locationMap.put(carCode,newLocation);

    }
    public Location get(String carCode){
        return locationMap.get(carCode);
    }
}

updateCarLocation方法中通过替换整个location对象来解决线程安全问题。

总结(如何将一个类改造成一个不可变类)

public class Demo {

    private final List<Integer> data = new ArrayList<>();

    public Demo() {
        data.add(1);
        data.add(2);
        data.add(3);
    }
    public List<Integer> getData(){
        return data;
    }
    public static void main(String[] args) {
        Demo demo = new Demo();
        List<Integer> data = demo.getData();
        data.add(4);
    }
}

看上面这段代码,在main方法中,从Demo中获取到list之后,还是可以像list中添加数据的,修改其里面的内容。因为getData返回的是一个引用,它指向的和Demo类中的data是同一对象,所以这样data中就多了个值4,所以得做防御性复制。接着来改造getData方法;

public List<Integer> getData(){
        return Collections.unmodifiableList(data);
}

这样改造完,如果再向其添加值后,就会抛出异常。


image.png
上一篇下一篇

猜你喜欢

热点阅读