首页 » 编写高质量代码:改善Java程序的151个建议 » 编写高质量代码:改善Java程序的151个建议全文在线阅读

《编写高质量代码:改善Java程序的151个建议》建议70:子列表只是原列表的一个视图

关灯直达底部

List接口提供了subList方法,其作用是返回一个列表的子列表,这与String类的subString有点类似,但它们的功能是否相同呢?我们来看如下代码:


public static void main(Stringargs){

//定义一个包含两个字符串的列表

List<String>c=new ArrayList<String>();

c.add(/"A/");

c.add(/"B/");

//构造一个包含c列表的字符串列表

List<String>c1=new ArrayList<String>(c);

//subList生成与c相同的列表

List<String>c2=c.subList(0,c.size());

//c2增加一个元素

c2.add(/"C/");

System.out.println(/"c==c1?/"+c.equals(c1));

System.out.println(/"c==c2?/"+c.equals(c2));

}


c1是通过ArrayList的构造函数创建的,c2是通过列表的subList方法创建的,然后c2又增加了一个元素C,现在的问题是输出的结果是什么呢?列表c与c1、c2之间是什么关系呢?

别忙着回答这个问题,我们先来回想一下String类的subString方法,看看它是如何工作的,代码如下:


public static void main(Stringargs){

String str=/"AB/";

String str1=new String(str);

String str2=str.substring(0)+/"C/";

System.out.println(/"str==str1?/"+str1.equals(str1));

System.out.println(/"str==str2?/"+str.equals(str2));

}


很明显,str与str1是相等的(虽然不是同一个对象,但用equals方法判断是相等的),但它们与str2不相等,这毋庸置疑,因为str2在对象池中重新生成了一个新的对象,其表面值是ABC,那当然与str和str1不相等了。

说完了subString的小插曲,现在回到List是否相等的判断上来。subList与subString的输出结果是一样的吗?让事实说话,运行结果如下:


c==c1?false

c==c2?true


很遗憾,与String类刚好相反,同样是一个sub类型的操作,为什么会相反呢?仅仅回答“为什么”似不足以平复我们的惊讶,下面就从最底层的源代码来进行分析。

c2是通过subList方法从c列表中生成的一个子列表,然后c2又增加了一个元素,可为什么增加了一个元素还会相等呢?我们来看subList源码:


public List<E>subList(int fromIndex, int toIndex){

return(this instanceof RandomAccess?

new RandomAccessSubList<E>(this, fromIndex, toIndex):

new SubList<E>(this, fromIndex, toIndex));

}


subList方法是由AbstractList实现的,它会根据是不是可以随机存取来提供不同的SubList实现方式,不过,随机存储的使用频率比较高,而且RandomAccessSubList也是SubList子类,所以所有的操作都是由SubList类实现的(除了自身的SubList方法外),那么,我们就直接来看SubList类的代码:


class SubList<E>extends AbstractList<E>{

//原始列表

private AbstractList<E>l;

//偏移量

private int offset;

//构造函数,注意list参数就是我们的原始列表

SubList(AbstractList<E>list, int fromIndex, int toIndex){

/*下标校验,省略*/

//传递原始列表

l=list;

offset=fromIndex;

//子列表的长度

size=toIndex-fromIndex;

}

//获得指定位置的元素

public E get(int index){

/*校验部分,省略*/

//从原始字符串中获得指定位置的元素

return l.get(index+offset);

}

//增加或插入

public void add(int index, E element){

/*校验部分,省略*/

//直接增加到原始字符串上

l.add(index+offset, element);

/*处理长度和修改计数器*/

}

/*其他方法省略*/

}


通过阅读这段代码,我们就非常清楚subList方法的实现原理了:它返回的SubList类也是AbstractList的子类,其所有的方法如get、set、add、remove等都是在原始列表上的操作,它自身并没有生成一个数组或是链表,也就是子列表只是原列表的一个视图(View),所有的修改动作都反映在了原列表上。

我们例子中的c2增加了一个元素C,不过增加的元素C到了c列表上,两个变量的元素仍保持完全一致,相等也就很自然了。

解释完相等的问题,再回过头来看看为什么变量c与c1不相等。很简单,因为通过ArrayList构造函数创建的List对象c1实际上是新列表,它是通过数组的copyOf动作生成的,所生成的列表c1与原列表c之间没有任何关系(虽然是浅拷贝,但元素类型是String,也就是说元素是深拷贝的),然后c又增加了元素,因为c1与c之间已经没有一毛钱的关系了,那自然是不相等了。

注意 subList产生的列表只是一个视图,所有的修改动作直接作用于原列表。