Java中的集合和封装

面向对象编程的核心租户是封装:不应允许调用者直接访问类的字段。这是新的语言,包括Kotlin,Swift和Ceylon,已经很好地解决了一流的属性。

Java没有第一类属性的概念。相反,JavaBeans规范是作为Java执行封装的方法引入的。编写JavaBeans意味着您需要将类的字段设置为私有,仅通过getter和setter方法公开它们。

如果你像我一样,你在编写JavaBeans时经常会感觉到你正在编写一堆很少用于任何实际目的的理论样板。我的大多数JavaBeans都包含私有字段及其相应的getter和setter,它们只能获取和设置这些私有字段。不止一次,我一直试图简单地将这些领域公之于众,并免除了getter / setter的大肆宣传,至少在IDE发出严厉的警告后,我的双腿之间的尾巴,以及JavaBeans标准。

但是,最近,我意识到封装和JavaBean / getter / setter模式在常见场景中非常有用:集合类型字段。怎么会这样?让我们编写一个简单的类:

公共 类 MyClass {

 
    private  List < String >  myStrings ;

 
}
 

我们有一个字段 - 一个字符串列表 - 调用  myStrings,它被封装在中MyClass。现在,我们需要提供访问器方法:

公共 类 MyClass {

 
    private  List < String >  myStrings ;

 
    public  void  setMyStrings(List < String >  s){
        这个。myStrings  =  s ;
    }

 
    public  List < String >  getMyStrings(){
        归还 这个。myStrings ;
    }

 
}
 

在这里,我们有一个适当封装的 - 如果不是详细的 - 类。所以我们做得很好,对吗?坚持这个想法。

可选课程

考虑在Java 8中引入的Optional类。如果你已经使用Optionals做了很多工作,你可能听说过你应该永远不会null 从返回Optional的方法返回的咒语  。为什么?考虑以下人为的例子:

公共 课 Foo {

 
    私人 字符串 栏 ;

 
    public  可选< String >  getBar(){
        return(bar  ==  null)? null:可选。的(巴);
    }

 
}
 

现在,客户可以这样使用该方法:

foo。getBar()。ifPresent(log :: info);
 

冒险投掷  NullPointerException。或者,他们可以执行  null 检查:

如果(FOO。getBar()!=  空){
    foo。getBar()。ifPresent(log :: info);
}
 

当然,这样做会击败Optionals的目的。事实上,这样违背了选配的目的,它的成为标准做法是,返回可选将任何API 从未返回一个  null 值。

回到收藏。就像Optional包含none或one一样,Collection包含none或some。和Optionals一样,没有理由返回  null 集合(除了可能在罕见的,特殊情况下,我目前无法想到的任何情况)。只需返回一个空(零大小)Collection即可指示缺少任何元素。

正是由于这个原因,确保返回Collection类型(包括数组)的方法永远不会返回null 值变得越来越常见  ,与返回Optional类型的方法相同。也许您或您的组织已经在编写新代码时采用了此规则。如果没有,你应该。毕竟,你(或你的客户)会这么做吗?:

boolean  isUnique  =  personDao。getPersonsByName(name)。size()==  1 ;
 

或者,让你的代码乱七八糟?:

列表< 人>  人 =  人道。getPersonsByName(name);
boolean  isUnique  =(persons  ==  null)? 虚假:人。size()==  1 ;
 

那么这与封装有什么关系呢?

保持对我们收藏的控制

回到我们MyClass班。实际上,一个实例MyClass很容易null 从该getMyStrings()方法返回  ; 事实上,一个新的实例就是这样做的。因此,为了遵守我们新的永不回归零收集指南,我们需要解决这个问题:

公共 类 MyClass {

 
    private  List < String >  myStrings  =  new  ArrayList <>();

 
    public  void  setMyStrings(List < String >  s){
        这个。myStrings  =  s ;
    }

 
    public  List < String >  getMyStrings(){
        归还 这个。myStrings ;
    }

 
}
 

问题解决了?不完全是。任何客户都可以打电话aMyClass.setMyStrings(null),在这种情况下我们会回到原点。

在这一点上,封装听起来像一个实际的 - 而不是单独的理论 - 概念。让我们扩展setMyStrings()方法:

