深入理解Java8中Stream的实现原理

Java:Lambda表达式、方法引用 阅读详情

目录

一.容器执行Lambda表达式的方式

1.回顾

2.Stream API

二.Stream的实现原理

1.一种直白的实现方式

2.Stream流水线解决方案

1).操作如何记录

2).操作如何叠加

3).叠加之后的操作如何执行

4).执行后的结果在哪里


一.容器执行Lambda表达式的方式

1.回顾

首先回顾一下容器执行Lambda表达式的方式,以ArrayList.forEach()方法为例,具体代码如下:

// ArrayList.forEach()
public void forEach(Consumer<? super E> action) {
    ...
    for (int i=0; modCount == expectedModCount && i < size; i++) {
        action.accept(elementData[i]);// 回调方法
    }
    ...
}

我们看到ArrayList.forEach()方法的主要逻辑就是一个for循环,在该for循环里不断调用action.accept()回调方法完成对元素的遍历。这完全没有什么新奇之处,回调方法在Java GUI的监听器中广泛使用。Lambda表达式的作用就是相当于一个回调方法,这很好理解。

2.Stream API

Stream API中大量使用Lambda表达式作为回调方法,但这并不是关键。理解Stream我们更关心的是另外两个问题:流水线和自动并行。使用Stream或许很容易写入如下形式的代码:

int longestStringLengthStartingWithA
        = strings.stream()
              .filter(s -> s.startsWith("A"))
              .mapToInt(String::length)
              .max();

上述代码求出以字母A开头的字符串的最大长度,一种直白的方式是为每一次函数调用都执一次迭代,这样做能够实现功能,但效率上肯定是无法接受的。类库的实现着使用流水线(Pipeline)的方式巧妙的避免了多次迭代,其基本思想是在一次迭代中尽可能多的执行用户指定的操作。为讲解方便我们汇总了Stream的所有操作。

Stream操作分类
中间操作(Intermediate operations)无状态(Stateless)unordered() filter() map() mapToInt() mapToLong() mapToDouble() flatMap() flatMapToInt() flatMapToLong() flatMapToDouble() peek()
有状态(Stateful)distinct() sorted() sorted() limit() skip()
结束操作(Terminal operations)非短路操作forEach() forEachOrdered() toArray() reduce() collect() max() min() count()
短路操作(short-circuiting)anyMatch() allMatch() noneMatch() findFirst() findAny()

Stream上的所有操作分为两类:中间操作和结束操作,中间操作只是一种标记,只有结束操作才会触发实际计算。中间操作又可以分为无状态的(Stateless)和有状态的(Stateful),无状态中间操作是指元素的处理不受前面元素的影响,而有状态的中间操作必须等到所有元素处理之后才知道最终结果,比如排序是有状态操作,在读取所有元素之前并不能确定排序结果;结束操作又可以分为短路操作和非短路操作,短路操作是指不用处理全部元素就可以返回结果,比如找到第一个满足条件的元素。之所以要进行如此精细的划分,是因为底层对每一种情况的处理方式不同。

二.Stream的实现原理

1.一种直白的实现方式

Stream_pipeline_naive

仍然考虑上述求最长字符串的程序,一种直白的流水线实现方式是为每一次函数调用都执一次迭代,并将处理中间结果放到某种数据结构中(比如数组,容器等)。具体说来,就是调用filter()方法后立即执行,选出所有以A开头的字符串并放到一个列表list1中,之后让list1传递给mapToInt()方法并立即执行,生成的结果放到list2中,最后遍历list2找出最大的数字作为最终结果。程序的执行流程如如所示:

这样做实现起来非常简单直观,但有两个明显的弊端:

  1. 迭代次数多。迭代次数跟函数调用的次数相等。
  2. 频繁产生中间结果。每次函数调用都产生一次中间结果,存储开销无法接受。

这些弊端使得效率底下,根本无法接受。如果不使用Stream API我们都知道上述代码该如何在一次迭代中完成,大致是如下形式:

int longest = 0;
for(String str : strings){
    if(str.startsWith("A")){// 1. filter(), 保留以A开头的字符串
        int len = str.length();// 2. mapToInt(), 转换成长度
        longest = Math.max(len, longest);// 3. max(), 保留最长的长度
    }
}

