四、实际应用
1. 数据统计分析
示例1:商品订单数据统计分析
package com.itheima.day4.analysis;import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.time.YearMonth;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.stream.Stream;import static java.util.stream.Collectors.*;public class AnalysisTest {/*数据格式0 1 2 3 4 5 6 7 8 9 10 11序号 下单时间 订单编号 商品编号 类别编号 类别码 品牌 价格 用户编号 年龄 性别 地区*/static final int INDEX = 0;static final int TIME = 1;static final int ORDER_ID = 2;static final int PRODUCT_ID = 3;static final int CATEGORY_ID = 4;static final int CATEGORY_CODE = 5;static final int BRAND = 6;static final int PRICE = 7;static final int USER_ID = 8;static final int USER_AGE = 9;static final int USER_SEX = 10;static final int USER_REGION = 11;static final DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss z");public static void main(String[] args) {case8();}// 不同年龄段女性所下不同类别订单private static void case8() {try (Stream<String> lines = Files.lines(Path.of("./data.txt"))) {Map<String, Map<String, Long>> map = lines.skip(1).map(line -> line.split(",")).filter(array -> array[USER_SEX].equals("女")).filter(array -> !array[CATEGORY_CODE].isEmpty()).collect(groupingBy(AnalysisTest::ageRange,groupingBy(AnalysisTest::firstCategory, TreeMap::new, counting())));for (Map.Entry<String, Map<String, Long>> e1 : map.entrySet()) {for (Map.Entry<String, Long> e2 : e1.getValue().entrySet()) {System.out.printf("%-12s%-15s%d%n", e1.getKey(), e2.getKey(), e2.getValue());}}} catch (IOException e) {throw new RuntimeException(e);}}static String ageRange(String[] array) {int age = Double.valueOf(array[USER_AGE]).intValue();if (age < 18) {return "[0,18)";} else if (age < 30) {return "[18,30)";} else if (age < 50) {return "[30,50)";} else {return "[50,∞)";}}static String priceRange(Double price) {if (price < 100) {return "[0,100)";} else if (price >= 100 && price < 500) {return "[100,500)";} else if (price >= 500 && price < 1000) {return "[500,1000)";} else {return "[1000,∞)";}}// 7) 按价格区间统计销量private static void case7() {try (Stream<String> lines = Files.lines(Path.of("./data.txt"))) {Map<String, Long> collect = lines.skip(1).map(line -> line.split(",")).map(array -> Double.valueOf(array[PRICE])).collect(groupingBy(AnalysisTest::priceRange, counting()));for (Map.Entry<String, Long> e : collect.entrySet()) {System.out.println(e);}} catch (IOException e) {throw new RuntimeException(e);}}// 6.1) 按类别统计销量private static void case61() {try (Stream<String> lines = Files.lines(Path.of("./data.txt"))) {Map<String, Long> collect = lines.skip(1).map(line -> line.split(",")).filter(array -> !array[CATEGORY_CODE].isEmpty()).collect(groupingBy(array -> array[CATEGORY_CODE], TreeMap::new, counting()));for (Map.Entry<String, Long> e : collect.entrySet()) {System.out.println(e);}} catch (IOException e) {throw new RuntimeException(e);}}static String firstCategory(String[] array) {String c = array[CATEGORY_CODE];int idx = c.indexOf(".");return c.substring(0, idx);}// 6.2) 按一级类别统计销量 (放入TreeMap排序)private static void case62() {try (Stream<String> lines = Files.lines(Path.of("./data.txt"))) {Map<String, Long> collect = lines.skip(1).map(line -> line.split(",")).filter(array -> !array[CATEGORY_CODE].isEmpty()).collect(groupingBy(AnalysisTest::firstCategory, TreeMap::new, counting()));for (Map.Entry<String, Long> e : collect.entrySet()) {System.out.println(e);}} catch (IOException e) {throw new RuntimeException(e);}}// 5.1) 每个地区下单最多的用户private static void case51() {try (Stream<String> lines = Files.lines(Path.of("./data.txt"))) {Map<String, Map<String, Long>> collect = lines.skip(1).map(line -> line.split(",")).collect(groupingBy(array -> array[USER_REGION], groupingBy(array -> array[USER_ID], counting())));collect.entrySet().stream().map(e -> Map.entry(e.getKey(),e.getValue().entrySet().stream().max(Map.Entry.comparingByValue()))).forEach(System.out::println);/*for (Map.Entry<String, Map<String, Long>> e1 : collect.entrySet()) {System.out.println(e1.getKey());System.out.println("------------------");int i = 0;for (Map.Entry<String, Long> e2 : e1.getValue().entrySet()) {if (i >= 5) {break;}System.out.println(e2);i++;}}*//*上海 ---> 上海1 5 ---> 2 102 103 3...广东 ---> 广东...*/} catch (IOException e) {throw new RuntimeException(e);}}// 5.2) 每个地区下单最多的前三用户private static void case52() {try (Stream<String> lines = Files.lines(Path.of("./data.txt"))) {Map<String, Map<String, Long>> collect = lines.skip(1).map(line -> line.split(",")).collect(groupingBy(array -> array[USER_REGION], groupingBy(array -> array[USER_ID], counting())));collect.entrySet().stream().map(e -> Map.entry(e.getKey(),
// e.getValue().entrySet().stream()
// .sorted(Map.Entry.<String, Long>comparingByValue().reversed())
// .limit(3)
// .collect(toList())e.getValue().entrySet().stream().collect(() -> new MyQueue<Map.Entry<String, Long>>(Map.Entry.comparingByValue(), 3),MyQueue::offer,MyQueue::addAll))).forEach(System.out::println);} catch (IOException e) {throw new RuntimeException(e);}}// 4) 下单最多的前十名用户private static void case41() {try (Stream<String> lines = Files.lines(Path.of("./data.txt"))) {// 先根据用户ID进行分组Map<String, Long> collect = lines.skip(1).map(line -> line.split(",")).collect(groupingBy(array -> array[USER_ID], counting()));// 再根据值进行降序排序,取前10名collect.entrySet().stream().sorted(Map.Entry.<String, Long>comparingByValue().reversed()).limit(10).forEach(System.out::println);} catch (IOException e) {throw new RuntimeException(e);}}/*** 小根堆* @param <E>*/static class MyQueue<E> extends PriorityQueue<E> {private final int max;public MyQueue(Comparator<? super E> comparator, int max) {super(comparator);this.max = max;}@Overridepublic boolean offer(E e) {boolean r = super.offer(e);// 如果小根堆节点的数量大于max,移除堆顶的元素再添加if (this.size() > max) {this.poll();}return r;}}// 4) 下单最多的前十名用户 - 内存占用更有优势private static void case42() {try (Stream<String> lines = Files.lines(Path.of("./data.txt"))) {// 使用小根堆收集元素MyQueue<Map.Entry<String, Long>> queue = lines.skip(1).map(line -> line.split(",")).collect(groupingBy(array -> array[USER_ID], counting())).entrySet().stream().collect(() -> new MyQueue<>(Map.Entry.comparingByValue(), 10),MyQueue::offer,AbstractQueue::addAll);while (!queue.isEmpty()) {System.out.println(queue.poll());}} catch (IOException e) {throw new RuntimeException(e);}}// 3) 统计销售量最高的商品private static void case3() {try (Stream<String> lines = Files.lines(Paths.get("./data.txt"))) {lines.skip(1).map(line -> line.split(",")).collect(groupingBy(array -> array[PRODUCT_ID], counting())).entrySet().stream().max(Map.Entry.comparingByValue()).ifPresent(System.out::println);} catch (IOException e) {throw new RuntimeException(e);}}// 2) 统计销售量最高的月份private static void case2() {try (Stream<String> lines = Files.lines(Path.of("./data.txt"))) {lines.skip(1).map(line -> line.split(",")).collect(groupingBy(array -> YearMonth.from(formatter.parse(array[TIME])), counting())).entrySet().stream()
// .max(Comparator.comparingLong(e -> e.getValue()));.max(Map.Entry.comparingByValue()).ifPresent(System.out::println);} catch (IOException e) {throw new RuntimeException(e);}}// 1) 统计每月的销售量private static void case1() {try (Stream<String> lines = Files.lines(Path.of("./data.txt"))) {Map<YearMonth, Long> collect = lines.skip(1).map(line -> line.split(",")).collect(groupingBy(array -> YearMonth.from(formatter.parse(array[TIME])), TreeMap::new, counting())); // TreeMap 按key排序for (Map.Entry<YearMonth, Long> e : collect.entrySet()) {System.out.println(e);}} catch (IOException e) {throw new RuntimeException(e);}}}
1)每月的销售量
1970-01 订单数1307
2020-01 订单数14270
2020-02 订单数17995
2020-03 订单数18688
2020-04 订单数11868
2020-05 订单数40334
2020-06 订单数41364
2020-07 订单数76418
2020-08 订单数100007
2020-09 订单数70484
2020-10 订单数104063
2020-11 订单数66060
2)销量最高的月份
1970-01 订单数1307
2020-01 订单数14270
2020-02 订单数17995
2020-03 订单数18688
2020-04 订单数11868
2020-05 订单数40334
2020-06 订单数41364
2020-07 订单数76418
2020-08 订单数100007
2020-09 订单数70484
2020-10 订单数104063 *
2020-11 订单数66060
3)销量最高的商品
1515966223517846928=2746
4)下单最多的前10名用户
1.515915625512423e+18 订单数1092
1.5159156255121183e+18 订单数1073
1.515915625512378e+18 订单数1040
1.515915625512377e+18 订单数1028
1.5159156255136955e+18 订单数1002
1.515915625512422e+18 订单数957
1.515915625513446e+18 订单数957
1.515915625513447e+18 订单数928
1.515915625514598e+18 订单数885
1.5159156255147195e+18 订单数869
5.1)每个地区下单最多的用户
上海=Optional[1.5159156255127636e+18=634]
广东=Optional[1.515915625512377e+18=1028]
天津=Optional[1.5159156255120858e+18=530]
四川=Optional[1.5159156255121551e+18=572]
浙江=Optional[1.5159156255121183e+18=564]
重庆=Optional[1.515915625512764e+18=632]
湖北=Optional[1.5159156255121183e+18=509]
湖南=Optional[1.5159156255120548e+18=545]
江苏=Optional[1.5159156255122386e+18=551]
海南=Optional[1.5159156255121178e+18=556]
北京=Optional[1.5159156255128172e+18=584]
5.2)每个地区下单最多的前3名用户
上海
--------------------------
1.5159156255127636e+18=634
1.515915625512118e+18=583
1.515915625512422e+18=561
广东
--------------------------
1.515915625512377e+18=1028
1.5159156255121544e+18=572
1.5159156255120845e+18=571
天津
--------------------------
1.5159156255120858e+18=530
1.5159156255122383e+18=504
1.5159156255123333e+18=481
四川
--------------------------
1.5159156255121551e+18=572
1.5159156255123768e+18=568
1.515915625512055e+18=552
浙江
--------------------------
1.5159156255121183e+18=564
1.515915625513058e+18=520
1.515915625512423e+18=513
重庆
--------------------------
1.515915625512764e+18=632
1.5159156255121188e+18=572
1.515915625512085e+18=562
湖北
--------------------------
1.5159156255121183e+18=509
1.515915625512818e+18=508
1.5159156255148017e+18=386
湖南
--------------------------
1.5159156255120548e+18=545
1.5159156255120855e+18=543
1.5159156255134449e+18=511
江苏
--------------------------
1.5159156255122386e+18=551
1.5159156255122842e+18=541
1.5159156255120842e+18=499
海南
--------------------------
1.5159156255121178e+18=556
1.5159156255128174e+18=547
1.5159156255122022e+18=545
北京
--------------------------
1.5159156255128172e+18=584
1.515915625512423e+18=579
1.5159156255123786e+18=558
6.1)按类别统计销量
accessories.bag 订单数 3063
accessories.umbrella 订单数 33
apparel.costume 订单数 2
apparel.glove 订单数 1942
apparel.shirt 订单数 235
apparel.shoes 订单数 2
apparel.sock 订单数 21
apparel.trousers 订单数 99
apparel.tshirt 订单数 372
appliances.environment.air_conditioner 订单数 7379
appliances.environment.air_heater 订单数 2599
appliances.environment.climate 订单数 101
appliances.environment.fan 订单数 3855
appliances.environment.vacuum 订单数 15971
appliances.environment.water_heater 订单数 3644
appliances.iron 订单数 8249
appliances.ironing_board 订单数 2128
appliances.kitchen.blender 订单数 8672
appliances.kitchen.coffee_grinder 订单数 811
appliances.kitchen.coffee_machine 订单数 1250
appliances.kitchen.dishwasher 订单数 2663
appliances.kitchen.fryer 订单数 97
appliances.kitchen.grill 订单数 1579
appliances.kitchen.hood 订单数 9045
appliances.kitchen.juicer 订单数 1187
appliances.kitchen.kettle 订单数 12740
appliances.kitchen.meat_grinder 订单数 4520
appliances.kitchen.microwave 订单数 7615
appliances.kitchen.mixer 订单数 2610
appliances.kitchen.oven 订单数 4000
appliances.kitchen.refrigerators 订单数 20259
appliances.kitchen.steam_cooker 订单数 464
appliances.kitchen.toster 订单数 1381
appliances.kitchen.washer 订单数 14563
appliances.personal.hair_cutter 订单数 2716
appliances.personal.massager 订单数 1724
appliances.personal.scales 订单数 6727
appliances.sewing_machine 订单数 1576
appliances.steam_cleaner 订单数 119
auto.accessories.alarm 订单数 252
auto.accessories.anti_freeze 订单数 109
auto.accessories.compressor 订单数 276
auto.accessories.player 订单数 117
auto.accessories.radar 订单数 80
auto.accessories.videoregister 订单数 533
computers.components.cdrw 订单数 158
computers.components.cooler 订单数 3377
computers.components.cpu 订单数 4147
computers.components.hdd 订单数 5054
computers.components.memory 订单数 1597
computers.components.motherboard 订单数 860
computers.components.power_supply 订单数 986
computers.components.sound_card 订单数 26
computers.components.videocards 订单数 1190
computers.desktop 订单数 1041
computers.ebooks 订单数 397
computers.gaming 订单数 164
computers.network.router 订单数 6473
computers.notebook 订单数 25866
computers.peripherals.camera 订单数 1041
computers.peripherals.joystick 订单数 1192
computers.peripherals.keyboard 订单数 3803
computers.peripherals.monitor 订单数 3272
computers.peripherals.mouse 订单数 12664
computers.peripherals.printer 订单数 3458
computers.peripherals.scanner 订单数 74
construction.components.faucet 订单数 133
construction.tools.drill 订单数 622
construction.tools.generator 订单数 46
construction.tools.heater 订单数 348
construction.tools.light 订单数 10
construction.tools.pump 订单数 65
construction.tools.saw 订单数 169
construction.tools.screw 订单数 2408
construction.tools.welding 订单数 183
country_yard.cultivator 订单数 33
country_yard.lawn_mower 订单数 111
country_yard.watering 订单数 5
country_yard.weather_station 订单数 53
electronics.audio.acoustic 订单数 438
electronics.audio.dictaphone 订单数 12
electronics.audio.headphone 订单数 20084
electronics.audio.microphone 订单数 1062
electronics.audio.subwoofer 订单数 70
electronics.calculator 订单数 35
electronics.camera.photo 订单数 348
electronics.camera.video 订单数 133
electronics.clocks 订单数 6474
electronics.smartphone 订单数 102365
electronics.tablet 订单数 6395
electronics.telephone 订单数 2437
electronics.video.projector 订单数 114
electronics.video.tv 订单数 17618
furniture.bathroom.bath 订单数 232
furniture.bathroom.toilet 订单数 44
furniture.bedroom.bed 订单数 451
furniture.bedroom.blanket 订单数 68
furniture.bedroom.pillow 订单数 1882
furniture.kitchen.chair 订单数 3084
furniture.kitchen.table 订单数 11260
furniture.living_room.cabinet 订单数 3117
furniture.living_room.chair 订单数 1439
furniture.living_room.shelving 订单数 2572
furniture.living_room.sofa 订单数 401
furniture.universal.light 订单数 22
kids.bottles 订单数 63
kids.carriage 订单数 41
kids.dolls 订单数 379
kids.fmcg.diapers 订单数 11
kids.skates 订单数 1159
kids.swing 订单数 8
kids.toys 订单数 643
medicine.tools.tonometer 订单数 1106
sport.bicycle 订单数 569
sport.diving 订单数 10
sport.ski 订单数 17
sport.snowboard 订单数 3
sport.tennis 订单数 87
sport.trainer 订单数 210
stationery.battery 订单数 5210
stationery.cartrige 订单数 2473
stationery.paper 订单数 1085
stationery.stapler 订单数 97
6.2)按一级类别统计销量
accessories 订单数 3096
apparel 订单数 2673
appliances 订单数 150244
auto 订单数 1367
computers 订单数 76840
construction 订单数 3984
country_yard 订单数 202
electronics 订单数 157585
furniture 订单数 24572
kids 订单数 2304
medicine 订单数 1106
sport 订单数 896
stationery 订单数 8865
7)按价格区间统计销量
[0,100)=291624
[1000,∞)=14514
[500,1000)=52857
[100,500)=203863
8)不同年龄段女性所下不同类别订单
[0,18) accessories 81
[0,18) apparel 60
[0,18) appliances 4326
[0,18) computers 1984
...
[18,30) accessories 491
[18,30) apparel 488
[18,30) appliances 25240
[18,30) computers 13076
...
[30,50) accessories 890
[30,50) apparel 893
[30,50) appliances 42755
[30,50) computers 21490
...
[50,∞) accessories 41
[50,∞) apparel 41
[50,∞) appliances 2255
[50,∞) computers 1109
...
2. 异步处理
示例1:使用ExecutorService
- 将结果作为方法的返回值返回
package com.itheima.day4.asynchronous;import org.slf4j.Logger;
import org.slf4j.LoggerFactory;import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.time.YearMonth;
import java.time.format.DateTimeFormatter;
import java.util.Map;
import java.util.TreeMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.function.Consumer;
import java.util.stream.Stream;import static java.util.stream.Collectors.*;public class C01ExecutorTest {static final int INDEX = 0;static final int TIME = 1;static final int ORDER_ID = 2;static final int PRODUCT_ID = 3;static final int CATEGORY_ID = 4;static final int CATEGORY_CODE = 5;static final int BRAND = 6;static final int PRICE = 7;static final int USER_ID = 8;static final int USER_AGE = 9;static final int USER_SEX = 10;static final int USER_REGION = 11;static final DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss z");static Logger logger = LoggerFactory.getLogger("Test");/*同步:没有执行完之前,后续代码不能执行异步:后续代码的执行,并不受异步操作的干扰要让一段逻辑异步:1. 需要有独立线程2. 逻辑需要封装至函数对象,才能让此逻辑不是立刻执行,而是在新线程中的未来某刻执行*/public static void main(String[] args) {
// logger.info("开始统计");
// monthlySalesReport(); // 同步操作
// new Thread(()->monthlySalesReport()).start(); // 异步操作(不建议自己创建线程)
// logger.info("执行其它操作");try (ExecutorService service = Executors.newFixedThreadPool(3)) {logger.info("开始统计");/*目标:将处理结果的逻辑放在 monthlySalesReport 之外做法1:将结果作为方法的返回值返回做法2:将结果的处理逻辑做成函数对象,通过方法参数传给monthlySalesReport(),让monthlySalesReport()产生结果后调用函数对象处理*/service.submit(() -> {Map<YearMonth, Long> map = monthlySalesReport();for (Map.Entry<YearMonth, Long> e : map.entrySet()) {logger.info(e.toString());}});logger.info("执行其它操作");}}/*** 按月统计销量* @return*/private static Map<YearMonth, Long> monthlySalesReport() {try (Stream<String> lines = Files.lines(Path.of("./data.txt"))) {Map<YearMonth, Long> collect = lines.skip(1).map(line -> line.split(",")).collect(groupingBy(array -> YearMonth.from(formatter.parse(array[TIME])), TreeMap::new, counting()));return collect;} catch (IOException e) {throw new RuntimeException(e);}}}
- 将处理结果的逻辑作为函数对象传递给方法
package com.itheima.day4.asynchronous;import org.slf4j.Logger;
import org.slf4j.LoggerFactory;import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.time.YearMonth;
import java.time.format.DateTimeFormatter;
import java.util.Map;
import java.util.TreeMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.function.Consumer;
import java.util.stream.Collectors;
import java.util.stream.Stream;import static java.util.stream.Collectors.counting;
import static java.util.stream.Collectors.groupingBy;public class C02ExecutorTest {static final int INDEX = 0;static final int TIME = 1;static final int ORDER_ID = 2;static final int PRODUCT_ID = 3;static final int CATEGORY_ID = 4;static final int CATEGORY_CODE = 5;static final int BRAND = 6;static final int PRICE = 7;static final int USER_ID = 8;static final int USER_AGE = 9;static final int USER_SEX = 10;static final int USER_REGION = 11;static final DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss z");static Logger logger = LoggerFactory.getLogger("Test");/*同步:没有执行完之前,后续代码不能执行异步:后续代码的执行,并不受异步操作的干扰要让一段逻辑异步:1. 需要有独立线程2. 逻辑需要封装至函数对象,才能让此逻辑不是立刻执行,而是在新线程中的未来某刻执行*/public static void main(String[] args) {/*1. 显式使用了线程池2. 使用了多个嵌套的函数对象,代码可读性差*/try (ExecutorService service = Executors.newFixedThreadPool(3)) {logger.info("开始统计");/*目标:将处理结果的逻辑放在 monthlySalesReport 之外做法2:将处理结果的逻辑作为函数对象传递给方法*/service.submit(() -> {monthlySalesReport((map)->{/*for (Map.Entry<YearMonth, Long> e : map.entrySet()) {logger.info(e.toString());}*/String string = map.entrySet().stream().map(e -> e.toString()).collect(Collectors.joining("\n"));try {// 写出到文件Files.writeString(Path.of("./result.txt"), string);} catch (IOException e) {throw new RuntimeException(e);}});});logger.info("执行其它操作");}}private static void monthlySalesReport(Consumer<Map<YearMonth, Long>> consumer) {try (Stream<String> lines = Files.lines(Path.of("./data.txt"))) {Map<YearMonth, Long> map = lines.skip(1).map(line -> line.split(",")).collect(groupingBy(array -> YearMonth.from(formatter.parse(array[TIME])), TreeMap::new, counting()));consumer.accept(map);} catch (IOException e) {throw new RuntimeException(e);}}}
- 改进:使用CompletableFuture
package com.itheima.day4.asynchronous;import org.slf4j.Logger;
import org.slf4j.LoggerFactory;import java.io.IOException;
import java.util.concurrent.CompletableFuture;public class C03CompletableFutureTest {static Logger logger = LoggerFactory.getLogger("Test");public static void main(String[] args) throws IOException {// 1) 异步执行任务
// CompletableFuture.runAsync() 在任务不需要返回结果时
// CompletableFuture.supplyAsync() 在任务需要处理结果时CompletableFuture.runAsync(() -> logger.info("异步操作1"));CompletableFuture.supplyAsync(() -> {logger.info("异步操作2");return "结果";}).thenApply(r -> r + "转换后").thenAccept(r -> logger.info(r));System.in.read(); // 不让主线程立刻结束// 2) 处理异步任务的结果/*thenApply(Function) 转换结果thenApplyAsync 异步转换结果thenAccept(Consumer) 消费结果thenAcceptAsync(Consumer) 异步消费结果*/}
}
package com.itheima.day4.asynchronous;import org.slf4j.Logger;
import org.slf4j.LoggerFactory;import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.time.YearMonth;
import java.time.format.DateTimeFormatter;
import java.util.Map;
import java.util.TreeMap;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.function.Consumer;
import java.util.stream.Stream;import static java.util.stream.Collectors.counting;
import static java.util.stream.Collectors.groupingBy;public class C04CompletableFutureTest {static final int INDEX = 0;static final int TIME = 1;static final int ORDER_ID = 2;static final int PRODUCT_ID = 3;static final int CATEGORY_ID = 4;static final int CATEGORY_CODE = 5;static final int BRAND = 6;static final int PRICE = 7;static final int USER_ID = 8;static final int USER_AGE = 9;static final int USER_SEX = 10;static final int USER_REGION = 11;static final DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss z");static Logger logger = LoggerFactory.getLogger("Test");// 1. 显式使用了线程池// 2. 函数对象嵌套使用,可读性差public static void main(String[] args) throws InterruptedException, IOException {// 改进前/*try (ExecutorService service = Executors.newFixedThreadPool(3)) {logger.info("开始统计");service.submit(() -> {monthlySalesReport(map -> {for (Map.Entry<YearMonth, Long> e : map.entrySet()) {logger.info(e.toString());}});});logger.info("执行其它操作");}*/// 改进后logger.info("开始统计");CompletableFuture.supplyAsync(()->monthlySalesReport()).thenAccept(map-> {for (Map.Entry<YearMonth, Long> e : map.entrySet()) {logger.info(e.toString());}});logger.info("执行其它操作");System.in.read();}private static Map<YearMonth, Long> monthlySalesReport() {try (Stream<String> lines = Files.lines(Path.of("./data.txt"))) {Map<YearMonth, Long> map = lines.skip(1).map(line -> line.split(",")).collect(groupingBy(array -> YearMonth.from(formatter.parse(array[TIME])), TreeMap::new, counting()));return map;} catch (IOException e) {throw new RuntimeException(e);}}
}
3. 框架设计
问题:什么是框架?
- 半成品软件,帮助开发者快速构建应用程序;
- 框架提供的都是固定不变的、已知的、可以重用的代码;
- 而那些每个应用不同的业务逻辑,变化的、未知的部分,则在框架外由开发者自己实现。
示例1:将未知交给子类
- Spring延迟创建Bean
Spring中的很多类有非常复杂的继承关系,并且它们分工明确,你做什么,我做什么,职责是划分好的。例如:
- DefaultSingletonBeanRegistry是父类,它有个职责是缓存单例bean,用下面方法实现:
public Object getSingleton(String beanName, ObjectFactory<?> factory)
- 但如何创建bean,这个父类是不知道的,创建bean是子类AbstractAutowireCapableBeanFactory的职责
Object createBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args) {...
}
- 父类中getSingleton的内部就要使用singletonFactory函数对象来获得创建好的对象
public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {...Object singletonObject = this.singletonObjects.get(beanName);if(singletonObject == null) {...singletonObject = singletonFactory.getObject();addSingleton(beanName, singletonObject);}
}
- 最后子类创建单例bean时,会把ObjectFactory这个函数对象传进入(创建其它scope bean,不需要用getSingleton缓存)
protected <T> T doGetBean(...) {...if (mbd.isSingleton()) {sharedInstance = getSingleton(beanName, () -> {...return createBean(beanName, mbd, args);}); }...
}
示例2:将未知交给用户
- jdbcTemplate
create table student (id int primary key auto_increment,name varchar(16),sex char(1)
);insert into student values(1, '赵一伤', '男'),(2, '钱二败', '男'),(3, '孙三毁', '男'),(4, '李四摧', '男'),(5, '周五输', '男'),(6, '吴六破', '男'),(7, '郑七灭', '男'),(8, '王八衰', '男');
package com.itheima.day4.framework;import com.zaxxer.hikari.HikariDataSource;
import org.springframework.jdbc.core.JdbcTemplate;import java.sql.*;
import java.util.ArrayList;
import java.util.List;// JdbcTemplate
public class C01JdbcTemplate {public static void main(String[] args) {HikariDataSource dataSource = new HikariDataSource();dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/test");dataSource.setUsername("root");dataSource.setPassword("root");// 未使用框架前的代码/*List<Student> list = new ArrayList<>();try (Connection conn = dataSource.getConnection()) {PreparedStatement stat = conn.prepareStatement("select * from student");ResultSet rs = stat.executeQuery();while (rs.next()) {int id = rs.getInt("id");String name = rs.getString("name");String sex = rs.getString("sex");list.add(new Student(id, name, sex));}for (Student student : list) {System.out.println(student);}} catch (SQLException e) {throw new RuntimeException(e);}*/JdbcTemplate template = new JdbcTemplate(dataSource);List<Student> list = template.query("select * from student", (rs, row) -> { // 第一个参数是ResultSet rs, 第二个参数是行号rowNum// 自动遍历ResultSet的每一行int id = rs.getInt("id");String name = rs.getString("name");String sex = rs.getString("sex");return new Student(id, name, sex);});for (Student student : list) {System.out.println(student);}}record Student(int id, String name, String sex) {}
}
- 对于query来讲,建立数据库连接,创建Statement对象,执行查询这些步骤都是固定的;
- 而结果要如何用java对象封装,这对框架代码是未知的,用RowMapper接口代表,将来它的lambda实现将结果转换成需要的java对象。
- ApplicationListener
package com.itheima.day4.framework;import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationEvent;
import org.springframework.context.ApplicationListener;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;// ApplicationListener
@SpringBootApplication
public class C02ApplicationListener {public static class MyEvent extends ApplicationEvent {public MyEvent(Object source) {super(source);}}public static void main(String[] args) {ConfigurableApplicationContext context = SpringApplication.run(C02ApplicationListener.class, args);context.publishEvent(new MyEvent("容器启动"));// event->void}@Beanpublic static ApplicationListener<MyEvent> listener() {return event -> System.out.println(event);}@RestControllerstatic class MyController {@Autowiredprivate ApplicationContext context;@GetMapping("/hello")public String hello() {context.publishEvent(new MyEvent("/hello 被访问"));return "hello";}}
}
- 对于spring来讲,它并不知道如何处理事件
- 因此可以提供一个类型为ApplicationListener的Lamba对象
- 延迟拼接条件 - Mybatis-Plus
package com.itheima.day4.framework.mybatis;import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;import java.util.List;@SpringBootApplication
@MapperScan
public class TestMyBatisPlus {public static void main(String[] args) {ConfigurableApplicationContext context = SpringApplication.run(TestMyBatisPlus.class, args);StudentMapper mapper = context.getBean(StudentMapper.class);test(mapper, List.of("赵一伤", "钱二败"));}static void test(StudentMapper mapper, List<String> names) {LambdaQueryWrapper<Student> query = new LambdaQueryWrapper<>();// where name in (?,?,...)// stu -> stu.getName()query.in(!names.isEmpty(), Student::getName, names);/*in => ineq => =...Student::getName => 列名*/for (Student student : mapper.selectList(query)) {System.out.println(student);}}
}
比较典型的用法有两处:
- 第一,在调用in等方法添加条件时,第一个参数是boolean为true时才会拼接SQL条件,否则不拼接
- 如何实现的呢?用DoSomething类型的lambda对象来延迟拼接操作
- 然而,它在实现 () -> appendSqlSegments(...)拼接时,是不断修改一个expression状态变量,为函数式编程所不齿(修改了外部变量)。
@FunctionalInterface
public interface DoSomething {void doIt();
}protected final Children maybeDo(boolean condition, DoSomething something) {if (condition) {something.doIt();}return typedThis;
}
- 第二,如果用LambdaQueryWrapper拼接sql条件时,为了取得列名,采用了这个办法
Student::getName
它要做的是很简单,但内部实现却比较复杂:
- 必须用Student::getName方法引用,而不能用其它Lambda对象;
- 它会实现Serializable接口,序列化时会把它变成SerializedLambda;
- 想办法拿到SerializedLambda对象(反射调用writeReplace);
- 通过SerializedLambda能够获得它对应的实际方法,也就是String getName()和所在类Student;
- 再通过方法名推导得到属性名(去掉is,get)即name;
- 所在类Student知道了,属性名name也有了,就可以进一步确定列名
- 属性上的@TableField指定的列名优先;
- 没有@TableField,把属性名当作列名。
为什么要这样做?
- 在代码中全面使用java的字段名,避免出现数据库表的列名。
package com.itheima.day4.framework.mybatis;import java.io.*;
import java.lang.invoke.SerializedLambda;
import java.lang.reflect.Method;
import java.util.function.Function;public class TestSerializable {public static void main(String[] args) throws Exception {// 可序列化的函数对象Type1 lambda = (Type1 & Serializable) Student::getName;// 函数对象 <=> 字节码 会额外存储类和方法的信息, 运行时就可以根据这些信息找到属性, 从而进一步确定【列名】/*for (Method method : lambda.getClass().getDeclaredMethods()) {System.out.println(method);}*/// 反射调用SerializedLambda invoke = (SerializedLambda) lambda.getClass().getDeclaredMethod("writeReplace").invoke(lambda);// invoke 是新对象,包含了原始函数对象的字节码,还包含了类和方法的额外信息System.out.println(invoke.getClass());System.out.println(invoke.getCapturingClass()); // 哪个类使用了这个函数对象 TestSerializableSystem.out.println(invoke.getImplClass()); // 哪个类实现了函数对象的逻辑 StudentSystem.out.println(invoke.getImplMethodName()); // 哪个方法实现了函数对象的逻辑 getName}interface Type1 {String abc(Student student);}
}
4. 并行计算
示例1:统计页面的访问次数
日志生成:
package com.itheima.day4.parallel;import java.io.FileWriter;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Random;public class LogGenerator {public static void main(String[] args) {for (int i = 0; i < 100; i++) {generate(i);}}private static void generate(int x) {// 设置日志文件名String logFileName = String.format("web_server_access_%d.log", x);// 创建文件写入器try (FileWriter fileWriter = new FileWriter(logFileName, true)) {// 设置日期格式SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");// 模拟生成10000条日志记录for (int i = 0; i < 100000; i++) {// 随机生成客户端IP地址String ipAddress = getRandomIpAddress();// 随机生成访问时间(假设为程序运行的过去一年内)Date date = getRandomDate(new Date(System.currentTimeMillis() - 365 * 24 * 60 * 60 * 1000), new Date());// 随机生成请求的URLString url = getRandomUrl();// 随机生成HTTP状态码int httpStatusCode = getRandomHttpStatusCode();// 构建日志字符串String logEntry = String.format("%s - [%s] %s %d\n",ipAddress, dateFormat.format(date), url, httpStatusCode);// 将日志记录写入文件fileWriter.write(logEntry);}} catch (IOException e) {e.printStackTrace();}}// 随机生成客户端IP地址private static String getRandomIpAddress() {String[] parts = new String[4];for (int i = 0; i < 4; i++) {parts[i] = String.valueOf(255 - new Random().nextInt(256));}return String.join(".", parts);}// 随机生成访问时间private static Date getRandomDate(Date start, Date end) {long startMillis = start.getTime();long endMillis = end.getTime();long randomMillis = startMillis + new Random().nextInt((int)(endMillis - startMillis));return new Date(randomMillis);}// 随机生成请求的URLprivate static String getRandomUrl() {String[] resources = {"/home", "/about", "/services", "/contact", "/products", "/login","/product1", "/product2", "/product3", "/product4", "/product5","/product6", "/product7", "/product8", "/product9", "/product10","/product11", "/product12", "/product13", "/product14", "/product15","/product16", "/product17", "/product18", "/product19", "/product20",};return String.join("/", resources[new Random().nextInt(resources.length)]);}// 随机生成HTTP状态码private static int getRandomHttpStatusCode() {return new Random().nextInt(6) + 200; // 随机生成2xx或3xx状态码}
}
统计页面访问次数:
package com.itheima.day4.parallel;import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Stream;import static java.util.function.Function.identity;
import static java.util.stream.Collectors.*;// 统计web页面的访问次数
public class ParallelTest {static Pattern reg = Pattern.compile("(\\S+) - \\[(.+)] (.+) (.+)");private static final int FILES = 100;public static void main(String[] args) throws ExecutionException, InterruptedException {parallel();}/*** 处理单个文件* @param i* @return*/private static Map<String, Long> one(int i) {try (Stream<String> lines = Files.lines(Path.of(String.format("web_server_access_%d.log", i)))) {return lines.map(reg::matcher) // reg::matcher 根据正则表达式进行匹配.filter(Matcher::find) // Matcher::find 过滤,返回布尔结果.map(matcher -> new String[]{matcher.group(1), matcher.group(2), matcher.group(3), matcher.group(4)}).collect(groupingBy(array -> array[2], counting())); // 根据访问路径分组} catch (IOException e) {throw new RuntimeException(e);}}/*** 串行计算 约11s*/private static void sequence() {long start = System.currentTimeMillis();Map<String, Long> m0 = new HashMap<>();for (int i = 0; i < FILES; i++) {Map<String, Long> mi = one(i);// 合并结果m0 = merge(m0, mi);}for (Map.Entry<String, Long> e : m0.entrySet()) {System.out.println(e);}System.out.println("cost: " + (System.currentTimeMillis() - start));}/*/login 2/product1 1===> /login 3/product1 1/product2 3/login 1/product2 3根据key进行合并,key相同的,值累加*/static Map<String, Long> merge(Map<String, Long> m1, Map<String, Long> m2) {return Stream.of(m1, m2).flatMap(m -> m.entrySet().stream()).collect(toMap(Map.Entry::getKey, Map.Entry::getValue, (v1, v2) -> v1 + v2));}/*** 并行计算 约5s* @throws InterruptedException* @throws ExecutionException*/private static void parallel() throws InterruptedException, ExecutionException {long start = System.currentTimeMillis();List<CompletableFuture<Map<String, Long>>> futures = new ArrayList<>();for (int i = 0; i < FILES; i++) {// 函数对象引用外部变量要求是final或Effective finalint k = i;futures.add(CompletableFuture.supplyAsync(() -> one(k)));}CompletableFuture<Map<String, Long>> f0 = futures.getFirst(); // 集合中第一个任务for (int i = 1; i < futures.size(); i++) {// 与剩下的任务结果两两合并CompletableFuture<Map<String, Long>> fi = futures.get(i);f0 = f0.thenCombine(fi, (m0, mi) -> merge(m0, mi));}Map<String, Long> map = f0.get();for (Map.Entry<String, Long> e : map.entrySet()) {System.out.println(e);}System.out.println("cost: " + (System.currentTimeMillis() - start));}
}
5. UI事件
示例1:按钮点击事件
package com.itheima.day4.ui;import javax.swing.*;// 在 UI 设计时的应用
public class UITest {public static void main(String[] args) {JFrame frame = new JFrame();JButton button = new JButton("点我");button.addActionListener(e -> System.out.println("已点击"));frame.add(button);frame.setSize(300, 200);frame.setVisible(true);}
}
五、实现原理
1. Lambda原理
package com.itheima.day5;import java.lang.invoke.*;
import java.lang.reflect.Method;
import java.util.function.BinaryOperator;// 把类存储到磁盘当中 ↓
// 在jdk 21中运行时添加虚拟机参数 -Djdk.invoke.LambdaMetafactory.dumpProxyClassFiles
// 早期 jdk添加的参数是(没有去进行版本对比了):-Djdk.internal.lambda.dumpProxyClasses
public class C01Lambda1 {public static void main(String[] args) throws Throwable {BinaryOperator<Integer> lambda = (a, b) -> a + b;System.in.read();/*lambda 表达式是一种语法糖,它仍然会被翻译成 类、对象、方法1. 方法从哪来 : 编译器发现代码中出现了 lambda,就会在当前类中生成 private static 方法,方法内包含的就是 lambda 的逻辑实验代码for (Method method : C01Lambda1.class.getDeclaredMethods()) {System.out.println(method);}打印结果:public static void com.itheima.day5.C01Lambda1.main(java.lang.String[]) throws java.lang.Throwableprivate static java.lang.Integer com.itheima.day5.C01Lambda1.lambda$main$0(java.lang.Integer,java.lang.Integer)2. 类和对象从哪来 : 运行期间动态生成等价于 - BinaryOperator<Integer> lambda = new MyLambda();3. MethodHandle 铺垫MethodHandle 的执行权限与上下文相关原本有权限调用的方法,正常能调用,通过 MethodHandle 反射也能调用原本没权限调用的方法,正常不能调用,MethodHandle 反射也调用不了a) 反射调用静态方法MethodHandle mh = MethodHandles.lookup().findStatic(C01Lambda1.class, "lambda$main$2",MethodType.methodType(Integer.class, Integer.class, Integer.class));System.out.println(mh.invoke(1, 2)); // 相当于 C01Lambda1.lambda$main$2(1,2)b) 反射调用非静态方法MethodHandle mh2 = MethodHandles.lookup().findVirtual(MyLambda.class, "apply",MethodType.methodType(Integer.class, Integer.class, Integer.class));System.out.println(mh2.invoke(new MyLambda(), 3, 4)); // 相当于通过 new MyLambda() 对象执行了 .apply(3,4)c) 反射调用构造MethodHandle mh3 = MethodHandles.lookup().findConstructor(MyLambda.class, MethodType.methodType(void.class));System.out.println(mh3.invoke()); // 相当于 new MyLambda()*/MethodHandles.Lookup lookup = MethodHandles.lookup();MethodHandle impl = lookup.findStatic(C01Lambda1.class, "lambda$main$2",MethodType.methodType(Integer.class, Integer.class, Integer.class));// 内部:生成函数对象所需的类 ASMCallSite cs = LambdaMetafactory.metafactory(lookup, // 1. lookup"apply", // 2. 接口方法名MethodType.methodType(BinaryOperator.class), // 3. 创建函数对象工厂方法长相 BinaryOperator factory()MethodType.methodType(Object.class, Object.class, Object.class), // 4. 接口方法长相impl, // 5. 实现方法 (本例就是下面的静态方法 lambda$main$2)MethodType.methodType(Integer.class, Integer.class, Integer.class) // 6. 函数对象实际长相(返回值和参数类型));// BinaryOperator factory() {return new MyLambda()}MethodHandle mh = cs.getTarget(); // 就是函数对象工厂方法BinaryOperator<Integer> invoke = (BinaryOperator<Integer>) mh.invoke();System.out.println(invoke.apply(5, 6));}static final class MyLambda implements BinaryOperator<Integer> {private MyLambda() {}@Overridepublic Integer apply(Integer a, Integer b) {return lambda$main$2(a, b);}}private static Integer lambda$main$2(Integer a, Integer b) {return a + b;}}
2. 方法引用原理
package com.itheima.day5;import java.lang.invoke.*;
import java.util.function.Function;// -Djdk.invoke.LambdaMetafactory.dumpProxyClassFiles
public class C02MethodReference {public static void main(String[] args) throws Throwable {// 方法引用是一种语法糖,它仍然会被翻译成 类、对象、方法// 1. 方法从哪来// 2. 类、对象从哪来Function<Student, String> func = Student::getName; // stu->stu.getName()MethodHandles.Lookup lookup = MethodHandles.lookup();MethodHandle impl = lookup.findVirtual(Student.class, "getName", MethodType.methodType(String.class));CallSite cs = LambdaMetafactory.metafactory(lookup,"apply",MethodType.methodType(Function.class),MethodType.methodType(Object.class, Object.class),impl,MethodType.methodType(String.class, Student.class));Function<Student, String> invoke = (Function<Student, String>) cs.getTarget().invoke();Student stu = new Student();stu.name = "张三";System.out.println(invoke.apply(stu));}static final class MyMethodReference implements Function<Student, String> {@Overridepublic String apply(Student student) {return student.getName();}}static class Student {private String name;public String getName() {return name;}}
}
- 方法引用是 Lambda 表达式的语法糖,底层会被编译成动态生成的类。
- LambdaMetafactory 负责在运行时生成函数式接口的实现,避免手动写匿名类。
- MethodHandle 提供高效的方法调用机制,比反射更快。
- 方法引用不会立即执行方法,而是绑定到函数式接口的方法(如 Function.apply)上。
3. 闭包原理
示例1:局部变量 - final 或 effective final
package com.itheima.day5;import java.lang.reflect.Method;
import java.util.function.BinaryOperator;// -Djdk.invoke.LambdaMetafactory.dumpProxyClassFiles
public class C03Closure {public static void main(String[] args) {int c = 10;BinaryOperator<Integer> lambda = (a, b) -> a + b + c; // invoke dynamic new C03Closure$$Lambda(10)test(lambda);for (Method method : C03Closure.class.getDeclaredMethods()) {System.out.println(method);}// 1. 方法// 2. 类和对象}static void test(BinaryOperator<Integer> lambda) {}final static class C03Closure$$Lambda implements BinaryOperator {private final int c;private C03Closure$$Lambda(int c) {this.c = c;}public Object apply(Object a, Object b) {return C03Closure.lambda$main$1(this.c, (Integer)a, (Integer)b);}}static private Integer lambda$main$1(int c, Integer a, Integer b) {return a + b + c;}
}
- 局部变量c会被Lambda表达式复制一份(因为局部变量存储在栈上,可能被销毁)。
- Java要求Lambda捕获的局部变量必须是final或effectively final(不可变),否则编译失败。
示例2:静态变量
package com.itheima.day5;import java.lang.reflect.Method;
import java.util.function.BinaryOperator;// -Djdk.invoke.LambdaMetafactory.dumpProxyClassFiles
public class C04Closure {static int c = 10;public static void main(String[] args) {BinaryOperator<Integer> lambda = (a, b) -> a + b + c;test(lambda);/*System.out.println(lambda.apply(1, 2));c = 20;System.out.println(lambda.apply(1, 2));*/for (Method method : C04Closure.class.getDeclaredMethods()) {System.out.println(method);}}static void test(BinaryOperator<Integer> lambda) {}static final class C04Closure$$Lambda implements BinaryOperator {private C04Closure$$Lambda() {}public Object apply(Object a, Object b) {return C04Closure.lambda$main$1((Integer)a, (Integer)b);}}private static Integer lambda$main$1(Integer a, Integer b) {return a + b + C04Closure.c;}
}
- static变量存储在方法区,生命周期与类相同,不会被销毁;
- Lambda表达式直接引用C04Closure.c,而不是复制它的值;
- 修改c会影响所有引用它的地方,包括Lambda表达式。
示例3:类上的非静态成员变量
package com.itheima.day5;import java.util.function.BinaryOperator;// -Djdk.invoke.LambdaMetafactory.dumpProxyClassFiles
public class C05Closure {private int c = 10; // 成员变量BinaryOperator<Integer> lambda = (a, b) -> a + b + c; // Lambda表达式捕获 'c'public static void main(String[] args) {new C05Closure(); // 创建对象,触发Lambda初始化}final class C05Closure$$Lambda implements BinaryOperator {private final C05Closure c; // 持有外部类引用private C05Closure$$Lambda(C05Closure c) {this.c = c;}public Object apply(Object a, Object b) {return this.c.lambda$new$1((Integer)a, (Integer)b);}}private Integer lambda$new$1(Integer a, Integer b) {return a + b + c; // 直接访问成员变量 'c'}
}
- 成员变量c存储在堆内存(属于对象),生命周期与对象一致;
- Lambda表达式通过外部类引用(C05Closure.this)访问c,而不是复制它的值;
- 因此,修改c会影响Lambda的行为,不同于局部变量的final限制。
变量类型 | 存储位置 | Lambda捕获方式 | 是否允许修改 | 原因 |
static变量 | 堆 | 直接引用(如C04Closure.c) | 允许修改 | 全局共享,生命周期与类相同 |
局部变量 | 栈帧 | 复制一份(必须是final或effectively final) | 不允许修改 | 栈帧随方法调用结束可能销毁,Lambda需要保证数据一致性 |
成员变量 | 堆(对象内) | 通过this引用(如this.c) | 允许修改 | 对象存活期间可修改,Lambda持有外部类的引用 |
4. Stream的构建与切分
package com.itheima.day5;import org.slf4j.Logger;
import org.slf4j.LoggerFactory;import java.io.IOException;
import java.util.Arrays;
import java.util.List;
import java.util.Spliterator;
import java.util.concurrent.CompletableFuture;
import java.util.stream.IntStream;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;// 流的构建与切分
public class C06Spliterator {static Logger logger = LoggerFactory.getLogger("Test");public static void main(String[] args) throws IOException {List<Integer> list = List.of(1, 2, 3, 4, 5, 6, 7, 8, 9);Integer[] array = {1, 2, 3, 4, 5, 6, 7, 8, 9};// Stream<Integer> s1 = list.stream();
// Stream<Integer> s2 = Arrays.stream(array);Spliterator<Integer> sp1 = list.spliterator();
// sp1.tryAdvance(System.out::println); // 1
// sp1.tryAdvance(System.out::println); // 2
// sp1.tryAdvance(System.out::println); // 3
// System.out.println("======================");
// sp1.forEachRemaining(System.out::println); // 4 5 6 7 8 9Spliterator<Integer> sp2 = sp1.trySplit();Spliterator<Integer> sp3 = sp2.trySplit();// System.out.println("sp1:");
// sp1.forEachRemaining(System.out::println); // 5 6 7 8 9
// System.out.println("sp2:");
// sp2.forEachRemaining(System.out::println); // 3 4
// System.out.println("sp3:");
// sp3.forEachRemaining(System.out::println); // 1 2// 合并CompletableFuture.supplyAsync(() -> StreamSupport.stream(sp1, false).reduce(0, Integer::sum)).thenAccept(x -> logger.info("{}", x)); // 35CompletableFuture.supplyAsync(() -> StreamSupport.stream(sp2, false).reduce(0, Integer::sum)).thenAccept(x -> logger.info("{}", x)); // 7CompletableFuture.supplyAsync(() -> StreamSupport.stream(sp3, false).reduce(0, Integer::sum)).thenAccept(x -> logger.info("{}", x)); // 3System.in.read();}
}
- 配合多线程实现并行处理,提高处理效率 -> 并行流