1218097468
2023/04/03阅读:30主题:默认主题
Java高效编程
Java高效编程技巧实践
第一章 引言
第二章 函数编程
实战案例:函数编程演变史
-
实战:根据实际场景演示,为了应对不断变化的业务需求,代码的多个改进版本
购物车案例:
同事在前段时间的618往购物车里加了一堆的东西,准备疯狂的消费一把。结果呢,资金有问题,所以他需要向女朋友申请费用,然后他的女朋友就要他对加入购物车里的商品进行不同维度的查找。比如:按价钱分,超过多少钱的不给买;按种类分,什么类型的不给买。
代码准备
-
商品类
/**
* 下单商品信息对象
* @author ShenTao
* @date 2022/9/25 21:40
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
@ToString
public class Sku {
//编号
private Integer skuId;
//商品名称
private String skuName;
//单价
private Double skuPrice;
//购买个数
private Integer totalNum;
//总价
private Double totalPrice;
//商品类型
private Enum skuCategory;
}
-
商品类别枚举
/**
* 商品类型枚举
*/
public enum SkuCategoryEnum {
CLOTHING(10, "服装类"),
ELECTRONICS(20, "数码类"),
SPORTS(30, "运动类"),
BOOKS(40, "图书类");
// 商品类型的编号
private Integer code;
// 商品类型的名称
private String name;
SkuCategoryEnum(Integer code, String name) {
this.code = code;
this.name = name;
}
}
-
购物车类
/**
* 购物车服务类
* @author ShenTao
* @date 2022/9/25 21:45
*/
public class CartService {
private static List<Sku> cartSkuList = new ArrayList<Sku>(){
{
add(new Sku(654032, "无人机", 4999.00, 1, 4999.00, SkuCategoryEnum.ELECTRONICS));
add(new Sku(642934, "VR一体机", 2299.00, 1, 2299.00, SkuCategoryEnum.ELECTRONICS));
add(new Sku(645321, "纯色衬衫", 409.00, 3, 1227.00, SkuCategoryEnum.CLOTHING));
add(new Sku(654327, "牛仔裤", 528.00, 1, 528.00, SkuCategoryEnum.CLOTHING));
add(new Sku(675489, "跑步机", 2699.00, 1, 2699.00, SkuCategoryEnum.SPORTS));
add(new Sku(644564, "Java编程思想", 79.80, 1, 79.80, SkuCategoryEnum.BOOKS));
add(new Sku(678678, "Java核心技术", 149.00, 1, 149.00, SkuCategoryEnum.BOOKS));
add(new Sku(697894, "算法", 78.20, 1, 78.20, SkuCategoryEnum.BOOKS));
add(new Sku(696968, "TensorFlow进阶指南", 85.10, 1, 85.10, SkuCategoryEnum.BOOKS));
}
};
}
代码1.0 硬编码业务逻辑 - 找到电子类产品
/**
* 购物车服务类
*
* @author ShenTao
* @date 2022/9/25 21:45
*/
public class CartService {
private static List<Sku> cartSkuList = new ArrayList<Sku>() {
{
add(new Sku(654032, "无人机", 4999.00, 1, 4999.00, SkuCategoryEnum.ELECTRONICS));
add(new Sku(642934, "VR一体机", 2299.00, 1, 2299.00, SkuCategoryEnum.ELECTRONICS));
add(new Sku(645321, "纯色衬衫", 409.00, 3, 1227.00, SkuCategoryEnum.CLOTHING));
add(new Sku(654327, "牛仔裤", 528.00, 1, 528.00, SkuCategoryEnum.CLOTHING));
add(new Sku(675489, "跑步机", 2699.00, 1, 2699.00, SkuCategoryEnum.SPORTS));
add(new Sku(644564, "Java编程思想", 79.80, 1, 79.80, SkuCategoryEnum.BOOKS));
add(new Sku(678678, "Java核心技术", 149.00, 1, 149.00, SkuCategoryEnum.BOOKS));
add(new Sku(697894, "算法", 78.20, 1, 78.20, SkuCategoryEnum.BOOKS));
add(new Sku(696968, "TensorFlow进阶指南", 85.10, 1, 85.10, SkuCategoryEnum.BOOKS));
}
};
/**
* 获取购物车列表
* @return 购物车列表信息
*/
public static List<Sku> getCartSkuList(){
return cartSkuList;
}
/**
* 找出购物车中所有电子产品
*
* @param cartSkuList 购物车列表
* @return 所有电子产品
*/
public static List<Sku> filterElectronicsSkus(List<Sku> cartSkuList) {
List<Sku> result = new ArrayList<>();
for (Sku sku : cartSkuList) {
if (SkuCategoryEnum.ELECTRONICS.equals(sku.getSkuCategory())) {
result.add(sku);
}
}
return result;
}
}
代码2.0 单一维度条件参数化
第一个需求就已经完成了,业务方在第一个需求的基础上,又提出了第二个需求:想看一下每类的商品都有哪些?
那么很明显,咱们第一个找出购物车中所有的电子产品是不满足要求的。那咱们在业务方提出的需求上进行迭代。再实现一个根据商品类型进行查找商品的方法。
/**
* 根据传入商品类型参数,找出购物车中同种商品类型的商品列表
*
* @param cartSkuList 购物车列表
* @param categoryEnum 商品类型
* @return 传入类型的商品列表
*/
public static List<Sku> filterSkusByCategory(List<Sku> cartSkuList, SkuCategoryEnum category) {
List<Sku> result = new ArrayList<>();
for (Sku sku : cartSkuList) {
if (category.equals(sku.getSkuCategory())) {
result.add(sku);
}
}
return result;
}
代码3.0 多维度条件参数化
第二个需求也完成了,但是业务方提出了另一个新需求:在兼容商品类别的基础上,想要看一下商品超过xxx多少钱的有什么?即支持商品类型或总价来过滤商品
/**
* 支持通过产品类型或总价过滤商品
*
* @param cartSkuList 购物车列表
* @param category 商品类型
* @param totalPrice 价格总价
* @param categoryOrPrice 商品类型或是总价 true:产品类型 false:产品总价
* @return 符合条件的产品列表
*/
public static List<Sku> filterSkus(List<Sku> cartSkuList, SkuCategoryEnum category, Double totalPrice, Boolean categoryOrPrice) {
List<Sku> result = new ArrayList<>();
for (Sku sku : cartSkuList) {
if ((categoryOrPrice && category.equals(sku.getSkuCategory())
|| (!categoryOrPrice && sku.getTotalPrice() > totalPrice))) {
result.add(sku);
}
}
return result;
}
代码4.0 判断逻辑参数化-实体类
-
Service
类
/**
* 根据不同的Sku判断标准,对Sku列表进行过滤
*
* @param cartSkuList 购物车信息列表
* @param predicate
* @return
*/
public static List<Sku> filterSkus(List<Sku> cartSkuList, SkuPredicate predicate) {
List<Sku> result = new ArrayList<>();
for (Sku sku : cartSkuList) {
//根据不同的Sku判断策略,对Sku进行判断
if (predicate.test(sku)) {
result.add(sku);
}
}
return result;
}
-
SkuPredicate
接口: 选择判断
/**
* Sku选择谓词接口
*/
public interface SkuPredicate {
/**
* 选择标准判断
*/
boolean test(Sku sku);
}
-
接口实现类:具体的判断标准
-
图书类判断标准
/**
* 对Sku的商品类型为图书类的判断标准
*/
public class SkuBooksCategoryPredicate implements SkuPredicate {
@Override
public boolean test(Sku sku) {
return SkuCategoryEnum.BOOKS.equals(sku.getSkuCategory());
}
}-
总价是否超出2000作为判断标准
/**
* 对Sku的总价是否超出2000作为判断标准
*/
public class SkuTotalPricePredicate implements SkuPredicate {
@Override
public boolean test(Sku sku) {
return sku.getTotalPrice() > 2000;
}
} -
-
使用
test
@Test
public void testFilterSkus() {
List<Sku> cartSkuList = CartService.getCartSkuList();
//根据商品类别
List<Sku> result = CartService.filterSkus(cartSkuList, new SkuBooksCategoryPredicate());
System.out.println(JSON.toJSONString(result, true));
//根据商品总价
result = CartService.filterSkus(cartSkuList, new SkuTotalPricePredicate());
System.out.println(JSON.toJSONString(result, true));
}
代码5.0 判断逻辑参数化-匿名类
从代码4.0中,我们想增加一个新的条件需要新建一个类,类中只实现了一个test
方法,怎么优化呢? -- 使用匿名类呀!
@Test
public void testFilterSkus5() {
List<Sku> cartSkuList = CartService.getCartSkuList();
//单价大于1000的商品
List<Sku> result = CartService.filterSkus(cartSkuList, new SkuPredicate() {
@Override
public boolean test(Sku sku) {
return sku.getSkuPrice() > 1000;
}
});
System.out.println(JSON.toJSONString(result, true));
}
代码6.0 Lambda表达式
//使用Lambda表达式
@Test
public void testFilterSkus6() {
List<Sku> cartSkuList = CartService.getCartSkuList();
//单价大于1000的商品
List<Sku> result = CartService.filterSkus(cartSkuList, (Sku sku) -> sku.getSkuPrice() > 1000);
System.out.println(JSON.toJSONString(result, true));
}
函数编程演化历程
函数编程演化历程:
-
将业务逻辑直接写死在代码里。 -
将单一维度的条件做为参数传入方法中。方法内根据参数进行业务逻辑实现。 -
将多个维度的条件做为参数传入方法中。业务实现需要根据不同的参数处理不同逻辑。 -
将业务逻辑封装为一个实体类,方法接受实体类为参数,方法内部调用实体类的处理逻辑。 -
调用方法时不在创建实体类,而是使用匿名函数的形式替代。 -
使用Lambda表达式替代匿名函数的形式,做为方法的参数。真正实现判断逻辑参数化传递。
Lambda表达式
-
Java8引入函数式编程风格
-
可以理解为一种匿名函数的替代,能够完全替代匿名内部类
-
通过行为参数化传递代码
表达式形式
-
形式
-
(parameters) -> expression
-
(parameters) -> { statement; }
-
-
特点
-
参数可选,可以为空; 一个参数可以不加括号;多个参数必须加括号。 -
大括号可选,主体一个表达式,可以不加大括号。 -
返回关键字可选,主体只有一个表达式,会自动返回;有多个语句,在大括号中指明返回值。
-
-
例子
-
形式一:没有参数
() -> System.out.println("hello world")
-
形式二:只有一个参数
name -> System.out.println("hello world" + name + "!");
-
形式三:没有参数,多个逻辑
() -> {
System.out.println("hello");
System.out.println("world");
}-
形式四:包含两个参数的方法
BinaryOperator<Long> functionAdd = (x, y) -> x + y;
Long result = functionAdd.apply(1L, 2L);-
形式五:对参数显示声明
BinaryOperator<Long> functionAdd = (Long x, Long y) -> x + y;
Long result = functionAdd.apply(1L, 2L); -
函数式接口
-
接口中只有一个抽象方法
-
Java8函数式接口注解:@FucntionInterface
用于校验方法是否符合函数式接口的定义
-
函数式接口的抽象方法签名:函数描述符,作为函数式接口的校验标准
自定义函数接口示例
常用函数接口
上述例子中,函数式接口还是比较具象的(需要新建一个接口),都到这一步了,还有没有更泛化的方法呢?-- 当然有啦,那就是Java提供给我们一些常见的函数接口:

