前言:
最近看到一篇文章,里面提及了google的common包下Lists.partition方法为懒加载,只有在遍历时才会真正分区。平时使用时并未感觉到,感觉有点好奇。特此将自己寻找的答案的过程整理记录下来。
源码:
public static <T> List<List<T>> partition(List<T> list, int size) {Preconditions.checkNotNull(list);Preconditions.checkArgument(size > 0);return (List)(list instanceof RandomAccess ? new Lists.RandomAccessPartition(list, size) : new Lists.Partition(list, size));}
判断了List的子类是否继承了RandomAccess接口,这个接口是个标记接口,实现它的实现类一般可以直接访问到具体的索引位置如ArrayList。像LinkedList虽然有索引,但是还是需要遍历全部找到位置的。此处的底层构造方法其实调用的是同一个。 内部类继承了AbstractList抽象类,并实现了List接口的get、size方法,重写了isEmpty方法。
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;}public List<T> get(int index) {Preconditions.checkElementIndex(index, this.size());int start = index * this.size;int end = Math.min(start + this.size, this.list.size());return this.list.subList(start, end);}public int size() {return IntMath.divide(this.list.size(), this.size, RoundingMode.CEILING);}public boolean isEmpty() {return this.list.isEmpty();}}
疑问一:内部类只重写了get方法,并未对此分片,它是如何做到的?
答案:AbstractList实现了iterator接口中方法(迭代器模式),迭代器中调用了get方法,实现了懒加载机制。
public boolean hasNext() {
//重写了size方法return cursor != size();}public E next() {checkForComodification();try {int i = cursor;//增强for的底层实际的就是迭代器,而next里面用到的get方法,E next = get(i);lastRet = i;cursor = i + 1;return next;} catch (IndexOutOfBoundsException e) {checkForComodification();throw new NoSuchElementException();}}//foreach的底层也是迭代器default void forEach(Consumer<? super T> action) {Objects.requireNonNull(action);for (T t : this) {action.accept(t);}}
疑问二:为什么不使用循环的时候,打印这个集合也会显示分区后的样子?
答案:toSting方法。AbstractList抽象类继承了AbstractCollection抽象类,而AbstractCollection重写了toString方法,AbstractList和Lists.partition均未重写该方法,说明调用就是AbstractCollection类的toString方法。而toString里面同样使用next方法。
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(' ');}}
而println方法里的String.valueOf同样调用了打印目标toString方法。所以实现了懒加载。
public void println(Object x) {String s = String.valueOf(x);synchronized (this) {print(s);newLine();}}public static String valueOf(Object obj) {return (obj == null) ? "null" : obj.toString();}
疑问三:没在代码里打印,debug的时候也可以看到分区成功?
网上查看了一下,ideaDebugger的设置,猜测同样有调用toString方法(此处并未验证,有兴趣的可以将这个去掉验证下)
如有其他答案,欢迎留言讨论。