Nous utilisons souvent la méthode subString pour diviser les objets String. En même temps, nous pouvons également utiliser subList, subMap et subSet pour diviser List, Map,. et Set, mais cette segmentation présente certains défauts.
Tout d'abord, regardons l'exemple suivant :
public static void main(String[] args) { List<Integer> list1 = new ArrayList<Integer>(); list1.add(1); list1.add(2); //通过构造函数新建一个包含list1的列表 list2 List<Integer> list2 = new ArrayList<Integer>(list1); //通过subList生成一个与list1一样的列表 list3 List<Integer> list3 = list1.subList(0, list1.size()); //修改list3 list3.add(3); System.out.println("list1 == list2:" + list1.equals(list2)); System.out.println("list1 == list3:" + list1.equals(list3)); }
Ceci exemple C'est très simple. Ce n'est rien de plus que de régénérer une liste identique à list1 via le constructeur et subList, puis de modifier list3 et enfin de comparer list1 == list2?, list1 == list3?. Selon notre pensée conventionnelle, cela devrait être comme ceci : parce que list3 ajoute un nouvel élément via add, alors il ne doit pas être égal à list1, et list2 est construit via list1, donc il devrait être égal, donc le résultat devrait être :
list1 == list2:true list1 == list3: false
Tout d'abord, que le résultat soit correct ou non, regardons le code source de subList :
public List<E> subList(int fromIndex, int toIndex) { subListRangeCheck(fromIndex, toIndex, size); return new SubList(this, 0, fromIndex, toIndex); }
subListRangeCheck La méthode consiste à déterminer si fromIndex et toIndex sont légaux. S'ils sont légaux, renvoyez directement un objet subList Notez qu'un paramètre this est passé lors de la génération du nouvel objet. important car il représente la liste originale.
/** * 继承AbstractList类,实现RandomAccess接口 */ private class SubList extends AbstractList<E> implements RandomAccess { private final AbstractList<E> parent; //列表 private final int parentOffset; private final int offset; int size; //构造函数 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; } //set方法 public E set(int index, E e) { rangeCheck(index); checkForComodification(); E oldValue = ArrayList.this.elementData(offset + index); ArrayList.this.elementData[offset + index] = e; return oldValue; } //get方法 public E get(int index) { rangeCheck(index); checkForComodification(); return ArrayList.this.elementData(offset + index); } //add方法 public void add(int index, E e) { rangeCheckForAdd(index); checkForComodification(); parent.add(parentOffset + index, e); this.modCount = parent.modCount; this.size++; } //remove方法 public E remove(int index) { rangeCheck(index); checkForComodification(); E result = parent.remove(parentOffset + index); this.modCount = parent.modCount; this.size--; return result; } }
Le SubLsit est la classe interne d'ArrayList. C'est la même chose que. ArrayList. Il hérite de AbstractList et implémente l'interface RandomAccess. Il fournit également des méthodes de liste couramment utilisées telles que get, set, add et delete. Mais son constructeur est un peu spécial. Il y a deux endroits à surveiller dans le constructeur :
1. this.parent = parent et parent est la liste passée devant, In. en d'autres termes, this.parent est une référence à la liste d'origine.
2. this.offset = offset fromIndex; this.parentOffset = fromIndex;. En même temps, il transmet même modCount (mécanisme Fail-Fast) dans le constructeur.
Regardons à nouveau la méthode get. Dans la méthode get, return ArrayList.this.elementData(offset index); à l'index de décalage de la liste d'origine. Le même principe s'applique à la méthode add :
parent.add(parentOffset + index, e); this.modCount = parent.modCount;
et à la méthode Remove
E result = parent.remove(parentOffset + index); this.modCount = parent.modCount;
Il est vrai que à ce stade, nous pouvons juger que la SubList renvoyée par subList est également une sous-classe de AbstractList. En même temps, ses méthodes telles que get, set, add, delete, etc. fonctionnent toutes sur la liste d'origine. nouvel objet comme subString . Ainsi, subList renvoie uniquement une vue de la liste d'origine, et toutes ses opérations seront finalement appliquées à la liste d'origine.
Ensuite, de l'analyse ici, nous pouvons conclure que le résultat ci-dessus devrait être exactement le contraire de notre réponse ci-dessus :
list1 == list2:false list1 == list3:true
Java细节(3.1):subList返回的只是原列表的一个视图,它所有的操作最终都会作用在原列表上
从上面我们知道subList生成的子列表只是原列表的一个视图而已,如果我们操作子列表它产生的作用都会在原列表上面表现,但是如果我们操作原列表会产生什么情况呢?
public static void main(String[] args) { List<Integer> list1 = new ArrayList<Integer>(); list1.add(1); list1.add(2); //通过subList生成一个与list1一样的列表 list3 List<Integer> list3 = list1.subList(0, list1.size()); //修改list3 list1.add(3); System.out.println("list1'size:" + list1.size()); System.out.println("list3'size:" + list3.size()); }
该实例如果不产生意外,那么他们两个list的大小都应该都是3,但是偏偏事与愿违,事实上我们得到的结果是这样的:
list1'size:3 Exception in thread "main" java.util.ConcurrentModificationException at java.util.ArrayList$SubList.checkForComodification(Unknown Source) at java.util.ArrayList$SubList.size(Unknown Source) at com.chenssy.test.arrayList.SubListTest.main(SubListTest.java:17)
list1正常输出,但是list3就抛出ConcurrentModificationException异常,看过我另一篇博客的同仁肯定对这个异常非常,fail-fast?不错就是fail-fast机制,在fail-fast机制中,LZ花了很多力气来讲述这个异常,所以这里LZ就不对这个异常多讲了(更多请点这里:Java提高篇(三四)—–fail-fast机制)。我们再看size方法:
public int size() { checkForComodification(); return this.size; }
size方法首先会通过checkForComodification验证,然后再返回this.size。
private void checkForComodification() { if (ArrayList.this.modCount != this.modCount) throw new ConcurrentModificationException(); }
该方法表明当原列表的modCount与this.modCount不相等时就会抛出ConcurrentModificationException。同时我们知道modCount 在new的过程中 “继承”了原列表modCount,只有在修改该列表(子列表)时才会修改该值(先表现在原列表后作用于子列表)。而在该实例中我们是操作原列表,原列表的modCount当然不会反应在子列表的modCount上啦,所以才会抛出该异常。
对于子列表视图,它是动态生成的,生成之后就不要操作原列表了,否则必然都导致视图的不稳定而抛出异常。最好的办法就是将原列表设置为只读状态,要操作就操作子列表:
//通过subList生成一个与list1一样的列表 list3 List<Integer> list3 = list1.subList(0, list1.size()); //对list1设置为只读状态 list1 = Collections.unmodifiableList(list1);
Java细节(3.2):生成子列表后,不要试图去操作原列表,否则会造成子列表的不稳定而产生异常
在开发过程中我们一定会遇到这样一个问题:获取一堆数据后,需要删除某段数据。例如,有一个列表存在1000条记录,我们需要删除100-200位置处的数据,可能我们会这样处理:
for(int i = 0 ; i < list1.size() ; i++){ if(i >= 100 && i <= 200){ list1.remove(i); /* * 当然这段代码存在问题,list remove之后后面的元素会填充上来, * 所以需要对i进行简单的处理,当然这个不是这里讨论的问题。 */ } }
这个应该是我们大部分人的处理方式吧,其实还有更好的方法,利用subList。在前面LZ已经讲过,子列表的操作都会反映在原列表上。所以下面一行代码全部搞定:
list1.subList(100, 200).clear();
简单而不失华丽!!!!!
以上就是Java提高配(三七)—–Java集合细节(三):subList的缺陷的内容,更多相关内容请关注PHP中文网(www.php.cn)!