Collection及其师傅Iterable

断了今生、忘了曾经 提交于 2020-01-07 17:32:48

【推荐】2019 Java 开发者跳槽指南.pdf(吐血整理) >>>

        上一节我们缕清了Collection家族的关系,接下来我们就来看看这个家族的创始者及其师傅。

1.  师傅Iterable

        Iterable是一个接口类,先看看接口里面的方法:

pic_java_src_iterable

其中第二个和第三个方法,都是java8新增的,看源码会发现,java8后,可以在接口里面定义默认(关键字default)的方法(必须实现,可以空实现),而且实现类中还可以重写default方法,这样一来有点类似抽象类,只不过接口类用来授艺,抽象类用来遗传。最终给我们代码的迭代带来很大便利。

        Iterable里面最主要的当然迭代器Iterator,Iterator本身是接口,这么说来接口师傅(水平有限)传授的都是大道的终极要义,功夫还是得弟子苦练。后来师傅能力提升(java8来了),可以给弟子大道的精华(default方法),让弟子可以毫不费力直接领悟,甚至弟子还可以加以创新(重写),但是要注意多接口实现造成的默认方法冲突问题(冲突很容易解决,就不多说)。关于Iterator在子类的实现可以查看ArrayList源码,很简单,只是大家用得多的是next()方法,不知有没有注意到previous()方法。值得一提的是在Iterator接口里面新增了默认方法:forEachRemaining方法,允许传入一个行为,其实就类似于Iterable的forEach方法。

        一看forEach方法,就知道这是遍历集合的,但是一般我们遍历集合可能有两种原因:一筛选出特定的元素(需要返回值);二处理元素(无需返回值)。由此一看这个方法它没有返回值,顿时就确定这是处理元素。我们看看forEach的参数:Consumer。

@FunctionalInterface
public interface Consumer<T> {
    void accept(T t);

    default Consumer<T> andThen(Consumer<? super T> after) {
        Objects.requireNonNull(after);
        return (T t) -> { accept(t); after.accept(t); };
    }
}

        Consumer:FunctionalInterface说明该接口属于函数式接口。它表示一个接受单个输入参数并且没有返回值的操作。不像其他函数式接口,Consumer接口期望执行带有副作用的操作,即Consumer的操作可能会更改输入参数的内部状态。现在java类的方法里面可以直接传递函数,而不仅限于值了。这在c++里面叫函数指针。

        从accept方法可知Consumer可以代表任意函数,但该函数的参数个数限制为一个且无返回值。

public class Main {

    public static void main(String[] args) {

        //eg.1
        String hello = "hello world!";
        Consumer<String> c = (str) -> System.out.println(str.toUpperCase());
        c.accept(hello);

        //eg.2
        List<Student> list = Arrays.asList(new Student("Ben", 80),
                                           new Student("John", 90));
        Consumer<Student> consumer = new Consumer<Student>() {
            @Override
            public void accept(Student s) {
                s.score *= 0.9;
            }
        };
        //list.forEach(consumer);
        list.forEach(consumer.andThen(consumer));   //每个元素先执行调用者行为,然后在执行参数行为
        list.forEach(new Consumer<Student>() {      //打印
            @Override
            public void accept(Student s) {
                System.out.println(s.name + ": " + s.score);
            }
        });



    }

    static class Student {
        String name;
        int score;

        public Student(String name, int score) {
            this.name = name;
            this.score = score;
        }
    }
}

        至于第三个方法spliterator()返回的是一个分片迭代器,它可以将集合分成多个片段,每个片段可以并行处理,在多核CPU下十分高效。在后面章节我会单独起一章讲解。

2.  徒弟Collection

        Collection接口除了一些基本的添加、移除、清除,转数组等操作,值得注意的还是新增的默认方法。

default boolean removeIf(Predicate<? super E> filter) {
    Objects.requireNonNull(filter);
    boolean removed = false;
    final Iterator<E> each = iterator();
    while (each.hasNext()) {
        if (filter.test(each.next())) {
            each.remove();
            removed = true;
        }
    }
    return removed;
}



default Stream<E> stream() {
    return StreamSupport.stream(spliterator(), false);
}

default Stream<E> parallelStream() {
    return StreamSupport.stream(spliterator(), true);
}

        在这里我简单介绍一下Predicate接口,它也是函数式接口,其实就类似于Consumer,只不过Consumer是处理数据,没有返回值,而Predicate是进行一些逻辑处理,返回一个boolean值。其他两个默认方法是将集合数据流化,便于做一些过滤,也看到了stream()方法里面也进行了分片迭代处理。

        关于java8新特性Stream的讲解,也排在后面的章节。

        好了,两个接口的讲解就到此结束了。

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!