是否遵循java约定只为集合提供getter?

是否遵循java约定只为集合提供getter?,java,Java,我最近遇到了如下代码: public List<Item> getItems() { if (items == null) { items = new ArrayList<Item>(); } return this.items; } 而不是 foo.setItems(myArrayList) 我以前没有见过这个习惯用法,我不能说我喜欢它,但是当我使用mapstruct.org(顺便说一下,这是一个很棒的工具)生成一些映射代码时

我最近遇到了如下代码:

public List<Item> getItems() {
    if (items == null) {
        items = new ArrayList<Item>();
    }
    return this.items;
}
而不是

foo.setItems(myArrayList)
我以前没有见过这个习惯用法,我不能说我喜欢它,但是当我使用mapstruct.org(顺便说一下,这是一个很棒的工具)生成一些映射代码时,mapstruct处理得很好,并且正确地生成了使用getter作为setter的代码


我只是想知道,这是不是一个常见的习语,我不知怎么错过了?对我来说,这似乎毫无意义,但也许这背后有一些我没有看到的智慧?

您的代码中有两件事:

  • ,这是一种常见的方法
  • 返回对可变内部
    ArrayList
    的引用,这不好,因为它会中断。如果要将项目添加到此列表,则应仅公开
    addItem()
    方法
  • 这很常见,但不推荐使用。 问题不在于懒惰的初始化(这一部分很好),而是应该是实现细节的暴露。一旦您公开了存储在字段中的实际的、可变的(!)列表对象,代码的调用者就可以对列表执行任何操作,甚至是您不期望的操作

    例如,当您真正想要允许的只是
    add()
    时,它们可以删除对象。他们可以从不同的线程修改它,使您的代码以有趣和令人沮丧的方式中断。他们甚至可以将它转换为一个原始的
    列表
    ,并用完全不同类型的对象填充它,使您的代码抛出
    ClassCastException
    s

    换句话说,它使强制类不变量变得不可能。

    请注意,有两种因素共同导致此问题:

  • 存储在野外的物体的曝光
  • 事实上,这个对象是可变的
  • 如果这两种说法中的任何一种都是错误的,那就没有问题了。所以这很好:

    public String getFoo() {
       return this.foo;
    }
    
    因为
    字符串
    是不可变的。这也很好:

    public List<String> getFooList() {
       return new ArrayList<>( this.fooList );
    }
    
    public List get傻瓜列表(){
    返回新的ArrayList(this.傻瓜列表);
    }
    
    因为现在您返回的是防御副本,而不是实际对象。(但是,如果列表中的元素是可变的,您将再次遇到麻烦。)


    这个问题有一个更微妙的变化。。。 想象一下这个场景:

    public class Foo { 
       private List<String> list;
       public Foo( List<String> list ) {
         this.list = list; // Don't do this
       }
       ...
    }
    
    公共类Foo{
    私人名单;
    公共食物办事处(名单){
    this.list=list;//不要这样做
    }
    ...
    }
    
    这看起来完全无害,你可以在很多地方看到。然而,这里也有一个隐藏的陷阱:在存储列表之前不复制,你就处于完全相同的情况。你无法阻止某人这样做:

    List<String> list = new ArrayList<>();
    list.add( "nice item" );
    Foo foo = new Foo( list );
    list.add( "hahahaha" );
    list.add( "i've just added more items to your list and you don't know about it." );
    list.add( "i'm an evil genius" );
    
    List List=new ArrayList();
    列表。添加(“佳品”);
    Foo Foo=新Foo(列表);
    列表。添加(“哈哈哈”);
    添加(“我刚刚在你的列表中添加了更多的项目,而你对此一无所知。”);
    添加(“我是一个邪恶的天才”);
    
    因此,在将可变对象分配给字段之前和返回字段时,都应该制作防御性副本。


    如果公开一个类的可变字段是如此危险,那么为什么人们不一直制作防御副本呢? 除了不知道为什么这不是一个好主意外,还有两大类借口

  • 性能。每次调用getter时复制一个对象是很昂贵的。这当然是真的,但它并不总是像你想象的那样昂贵。如果您发现自己为防御性拷贝付出了太高的成本,通常有办法:例如,将类设计为不可变的。如果所有类都是不可变的,那么就永远不需要防御副本
  • 只有我会调用此代码,我不会滥用它。承诺。根据具体情况,这同样是有效的。如果您自己编写一个小型的独立应用程序,您可能不会滥用它。或者,如果您正在编写一个小型库,但只公开不属于公共API的类的详细信息。但在大多数其他情况下,你无法确定某个地方的人是否会滥用它。当这种情况发生时,您的代码可能不会突然中断。它通常只是开始做一些轻微的…偶尔奇怪的事情。这是最难找到的bug

  • 这是一个常见的习惯用法,但我不建议使用它,因为调用者可以执行以下操作:

    final List<Item> items = bean.getItems();
    final List notItems = (List) items;
    notItems.add(new NotAnItem());
    

    公共作废设置项(最终列表项){
    if(items==null){
    抛出新的NullPointerException(“items为null”);
    }
    this.items=新阵列列表(items);
    }
    
    我认为这令人困惑。当我只有getter时,我希望返回不可修改的集合。我最好将它命名为
    getItemList()
    ,以表明可以在其上放置元素。实际上,在我看来,这比从外部接受任何未知列表要好。至少,该类可以保证其列表是可变的,并且是ArrayList。更好的办法是避免暴露列表,并提供addItem()方法。
    setItems()
    也会让人困惑。“设置”是指“如果为空则设置或替换现有”
    addItems()
    会更清楚。@JBNizet它更好,但只是在关闭但不锁上前门比敞开大门更安全的意义上。@biziclop同意,因此我的评论的第二部分:不要公开列表,通过专用的addItem()方法对其进行修改。谢谢。我不清楚这对懒惰有什么帮助。毕竟,如果目标只是不情愿地生成对象,那么使用这种方法,您生成的新对象不发出任何信号,而不是返回null?只需将该方法设置为returnitems,就可以生成较少的对象?如果我有一个预先存在的arraylist,我想用它来填充这个结构,我必须将所有元素复制到新创建的arraylist中,而不是仅仅调用setItems——同样,生成更多的对象?@user4782738返回null会
    final List<Item> items = bean.getItems();
    final List notItems = (List) items;
    notItems.add(new NotAnItem());
    
    public List<Item> getItems() {
        if (items == null) {
            return Collections.emptyList();
        }
        return Collections.unmodifiableList(this.items);
    }
    
    public void setItems(final List<Item> items) {
        if (items == null) {
            throw new NullPointerException("items is null");
        }
        this.items = new ArrayList<>(items);
    }