方法引用
在代码中,可能会看到如下写法
Optional.ofNullable(cartSkuList)
.map(List::stream)
.orElseGet(Stream::empty)
.sorted(Comparator.comparing(Sku::getTotalPrice))
.forEach(result::add);
是不是很晕?这里的::
是什么意思呀? 这一串一串的是什么东西啊?
方法引用定义
调用特定方法的Lambda表达式的一种快捷写法,可以让你重复使用现有的方法定义,并像Lambda表达式一样传递他们。

方法引用方式及举例
方法引用主要分为三种方式,通过例子来体会方法引用。
-
指向静态方法的方法引用
(args) -> ClassName.staticMethod(args);
==> ClassName::staticMethod;
举例:
@Test
public void test1() {
Consumer<String> consumer1 = (String number) -> Integer.parseInt(number);
Consumer<String> consumer2 = Integer::parseInt;
}
-
指向任意类型实例方法的方法引用
(args) -> args.instanceMethod();
==> ClassName::instanceMethod()
举例:
@Test
public void test02() {
Consumer<String> consumer1 =(String str) -> str.length();
Consumer<String> consumer2 = String::length;
}
-
指向现有对象的实例方法的方法引用
(args) -> Object.instanceMethod();
==> object::instanceMethod;
@Test
public void test03() {
StringBuilder sb = new StringBuilder();
Consumer<String> consumer1 = (String str) -> sb.append(str);
Consumer<String> consumer2 = sb::append;
}
-
构造方法的方法引用
Optional.ofNullable(skuList).orElseGet(ArrayList::new);
方法引用具体示例
public class MethodReferenceTest {
static class Sku {
private String skuName;
private Integer skuPrice;
public Integer getSkuPrice() {
return this.skuPrice;
}
//静态方法
public static int staticComparePrice(Sku sku1, Sku sku2) {
return sku1.getSkuPrice() - sku2.getSkuPrice();
}
//非静态方法
public int instanceComparePrice(Sku sku) {
return this.getSkuPrice() - sku.getSkuPrice();
}
}
class PriceComparator {
public int instanceComparePrice(Sku sku1, Sku sku2) {
return sku1.getSkuPrice() - sku2.getSkuPrice();
}
}
@Test
public void test() {
List<Sku> skuList = new ArrayList<>();
//lambda表达式
skuList.sort((sku1, sku2) -> sku1.getSkuPrice() - sku2.getSkuPrice());
//使用方法引用替换
//类名::静态方法名
skuList.sort(Sku::staticComparePrice);
//展开写法
skuList.sort((Sku sku1, Sku sku2) -> {
return Sku.staticComparePrice(sku1, sku2);
});
//类名::实例方法名
skuList.sort(Sku::instanceComparePrice);
//展开写法
skuList.sort((Sku object, Sku sku) -> {
return object.instanceComparePrice(sku);
});
//对象::实例方法名
PriceComparator priceComparator = new PriceComparator();
skuList.sort(priceComparator::instanceComparePrice);
//展开写法
skuList.sort((Sku sku1, Sku sku2) -> {
return priceComparator.instanceComparePrice(sku1, sku2);
});
//构造方法引用
Optional.ofNullable(skuList)
.orElseGet(ArrayList::new);
}
}
Tips:常用List集合初始化方式:
1. 先创建List再赋值
标准方式,先创建集合对象,然后逐个调用add
方法初始化。用起来比较繁琐,不太方便!
List<Integer> list = new ArrayList<>();
list.add(1);
list.add(2);
list.add(3);
2. 使用{{}}
双大括号初始化
使用匿名内部类完成初始化。外层的{}
定义了一个ArrayList的匿名内部类,内层的{}
定义了一个实例初始化代码块。有内存泄漏的风险!不建议在生产项目中使用!
List<Integer> list3 = new ArrayList<Integer>(){
{
add(1);
add(2);
add(3);
}
};
3. 使用Arrays.asList
使用Arrays的静态方法asList
初始化。返回的list集合是不可变的!
import java.util.Arrays;
List<Integer> list = Arrays.asList(1, 2, 3);
4. 使用Stream
(JDK8以上)
使用JDK8引入的Stream的of
方法生成一个stream对象,调用collect
方法进行收集,形成一个List集合。
import java.util.stream.Stream;
List<Integer> list = Stream.of(1, 2, 3).collect(Collectors.toList());
5. 使用Google Guava工具集Lists
借助Google Guava工具集中的Lists
工具类初始化。需要引入Guava包才能用!
import com.google.common.collect.Lists;
List<Integer> list = Lists.newArrayList(1, 2, 3);
6. 使用List
(JDK9以上)
使用JDK9引入的List.of
方法完成初始化。
import com.sun.tools.javac.util.List;
List<Integer> list = List.of(1, 2, 3);
第三章 流式编程
先感受一下流式编程的魅力吧
接着上述的例子,一个新的需求如下,我们来对比一下
想要看看购物车有什么商品 图书类商品都给买 其余的商品中买两件最贵的 只需要两件商品的名称和总价
-
原始集合操作
public void oldCartHandle() {
//1. 打印所有商品
List<Sku> cartSkuList = CartService.getCartSkuList();
for (Sku sku : cartSkuList) {
System.out.println(JSON.toJSONString(sku, true));
}
//2. 图书类过滤掉
List<Sku> noteBookSkuList = new ArrayList<>();
for (Sku sku : cartSkuList) {
if (!sku.getSkuCategory().equals(SkuCategoryEnum.BOOKS)) {
noteBookSkuList.add(sku);
}
}
//3. 排序
noteBookSkuList.sort(new Comparator<Sku>() {
@Override
public int compare(Sku o1, Sku o2) {
return o2.getSkuPrice().compareTo(o1.getSkuPrice());
}
});
//4.TOP2
List<Sku> top2SkuList = new ArrayList<>();
for (int i = 0; i < 2; i++) {
top2SkuList.add(noteBookSkuList.get(i));
}
//5. 求两件商品的总价
Double money = 0.0;
for (Sku sku : top2SkuList) {
money += sku.getTotalPrice();
}
//6. 获取两件商品的名称
List<String> resultSkuNameList = new ArrayList<>();
for (Sku sku : top2SkuList) {
resultSkuNameList.add(sku.getSkuName());
}
System.out.println(JSON.toJSONString(resultSkuNameList, true));
System.out.println("商品总价:" + money);
}
-
流式编程
/**
* 以stream流方式实现需求
*/
public void newCartHandle() {
AtomicReference<Double> money = new AtomicReference<>(0.0);
//以流的方式
//1. 打印商品信息 -> 过滤图书商品 -> 排序 -> 总价top的商品 -> 计算商品的总价值 -> 获取商品名称 —> 输出结果
List<String> resultSkuNameList = CartService.getCartSkuList()
.stream()
.peek(sku -> {
System.out.println(JSON.toJSONString(sku, true));
})
.filter(sku -> !SkuCategoryEnum.BOOKS.equals(sku.getSkuCategory()))
.sorted(Comparator.comparing(Sku::getTotalPrice).reversed())
.limit(2)
.peek(sku -> money.set(money.get() + sku.getTotalPrice()))
.map(Sku::getSkuName)
.collect(Collectors.toList());
System.out.println(JSON.toJSONString(resultSkuNameList, true));
System.out.println("商品总价:" + money);
}
怎么样?流式编程方式是不是很简洁 很优雅? 但是小伙伴可能不熟悉相关函数,是不是很抓狂? 没关系,接下来我们来介绍其详细使用!
流是什么
从支持数据处理操作的源生成的元素序列 -- Java8实战
流和集合的区别
-
空间和时间:集合需要获取所有数据,流只需要当前数据 -
遍历次数:集合能够遍历多次,流只能遍历一次 -
外部迭代和内部迭代:集合需要外部迭代,流式内部迭代
流的组成
流由三部分组成:数据源 + 中间操作 + 终端操作

