java8 Stream与λ表达式

1 篇文章 0 订阅
1 篇文章 0 订阅

λ表达式

什么是λ表达式

λ表达式有三部分组成:参数列表,箭头(->),以及一个表达式或者语句块。
public int add(int x, int y) {
    return x + y;
}
转换为λ表达式
(int x, int y) -> x + y;
去除参数类型
(x, y) -> x + y;
无参 以及 只有一个参数
() -> { System.out.println("Hello Lambda!"); 
c -> { return c.size(); }

λ表达式的类型

λ表达式可以被 **当做** 是一个Object(注意措辞)。λ表达式的类型,叫做“目标类型(target type)”。λ表达式的目标类型是“函数接口(functional interface)”,这是Java8新引入的概念。它的定义是:一个接口,如果只有一个显式声明的抽象方法,那么它就是一个函数接口。一般用@FunctionalInterface标注出来(也可以不标)。举例如下:
@FunctionalInterface
public interface Runnable{
    void run(); 
}

public interface Callable<V>{
    V call() throws Exception; 
}

public interface Comparator<T>{
    int compare(T o1, T o2); boolean equals(Object obj); 
}
所以 可以定义
Runnable r1 = () -> {System.out.println("Hello Lambda!");};

思考:可否定义 Object o = () -> {System.out.println(“Hello Lambda!”);};

JDK常用的预定义接口函数

//入参为T,返回R
@FunctionalInterface
public interface Function<T, R> {  
    R apply(T t);
}

//入参为T,无返回值
@FunctionalInterface
public interface Consumer<T> {
    void accept(T t);
}

//无入参,返回T(通常配合构造方法)
@FunctionalInterface
public interface Supplier<T> {
    T get();
}

//入参为T,返回值为boolean
@FunctionalInterface
public interface Predicate<T> {
    boolean test(T t);
}

//入参为T,U,返回值为R 类似还有BiConsumer等
@FunctionalInterface
public interface BiFunction<T, U> {
    R accept(T t, U u);
}

方法引用

方法引用让开发者可以直接引用现存的方法、Java类的构造方法或者实例对象。方法引用和Lambda表达式配合使用,使得java类的构造方法看起来紧凑而简洁,没有很多复杂的模板代码。
public static class Car {
    public static Car create( final Supplier<Car> supplier ) {
        return supplier.get();
    }              

    public static void collide( final Car car ) {
        System.out.println( "Collided " + car.toString() );
    }

    public void follow( final Car another ) {
        System.out.println( "Following the " + another.toString() );
    }

    public void repair() {   
        System.out.println( "Repaired " + this.toString() );
    }
}
第一种方法引用的类型是构造器引用,语法是Class::new,或者更一般的形式:`Class::new`。注意:这个构造器没有参数。
Car car = Car.create(Car::new);
List<Car> cars = Arrays.asList(car);
第二种方法引用的类型是静态方法引用,语法是Class::static_method。注意:这个方法接受一个Car类型的参数。
cars.forEach(Car::collide);
第三种方法引用的类型是某个类的成员方法的引用,语法是Class::method,注意,这个方法没有定义入参:
cars.forEach(Car::repair);
第四种方法引用的类型是某个实例对象的成员方法的引用,语法是instance::method。注意:这个方法接受一个Car类型的参数:
final Car police = Car.create(Car::new);
cars.forEach(police::follow);

什么是流

Stream 不是数据结构,不保存数据,它是有关算法和计算的,就如同一个高级版本的迭代器(Iterator),单向,不可往复,数据只能遍历一次,遍历过一次后即用尽了,就好比流水从面前流过,一去不复返。同时又与迭代器不同,迭代器只能串行操作,Stream可以并行化操作。

流的构成

当我们使用一个流的时候,通常包括三个基本步骤: 获取一个数据源(source)→数据转换→执行操作获取想要的结果,每次转换原有 Stream 对象不改变,返回一个新的 Stream 对象(可以有多次转换),这就允许对其操作可以像链条一样排列,变成一个管道 ,如下图所示。

常用的构建流的方式:

  • 集合Collection
 Collection.stream()
 Collection.parallelStream()
  • 数组
Stream.of(T[] tArray)
  • 多个相同类型对象
Stream.of("chaimm","peter","john"); 

流的基本使用

常见操作

Stream操作分类
中间操作无状态 map (mapToInt, flatMap 等)、filter、peek
有状态 distinct、sorted、limit、skip
终结操作非短路 forEach、forEachOrdered、toArray、reduce、collect、min、 max、 count
短路操作anyMatch、allMatch、noneMatch、findFirst、findAny

Stream中的操作可以分为两大类:中间操作与终结操作

中间操作(Intermediate):一个流可以后面跟随零个或多个 intermediate 操作。其目的主要是打开流,做出某种程度的数据映射/过滤,然后返回一个新的流,交给下一个操作使用。这类操作都是惰性化的(lazy),就是说,仅仅调用到这类方法,并没有真正开始流的遍历。中间操作又可以分为无状态(Stateless)操作与有状态(Stateful)操作,前者是指元素的处理不受之前元素的影响;后者是指该操作只有拿到所有元素之后才能继续下去。

终结操作(Terminal):一个流只能有一个 terminal 操作,当这个操作执行后,流就被使用“光”了,无法再被操作。所以这必定是流的最后一个操作。Terminal 操作的执行,才会真正开始流的遍历,并且会生成一个结果。终结操作又可以分为短路与非短路操作,短路是指遇到某些符合条件的元素就可以得到最终结果,比如找到第一个满足条件的元素。而非短路是指必须处理所有元素才能得到最终结果。

map/flatMap

对流中的每个元素执行一个函数,使得元素转换成另一种类型输出。

map 一对一 ( 入参 Function<T, R>

List<Person> persons = new ArrayLisy<>();
List<Long> result = persons.stream().map(x -> x.getId())
        .collect(Collectors.toList());

List<Student> result = persons.stream().map(x -> {
    Student s = new Student();
    s.setName(x.getName());
    return s;
}).collect(Collectors.toList());

flatMap 一对多 (入参 Function<T, ? extends Stream>

List<List<Person>> listAll = new ArrayList<>();

List<Person> result = listAll.stream().flatMap(x -> x.stream())
        .collect(Collectors.toList());

List<Student> result = listAll.stream().flatMap(x -> x.stream())
        .map(x -> {
            Student s = new Student();
            s.setName(x.getName());
            return s;
        }).collect(Collectors.toList());
#### filter filter 对原始 Stream 进行某项测试,通过测试的元素被留下来生成一个新 Stream。 (入参 Predicate)
Integer[] nums = new Integer[]{1, 2, 3, 4, 5, 6};
Integer[] evens = Stream.of(nums).filter(n -> n%2 == 0)
        .toArray(Integer[]::new);

List<Student> result = persons.stream().filter(x -> x.getAge() < 20)
        .map(x -> {
            Student s = new Student();
            s.setName(x.getName());
            return s;
        }).collect(Collectors.toList());
peek

peek 方法我们可以拿到元素,然后做一些其他事情。(入参 Consumer)

List<Long> result = persons.stream().map(x -> x.getId())
        .peek(x -> System.out.println(x))
        .collect(Collectors.toList());

Map<Long, Person> map = new HashMap<>();
List<Student> result = persons.stream()
        .filter(x -> x.getAge() < 20 )
        .peek(x -> map.put(x.getId(), x) )
        .map(x -> {
            Student s = new Student();
            s.setName(x.getName());
            return s;
        }).collect(Collectors.toList());
limit/skip

limit 返回 Stream 的前面 n 个元素,skip 则是扔掉前 n 个元素

List<Long> result = persons.stream().map(x -> x.getId())
    .limit(10).skip(3).collect(Collectors.toList());
sorted

对元素进行排序 (入参 Comparator)

List<Person> personList2 = persons.stream()
    .sorted((p1, p2) -> p1.getName().compareTo(p2.getName()))
    .collect(Collectors.toList());
forEach

对元素进行遍历消费 (入参 Consumer)

persons.stream().sorted((p1, p2) -> p1.getName().compareTo(p2.getName()))
    .forEach(peron -> {
        System.out.println(persion.getName());
    });
collect

对元素进行收集

List<Person> personList2 = persons.stream()
    .sorted((p1, p2) -> p1.getName().compareTo(p2.getName()))
    .collect(Collectors.toList());

List<Person> personList2 = persons.stream()
    .sorted((p1, p2) -> p1.getName().compareTo(p2.getName()))
    .collect(ArrayList::new, ArrayList::add, ArrayList::addAll);
Collectors.toMap

转换为MAP

Map<Long, String> personIdNameMap = persons.stream()
    .collect(Collectors.toMap(Person::getId, Person::getName);

Map<Long, String> personIdNameMap = persons.stream()
    .collect(Collectors.toMap(Person::getId, Person::getName, (v1,v2)->v1);
Collectors.groupingBy

分组

Map<Integer, List<Person>> personAgeMap = persons.stream()
    .collect(Collectors.groupingBy(Person::getAge));

Map<Integer, Map<Long, Person>> personAgeMap =  persons.stream()
    .collect(Collectors.groupingBy(Person::getAge,
        Collectors.toMap(Person::getId, Function.identity())));
Collectors.collectingAndThen

收集然后处理

Map<Integer, Integer> personAgeMap =  persons.stream()
    .collect(Collectors.groupingBy(Person::getAge,
        Collectors.collectingAndThen(
            Collectors.toList(),
            list->list.size()
        )));

接口方法

接口default方法

默认方法使得开发者可以在不破坏兼容性的前提下,往现存接口中添加新的方法,即不强制那些实现了该接口的类也同时实现这个新加的方法。 默认方法和抽象方法之间的区别在于抽象方法需要实现,而默认方法不需要。接口提供的默认方法会被接口的实现类继承或者覆写
private interface HelloService {
    default String sayHello() { 
        return "hello"; 
    }        
}

private static class HelloImpl implements HelloService {
}

private static class HelloWorldImpl implements HelloService {
    @Override
    public String sayHello() {
        return "hello world";
    }
}
> 思考:为啥要加入default方法?

接口static方法

private interface HelloService {
    static boolean testHello(String s) { 
        return Objects.equals(s,"hello");
    }        
}

Stream流水线解决方案

Stage(Pipeline)

java8用Stage来记录Stream的中间操作,很多Stream操作会需要一个回调函数(Lambda表达式),因此一个完整的操作是 **数据来源,操作,回调函数** 构成的三元组。Stream中使用Stage的概念来描述一个完整的操作,并用某种实例化后的Pipeline来代表Stage,然后将具有先后顺序的各个Stage连到一起,就构成了整个流水线。

Sink

有了操作,我们需要将所有操作叠加起来,让流水线起到应有的作用,java用Sink来协调相邻Stage之间的调用关系。每个Stage必须实现opWrapSink方法。Sink接口的主要方法如下

方法名作用
void begin(long size)开始遍历元素之前调用该方法,通知Sink做好准备
void end()所有元素遍历完成之后调用,通知Sink没有更多的元素了
boolean cancellationRequested()是否可以结束操作,可以让短路操作尽早结束
void accept(T t)遍历元素时调用,接受一个待处理元素,并对元素进行处理

每个Stage都会将自己的操作封装到一个Sink里,前一个Stage只需调用后一个方法即可,并不需要知道其内部是如何处理的。当然对于有状态的操作,Sink的begin()和end()方法也是必须实现的。比如Stream.sorted()是一个有状态的中间操作,其对应的Sink.begin()方法可能创建一个乘放结果的容器,而accept()方法负责将元素添加到该容器,最后end()负责对容器进行排序。对于短路操作,Sink.cancellationRequested()也是必须实现的,比如Stream.findFirst()是短路操作,只要找到一个元素,cancellationRequested()就应该返回true,以便调用者尽快结束查找。Sink的四个接口方法常常相互协作,共同完成计算任务。实际上Stream API内部实现的的本质,就是如何重载Sink的这四个接口方法

map方法的主要实现

public final <R> Stream<R> map(Function<? super P_OUT, ? extends R> mapper) {
    Objects.requireNonNull(mapper);
    return new StatelessOp<P_OUT, R>(this, StreamShape.REFERENCE,
                                 StreamOpFlag.NOT_SORTED | StreamOpFlag.NOT_DISTINCT) {
        @Override
        Sink<P_OUT> opWrapSink(int flags, Sink<R> sink) {
            return new Sink.ChainedReference<P_OUT, R>(sink) {
                @Override
                public void accept(P_OUT u) {
                    downstream.accept(mapper.apply(u));
                }
            };
        }
    };
}
static abstract class ChainedReference<T, E_OUT> implements Sink<T> {
    protected final Sink<? super E_OUT> downstream;

    public ChainedReference(Sink<? super E_OUT> downstream) {
        this.downstream = Objects.requireNonNull(downstream);
    }

    @Override
    public void begin(long size) {
        downstream.begin(size);
    }

    @Override
    public void end() {
        downstream.end();
    }

    @Override
    public boolean cancellationRequested() {
        return downstream.cancellationRequested();
    }
}
// Stream.sort()方法用到的Sink实现
class RefSortingSink<T> extends AbstractRefSortingSink<T> {
    private ArrayList<T> list;// 存放用于排序的元素
    RefSortingSink(Sink<? super T> downstream, Comparator<? super T> comparator) {
        super(downstream, comparator);
    }
    @Override
    public void begin(long size) {
        ...
        // 创建一个存放排序元素的列表
        list = (size >= 0) ? new ArrayList<T>((int) size) : new ArrayList<T>();
    }
    @Override
    public void end() {
        list.sort(comparator);// 只有元素全部接收之后才能开始排序
        downstream.begin(list.size());
        if (!cancellationWasRequested) {// 下游Sink不包含短路操作
            list.forEach(downstream::accept);// 2. 将处理结果传递给流水线下游的Sink
        }
        else {// 下游Sink包含短路操作
            for (T t : list) {// 每次都调用cancellationRequested()询问是否可以结束处理。
                if (downstream.cancellationRequested()) break;
                downstream.accept(t);// 2. 将处理结果传递给流水线下游的Sink
            }
        }
        downstream.end();
        list = null;
    }
    @Override
    public void accept(T t) {
        list.add(t);// 1. 使用当前Sink包装动作处理t,只是简单的将元素添加到中间列表当中
    }
}

多个Sink叠加

多个Stage组成的链路如图所示,那么什么时候出发执行结束操作(Terminal Operation),一旦调用某个结束操作,就会触发整个流水线的执行。

final <P_IN> void copyInto(Sink<P_IN> wrappedSink, Spliterator<P_IN> spliterator){
    ...
    if (!StreamOpFlag.SHORT_CIRCUIT.isKnown(getStreamAndOpFlags())) {
        wrappedSink.begin(spliterator.getExactSizeIfKnown());// 通知开始遍历
        spliterator.forEachRemaining(wrappedSink);// 迭代
        wrappedSink.end();// 通知遍历结束
    }
    ...
}

元空间

永久代的消除

从JDK1.7开始,贮存在永久代的一部分数据已经转移到了Java Heap或者是Native Heap。符号引用(Symbols)转移到了native heap;字面量(interned strings)转移到了java heap;类的静态变量(class statics)转移到了java heap。但永久代仍然存在于JDK7,并没有完全的移除。在JDK1.8版本中永久带被彻底移除。永久代的参数-XX:PermSize和-XX:MaxPermSize也被移除。该参数在JDK1.8使用会有警告

元空间

JDK1.8将类信息存储在元空间中,元空间并不在虚拟机中,而是使用本地内存。因此,默认情况下,元空间的大小仅受本地内存限制,但可以通过以下参数来指定元空间的大小:

-XX:MetaspaceSize,初始空间大小,达到该值就会触发垃圾收集进行类型卸载,同时GC会对该值进行调整:如果释放了大量的空间,就适当降低该值;如果释放了很少的空间,那么在不超过MaxMetaspaceSize时,适当提高该值。

-XX:MaxMetaspaceSize,最大空间,默认是没有限制的(取决于内存)。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值