无论是在学习中还是在工作中,对于String类型我们并不陌生,也会经常使用到subString()方法。
使用subString()方法得到子字符串,当子字符串添加一个元素改变时,并不影响原来的字符串。
subString(1,3)包含1索引位置的字符,不包含3索引位置的字符。
看下面的例子,也许你会更加明白!
public class SubStringTest { private static String str; private static String subStr; public static void main(String[] args) { str = "01234"; subStr = str.substring(1,3); print(); subStr += "5"; System.out.println("----------此时将subStr中添加了一个5----------"); print(); } /** * 输出 */ public static void print(){ System.out.println("str = " + str); System.out.println("subStr = " + subStr); } }
结果:
然而,在List集合中有这么一个方法subList()。第一眼看上去和String中的subString()很是相像,这使我们会想当然的认为它们是类似的。如果我们这样想就陷入了误区,空口白牙谁会相信,好了,废话不多说,上菜吧!
import java.util.ArrayList; import java.util.List; public class SubListTest { private static List list; private static List subList; public static void main(String[] args) { list = new ArrayList(); list.add("1"); list.add("2"); list.add("3"); list.add("4"); list.add("5"); subList = list.subList(1,3); System.out.println("==========对子List修改(增、删、改)之前=========="); print(); System.out.println("==========对子List修改(增、删、改)之后=========="); subList.add("a"); print(); } /** * 输出 */ public static void print(){ System.out.println("原List: " + list); System.out.println("子List: " + subList); } }
结果:
从结果来看,由subList()方法得到子List,当子List中添加一个元素a后,原List也跟着增加了。看到这里,我们会有疑问,这是为什么呢?要弄明白这个问题,我们需要来翻一翻源码了。
以ArrayList中的subList()来分析,下面是ArrayList的subList()的源码:
public List<E> subList(int fromIndex, int toIndex) { subListRangeCheck(fromIndex, toIndex, size); return new SubList(this, 0, fromIndex, toIndex); }
该方法返回的是ArrayList的内部类SubList的一个实例,同时也将当前的ArrayList对象作为第一个参数传入该构造方法。我们看一下这个构造方法的源码:
private class SubList extends AbstractList<E> implements RandomAccess { private final AbstractList<E> parent; SubList(AbstractList<E> parent, int offset, int fromIndex, int toIndex) { this.parent = parent; this.parentOffset = fromIndex; this.offset = offset + fromIndex; this.size = toIndex - fromIndex; this.modCount = ArrayList.this.modCount; } }
通过这个构造方法的源码可知,在创建这个内部类ArrayList.SubList的实例时,会将外部类ArrayList的引用作为该内部类对象中的parent。也就是说,这个ArrayList.SubList内部类实例中的parent字段会拥有ArrayList对象的一个引用, 只是添加了一定的偏移量而已. 由于 List 中存放的元素都是引用类型, 而非基本类型, 所以, 这个子 List
中的每一个元素所代表的引用, 其实就和原 List
中在相同索引处偏移 fromIndex 位置后的那个位置上的元素所代表的引用, 二者指向的是相同的对象. 我们换用更直白的方式来说, 就是:
假设有1,2,3,4,5这5个字符串,被依次添加到原List中,假设我们将原List称作ListA,这时,这5个对象中的每一个都分别被一个引用指向着,这些引用刚好就是ListA中存放的所有元素。(PS:ListA中存放的元素其实是引用,而不是对象本身)这时对ListA执行了subList(1,3)方法,创建了一个子List,我们将这个子List称作ListB。那么这时,"1","4","5"各自还只是被一个引用指向着,但是"2","3"却分别被两个引用指向着,一个引用来自ListA,一个来自ListB,请看下面表格:
在创建子List(即:ListB)之前,每个String被引用的情况如下:
对象 | 被指向的引用 | 被指向的引用的总数 |
"1" | ListA.get(0) | 1 |
"2" | ListA.get(1) | 1 |
"3" | ListA.get(2) | 1 |
"4" | ListA.get(3) | 1 |
"5" | ListA.get(4) | 1 |
在创建子 List
( 即: listB) 之后, 各个 Integer 对象被引用指向的情况如下:
对象 | 被指向的引用 | 被指向的引用的总数 | |
"1" | ListA.get(0) | 1 | |
"2" | ListA.get(1) | ListB.get(0) | 2 |
"3" | ListA.get(2) | ListB.get(1) | 2 |
"4" | ListA.get(3) | 1 | |
"5" | ListA.get(4) | 1 |
留意红色的字. 在创建了 listB, 也就是子 List
以后, "2"和"3"对象, 都分别被 listA 和 listB中各有一个引用所指向着. 而且还有个规律: listB 中每一个元素(其实这里的元素是引用, 不是对象本身) 所指向的对象, 都会同时被两个引用所指向着. 所以, 对于这些同时被两个引用所指向的对象来说, 不论是用哪个引用来修改这些对象的值, 或者对他们进行增删, 都将影响到另外一个引用的指向结果.
ListA.get(0)