流操作分类
-
有状态/无状态:需要所有数据的操作是有状态操作, 无需所有数据的操作是无状态的 -
短路/无短路:找到对应的数据即停的操作是短路操作,所有数据都需要执行一遍的操作是非短路操作

流操作分类,具体版:

流的使用
中间操作
-
无状态操作
-
fliter
(过滤)的使用,参数为Predicate函数式接口。 过滤掉不符合断言判断的数据
//过滤只剩下BOOKS类别的商品
@Test
public void filterTest() {
SkuList.stream()
.filter(sku -> SkuCategoryEnum.BOOKS.equals(sku.getSkuCategory()))
.forEach(item -> System.out.println(JSON.toJSONString(item, true)));
}-
map
(映射)的使用,参数为Function函数式接口。 使用给定函数转换流的元素,返回一个流。
//将Sku对象转换成skuName,返回商品名
@Test
public void mapTest() {
SkuList.stream()
.map(Sku::getSkuName)
.forEach(item -> System.out.println(JSON.toJSONString(item, true)));
}-
flatMap
(扁平化)的使用:参数为Function接口。接收一个元素返回新的流,可为多个流。
//本例将商品名称切分并返回一个字符流
@Test
public void flatMapTest() {
SkuList.stream()
.flatMap(sku -> Arrays.stream(sku.getSkuName().split("")))
.forEach(item -> System.out.println(JSON.toJSONString(item, true)));
}-
peek
(遍历)的使用:参数为Consumer接口,功能等同于forEach
//本例打印sku名称(注意:peek和forEach是交替执行的,这也是无状态操作下流的特点)
@Test
public void peekTest() {
SkuList.stream()
.peek(sku -> System.out.printf(sku.getSkuName()))
.forEach(item -> System.out.println(JSON.toJSONString(item, true)));
}
//加上有状态的操作 结果对比,发现先执行完中间操作后才执行终端操作
@Test
public void peekTest2() {
SkuList.stream()
.peek(sku -> System.out.printf(sku.getSkuName()))
.sorted(Comparator.comparing(Sku::getTotalPrice))
.forEach(item -> System.out.println(JSON.toJSONString(item, true)));
} -
-
有状态操作
-
distinct
(去重)的使用:获取去重后的流
//获得去重后的商品类别
@Test
public void distinctTest() {
SkuList.stream()
.map(Sku::getSkuCategory)
.distinct()
.forEach(item -> System.out.println(JSON.toJSONString(item, true)));
}-
skip
(跳过)的使用:跳过前几条数据
//过滤掉总价最低的3件商品
@Test
public void skipTest() {
SkuList.stream()
.sorted(Comparator.comparing(Sku::getTotalPrice))
.skip(3)
.forEach(item -> System.out.println(JSON.toJSONString(item, true)));
}-
limit
(截断)的使用:取前几条数据
//取总价最低的三件商品
@Test
public void skipTest() {
SkuList.stream()
.sorted(Comparator.comparing(Sku::getTotalPrice))
.skip(3)
.forEach(item -> System.out.println(JSON.toJSONString(item, true)));
}-
sorted
(排序)的使用:参数为Comparator接口。 将流中数据排序。
//按照总价将商品排序
@Test
public void sortTest() {
SkuList.stream()
.sorted(Comparator.comparing(Sku::getTotalPrice))
.forEach(item -> System.out.println(JSON.toJSONString(item, true)));
} -
终端操作
-
短路操作
-
allMatch
(所有匹配):所有流数据满足条件,返回true
@Test
public void allMatchTest() {
boolean match = SkuList.stream()
.peek(sku -> System.out.println(sku.getSkuName()))
.allMatch(sku -> sku.getTotalPrice() > 100);
System.out.println(match);
}-
anyMatch
(任意匹配):任意流数据满足条件,返回true
@Test
public void anyMatchTest() {
boolean match = SkuList.stream()
.peek(sku -> System.out.println(sku.getSkuName()))
.anyMatch(sku -> sku.getTotalPrice() < 100);
System.out.println(match);
}-
noneMatch
(不匹配):所有流数据都不满足条件,返回true
@Test
public void noneMatchTest() {
boolean match = SkuList.stream()
.peek(sku -> System.out.println(sku.getSkuName()))
.noneMatch(sku -> sku.getTotalPrice() > 100);
System.out.println(match);
}-
findFirst
(查找首个):返回满足条件的第一个数据
@Test
public void findFirstTest() {
Optional<Sku> opt = SkuList.stream().findFirst();
System.out.println(JSON.toJSONString(opt, true));
}-
finAny
(查找任意一个):返回满足条件的数据,不一定是第一个(多线程下)
@Test
public void findAnyTest() {
Optional<Sku> opt = SkuList.stream().findAny();
System.out.println(JSON.toJSONString(opt, true));
} -
-
非短路操作
-
forEach
(遍历):遍历流中的数据
@Test
public void filterTest() {
SkuList.stream()
.forEach(item -> System.out.println(JSON.toJSONString(item, true)));
}-
max
(最大值):获取流中的最大值元素
public void maxTest() {
OptionalDouble max = SkuList.stream()
.mapToDouble(Sku::getTotalPrice)
.max();
System.out.println(max.getAsDouble());
}-
min
(最小值):获取流中的最小值元素
@Test
public void minTest() {
OptionalDouble min = SkuList.stream()
.mapToDouble(Sku::getTotalPrice)
.min();
System.out.println(min.getAsDouble());
}-
count
(总数):获取流的总数
@Test
public void countTest() {
long count = SkuList.stream()
.count();
System.out.println(count);
}-
collect
(汇总):将流中的元素累积成一个结果-
collect :终端操作
Collector : 接口
Collectors:工具类
-
将流中的数据聚合为list返回
//将流中总价大于100的流作为list返回
@Test
public void toList() {
List<Sku> list = CartService.getCartSkuList();
List<Sku> collect = list.stream()
.filter(sku -> sku.getTotalPrice() > 100)
.collect(Collectors.toList());
System.out.println(JSON.toJSONString(collect, true));
}-
分组,返回 Map<Object, List >形式
//将流中的数据以类别分组
@Test
public void group() {
List<Sku> list = CartService.getCartSkuList();
Map<Object, List<Sku>> map = list.stream()
.collect(Collectors.groupingBy(Sku::getSkuCategory));
System.out.println(JSON.toJSONString(map, true));
}-
分区,返回两种值一种为true, 一种为false
//将流中的数据以总价是否大于100分区
@Test
public void partition() {
List<Sku> list = CartService.getCartSkuList();
Map<Boolean, List<Sku>> map = list.stream()
.collect(Collectors.partitioningBy(sku -> sku.getTotalPrice() > 100));
System.out.println(JSON.toJSONString(map, true));
} -
-
reduce(归约) : 将Stream流中元素转换成一个值
归约操作接口定义
identity -- 初始值
accumulator -- 计算逻辑
combiner -- 并行执行时多个结果的合并方式
<U> U reduce(U identity,
BiFunction<U, ? super T, U> accumulator,
BinaryOperator<U> combiner);//计算订单信息的平均价格
@Test
public void reduceTest() {
@Data
@AllArgsConstructor
class Order {
private Integer id;
private Integer productCount;
private Double totalAmount;
}
//初始化数据
List<Order> list = Lists.newArrayList();
list.add(new Order(1, 2, 25.12));
list.add(new Order(2, 5, 527.23));
list.add(new Order(3, 3, 23332.12));
//使用reduce 计算平均价格
Order order = list.stream()
.parallel() //参数3的执行前提是执行该条语句,即并行操作
.reduce(
//初始值
new Order(0, 0, 0.0),
//第二个参数:Stream中两个元素的计算逻辑
(Order o1, Order o2) -> {
System.out.println("执行 计算逻辑 部分");
int productCount = o1.getProductCount() + o2.getProductCount();
double totalAmount = o1.getTotalAmount() + o2.getTotalAmount();
return new Order(0, productCount, totalAmount);
},
//并行情况下,多个并行结果如何合并
//如果是并行计算,那多个并行计算的结果是如何汇总的呢?
//或者说,第三个参数是:汇总并行计算的逻辑
(Order o1, Order o2) -> {
System.out.println("执行 合并逻辑");
int productCount = o1.getProductCount() + o2.getProductCount();
double totalAmount = o1.getTotalAmount() + o2.getTotalAmount();
return new Order(0, productCount, totalAmount);
});
System.out.println(JSON.toJSONString(order, true));
}-
collect(汇总)接口:将集合元素汇总成一个值
//根据account字段将count和amount相加,返回Map<String, Order>
@Test
public void collectTest() {
@Data
@AllArgsConstructor
class Order {
private Integer id;
private String account;
private Integer productCount;
private Double totalAmount;
}
//初始化数据
List<Order> list = Lists.newArrayList();
list.add(new Order(1, "zhangSan", 2, 25.12));
list.add(new Order(2, "zhangSan", 5, 527.23));
list.add(new Order(3, "liSi", 3, 23332.12));
HashMap<String, Order> collect = list.stream()
.collect(
() -> new HashMap<String, Order>(),
(HashMap<String, Order> map, Order newOrder) -> {
String account = newOrder.getAccount();
if (map.containsKey(account)) {
Order order = map.get(account);
order.setProductCount(
newOrder.getProductCount() + order.getProductCount());
order.setTotalAmount(
newOrder.getTotalAmount() + order.getTotalAmount());
} else {
map.put(account, newOrder);
}
},
(HashMap<String, Order> map1, HashMap<String, Order> map2) -> {
map2.forEach((key, value) -> {
map1.merge(key, value, (order1, order2) -> {
return new Order(0,
key,
order1.getProductCount() + order2.getProductCount(),
order1.getTotalAmount() + order2.getTotalAmount()
);
});
});
}
);
System.out.println(JSON.toJSONString(collect, true));
} -
流的创建
-
由值创建流
@Test
public void streamFromValue() {
Stream<Integer> stream = Stream.of(1, 2, 3, 4, 5);
stream.forEach(System.out::println);
}
-
由数组创建流
@Test
public void streamFromArray() {
int[] numbers = {1, 2, 3, 4, 5};
IntStream stream = Arrays.stream(numbers);
stream.forEach(System.out::println);
}
-
由文件生成流
@Test
public void streamFromFile() throws IOException {
Stream<String> stream = Files.lines(Paths.get("src/test/java/com/st/stream/StreamConstructor.java"));
stream.forEach(System.out::println);
}
-
由函数生成流(无限流)
//生成偶数流
@Test
public void streamFromFunction() {
Stream<Integer> stream = Stream.iterate(0, n -> n + 2);
stream.forEach(System.out::println);
}
//生成随机流
@Test
public void streamFromFunction2() {
Stream<Double> stream = Stream.generate(Math::random);
stream.forEach(System.out::println);
}
第四章 资源关闭
垃圾回收(GC)的特点
-
垃圾回收机制只负责回收堆内存资源,不会回收任何物理资源 -
程序无法精准的控制垃圾回收动作发生的具体发生时间 -
在垃圾回收之前,最会先调用它的finalize方法
常见需要手动释放的物理资源
-
文件、流资源 -
套接字资源 -
数据库连接资源
传统方式关闭资源
/**
* 1.创建输入 输出流
* 2.执行文件拷贝
* 3.关闭文件流资源
*/
@Test
public void copyFile() {
String originalUrl = "src/test/java/com/st/resource/FileCopyTest.java";
String targetUrl = "src/test/java/com/st/resource/target.java";
//声明文件输入流 文件输出流
FileInputStream originalFileInputStream = null;
FileOutputStream targetFileOutputStream = null;
//读取的字节信息
int content;
try {
FileInputStream fileInputStream = new FileInputStream(originalUrl);
FileOutputStream fileOutputStream = new FileOutputStream(targetUrl);
//迭代,读取、写入字节
while ((content = originalFileInputStream.read()) != -1) {
targetFileOutputStream.write(content);
}
} catch (IOException e) {
throw new RuntimeException(e);
} finally {
if (targetFileOutputStream != null) {
try {
targetFileOutputStream.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
if (originalFileInputStream != null) {
try {
originalFileInputStream.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
}
TWR方式关闭流资源
@Test
public void copyFile() {
String originalUrl = "src/test/java/com/st/resource/FileCopyTest.java";
String targetUrl = "src/test/java/com/st/resource/target.java";
try (
FileInputStream inputStream = new FileInputStream(originalUrl);
FileOutputStream outputStream = new FileOutputStream(targetUrl);
) {
int content = 0;
//迭代,读取、写入字节
while ((content = inputStream.read()) != -1) {
outputStream.write(content);
}
} catch (IOException e) {
throw new RuntimeException(e);
}
}
第五章 Guava
Optional的使用
Optional三种创建方式
-
Optional.empty()
: 创建空的Optional对象 -
Optional.of("zhangSan")
: 使用非null值创建Optional对象 -
Optional.ofNullable("abc")
: 使用任意值创建Optional对象
Optional 使用
-
optional.isPresent()
:判断引用缺失的方法(不建议使用) -
optional.ifPresent()
:如果引用非空,则执行 类似方法还有:map filter flatMap -
optional.orElse("引用缺失")
:当optional引用缺失时执行 -
optional.orElseThrow(() -> new RuntimeException("返回一个异常!"))
: 如果引用缺失,则抛出一个异常
不可变集合
创建对象的不可变拷贝是一项很好到防御性编程技巧,Guava为所有的JDK标准集合类型和Guava新集合类型都提供了简单易用的不可变版本。
不可变对象的优点
-
当对象被不可信的库调用时,不可变形式是安全的 -
不可变对象被多个线程调用时,不存在竞态条件问题 -
不可变集合不需要考虑变化,因此可以省略时间和空间 -
不可变对象因为是固定不变,可以作为常量来安全使用
不可变集合创建方式
-
copyOf
方法:ImmutableSet.copyOf(set)
-
of
方法:ImmutableSet.of("a", "b", "c")
-
Builder
工具:ImmutableSet.builder().build()
第六章 线程池
什么是线程池
线程池顾名思义就是事先创建若干个可执行的线程放入一个池(容器)中,需要的时候从池中获取线程,不用自行创建,使用完毕不需要销毁线程而是放回池中,从而减少创建和销毁线程对象的开销。
线程池好处
-
降低资源消耗 -
响应速度 -
提高线程的可管理性
简单线程池设计 - 你能独立设计一个简单的线程池吗?

线程池组成部分:队列 + 线程池子 + 执行器
线程池执行流程:提交任务放入队列中,执行器从队列取任务并从池子中取线程执行该任务,并将执行结果异步返回。
线程池的使用
线程池的核心参数
/**
corePoolSize:池中的线程数,即使其处于IDLE状态
maximumPoolSize:池中允许的最大线程数
keepAliveTime:当线程数大于核心时,空闲线程在终止之前等待新任务的最长时间
unit:keepAliveTime的时间单位
workQueue:队列,用于在执行task之前保存task
handler:当达到了线程边界和队列容量,无法及时处理时,reject task使用的处理程序
*/
public ThreadPoolExecutor(
int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
RejectedExecutionHandler handler) {}
线程池处理流程

线程池可选择的阻塞队列
-
无界队列:队列大小无限制,常用的为无界的LinkedBlockingQueue,使用该队列作为阻塞队列时要尤其当心,当任务耗时较长时可能会导致大量新任务在队列中堆积最终导致OOM。 当然这种队列,maximumPoolSize 的值也就无效了。 -
有界队列:当使用有限的 maximumPoolSizes 时,有界队列有助于防止资源耗尽,但是可能较难调整和控制。常用的有两类,一类是遵循FIFO原则的队列如ArrayBlockingQueue,另一类是优先级队列如PriorityBlockingQueue。使用有界队列时队列大小需和线程池大小互相配合,线程池较小有界队列较大时可减少内存消耗,降低cpu使用率和上下文切换,但是可能会限制系统吞吐量。 -
同步移交队列,如果不希望任务在队列中等待而是希望将任务直接移交给工作线程,可使用SynchronousQueue作为等待队列,该队列不是一个真正的队列,而是一种线程之间移交的机制。要将一个元素放入SynchronousQueue中,必须有另一个线程正在等待接收这个元素。当然只有在使用无界线程池或者有饱和策略时才建议使用该队列。
线程池可供选择的饱和策略
-
AbortPolicy
: 终止策略(默认) -
DiscardPolicy
: 抛弃策略(默认) -
DiscardOldestPolicy
: 抛弃旧任务策略(默认) -
CallerRunsPolicy
: 调用者运行策略(默认)
线程池的执行示意图

线程池的提交任务
-
返回
Future
-
返回
Runnable
线程池的状态
线程池的状态转移有两条路径:
-
当调用 shutdown() 方法时,线程池的状态会从 RUNNING 到 SHUTDOWN,再到 TIDYING,最后到 TERMENATED 销毁状态。 -
当调用 shutdownNow() 方法时,线程池的状态会从 RUNNING 到 STOP,再到 TIDYING,最后到 TERMENATED 销毁状态。

第七章 Lombok
Lombok介绍
Lombok实现原理
-
注解处理器 -
JSR269插入式注解处理器

常用注解

第八章 验证框架
分层验证与JavaBean验证
-
分层验证模型

-
JavaBean验证模型

什么是JCP JSR


BeanValidation与HibernateValidation

SpringValidation对HibernateValidation进行了二次封装,提供了更高效的验证方式。
常用约束注解
空值校验类
-
@Null
-
@NotNull
-
@NotEmpty
-
@NotBlank
范围校验类
-
@Min
-
@Size
-
@Digits
-
@Future
-
@Negative
其他校验类
-
@Email
-
@URL
-
@AssertTrue
-
@Pattern
作者介绍