采用这种方式我们不但减少了迭代次数,也避免了存储中间结果,显然这就是流水线,因为我们把三个操作放在了一次迭代当中。只要我们事先知道用户意图,总是能够采用上述方式实现跟Stream API等价的功能,但问题是Stream类库的设计者并不知道用户的意图是什么。如何在无法假设用户行为的前提下实现流水线,是类库的设计者要考虑的问题。

2.Stream流水线解决方案

我们大致能够想到,应该采用某种方式记录用户每一步的操作,当用户调用结束操作时将之前记录的操作叠加到一起在一次迭代中全部执行掉。沿着这个思路,有几个问题需要解决:

  1. 用户的操作如何记录?
  2. 操作如何叠加?
  3. 叠加之后的操作如何执行?
  4. 执行后的结果(如果有)在哪里?

1).操作如何记录

Java_stream_pipeline_classes

注意这里使用的是“操作(operation)”一词,指的是“Stream中间操作”的操作,很多Stream操作会需要一个回调函数(Lambda表达式),因此一个完整的操作是<数据来源,操作,回调函数>构成的三元组。Stream中使用Stage的概念来描述一个完整的操作,并用某种实例化后的PipelineHelper来代表Stage,将具有先后顺序的各个Stage连到一起,就构成了整个流水线。跟Stream相关类和接口的继承关系图示。

还有IntPipeline, LongPipeline, DoublePipeline没在图中画出,这三个类专门为三种基本类型(不是包装类型)而定制的,跟ReferencePipeline是并列关系。图中Head用于表示第一个Stage,即调用调用诸如Collection.stream()方法产生的Stage,很显然这个Stage里不包含任何操作;StatelessOp和StatefulOp分别表示无状态和有状态的Stage,对应于无状态和有状态的中间操作。

Stream流水线组织结构示意图如下:

Stream_pipeline_example

图中通过Collection.stream()方法得到Head也就是stage0,紧接着调用一系列的中间操作,不断产生新的Stream。这些Stream对象以双向链表的形式组织在一起,构成整个流水线,由于每个Stage都记录了前一个Stage和本次的操作以及回调函数,依靠这种结构就能建立起对数据源的所有操作。这就是Stream记录操作的方式。

2).操作如何叠加

以上只是解决了操作记录的问题,要想让流水线起到应有的作用我们需要一种将所有操作叠加到一起的方案。你可能会觉得这很简单,只需要从流水线的head开始依次执行每一步的操作(包括回调函数)就行了。这听起来似乎是可行的,但是你忽略了前面的Stage并不知道后面Stage到底执行了哪种操作,以及回调函数是哪种形式。换句话说,只有当前Stage本身才知道该如何执行自己包含的动作。这就需要有某种协议来协调相邻Stage之间的调用关系。

这种协议由Sink接口完成,Sink接口包含的方法如下表所示:

方法名作用
void begin(long size)开始遍历元素之前调用该方法,通知Sink做好准备。
void end()所有元素遍历完成之后调用,通知Sink没有更多的元素了。
boolean cancellationRequested()是否可以结束操作,可以让短路操作尽早结束。
void accept(T t)遍历元素时调用,接受一个待处理元素,并对元素进行处理。Stage把自己包含的操作和回调方法封装到该方法里,前一个Stage只需要调用当前Stage.accept(T t)方法就行了。

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

有了Sink对操作的包装,Stage之间的调用问题就解决了,执行时只需要从流水线的head开始对数据源依次调用每个Stage对应的Sink.{begin(), accept(), cancellationRequested(), end()}方法就可以了。一种可能的Sink.accept()方法流程是这样的:

void accept(U u){
    1. 使用当前Sink包装的回调函数处理u
    2. 将处理结果传递给流水线下游的Sink
}

Sink接口的其他几个方法也是按照这种[处理->转发]的模型实现。下面我们结合具体例子看看Stream的中间操作是如何将自身的操作包装成Sink以及Sink是如何将处理结果转发给下一个Sink的。先看Stream.map()方法:

// Stream.map(),调用该方法将产生一个新的Stream
public final <R> Stream<R> map(Function<? super P_OUT, ? extends R> mapper) {
    ...
    return new StatelessOp<P_OUT, R>(this, StreamShape.REFERENCE,
                                 StreamOpFlag.NOT_SORTED | StreamOpFlag.NOT_DISTINCT) {
        @Override /*opWripSink()方法返回由回调函数包装而成Sink*/
        Sink<P_OUT> opWrapSink(int flags, Sink<R> downstream) {
            return new Sink.ChainedReference<P_OUT, R>(downstream) {
                @Override
                public void accept(P_OUT u) {
                    R r = mapper.apply(u);// 1. 使用当前Sink包装的回调函数mapper处理u
                    downstream.accept(r);// 2. 将处理结果传递给流水线下游的Sink
                }
            };
        }
    };
}

上述代码看似复杂,其实逻辑很简单,就是将回调函数mapper包装到一个Sink当中。由于Stream.map()是一个无状态的中间操作,所以map()方法返回了一个StatelessOp内部类对象(一个新的Stream),调用这个新Stream的opWripSink()方法将得到一个包装了当前回调函数的Sink。

再来看一个复杂一点的例子。Stream.sorted()方法将对Stream中的元素进行排序,显然这是一个有状态的中间操作,因为读取所有元素之前是没法得到最终顺序的。抛开模板代码直接进入问题本质,sorted()方法是如何将操作封装成Sink的呢?sorted()一种可能封装的Sink代码如下:

// 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的四个接口方法是如何协同工作的:

  1. 首先beging()方法告诉Sink参与排序的元素个数,方便确定中间结果容器的的大小;
  2. 之后通过accept()方法将元素添加到中间结果当中,最终执行时调用者会不断调用该方法,直到遍历所有元素;
  3. 最后end()方法告诉Sink所有元素遍历完毕,启动排序步骤,排序完成后将结果传递给下游的Sink;
  4. 如果下游的Sink是短路操作,将结果传递给下游时不断询问下游cancellationRequested()是否可以结束处理。

3).叠加之后的操作如何执行

Stream_pipeline_Sink

Sink完美封装了Stream每一步操作,并给出了[处理->转发]的模式来叠加操作。这一连串的齿轮已经咬合,就差最后一步拨动齿轮启动执行。是什么启动这一连串的操作呢?也许你已经想到了启动的原始动力就是结束操作(Terminal Operation),一旦调用某个结束操作,就会触发整个流水线的执行。

结束操作之后不能再有别的操作,所以结束操作不会创建新的流水线阶段(Stage),直观的说就是流水线的链表不会在往后延伸了。结束操作会创建一个包装了自己操作的Sink,这也是流水线中最后一个Sink,这个Sink只需要处理数据而不需要将结果传递给下游的Sink(因为没有下游)。对于Sink的[处理->转发]模型,结束操作的Sink就是调用链的出口。

我们再来考察一下上游的Sink是如何找到下游Sink的。一种可选的方案是在PipelineHelper中设置一个Sink字段,在流水线中找到下游Stage并访问Sink字段即可。但Stream类库的设计者没有这么做,而是设置了一个Sink AbstractPipeline.opWrapSink(int flags, Sink downstream)方法来得到Sink,该方法的作用是返回一个新的包含了当前Stage代表的操作以及能够将结果传递给downstream的Sink对象。为什么要产生一个新对象而不是返回一个Sink字段?这是因为使用opWrapSink()可以将当前操作与下游Sink(上文中的downstream参数)结合成新Sink。试想只要从流水线的最后一个Stage开始,不断调用上一个Stage的opWrapSink()方法直到最开始(不包括stage0,因为stage0代表数据源,不包含操作),就可以得到一个代表了流水线上所有操作的Sink,用代码表示就是这样:

// AbstractPipeline.wrapSink()
// 从下游向上游不断包装Sink。如果最初传入的sink代表结束操作,
// 函数返回时就可以得到一个代表了流水线上所有操作的Sink。
final <P_IN> Sink<P_IN> wrapSink(Sink<E_OUT> sink) {
    ...
    for (AbstractPipeline p=AbstractPipeline.this; p.depth > 0; p=p.previousStage) {
        sink = p.opWrapSink(p.previousStage.combinedFlags, sink);
    }
    return (Sink<P_IN>) sink;
}

现在流水线上从开始到结束的所有的操作都被包装到了一个Sink里,执行这个Sink就相当于执行整个流水线,执行Sink的代码如下:

// AbstractPipeline.copyInto(), 对spliterator代表的数据执行wrappedSink代表的操作。
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();// 通知遍历结束
    }
    ...
}

上述代码首先调用wrappedSink.begin()方法告诉Sink数据即将到来,然后调用spliterator.forEachRemaining()方法对数据进行迭代(Spliterator是容器的一种迭代器,参阅),最后调用wrappedSink.end()方法通知Sink数据处理结束。逻辑如此清晰。

4).执行后的结果在哪里

最后一个问题是流水线上所有操作都执行后,用户所需要的结果(如果有)在哪里?首先要说明的是不是所有的Stream结束操作都需要返回结果,有些操作只是为了使用其副作用(Side-effects),比如使用Stream.forEach()方法将结果打印出来就是常见的使用副作用的场景(事实上,除了打印之外其他场景都应避免使用副作用),对于真正需要返回结果的结束操作结果存在哪里呢?

特别说明:副作用不应该被滥用,也许你会觉得在Stream.forEach()里进行元素收集是个不错的选择,就像下面代码中那样,但遗憾的是这样使用的正确性和效率都无法保证,因为Stream可能会并行执行。大多数使用副作用的地方都可以使用归约操作更安全和有效的完成。

// 错误的收集方式
ArrayList<String> results = new ArrayList<>();
stream.filter(s -> pattern.matcher(s).matches())
      .forEach(s -> results.add(s));  // Unnecessary use of side-effects!
// 正确的收集方式
List<String>results =
     stream.filter(s -> pattern.matcher(s).matches())
             .collect(Collectors.toList());  // No side-effects!

回到流水线执行结果的问题上来,需要返回结果的流水线结果存在哪里呢?这要分不同的情况讨论,下表给出了各种有返回结果的Stream结束操作。

返回类型对应的结束操作
booleananyMatch() allMatch() noneMatch()
OptionalfindFirst() findAny()
归约结果reduce() collect()
数组toArray()
  1. 对于表中返回boolean或者Optional的操作(Optional是存放 一个 值的容器)的操作,由于值返回一个值,只需要在对应的Sink中记录这个值,等到执行结束时返回就可以了。
  2. 对于归约操作,最终结果放在用户调用时指定的容器中(容器类型通过收集器指定)。collect(), reduce(), max(), min()都是归约操作,虽然max()和min()也是返回一个Optional,但事实上底层是通过调用reduce()方法实现的。
  3. 对于返回是数组的情况,毫无疑问的结果会放在数组当中。这么说当然是对的,但在最终返回数组之前,结果其实是存储在一种叫做Node的数据结构中的。Node是一种多叉树结构,元素存储在树的叶子当中,并且一个叶子节点可以存放多个元素。这样做是为了并行执行方便。关于Node的具体结构,我们会在下一节探究Stream如何并行执行时给出详细说明。

结语:

本文详细介绍了Stream流水线的组织方式和执行过程,学习本文将有助于理解原理并写出正确的Stream代码,同时打消你对Stream API效率方面的顾虑。如你所见,Stream API实现如此巧妙,即使我们使用外部迭代手动编写等价代码,也未必更加高效。

转自:http://www.cnblogs.com/CarpenterLee/p/6637118.html

从初学者到专家:Java的Lambda表达式完整指南 Lambda是Java中的一种函数式编程特性,它允许以简洁、灵活的方式定义行为(函数),并将其作为参数传递给方法或操作集合数据。通过Lambda表达式,我们可以更方便地实现函数式编程的思想,简化代码编写,并提高代码的可读性和可维护性。它是Java语言在处理函数对象和函数式编程方面的重要进步,为开发者提供了更多的编程选择和表达能力。 阅读详情

相关推荐

Java8 Stream源码精讲(四):一文说透四种终止操作

本文先解释了Stream调用过程中,终止操作是所声明的lambda表达式的触发时机,讲解了中间操作调用时的链表封装和终止操作的调用流程。依据是否完整遍历Stream元素,将终止操作划分为非短路操作和短路操作,通过带领大家阅读源码,深入分析了ForEachOp、ReduceOp、MatchOp和FindOp四种终止操作的实现原理,以及每一个终止操作方法是如何实现的。本篇文章是本系列的第四章,相信大家完整阅读之后,对Stream的整体工作流程和原理有了更深入的理解。

xhbzl的博客 1255

Java中Lambda表达式的使用

此笔记仅用作复习使用:https://www.cnblogs.com/franson-2016/p/5593080.html Lambda表达式是Java SE 8中一个重要的新特性。lambda表达式允许你通过表达式来代替功能接口。 lambda表达式就和方法一样,它提供了一个正常的参数列表和一个使用这些参数的主体(body,可以是一个表达式或一个代码块)。 Lambda表达式还增强了集合库。...

程序员的博客 6万+

Java8 Stream源码精讲:中间操作原理详解

通过分析创建Stream的过程,详细介绍了Spliterator接口定义,Spliterator子类的实现细节,Spliterator在Stream中的调用时机,以及代表源阶段Stream的Head类结构。本章将继续带着大家深入理解什么是Stream中间操作,进入每一个中间操作的源码了解我们定义的lambda表达式是如何在流上处理数据的。 中间操作 Stream是惰性流,中间操作只是将lambda表达式记录下来,返回一个新的Stream,只有终止操作被调用时,才会触发计算。这样可以保证数据被尽量少的遍历

JavaShark的博客 597

Java中Lambda表达式使用及详解

Java中Lambda表达式使用及详解 前言 一、Lambda表达式的简介 Lambda表达式(闭包):java8的新特性,lambda运行将函数作为一个方法的参数,也就是函数作为参数传递到方法中。使用lambda表达式可以让代码更加简洁。 Lambda表达式的使用场景:用以简化接口实现。 关于接⼝实现,可以有很多种⽅式来实现。例如:设计接⼝的实现类、使⽤匿名内部类。 但是lamb

qq_45263520的博客 7万+

让你彻底了解 Java 8 的 Lambda、函数式接口、Stream 用法和原理

就在今年 Java 25周岁了,可能比在座的各位中的一些少年年龄还大,但令人遗憾的是,竟然没有我大,不禁感叹,Java 还是太小了。(难道我会说是因为我老了?) 而就在上个月,Java 15 的试验版悄悄发布了,但是在 Java 界一直有个神秘现象,那就是「你发你发任你发,我的最爱 Java 8」. 据 Snyk 和 The Java Magazine 联合推出发布的 2020 JVM 生态调查报告显示,在所有的 Java 版本中,仍然有 64% 的开发者使用 Java 8。另外一些开发者可能.

436

java8 Stream的实现原理 (从零开始实现一个stream流)

1.从零开始实现stream流 1.1 java8 stream介绍 java8新增了stream流的特性,能够让用户以函数式的方式、更为简单的操纵集合等数据结构,并实现了用户无感知的并行计算。 1.2 从零开始实现一个stream流 相信很多人在使用过java8的streamAPI接口之后,都会对其实现原理感到好奇,但往往在看到jdk的stream源码后却被其复杂的抽象、封装给弄糊涂了,而无法很好的理解其背后的原理。究其原因,是因为jdk的stream源码是高度工程化的代码,工程化的代码为了效率和满足各式

阿杰同学的博客 2213

从0开始实现java Stream功能

流本身//当前节点的数据 protected T value;//用于获取下一个节点的表达式 protected StreamNodeSupplier < T > nextNode;//是否开始节点 protected boolean start;//是否结束节点 public boolean isEnd() {} }} }StreamNode构造类} }

爱码猿的博客 1825

【深度学习】肺结节分割项目实战二:分割肺实质

在后续训练模型的时候如果直接使用原始的肺部CT图像,其中的非肺区域会对训练造成干扰,因此我们需要提取分割肺实质。这一部分参考了kaggle上的教程。 从这张CT图像中可以看出扫描器以外的部分为黑色,背景和肺内部颜色较深,非肺组织和骨头颜色较浅。我们的目的是将肺部分割出来。由于肺部区域和其他部分对比明显,我们可以设置一个合适的阈值对图像中的区域进行隔离。 import argparse import os import numpy as np from medpy.filter.smoothing imp

weixin_42182906的博客 7242

Java8新特性之parallelStream详解

一、什么是流? Stream是java8中新增加的一个特性,首先Stream 不是集合元素,它不是数据结构并不保存数据,它是有关算法和计算的,它更像一个高级版本的 Iterator。原始版本的 Iterator,用户只能显式地一个一个遍历元素并对其执行某些操作;高级版本的 Stream,用户只要给出需要对其包含的元素执行什么操作,比如 “过滤掉长度大于 10 的字符串”、“获取每个字符串的首字母...

lixinkuan的博客 4433

实验六 存储器实验

只读存储器(ROM)是一种在正常工作时其存储的数据固定不变,其中的数据只能读出,不能写入,即使断电也能够保留数据,要想在只读存储器中存入或改变数据,必须具备特定的条件。当地址为00000101时,会分别在数据位宽为8,16,32的只读寄存器中找到地址为00000101所对应的十进制地址位置,也就是地址为5的数据,并将其分别以8位,16位,32位输出。因此,输出保持为0。因此,当异步清零信号为1时,无论其他输入信号是什么,触发器的输出都会被强制清零,输出为0。1)寄存器输入0011,使能端1,异步清零0。

wenmou1111的博客 3005

Windows11 下 Detectron2 的安装与配置全攻略

本文提供了在Windows11系统下安装和配置Detectron2的完整教程。详细介绍了从环境准备、创建虚拟环境、安装PyTorch与CUDA,到修改源码、编译安装以及运行验证Demo的全过程,帮助开发者克服Windows平台上的常见问题,成功部署这一强大的计算机视觉库。

weixin_29081703的博客 247

Collector原理解析

collect函数式编程原理解析

qq_44918331的博客 927

MOS管驱动电路设计全攻略

MOS管驱动电路设计全攻略

让你爱上电路设计 3635

Lambda 表达式详解:从语法到原理

从六行样板代码压缩到一行核心逻辑,这正是 Lambda 最直观的价值。但 Lambda 表达式绝不仅仅是一个“语法糖”,它背后牵涉到函数式接口、类型推断、方法引用、闭包、invokedynamic 指令、Stream 流式处理等一系列重要概念。理解这些概念,才能真正用好 Lambda,并理解现代 Java 生态中大量基于函数式风格的框架代码。

油墨香^-^的博客 205

手把手教你学Simulink——基于智能电网与AI融合的场景实例:强化学习在并网逆变器控制中的应用

本文介绍了如何利用Simulink和强化学习工具包实现并网逆变器的智能控制。针对传统PI控制方法的局限性,提出采用强化学习(RL)构建自适应控制系统。通过详细步骤展示了从建立主电路模型、设计强化学习环境、选择训练智能体(如PPO算法)到仿真对比测试的全过程。实验结果表明,RL控制器在动态响应、THD指标和参数鲁棒性等方面优于传统PI控制器,突显了数据驱动控制在复杂电力系统中的优势。文章还探讨了该技术在微电网协同控制、在线学习等场景的拓展应用,为智能电网控制提供了新思路。

MHD0815的博客 337

【AI应用实战-Codex】Mac intel芯片下安装Codex(二)

摘要:本文介绍了在Intel芯片Mac上安装OpenAI Codex桌面应用时遇到的问题及解决方法。由于官方仅提供ARM64版本,导致Intel Mac无法直接运行。解决方案包括:1)使用社区脚本将ARM版DMG与x64 Electron重新打包成兼容版本;2)使用VSCode插件配合CLI作为替代方案。文章详细记录了重新打包过程中遇到的"electron_common_owl_features"绑定缺失错误,通过注入stub代码、重算ASAR完整性哈希等步骤成功解决问题。最终生成的Co

3535

Java8特性之Stream的原理解析和日常使用

Java8特性之Stream流的原理和日常使用,可以直观的理解Stream流操作和玩转集合

weixin_43805705的博客 1843

蓝牙学习笔记之SMP协议(十四)

目录 写在前面 SM规范简述 安全管理器 加密工具 配对流程 安全属性 IO功能 加密密钥大小 配对算法 低功耗蓝牙安全 密钥和值的定义 分发密钥的生成 密钥分配 安全管理器会话协议 介绍 ​​​​​​​命令格式 写在前面 安全规范协议,主要是描述LE控制器设备配对、认证、加密等过程进行规定,同时也对相关算法进行了说明。 低功耗蓝牙的安全管理经历了几个阶段,L...

在云巅的博客 3663

深入理解Java 8 Lambda(原理篇——Lambda底层实现原理)(转载)

原文出处:https://segmentfault.com/a/1190000023747150?utm_source=tag-newest 前言 经常会在写业务代码的时候,有这样的需求: 筛选出条件为XX的实体类ID List List<Long> waitTaskList = wflInsTaskList.stream().filter(wflInsTask -> { return wflInsTask.getArrivalStatus().e

xydj8923的专栏 7336

Python 复现AlexNet框架代码

【代码】Python 复现AlexNet框架代码。

Quintin 2421

visio安装包

Microsoft Office Visio 是一款专业的制图软件。利用经过更新的形状、协作工具和数据链接图表,创建专业级图表以简化复杂的信息。

java8的stream_java8中Stream的使用示例教程

前言Java8中提供了Stream对集合操作作出了极大的简化,学习了Stream之后,我们以后不用使用for循环就能对集合作出很好的操作。本文将给大家详细介绍关于java8 Stream使用的相关内容,下面话不多说了,来一起看看详细的介绍吧1. 原理Stream 不是集合元素,它不是数据结构并不保存数据,它是有关算法和计算的,它更像一个高级版本的 Iterator。原始版本的 Iterator,用...

weixin_39867594的博客 224

jre9下载 jdk9

(Java SE Runtime Environment 9.0) 9.0u175 官方最新版 【x86|x64】

JAVA8的lambda及stream详解

JAVA8 lambda及stream详解 一、lambda详解 1.1简介 lambda是JAVA8中提供的一个语法糖,它使用代码编写更加简洁、紧凑、灵活。Lambda是使用一串实现单个接口方法的语法,而这个接口必需是函数式接口。 示例: ###1.2详解 ##stream学习 ...

jvortex的专栏 822

WINCC7.3破解文件

WINCC7.3硬件够破解文件,有需要的下载!WINCC7.3硬件够破解文件,有需要的下载!

精雕软件(JDpaint) 5.5 绿色版

精雕软件(JDpaint) 5.5 绿色版精雕软件(JDpaint) 5.5 绿色版精雕软件(JDpaint) 5.5 绿色版

Java 8-stream实现原理分析(一)

背景介绍 Spliterator和Iterator Iterator Spliterator 流来源 实现方式 代码分析 Sink执行分析 filter的begin() map的begin() sorted的begin() filter的accpet() map的accpet() sorted的accpet() filter的end() map的end() sorted的end() Re...

pfjia的博客 1万+

JDK8:Stream流0基础使用与深入理解,Stream流源码分析

流操作是Java8提供一个重要新特性,它允许开发人员以声明性方式处理集合,其核心类库主要改进了对集合类的API和新增Stream操作。Stream类中每一个方法都对应集合上的一种操作。将真正的函数式编程引入到Java中,能让代码更加简洁,极大地简化了集合的处理操作,提高了开发的效率和生产力。同时stream不是一种数据结构,它只是某种数据源的一个视图,数据源可以是一个数组,Java容器或I/O channel等。在Stream中的操作每一次都会产生新的流,内部不会像普通集合操作一样立刻获取值,而是。

秃了也弱了 2900

Java8新特性3:Stream1——什么是Stream,Stream的特性,如何使用Stream,Stream与Collection集合的区别

最近打算写一个Stream流的系列,这是基于我看完《java8 in action》英文原版 Stream部分的一些总结,如果你看《java8 in action》有点难以理解 ,那么你可以参考一下我的博客,可以让你清晰Stream的使用和原理。整个流的系列大概按以下方向写: 一、Stream的概念和特性,Stream的使用,Stream与Collection集合的区别。 二、读懂Stream...

aigoV的博客 1844
上一篇: 解决Mysql报错:the service mysql57 failed the most the most recent status change request with the message
下一篇: Java8中的Stream详解
L聪聪聪
博客等级 码龄10年 272粉丝 65原创
评论 5
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值