5万字长文:Stream和Lambda表达式最佳实践-附PDF下载(一)

简介: 5万字长文:Stream和Lambda表达式最佳实践-附PDF下载(一)

目录



1. Streams简介


今天要讲的Stream指的是java.util.stream包中的诸多类。Stream可以方便的将之前的结合类以转换为Stream并以流式方式进行处理,大大的简化了我们的编程,Stream包中,最核心的就是interface Stream


image.png


从上面的图中我们可以看到Stream继承自BaseStream。Stream中定义了很多非常实用的方法,比如filter,map,flatmap,forEach,reduce,collect等等。接下来我们将会逐一讲解。


1.1 创建Stream


Stream的创建有很多方式,java引入Stream之后所有的集合类都添加了一个stream()方法,通过这个方法可以直接得到其对应的Stream。也可以通过Stream.of方法来创建:


//Stream Creation
        String[] arr = new String[]{"a", "b", "c"};
        Stream<String> stream = Arrays.stream(arr);
        stream = Stream.of("a", "b", "c");


1.2 Streams多线程


如果我们想使用多线程来处理集合类的数据,Stream提供了非常方便的多线程方法parallelStream():


//Multi-threading
        List<String> list =new ArrayList();
        list.add("aaa");
        list.add("bbb");
        list.add("abc");
        list.add("ccc");
        list.add("ddd");
        list.parallelStream().forEach(element -> doPrint(element));


1.3 Stream的基本操作



Stream的操作可以分为两类,一类是中间操作,中间操作返回Stream,因此可以级联调用。 另一类是终止操作,这类操作会返回Stream定义的类型。


//Operations
        long count = list.stream().distinct().count();


上面的例子中,distinct()返回一个Stream,所以可以级联操作,最后的count()是一个终止操作,返回最后的值。


Matching


Stream提供了anyMatch(), allMatch(), noneMatch()这三种match方式,我们看下怎么使用:


//Matching
        boolean isValid = list.stream().anyMatch(element -> element.contains("h"));
        boolean isValidOne = list.stream().allMatch(element -> element.contains("h"));
        boolean isValidTwo = list.stream().noneMatch(element -> element.contains("h"));


Filtering


filter() 方法允许我们对Stream中的数据进行过滤,从而得到我们需要的:


Stream<String> filterStream = list.stream().filter(element -> element.contains("d"));


上面的例子中我们从list中选出了包含“d”字母的String。


Mapping


map就是对Stream中的值进行再加工,然后将加工过后的值作为新的Stream返回。


//Mapping
        Stream<String> mappingStream = list.stream().map(element -> convertElement(element));
    private static String convertElement(String element) {
        return "element"+"abc";
    }


上的例子中我们把list中的每个值都加上了“abc”然后返回一个新的Stream。


FlatMap


flatMap和Map很类似,但是他们两个又有不同,看名字我们可以看到flatMap意思是打平之后再做Map。


怎么理解呢?


假如我们有一个CustBook类:


@Data
public class CustBook {
    List<String> bookName;
}


CustBook定义了一个bookName字段。


先看一下Map返回的结果:


List<CustBook> users = new ArrayList<>();
        users.add(new CustBook());
Stream<Stream<String>> userStreamMap
                = users.stream().map(user -> user.getBookName().stream());


在上面的代码中,map将每一个user都转换成了stream,所以最后的结果是返回Stream的Stream。


如果我们只想返回String,则可以使用FlatMap:


List<CustBook> users = new ArrayList<>();
        users.add(new CustBook());
        Stream<String> userStream
                = users.stream().map(user -> user.getBookName().stream());


简单点讲FlatMap就是将层级关系铺平重来。


Reduction


使用reduce() 方法可以方便的对集合的数据进行运算,reduce()接收两个参数,第一个是开始值,后面是一个函数表示累计。


//Reduction
        List<Integer> integers = Arrays.asList(1, 1, 1);
        Integer reduced = integers.stream().reduce(100, (a, b) -> a + b);


上面的例子我们定义了3个1的list,然后调用reduce(100, (a, b) -> a + b)方法,最后的结果是103.


Collecting


collect()方法可以方便的将Stream再次转换为集合类,方便处理和展示:


List<String> resultList
                = list.stream().map(element -> element.toUpperCase()).collect(Collectors.toList());


2. functional interface的分类和使用


java 8引入了lambda表达式,lambda表达式实际上表示的就是一个匿名的function。


在java 8之前,如果需要使用到匿名function需要new一个类的实现,但是有了lambda表达式之后,一切都变的非常简介。


我们看一个之前讲线程池的时候的一个例子:


//ExecutorService using class
        ExecutorService executorService = Executors.newSingleThreadExecutor();
        executorService.submit(new Runnable() {
            @Override
            public void run() {
            log.info("new runnable");
            }
        });


executorService.submit需要接收一个Runnable类,上面的例子中我们new了一个Runnable类,并实现了它的run()方法。


上面的例子如果用lambda表达式来重写,则如下所示:


//ExecutorService using lambda
        executorService.submit(()->log.info("new runnable"));


看起是不是很简单,使用lambda表达式就可以省略匿名类的构造,并且可读性更强。


那么是不是所有的匿名类都可以用lambda表达式来重构呢?也不是。


我们看下Runnable类有什么特点:


@FunctionalInterface
public interface Runnable


Runnable类上面有一个@FunctionalInterface注解。这个注解就是我们今天要讲到的Functional Interface。


2.1 Functional Interface


Functional Interface是指带有 @FunctionalInterface 注解的interface。它的特点是其中只有一个子类必须要实现的abstract方法。如果abstract方法前面带有default关键字,则不做计算。


其实这个也很好理解,因为Functional Interface改写成为lambda表达式之后,并没有指定实现的哪个方法,如果有多个方法需要实现的话,就会有问题。


@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface FunctionalInterface {}


Functional Interface一般都在java.util.function包中。


根据要实现的方法参数和返回值的不同,Functional Interface可以分为很多种,下面我们分别来介绍。


2.2 Function:一个参数一个返回值


Function接口定义了一个方法,接收一个参数,返回一个参数。


@FunctionalInterface
public interface Function<T, R> {
    /**
     * Applies this function to the given argument.
     *
     * @param t the function argument
     * @return the function result
     */
    R apply(T t);


一般我们在对集合类进行处理的时候,会用到Function。


Map<String, Integer> nameMap = new HashMap<>();
        Integer value = nameMap.computeIfAbsent("name", s -> s.length());


上面的例子中我们调用了map的computeIfAbsent方法,传入一个Function。


上面的例子还可以改写成更短的:


Integer value1 = nameMap.computeIfAbsent("name", String::length);


Function没有指明参数和返回值的类型,如果需要传入特定的参数,则可以使用IntFunction, LongFunction, DoubleFunction:


@FunctionalInterface
public interface IntFunction<R> {
    /**
     * Applies this function to the given argument.
     *
     * @param value the function argument
     * @return the function result
     */
    R apply(int value);
}


如果需要返回特定的参数,则可以使用ToIntFunction, ToLongFunction, ToDoubleFunction:


@FunctionalInterface
public interface ToDoubleFunction<T> {
    /**
     * Applies this function to the given argument.
     *
     * @param value the function argument
     * @return the function result
     */
    double applyAsDouble(T value);
}


如果要同时指定参数和返回值,则可以使用DoubleToIntFunction, DoubleToLongFunction, IntToDoubleFunction, IntToLongFunction, LongToIntFunction, LongToDoubleFunction:


@FunctionalInterface
public interface LongToIntFunction {
    /**
     * Applies this function to the given argument.
     *
     * @param value the function argument
     * @return the function result
     */
    int applyAsInt(long value);
}


2.3 BiFunction:接收两个参数,一个返回值


如果需要接受两个参数,一个返回值的话,可以使用BiFunction:BiFunction, ToDoubleBiFunction, ToIntBiFunction, ToLongBiFunction等。


@FunctionalInterface
public interface BiFunction<T, U, R> {
    /**
     * Applies this function to the given arguments.
     *
     * @param t the first function argument
     * @param u the second function argument
     * @return the function result
     */
    R apply(T t, U u);


我们看一个BiFunction的例子:


//BiFunction
        Map<String, Integer> salaries = new HashMap<>();
        salaries.put("alice", 100);
        salaries.put("jack", 200);
        salaries.put("mark", 300);
        salaries.replaceAll((name, oldValue) ->
                name.equals("alice") ? oldValue : oldValue + 200);


2.4 Supplier:无参的Function


如果什么参数都不需要,则可以使用Supplier:


@FunctionalInterface
public interface Supplier<T> {
    /**
     * Gets a result.
     *
     * @return a result
     */
    T get();
}


2.5 Consumer:接收一个参数,不返回值


Consumer接收一个参数,但是不返回任何值,我们看下Consumer的定义:


@FunctionalInterface
public interface Consumer<T> {
    /**
     * Performs this operation on the given argument.
     *
     * @param t the input argument
     */
    void accept(T t);


看一个Consumer的具体应用:


//Consumer
        nameMap.forEach((name, age) -> System.out.println(name + " is " + age + " years old"));


2.6 Predicate:接收一个参数,返回boolean


Predicate接收一个参数,返回boolean值:


@FunctionalInterface
public interface Predicate<T> {
    /**
     * Evaluates this predicate on the given argument.
     *
     * @param t the input argument
     * @return {@code true} if the input argument matches the predicate,
     * otherwise {@code false}
     */
    boolean test(T t);


如果用在集合类的过滤上面那是极好的:


//Predicate
        List<String> names = Arrays.asList("A", "B", "C", "D", "E");
        List<String> namesWithA = names.stream()
                .filter(name -> name.startsWith("A"))
                .collect(Collectors.toList());


2.7 Operator:接收和返回同样的类型


Operator接收和返回同样的类型,有很多种Operator:UnaryOperator BinaryOperator ,DoubleUnaryOperator, IntUnaryOperator, LongUnaryOperator,

DoubleBinaryOperator, IntBinaryOperator, LongBinaryOperator等。


@FunctionalInterface
public interface IntUnaryOperator {
    /**
     * Applies this operator to the given operand.
     *
     * @param operand the operand
     * @return the operator result
     */
    int applyAsInt(int operand);


我们看一个BinaryOperator的例子:


//Operator
        List<Integer> values = Arrays.asList(1, 2, 3, 4, 5);
        int sum = values.stream()
                .reduce(0, (i1, i2) -> i1 + i2);


3. Lambda表达式最佳实践


Lambda表达式java 8引入的函数式编程框架。之前的文章中我们也讲过Lambda表达式的基本用法。


本文将会在之前的文章基础上更加详细的讲解Lambda表达式在实际应用中的最佳实践经验。


3.1 优先使用标准Functional接口


之前的文章我们讲到了,java在java.util.function包中定义了很多Function接口。基本上涵盖了我们能够想到的各种类型。


假如我们自定义了下面的Functional interface:


@FunctionalInterface
public interface Usage {
    String method(String string);
}


然后我们需要在一个test方法中传入该interface:


public String test(String string, Usage usage) {
    return usage.method(string);
}


上面我们定义的函数接口需要实现method方法,接收一个String,返回一个String。这样我们完全可以使用Function来代替:


public String test(String string, Function<String, String> fn) {
    return fn.apply(string);
}


使用标准接口的好处就是,不要重复造轮子。


3.2 使用@FunctionalInterface注解


虽然@FunctionalInterface不是必须的,不使用@FunctionalInterface也可以定义一个Functional Interface。


但是使用@FunctionalInterface可以在违背Functional Interface定义的时候报警。


如果是在维护一个大型项目中,加上@FunctionalInterface注解可以清楚的让其他人了解这个类的作用。


从而使代码更加规范和更加可用。


所以我们需要这样定义:


@FunctionalInterface
public interface Usage {
    String method(String string);
}


而不是:


public interface Usage {
    String method(String string);
}
相关文章
|
1月前
|
数据采集 Web App开发 JavaScript
Puppeteer自动化:使用JavaScript定制PDF下载
在现代Web开发中,自动化工具如Puppeteer可显著提升效率并减少重复工作。Puppeteer是一款强大的Node.js库,能够控制无头Chrome或Chromium浏览器,适用于网页快照生成、数据抓取及自动化测试等任务。本文通过示例展示了如何使用Puppeteer自动化生成定制化的PDF文件,并介绍了如何通过配置代理IP、设置user-agent和cookie等技术增强自动化过程的灵活性与稳定性。具体步骤包括安装Puppeteer、配置代理IP、设置user-agent和cookie等,最终生成符合需求的PDF文件。此技术可应用于报表生成、发票打印等多种场景。
112 6
Puppeteer自动化:使用JavaScript定制PDF下载
|
3月前
|
XML 缓存 JSON
为什么浏览器中有些图片、PDF等文件点击后有些是预览,有些是下载
为什么浏览器中有些图片、PDF等文件点击后有些是预览,有些是下载
257 0
|
4月前
|
安全 算法 文件存储
共享资料下载,自动转PDF并添加隐形水印
云盒子企业网盘增强文件安全,支持下载时自动转PDF并加水印。管理员可配置目录规则,选择明水印、隐形水印或点阵水印。明水印直观防复制,隐形水印用于隐蔽追踪,点阵水印不影响阅读。文件格式支持度和水印类型取决于设置。此功能适用于文档安全、版权保护等场景。欲知详情或测试,访问[云盒子官网](yhz66.com)咨询客服。
108 1
|
21天前
|
前端开发 API
前端界面生成PDF并导出下载
【10月更文挑战第21天】利用合适的第三方库,你可以在前端轻松实现界面生成 PDF 并导出下载的功能,为用户提供更方便的文档分享和保存方式。你还可以根据具体的需求进一步优化和定制生成的 PDF 文件,以满足不同的业务场景要求。
|
2月前
|
移动开发 前端开发 JavaScript
使用html-to-image代替html2canvas,结合jspdf实现下载pdf(下载截图下载前端dom元素)
本文介绍了在前端项目中,当使用`html2canvas`遇到问题时,如何使用`html-to-image`库作为替代方案,结合`jspdf`实现将DOM元素生成为PDF文件并提供下载。文章首先讨论了`html2canvas`可能遇到的问题,并提供了该库的使用示例代码。随后,详细介绍了`html-to-image`库的安装和使用方法,展示了如何将DOM元素转换为Canvas,再利用`jspdf`生成PDF文件。最后,文章通过示例代码说明了整个转换和下载的过程,并展示了效果截图。
89 0
|
3月前
|
数据采集 前端开发 开发者
解决PuppeteerSharp生成PDF颜色问题的最佳实践
使用PuppeteerSharp生成PDF时颜色丢失是个常见问题。本文介绍如何通过正确配置PdfOptions与CSS规则(如设置`PrintBackground`为`true`及使用`@media print`确保颜色准确显示),结合爬虫代理IP、User-Agent和Cookie设置等技巧来解决此问题,并提供了完整的代码示例。这些方法不仅有助于保持PDF的颜色准确性,还能增强爬虫的稳定性和效率。
解决PuppeteerSharp生成PDF颜色问题的最佳实践
|
4月前
|
JavaScript 前端开发 程序员
《JavaScript权威指南第7版》中文PDF+英文PDF+源代码 +JavaScript权威指南(第6版)(附源码)PDF下载阅读分享推荐
JavaScript是Web标准语言,广泛应用于各类浏览器,造就了其最广泛部署的地位。Node.js的兴起扩展了JavaScript的使用场景,使其成为开发者首选语言。无论新手还是经验丰富的程序员,都能受益于学习JavaScript。[《JavaScript权威指南第7版》资源链接](https://zhangfeidezhu.com/?p=224)
271 5
《JavaScript权威指南第7版》中文PDF+英文PDF+源代码 +JavaScript权威指南(第6版)(附源码)PDF下载阅读分享推荐
|
4月前
ChatGPT提问获取高质量答案的艺术PDF下载书籍推荐分享
**掌握ChatGPT高质量Prompt技巧的指南,教你艺术性提问以获取卓越答案。适用于各层次用户,提升内容创作效率。了解Prompt工程,作为对话模式触发器,有效引导ChatGPT生成人类般文本。点击获取PDF资源:[ChatGPT提问艺术](https://zhangfeidezhu.com/?p=334)**
56 0
ChatGPT提问获取高质量答案的艺术PDF下载书籍推荐分享
|
30天前
|
Java Apache Maven
将word文档转换成pdf文件方法
在Java中,将Word文档转换为PDF文件可采用多种方法:1) 使用Apache POI和iText库,适合处理基本转换需求;2) Aspose.Words for Java,提供更高级的功能和性能;3) 利用LibreOffice命令行工具,适用于需要开源解决方案的场景。每种方法都有其适用范围,可根据具体需求选择。
|
30天前
|
Java Apache Maven
Java将word文档转换成pdf文件的方法?
【10月更文挑战第13天】Java将word文档转换成pdf文件的方法?
138 1