Java中到底应该怎么写getter?

很多人都知道Java一般会用getter来暴露内部成员

但是我可以断言99.9%的程序员不管你有多少年经验恐怕都写不出真正正确的getter

我真不是危言耸听

首先如果getter的返回类型是primitive或者immutable的

这种情况下大家都基本能写对

小编这里有一份Java学习资料,加我的QQ群:985331340免费获取。

public class Vec2 {
    public Vec2(float x,float y) {
        this.x = x;
        this.y = y;
    }

    public void setX(float x) {
        this.x = x;
    }

    public void setY(float y) {
        this.y = y;
    }

    public float getX() {
        return x;
    }

    public float getY() {
        return y;
    }

    public Vec2 copy() {
        return new Vec2(x,y);
    }

    float x,y;
}

这里大家都没有问题

因为Vec2中的x,y都是primitive

get的时候返回的是值的拷贝

所以这里没有任何问题

大家注意这里的Vec2设计是mutable的

Java中在写getter的时候最难处理的就是返回一个mutable的对象了

如果你还是非常naive的这么写

public Vec2 getPos() {
    return pos;
}

那就会出现两个问题

1.如果你修改了pos指向的对象的x,y,会影响其他地方

2.如果其他地方修改了pos指向的对象的x,y,会影响到你

所以一般有经验的程序员会知道这里应该返回拷贝

public Vec2 getPos() {
    return pos.copy();
}

不过问题到这里就结束了么

当然不是

这里Vec2就两个内部数据

拷贝的成本不大

但是试想一下如果这里是一个List呢?

如果List中有很多数据的话每次get都返回拷贝会极大的影响性能

然后肯定有一些更有经验的程序员会说返回Collections.unmodifiableList(list)啊

public List<Integer> getNumbers() {
    return Collections.unmodifiableList(list);
}

此时返回的List一旦调用任何会修改list内部数据的方法就会发生异常

不过大家发现没有

这只解决了上面出现的两个问题中的问题1

因为这么做只是让外部无法修改返回的List而已

内部仍然可以修改list的内部数据

一旦list的内部数据被修改仍然会影响到你

现在估计很多程序员已经不知道怎么办了

我们来看看其他语言是怎么解决这个问题的

其中做的最好的是Swift

Swift采用了COW(copy on write)的方式来解决这个问题

很遗憾的是从各个角度来说Java都难以实现(因为想要优雅的实现有两个条件 第一需要支持值类型,第二需要用引用计数来管理引用类型 很显然Java都不具备 就算勉强实现了 是极其丑陋的 并且使用的时候需要遵守很多规范 这里我不展开讲了 因为文章目的不在于此 而且估计很多Java程序员看不懂)

既然Java不能优雅的做到COW

那么这个问题就无解了么?

当然不是

解决问题的方法当然还是拷贝

只是我们不在get的时候进行拷贝

而是在内部对list进行所有写操作之前进行拷贝(需要把所有对list的写操作都合并到一起)

并且在get的时候返回一个unmodifiable的对象

这样就能同时解决以上两个问题 并且也尽可能的避免了性能问题

public class Foo {
    void updateList() { 
        var newBar = new ArrayList(bar);
        
        //updating list...

        bar = newBar; 
    }

    public List<Integer> getBar() {
        return Collections.unmodifiableList(bar);
    }

    volatile List<Integer> bar = new ArrayList<>();
    //这里之所以要加volatile 是防止bar=newBar被重排到更新完newBar的内容之前(因为这里并没有依赖关系)
    //如果出现这种情况,get就可能在newBar没更新完之前返回newBar引用,导致返回的List中的内容仍然可能在更新newBar的期间会改变
}

所以结论是:

1.对于immutable对象 直接返回即可

2.对于小的mutable对象 get的时候直接返回其拷贝

3.对于大的mutable对象 get的时候对外返回其的immutable view 并且把所有内部对其的修改合并到一个方法中 在对其修改之前进行拷贝

作者:KiKi

原文链接:https://zhuanlan.zhihu.com/p/58154308

猜你喜欢

转载自blog.csdn.net/qq_43202482/article/details/88116038
今日推荐