lambda 流 peek java_JDK8 流与λ表达式

λ表达式

什么是λ表达式

λ表达式有三部分组成:参数列表,箭头(->),以及一个表达式或者语句块。

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 call() throws Exception;

}

public interface Comparator{

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 {

R apply(T t);

}

//入参为T,无返回值

@FunctionalInterface

public interface Consumer {

void accept(T t);

}

//无入参,返回T(通常配合构造方法)

@FunctionalInterface

public interface Supplier {

T get();

}

//入参为T,返回值为boolean

@FunctionalInterface

public interface Predicate {

boolean test(T t);

}

//入参为T,U,返回值为R 类似还有BiConsumer等

@FunctionalInterface

public interface BiFunction {

R accept(T t, U u);

}

方法引用

方法引用让开发者可以直接引用现存的方法、Java类的构造方法或者实例对象。方法引用和Lambda表达式配合使用,使得java类的构造方法看起来紧凑而简洁,没有很多复杂的模板代码。

public static class Car {

public static Car create( final Supplier 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 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 对象(可以有多次转换),这就允许对其操作可以像链条一样排列,变成一个管道

,如下图所示。

a6ee65618a1c

image

常用的构建流的方式:

集合Collection

Collection.stream()

Collection.parallelStream()

数组

Stream.of(T[] tArray)

多个相同类型对象

Stream.of("chaimm","peter","john");

流的基本使用

常见操作

中间操作

无状态

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)

List persons = new ArrayLisy<>();

List result = persons.stream().map(x -> x.getId())

.collect(Collectors.toList());

List result = persons.stream().map(x -> {

Student s = new Student();

s.setName(x.getName());

return s;

}).collect(Collectors.toList());

flatMap 一对多 (入参 Function)

List> listAll = new ArrayList<>();

List result = listAll.stream().flatMap(x -> x.stream())

.collect(Collectors.toList());

List 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 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 result = persons.stream().map(x -> x.getId())

.peek(x -> System.out.println(x))

.collect(Collectors.toList());

Map map = new HashMap<>();

List 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 result = persons.stream().map(x -> x.getId())

.limit(10).skip(3).collect(Collectors.toList());

sorted

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

List 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 personList2 = persons.stream()

.sorted((p1, p2) -> p1.getName().compareTo(p2.getName()))

.collect(Collectors.toList());

List personList2 = persons.stream()

.sorted((p1, p2) -> p1.getName().compareTo(p2.getName()))

.collect(ArrayList::new, ArrayList::add, ArrayList::addAll);

Collectors.toMap

转换为MAP

Map personIdNameMap = persons.stream()

.collect(Collectors.toMap(Person::getId, Person::getName);

Map personIdNameMap = persons.stream()

.collect(Collectors.toMap(Person::getId, Person::getName, (v1,v2)->v1);

Collectors.groupingBy

分组

Map> personAgeMap = persons.stream()

.collect(Collectors.groupingBy(Person::getAge));

Map> personAgeMap = persons.stream()

.collect(Collectors.groupingBy(Person::getAge,

Collectors.toMap(Person::getId, Function.identity())));

Collectors.collectingAndThen

收集然后处理

Map 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连到一起,就构成了整个流水线。

a6ee65618a1c

image

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 Stream map(Function super P_OUT, ? extends R> mapper) {

Objects.requireNonNull(mapper);

return new StatelessOp(this, StreamShape.REFERENCE,

StreamOpFlag.NOT_SORTED | StreamOpFlag.NOT_DISTINCT) {

@Override

Sink opWrapSink(int flags, Sink sink) {

return new Sink.ChainedReference(sink) {

@Override

public void accept(P_OUT u) {

downstream.accept(mapper.apply(u));

}

};

}

};

}

static abstract class ChainedReference implements Sink {

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 extends AbstractRefSortingSink {

private ArrayList list;// 存放用于排序的元素

RefSortingSink(Sink super T> downstream, Comparator super T> comparator) {

super(downstream, comparator);

}

@Override

public void begin(long size) {

...

// 创建一个存放排序元素的列表

list = (size >= 0) ? new ArrayList((int) size) : new ArrayList();

}

@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 void copyInto(Sink wrappedSink, Spliterator 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,最大空间,默认是没有限制的(取决于内存)。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值