Guava中Lists.partition(List, size) 方法懶划分/懶分區
背景
前幾天有同事使用這個方法,不小心點進去查看源碼,源碼如下,然他通過idea工具debug發現執行完Lists.partition(List, size)
這一行直接就現實了個size大小如下圖:看了源碼后根本就沒有顯示的調用size這些啊,就在那思考不知道為什么?
Lists.partition(List, size)
源碼如下:
@GwtCompatible(emulated = true)
public final class Lists {
.....
public static <T> List<List<T>> partition(List<T> list, int size) {
checkNotNull(list);
checkArgument(size > 0);
return (list instanceof RandomAccess)
? new RandomAccessPartition<>(list, size)
: new Partition<>(list, size);
}
private static class Partition<T> extends AbstractList<List<T>> {
final List<T> list;
final int size;
Partition(List<T> list, int size) {
this.list = list;
this.size = size;
}
@Override
public List<T> get(int index) {
checkElementIndex(index, size());
int start = index * size;
int end = Math.min(start + size, list.size());
return list.subList(start, end);
}
@Override
public int size() {
return IntMath.divide(list.size(), size, RoundingMode.CEILING);
}
@Override
public boolean isEmpty() {
return list.isEmpty();
}
}
private static class RandomAccessPartition<T> extends Partition<T> implements RandomAccess {
RandomAccessPartition(List<T> list, int size) {
super(list, size);
}
}
.....
}
分析
發現了問題,我們就應該分析為什么會出現這情況?
通過上面貼出來的代碼以及Guava的源碼,發現就是創建了一個Partition類過后啥都沒做。為什么?
顯示為什么調用toString?
通過查看idea--setting--Debugger--java可以看到。
那我們跟進代碼后發現在AbstractCollection
類中重寫了toString方法,代碼如下:
public String toString() {
Iterator<E> it = iterator();
if (! it.hasNext())
return "[]";
StringBuilder sb = new StringBuilder();
sb.append('[');
for (;;) {
E e = it.next();
sb.append(e == this ? "(this Collection)" : e);
if (! it.hasNext())
return sb.append(']').toString();
sb.append(',').append(' ');
}
}
剛才不是說會調用toString方法么?那問題來了應該顯示一個數組,不應該顯示size ?
應該顯示一個數組,不應該顯示size
在上面的截圖中我們可以看到復選框勾選了好幾個,其中有個Enable alternative view for Collections classes.好現在我們將這個去掉。看看執行的結果如下圖:
通過結果,我們發現現在顯示的是數組了。看來Enable alternative view for Collections classes這個選項是專門針對集合來顯示的。
如何是懶划分/懶分區
懶划分/懶分區意思就是當我們真正使用的時候才會去划分。下面分析下如何來懶划分的。驗證demo如下:
public class Test {
public static void main(String[] args) {
List<String> alist= Lists.newArrayList("12","a","34");
List<List<String>> sList = Lists.partition(alist, 2);
sList.forEach(e -> {
System.out.println(sList);
});
}
}
我們看下forEach的源碼如下:
public interface Iterable<T> {
....
default void forEach(Consumer<? super T> action) {
Objects.requireNonNull(action);
for (T t : this) {
action.accept(t);
}
}
....
}
看到這里是否有點懵逼。這里套了一層循環,沒有顯示調用分區呢?這個需要對for循環深入研究才行。我們可以通過簡單寫一個for循環后查看字節碼看看底層到底是如何實現循環的。簡單的for循環如下:
public class Test2 {
public static void main(String[] args) {
List<Integer> il = Lists.newArrayList(12,1,3,4);
for (Integer i : il) {}
}
}
通過javap -c Test2.class反解析字節碼后如下圖:
從圖中這些關鍵字信息
38: invokeinterface #5, 1 // InterfaceMethod java/util/List.iterator:()Ljava/util/Iterator;
45: invokeinterface #6, 1 // InterfaceMethod java/util/Iterator.hasNext:()Z
54: invokeinterface #7, 1 // InterfaceMethod java/util/Iterator.next:()Ljava/lang/Object;
這說明了我們for循環底層上還是通過迭代器來實現的。我們也可以看出依次調用iterator()->hasNext()->next()
。
從開篇源碼看出Guava中Partition的繼承關系,發現當底層循環的時候調用iterator()
方法是其實是調用AbstractList#iterator()
方法,AbstractList#iterator()
涉及源碼如下
public abstract class AbstractList<E> extends AbstractCollection<E> implements List<E> {
...
public Iterator<E> iterator() {
return new Itr();
}
...
private class Itr implements Iterator<E> {
int cursor = 0;
int lastRet = -1;
int expectedModCount = modCount;
public boolean hasNext() {
return cursor != size();
}
public E next() {
checkForComodification();
try {
int i = cursor;
E next = get(i);
lastRet = i;
cursor = i + 1;
return next;
} catch (IndexOutOfBoundsException e) {
checkForComodification();
throw new NoSuchElementException();
}
}
public void remove() {
if (lastRet < 0)
throw new IllegalStateException();
checkForComodification();
try {
AbstractList.this.remove(lastRet);
if (lastRet < cursor)
cursor--;
lastRet = -1;
expectedModCount = modCount;
} catch (IndexOutOfBoundsException e) {
throw new ConcurrentModificationException();
}
}
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
}
}
通過上面的源碼,發現當調用hasNext()方法的時候會調用size()方法。然size()方法就是我們Guava中這段代碼
public int size() {
return IntMath.divide(list.size(), size, RoundingMode.CEILING);
}
當調用next()方法的時候,先是調用子類實現的get(i)方法,子類也就是Guava中這段代碼
public List<T> get(int index) {
checkElementIndex(index, size());
int start = index * size;
int end = Math.min(start + size, list.size());
return list.subList(start, end);
}
到此已基本分析完成了。
總結
我們在學習任何知識點的時候都要好好分析,想想,為什么要這樣設計?設計的好處是什么?還有就是知識的串聯。
比如for 循環。如果不深入研究,你也不知道為啥會調用iterator等一系列的方法。