Guava中Lists.partition(List, size) 方法懶划分/懶分區


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等一系列的方法。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM