Effective Java 案例分享(九)
46、使用无副作用的Stream
本章节主要举例了Stream的几种用法。
案例一:
// Uses the streams API but not the paradigm--Don't do this!
Map<String, Long> freq = new HashMap<>();
try (Stream<String> words = new Scanner(file).tokens()) {words.forEach(word -> {freq.merge(word.toLowerCase(), 1L, Long::sum);});
}
案例一使用了forEach,代码看上去像是stream,但是并不是。为了操作HashMap不得不使用循环,导致代码更长。
// Proper use of streams to initialize a frequency table
Map<String, Long> freq;
try (Stream<String> words = new Scanner(file).tokens()) {freq = words.collect(groupingBy(String::toLowerCase, counting()));
}
代码二使用了Stream语法做了和代码一相同的事情,但是代码更短,语义更明确。ForEach应该只负责结果的输出,而不是用来做计算。
案例二:
// Pipeline to get a top-ten list of words from a frequency table
List<String> topTen = freq.keySet().stream().sorted(comparing(freq::get).reversed()).limit(10).collect(toList());
案例二给freq做排序,输出做多10个元素到List。**Collectors包含很多常用的静态方法,所以直接静态引用Collectors是非常明智的。**例如案例中的comparing方法。
案例三:
// Using a toMap collector to make a map from string to enum
private static final Map<String, Operation> stringToEnum = Stream.of(values()).collect(toMap(Object::toString, e -> e));
案例三将valus的值输出为以String为key, value为Operation的Map。
案例四:
// Collector to generate a map from key to chosen element for key
Map<Artist, Album> topHits
= albums.collect(toMap(Album::artist, a->a, maxBy(comparing(Album::sales))));
案例四将albums输出为,Album::artist为key,Album为value,并按照Album::sales排序。
案例五:
Map<String, Long> freq = words.collect(groupingBy(String::toLowerCase, counting()));
案例五将words的key变为小写,并出输出。
47、返回值优先使用Collection,而不是Stream
如果一个方法需要按顺序返回多个数据,推荐的返回值类型为Collection。常用的Collection有List,Map。大多数情况下都需要遍历数据,Stream虽然也可以,但是遍历不如Collection方便,并且Collection可以方便的转成Stream。但是不要为了返回Collection而保存大量的元素。
例如:Set有a,b,c三个元素,返回所有的元素组合。 结果:{a},{ab},{abc},{ac},{b},{bc},{c},{}。
结果的个数是当前元素数量的2的n次方,如果Set里面包含更多的子元素,把所有的结果保存下来返回Collection就会占用非常大的内容空间。
// Returns a stream of all the sublists of its input list
public class SubLists {public static <E> Stream<List<E>> of(List<E> list) {return Stream.concat(Stream.of(Collections.emptyList()),prefixes(list).flatMap(SubLists::suffixes));}private static <E> Stream<List<E>> prefixes(List<E> list) {return IntStream.rangeClosed(1, list.size()).mapToObj(end -> list.subList(0, end));}private static <E> Stream<List<E>> suffixes(List<E> list) {return IntStream.range(0, list.size()).mapToObj(start -> list.subList(start, list.size()));}
}
Stream的写法类似使用了for-loop:
for (int start = 0; start < src.size(); start++)for (int end = start + 1; end <= src.size(); end++)System.out.println(src.subList(start, end));
48、谨慎的使用Stream并发
Stream提供了parallel()函数用于多线程操作,目标是提高运行效率,但是实际上可能并不会这样。
// Stream-based program to generate the first 20 Mersenne primes
public static void main(String[] args) {primes().map(p -> TWO.pow(p.intValueExact()).subtract(ONE)).filter(mersenne -> mersenne.isProbablePrime(50)).limit(20).forEach(System.out::println);
}
static Stream<BigInteger> primes() {return Stream.iterate(TWO, BigInteger::nextProbablePrime);
}
上面的代码正常运行时间为12.5s,使用了parallel()函数后,代码的速度并没与提升,且cpu提高到90%一直未执行结束,作者在半小时后强制关闭了程序。
如果资源来自Stream.iterate或者limit这种有中间操作,让管道并行不太可能提升提升效率。所以不能随意的使用parallel()。**如果Stream的数据来自于ArrayList , HashMap , HashSet , ConcurrentHashMap instances,arrays,int ranges,long ranges,使用parallel会让运行效率更高。**让Stream并行除了导致运行效率降低,还有可能出现错误的结果以及不可以预料的情况,所以在使用paralle()一定要经过测试验证,保证自己编写的代码运行正确。
在合适的环境下,Stream在多核机器下使用paralle()会得到接近线性的加速,例如如下代码:
// Prime-counting stream pipeline - benefits from parallelization
static long pi(long n) {return LongStream.rangeClosed(2, n).mapToObj(BigInteger::valueOf).filter(i -> i.isProbablePrime(50)).count();
}// Prime-counting stream pipeline - parallel version
static long pi(long n) {return LongStream.rangeClosed(2, n)// 此处使用了并行.parallel().mapToObj(BigInteger::valueOf).filter(i -> i.isProbablePrime(50)).count();
}
在作者的机器上第一个代码运行时间耗时31s,使用parallel()之后耗时降到9.2s。
49、检查参数的合法性
在大多数的方法和构造函数中都需要传递必要的参数,对每一个参数的合法性验证是非常重要的。在public和protected方法中,需要在JavaDoc说明参数的含义和有效范围,如果参数不合法是否抛出异常:
/**
* Returns a BigInteger whose value is (this mod m). This method
* differs from the remainder method in that it always returns a
* non-negative BigInteger.
*
* @param m the modulus, which must be positive
* @return this mod m
* @throws ArithmeticException if m is less than or equal to 0
*/
public BigInteger mod(BigInteger m) {if (m.signum() <= 0)throw new ArithmeticException("Modulus <= 0: " + m);... // Do the computation
}
常见的NullPointerException,可以使用@Nullable注解标注参数不可为null,在Java 7中,提供了Objects.requireNonNull方法帮助检查对象是否为空,为空则会抛出NullPointerException。在Java 9,java.util.Objects还提供了检查索引越界的方法:checkFromIndexSize , checkFromToIndex , checkIndex。还可以使用assert:
// Private helper function for a recursive sort
private static void sort(long a[], int offset, int length) {assert a != null;assert offset >= 0 && offset <= a.length;assert length >= 0 && length <= a.length - offset;... // Do the computation
}
如果断言不成立,将会抛出AssertionError。
总之检查参数合法性是非常必要的,它可以防止运行非法的参数造成程序的错误,每一个程序员都应该养成良好的编码习惯。
50、做必要的防御性Copy
为了防止保存的变量被其他人破坏,需要做一些防御性的对象拷贝。例如以下代码:
// Broken "immutable" time period class
public final class Period {private final Date start;private final Date end;/*** @param start the beginning of the period* @param end the end of the period; must not precede start* @throws IllegalArgumentException if start is after end* @throws NullPointerException if start or end is null*/public Period(Date start, Date end) {if (start.compareTo(end) > 0)throw new IllegalArgumentException(start + " after " + end);this.start = start;this.end = end;}public Date start() {return start;}public Date end() {return end;}... // Remainder omitted
}
Period的构造函数保存了start和end,但是这种做法不安全的,因为外部可以改变start和end的变量,所以不能保证此类运算结果不变:
// Attack the internals of a Period instance
Date start = new Date();
Date end = new Date();
Period p = new Period(start, end);
end.setYear(78); // Modifies internals of p!
为了解决此问题,应该保存start和end的副本,而不是直接保存start和end:
// Repaired constructor - makes defensive copies of parameters
public Period(Date start, Date end) {this.start = new Date(start.getTime());this.end = new Date(end.getTime());if (this.start.compareTo(this.end) > 0)throw new IllegalArgumentException(this.start + " after " + this.end);
}
除了参数以外,返回值也需要考虑返回副本,尤其是List、Map、Array,要防止直接返回原始数据,导致外部增删改查影响了原始数据。
相关文章:
Effective Java 案例分享(九)
46、使用无副作用的Stream 本章节主要举例了Stream的几种用法。 案例一: // Uses the streams API but not the paradigm--Dont do this! Map<String, Long> freq new HashMap<>(); try (Stream<String> words new Scanner(file).tokens()) …...

SpringBoot复习:(56)使用@Transactional注解标记的方法的执行流程
首先,如果在某个类或某个方法被标记为Transactional时,Spring boot底层会在创建这个bean时生成代理对象(默认使用cglib) 示例: 当调用studentService的addStudent方法时,会直接跳到CglibAopProxy类去执行intercept方…...

JVM——引言+JVM内存结构
引言 什么是JVM 定义: Java VirtualMachine -java 程序的运行环境 (ava 二进制字节码的运行环境) 好处: 一次编写,到处运行自动内存管理,垃圾回收功能数组下标越界检查,多态 比较: jvm jre jdk 学习jvm的作用 面试理解底层实现原理中…...
open cv学习 (十)图形检测
图形检测 demo1 # 绘制几何图像的轮廓 import cv2img cv2.imread("./shape1.png")gray cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) # 将图像二值化 t, binary cv2.threshold(gray, 127, 255, cv2.THRESH_BINARY)# 检测图像中的所有轮廓 contours, hierarchy cv2.f…...

【C语言】字符函数和字符串函数
目录 1.求字符串长度strlen 2.长度不受限制的字符串函数 字符串拷贝strcpy 字符串追加strcat 字符串比较strcmp 3.长度受限制的字符串函数介绍strncpy strncat 编辑strncmp 4.字符串查找strstr 5.字符串分割strtok 6.错误信息报告 strerror perror 7.字符分类函…...

前馈神经网络正则化例子
直接看代码: import torch import numpy as np import random from IPython import display from matplotlib import pyplot as plt import torchvision import torchvision.transforms as transforms mnist_train torchvision.datasets.MNIST(root…...

spring的核心技术---bean的生命周期加案例分析详细易懂
目录 一.spring管理JavaBean的初始化过程(生命周期) Spring Bean的生命周期: 二.spring的JavaBean管理中单例模式及原型(多例)模式 2.1 . 默认为单例,但是可以配置多例 2.2.举例论证 2.2.1 默认单例 2.2…...

【Maven教程】(一)入门介绍篇:Maven基础概念与其他构建工具:理解构建过程与Maven的多重作用,以及与敏捷开发的关系 ~
Maven入门介绍篇 1️⃣ 基础概念1.1 构建1.2 maven对构建的支持1.3 Maven的其他作用 2️⃣ 其他构建工具2.1 IDE2.2 Make2.3 Ant2.4 Jenkins 3️⃣ Maven与敏捷开发🌾 总结 1️⃣ 基础概念 "Maven"可以翻译为 “知识的积累者” 或 “专家”。这个词源于波…...

今天,谷歌Chrome浏览器部署抗量子密码
谷歌已开始部署混合密钥封装机制(KEM),以保护在建立安全的 TLS 网络连接时共享对称加密机密。 8月10日,Chrome 浏览器安全技术项目经理Devon O’Brien解释说,从 8 月 15 日发布的 Chrome 浏览器 116 开始,谷…...

SUMO traci接口控制电动车前往充电站充电
首先需要创建带有停车位的充电站(停车场和充电站二合一),具体参考我的专栏中其他文章。如果在仿真的某个时刻,希望能够控制电动车前往指定的充电站充电,并且在完成充电后继续前往车辆原来的目的地,那么可以使用以下API:…...
现代CSS中的换行布局技术
在现代网页设计中,为了适应不同屏幕尺寸和设备类型,换行布局是一项重要的技术。通过合适的布局技术,我们可以实现内容的自适应和优雅的排版。本文将介绍CSS中几种常见的换行布局技术,探索它们的属性、代码示例和解析,帮…...
简单理解Python中的深拷贝与浅拷贝
I. 简介 深拷贝会递归的创建一个完全独立的对象副本,包括所有嵌套的对象,而浅拷贝只复制嵌套对象的引用,不复制嵌套对象本身。 简单来说就是两者都对原对象进行了复制,因此使用is运算符来比较新旧对象时,返回的都是F…...

C++之std::pair<uint64_t, size_t>应用实例(一百七十七)
简介: CSDN博客专家,专注Android/Linux系统,分享多mic语音方案、音视频、编解码等技术,与大家一起成长! 优质专栏:Audio工程师进阶系列【原创干货持续更新中……】🚀 人生格言: 人生…...

前端打开后端返回的HTML格式的数据
前端打开后端返回的 HTML格式 的数据: 后端返回的数据格式如下示例: 前端通过 js 方式处理(核心代码如下) console.log(回调, path); // path 是后端返回的 HTML 格式数据// 必须要存进localstorage,否则会报错&am…...
How to deal with document-oriented data
Schema designData models for e-commerceNuts and bolts of databases, collection, and documents. Principles of schema design What are your application access pattern?Whats the basic unit of data? the basic unit of data is the BSON documentWhat are the ca…...
Http 状态码汇总
文章目录 Http 状态码汇总1xx(信息性状态码)2xx(成功状态码)3xx(重定向状态码)4xx(客户端错误状态码)5xx(服务器错误状态码) Http 状态码汇总 1xx(…...
mysql自定义实体类框架
根据表结构自动生产实体类和方法,根据反射与io生成,可自定义扩展方法 package com.digital.web.front; /*** pom依赖* <dependency>* <groupId>mysql</groupId>* <artifactId>mysql-connector-java</artifactId>* <version>5.1.27</ve…...
批量将Excel中的第二列内容从拼音转换为汉字
要批量将Excel中的第二列内容从拼音转换为汉字,您可以使用Python的openpyxl库来实现。下面是一个示例代码,演示如何读取Excel文件并将第二列内容进行拼音转汉字: from openpyxl import load_workbook from xpinyin import Pinyin # 打开Exce…...
消息推送:精准推送,提升运营效果,增添平台活力
对于app开发者而言,没有什么途径比消息推送更能直接、即时地触及目标用户群体了。消息推送与我们的日常生活息息相关,各种APP的状态和通知都通过消息推送来告知用户,引起用户的注意,吸引用户点开app。总而言之,推送服务…...

[保研/考研机试] KY43 全排列 北京大学复试上机题 C++实现
题目链接: 全排列https://www.nowcoder.com/share/jump/437195121692001512368 描述 给定一个由不同的小写字母组成的字符串,输出这个字符串的所有全排列。 我们假设对于小写字母有a < b < ... < y < z,而且给定的字符串中的字…...
Qwen3-Embedding-0.6B深度解析:多语言语义检索的轻量级利器
第一章 引言:语义表示的新时代挑战与Qwen3的破局之路 1.1 文本嵌入的核心价值与技术演进 在人工智能领域,文本嵌入技术如同连接自然语言与机器理解的“神经突触”——它将人类语言转化为计算机可计算的语义向量,支撑着搜索引擎、推荐系统、…...

第一篇:Agent2Agent (A2A) 协议——协作式人工智能的黎明
AI 领域的快速发展正在催生一个新时代,智能代理(agents)不再是孤立的个体,而是能够像一个数字团队一样协作。然而,当前 AI 生态系统的碎片化阻碍了这一愿景的实现,导致了“AI 巴别塔问题”——不同代理之间…...
Spring AI 入门:Java 开发者的生成式 AI 实践之路
一、Spring AI 简介 在人工智能技术快速迭代的今天,Spring AI 作为 Spring 生态系统的新生力量,正在成为 Java 开发者拥抱生成式 AI 的最佳选择。该框架通过模块化设计实现了与主流 AI 服务(如 OpenAI、Anthropic)的无缝对接&…...

JUC笔记(上)-复习 涉及死锁 volatile synchronized CAS 原子操作
一、上下文切换 即使单核CPU也可以进行多线程执行代码,CPU会给每个线程分配CPU时间片来实现这个机制。时间片非常短,所以CPU会不断地切换线程执行,从而让我们感觉多个线程是同时执行的。时间片一般是十几毫秒(ms)。通过时间片分配算法执行。…...

tree 树组件大数据卡顿问题优化
问题背景 项目中有用到树组件用来做文件目录,但是由于这个树组件的节点越来越多,导致页面在滚动这个树组件的时候浏览器就很容易卡死。这种问题基本上都是因为dom节点太多,导致的浏览器卡顿,这里很明显就需要用到虚拟列表的技术&…...

AirSim/Cosys-AirSim 游戏开发(四)外部固定位置监控相机
这个博客介绍了如何通过 settings.json 文件添加一个无人机外的 固定位置监控相机,因为在使用过程中发现 Airsim 对外部监控相机的描述模糊,而 Cosys-Airsim 在官方文档中没有提供外部监控相机设置,最后在源码示例中找到了,所以感…...
NPOI操作EXCEL文件 ——CAD C# 二次开发
缺点:dll.版本容易加载错误。CAD加载插件时,没有加载所有类库。插件运行过程中用到某个类库,会从CAD的安装目录找,找不到就报错了。 【方案2】让CAD在加载过程中把类库加载到内存 【方案3】是发现缺少了哪个库,就用插件程序加载进…...

ArcGIS Pro+ArcGIS给你的地图加上北回归线!
今天来看ArcGIS Pro和ArcGIS中如何给制作的中国地图或者其他大范围地图加上北回归线。 我们将在ArcGIS Pro和ArcGIS中一同介绍。 1 ArcGIS Pro中设置北回归线 1、在ArcGIS Pro中初步设置好经纬格网等,设置经线、纬线都以10间隔显示。 2、需要插入背会归线…...

MeshGPT 笔记
[2311.15475] MeshGPT: Generating Triangle Meshes with Decoder-Only Transformers https://library.scholarcy.com/try 真正意义上的AI生成三维模型MESHGPT来袭!_哔哩哔哩_bilibili GitHub - lucidrains/meshgpt-pytorch: Implementation of MeshGPT, SOTA Me…...
Java多线程实现之Runnable接口深度解析
Java多线程实现之Runnable接口深度解析 一、Runnable接口概述1.1 接口定义1.2 与Thread类的关系1.3 使用Runnable接口的优势 二、Runnable接口的基本实现方式2.1 传统方式实现Runnable接口2.2 使用匿名内部类实现Runnable接口2.3 使用Lambda表达式实现Runnable接口 三、Runnabl…...