public  void  setMyStrings(List < String >  s){
    if(s  ==  null){
        这个。myStrings。clear();
    } else {
        这个。myStrings  =  s ;
    }
}
 

现在,即使  null 传递给setter,  myStrings 也会保留一个有效的引用(在这里的例子中,我们  null 认为应该清除元素,这是一个合理的假设)。当然,调用aMyClass.getMyStrings() = null对MyClass“基础myStrings 变量”  没有影响  。我们都这样做了吗?

呃,好吧,有点儿。我们可以在这里停下来 但实际上,我们应该做的更多。

考虑到我们正在取代我们的私人  ArrayList 与  List 调用者传递给我们。这有两个问题:首先,我们不再知道所List 使用的确切  实现  myStrings。从理论上讲,这应该不是问题,对吗?好吧,考虑一下:

myClass。setMyStrings(收藏。unmodifiableList(“嘿,疑难杂症!” ));
 

因此,如果我们更新MyClass它试图修改内容   myStrings,那么坏事可能会在运行时开始发生。

第二个问题是调用者保留对我们的底层的引用  List。所以现在,调用者现在可以直接操纵我们的  List。

我们应该做的是将传递给我们的元素存储在 初始化的  ArrayList to中  myStrings。虽然我们正在努力,但我们真正拥抱封装。我们应该从外部呼叫者隐藏我们班级的内部。实际情况是我们类的调用者不应该关心是否存在底层List,Set,数组或一些运行时动态代码生成voodoo,它们存储我们传递给它的字符串。他们应该知道的是,字符串以某种方式存储。所以让我们setMyStrings()这样更新方法:

public  void  setMyStrings(Collection < String >  s){
    这个。myStrings。clear();
    if(s  !=  null){
        这个。myStrings。addAll(s);
    } 
}
 

这样可以确保  myStrings 以输入参数中包含的相同元素结束(如果传递null,则为  空),同时确保调用者没有引用   myStrings。

现在   myStrings'参考不能改变,让我们把它变成一个常数:

公共 类 MyClass {
    private  final  List < String >  myStrings  =  new  ArrayList <>();
    ...
}
 

虽然我们在这里,但我们不应该通过我们的getter返回我们的基础List。这也会使调用者直接引用myStrings。为了解决这个问题,请回想一下有效Java击败我们头脑的“防御性副本”口号(或至少应该有):

public  List < String >  getMyStrings(){
    //取决于我们想要返回的确切内容
    返回 新 的ArrayList <> (此。myStrings);  
}
 

此时,我们有一个封装良好的类,null无论何时调用其getter ,都不需要  -checking。但是,我们已经从客户那里获得了一些控制权。由于他们不再可以直接访问我们的基础列表,因此他们不能再添加或删除单个字符串。 

没问题。如果我们可以简单地添加像

public  void  addString(String  s){
    这个。myStrings。添加(小号);
}
 

public  void  removeString(String  s){
    这个。myStrings。除去(小号);
}
 

我们的调用者是否需要一次向MyClass实例添加多个字符串?那也没关系:

public  void  addStrings(Collection < String >  c){
    if(c  !=  null){
        这个。myStrings。addAll(c);
    }
}
 

等等...

public  void  clearStrings(){
    这个。myStrings。clear();
}

 
public  void  replaceStrings(Collection < String >  c){
    clearStrings();
    addStrings(c);
}
 

收集我们的想法

以下是我们班级最终的样子:

公共 类 MyClass {

 
    private  final  List < String >  myStrings  =  new  ArrayList <>();

 
    public  void  setMyStrings(Collection < String >  s){
        这个。myStrings。clear();
        if(s  !=  null){
            这个。myStrings。addAll(s);
        } 
    }

 
    public  List < String >  getMyStrings(){
        返回 新 的ArrayList <> (此。myStrings);
    }

 
    public  void  addString(String  s){
        这个。myStrings。添加(小号);
    }

 
    public  void  removeString(String  s){
        这个。myStrings。除去(小号);
    }

 
    //也许还有一些更有帮助的方法......

 
}
 

有了这个,我们实现了一个类:

仍然基本上是一个符合JavaBean规范的POJO
完全封装其私人成员
并且,最重要的是,它确保其返回Collection的方法始终只执行 - 返回Collection并且永远不会返回null。
 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值