java3.日志+java8
日志
@RunWith(SpringRunner.class)
Springboot的@RunWith(SpringRunner.class)
注解的意义在于Test测试类要使用注入的类,比如@Autowired注入的类,
有了@RunWith(SpringRunner.class)这些类才能实例化到spring容器中,自动注入才能生效,
不然直接一个NullPointerExecption
FIB WARNNING
sout/syso禁用!!!
声明:本节课之后,除非临时调试代码(用完立刻删除,),否则,所有的代码中不能出现任何的sout,养成dbug调试的好习惯,就不需要临时调试sout代码
日志级别
日志级别:trace < debug < info < warn < error
输出规则:日志输出自动会以指定的日志级别及更高日志级别进行日志输出:比如,当前日志是info,日志输出的级别包括:info,warn,error
-
trace:
实际开发中,几乎不用
-
debug:
代码调试日志级别,也是线上定位问题的重要手段(线上没法断点调试),真实的生产环境值,默认是不开debug级别日志,这个日志记录很详细,只有出了问题再开debug
-
info:
实际开发过程中,使用info记录关键流程日志,springboot默认的日志级别就是info,日志输出小技巧:使用{}进行变量占位,方便进行日志处理
-
warn:
警告日志,他记录的不一定是错误(异常),一般是记录:异常流程(余额不足,用户状态异常)
-
error:
错误日志,是级别最高的日志,记录的是系统产生的所有的异常信息(必然输出)
手动调整日志配置
# 更改默认日志级别
logging.level.com.lcywings.springbootlog03lcywings=debug
# 指定日志输出到文件,不指定具体路径,默认是当前项目的跟路径下
# 约定:所有的日志都不会是覆盖记录,而是自动追加
logging.file=sbt.log
logging.file=E:\sbt.log
# 指定日志输出目录,跟logging.file配置不能同时使用,作用:将日志输出到指定目录下
# 默认的日志输出名称是spring.log
logging.path=E:\log
# 指定文件的日志格式,不需要单独配置,使用springboot约定的即可(没有重新使用logback)
logging.pattern.file=%d{yyyy-MM-dd HH:mm:ss} *** [%thread] *** %-5level *** %logger{50} *** %msg%n
logback-spring日志配置
有三处需要修改,LOG_HOME、appName、 logger
<?xml version="1.0" encoding="UTF-8"?>
<!--
scan:配置文件发生改变,是否被重新加载,默认值为true。
scanPeriod:设置监测配置文件是否有修改的时间间隔,当scan为true时,此属性生效。默认的时间间隔为1分钟。
debug:是否打印出logback内部日志信息,实时查看logback运行状态。默认值为false。
-->
<configuration scan="true" scanPeriod="60 seconds" debug="false">
<!-- 日志的根目录,可以自动创建log目录,一般公司都是统一的日志目录,不能乱写 -->
<property name="LOG_HOME" value="E:\log" />
<!-- 日志文件名称 -->
<property name="appName" value="lcyWings-springboot-log"/>
<!-- 控制台输出 -->
<appender name="stdout" class="ch.qos.logback.core.ConsoleAppender">
<!--
日志输出格式:
%d{日期时间格式},
%thread:线程名,
%-5level:级别从左显示5个字符宽度
%logger{60} 表示logger名字最长60个字符,否则按照句点分割。
%msg:记录的日志消息,
%n是换行符
-->
<layout class="ch.qos.logback.classic.PatternLayout">
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{60} - %msg%n</pattern>
</layout>
</appender>
<!-- 滚动文件记录,先将日志记录到指定文件,当符合某个条件时,将日志记录到其他文件 -->
<appender name="appLogAppender" class="ch.qos.logback.core.rolling.RollingFileAppender">
<!-- 指定服务器记录日志文件的名称 -->
<file>${LOG_HOME}/${appName}.log</file>
<!--
当发生滚动时,决定 RollingFileAppender 的行为,涉及文件移动和重命名
TimeBasedRollingPolicy: 最常用的滚动策略,它根据时间来制定滚动策略,既负责滚动也负责出发滚动。
-->
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<!--
滚动时产生的文件的存放位置及文件名称 %d{yyyy-MM-dd}:按天进行日志滚动
%i:当文件大小超过maxFileSize时,按照i进行文件滚动
-->
<fileNamePattern>${LOG_HOME}/${appName}-%d{yyyy-MM-dd}-%i.log</fileNamePattern>
<!--
可选节点,控制保留的归档文件的最大数量,超出数量就删除旧文件。假设设置每天滚动,
且maxHistory是365,则只保存最近365天的文件,删除之前的旧文件。注意,删除旧文件是,
那些为了归档而创建的目录也会被删除。
-->
<MaxHistory>30</MaxHistory>
<!--
当日志文件超过maxFileSize指定的大小是,根据上面提到的%i进行日志文件滚动 注意此处配置SizeBasedTriggeringPolicy是无法实现按文件大小进行滚动的,必须配置timeBasedFileNamingAndTriggeringPolicy
-->
<timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
<maxFileSize>30MB</maxFileSize>
</timeBasedFileNamingAndTriggeringPolicy>
</rollingPolicy>
<!-- 日志输出格式: -->
<layout class="ch.qos.logback.classic.PatternLayout">
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [ %thread ] - [ %-5level ] [ %logger{60} : %line ] - %msg%n</pattern>
</layout>
</appender>
<!--
logger主要用于存放日志对象,也可以定义日志类型、级别
name:表示匹配的logger类型前缀,也就是包的前半部分
level:要记录的日志级别,包括 TRACE < DEBUG < INFO < WARN < ERROR
additivity:作用在于children-logger是否使用 rootLogger配置的appender进行输出,
false:表示只用当前logger的appender-ref
true:表示当前logger的appender-ref和rootLogger的appender-ref都有效
-->
<logger name="com.lcywings.springbootlog03lcywings" level="info" />
<logger name="org.springframework" level="debug" additivity="false"/>
<!--
root与logger是父子关系,没有特别定义则默认为root,任何一个类只会和一个logger对应,
要么是定义的logger,要么是root,判断的关键在于找到这个logger,然后判断这个logger的appender和level。
-->
<root level="debug">
<appender-ref ref="stdout" />
<appender-ref ref="appLogAppender" />
</root>
</configuration>
@Slf4j
@Slf4j是用作日志输出的,在类的开头加入该注解,简化代码,可以直接使用log对象输出日志。
lambda表达式
简单的Lambda表达式使用前后对比
// 使用前
// 普通写法
Runnable runnable1 = new Runnable() {
@Override
public void run() {
log.info("------ aaaaaa ------");
}
};
// 启动线程
new Thread(runnable1).start();
// 使用后
// lambda写法
Runnable runnable2 = () -> log.info("------ bbbbbb ------");
// 启动线程
new Thread(runnable2).start();
Lambda简化流程(四级递减)
带一个参数,没有返回值:
1.不使用Lambda表达式
// java8有一个默认提供的函数式接口:消费性接口
log.info("------ 普通写法 ------");
Consumer<String> consumer1 = new Consumer<String>() {
@Override
public void accept(String s) {
log.info("****** 消费数据:{} ******", s);
}
};
consumer1.accept("consumer接口,没有使用lambda表达式");
2.最复杂模式
// 写法1:标准写法
Consumer<String> consumer2 = (String s) -> {
log.info("****** 消费数据:{} ******", s);
};
consumer2.accept("consumer接口,使用lambda表达式");
3.省略参数
// 写法2:省略参数类型,java类型自动推断
List<String> list = new ArrayList<>();
Consumer<String> consumer3 = (s) -> {
log.info("****** 消费数据:{} ******", s);
};
consumer3.accept("consumer接口,使用lambda表达式,省略参数类型");
4.省略小括号
// 写法3:省略参数类型的基础上,如果只有一个参数,可以省略小括号
Consumer<String> consume4 = s -> {
log.info("****** 消费数据:{} ******", s);
};
consume4.accept("consumer接口,使用lambda表达式,省略参数类型,一个参数可以省略小括号");
5.省略大括号(单参无返自动类型,究极省略状态)
// 写法4:省略参数类型的基础上,如果只有一个参数,可以省略大括号(跟返回值类型没有关系)
Consumer<String> consume5 = s -> log.info("****** 消费数据:{} ******", s);
consume5.accept("consumer接口,使用lambda表达式,省略参数类型,可以省略大括号");
Lambda表达式写法:多个参数,有返回值
二级递减+不可省略
1.不使用Lambda表达式
Comparator<Integer> comparator1 = new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
return o1.compareTo(o2);
}
};
log.info("****** 11和15的比较结果:{} ******", comparator1.compare(11, 15));
2.省略参数类型
// 写法1:可以省略参数类型,但是多个参数不能省略小括号
Comparator<Integer> comparator2 = (o1, o2) -> o1.compareTo(o2);
log.info("****** 13和13的比较结果:{} ******", comparator2.compare(13, 13));
3.使用方法引用 究极省略,至尊无敌
// 写法2:使用方法引用
Comparator<Integer> comparator3 = Integer::compareTo;
log.info("****** 19和17的比较结果:{} ******", comparator3.compare(19, 17));
4.不可以省略大括号(多语句行为)
// 写法3:方法实现如果是多条语句,不可以省略大括号
Comparator<Integer> comparator4 = (o1, o2) -> {
log.info("第一个数:{}", o1);
log.info("第二个数:{}", o2);
return o1.compareTo(o2);
};
log.info("****** 19和17的比较结果:{} ******", comparator4.compare(19, 17));
四大基本接口
消费性接口
/**
* @author : Lcywings
* @date : 2021/7/19 11:20
* @acl : true
* @description : 消费型接口 - 方法有一个参数,没有返回值
*/
static void testLambdaConsumer() {
// 有钱 1000000,以前如果实现不同的业务逻辑,必须增加不同的方法实现
consumData(1000000, money -> log.info("------ 有{}钱,买个小房子 ------", money));
consumData(1000000, money -> {
log.info("------ 有{}钱,买个小房子 ------", money);
log.info("------ 剩下的钱,吃喝玩乐 ------", money);
});
consumData(1000000, new Consumer<Double>() {
@Override
public void accept(Double aDouble) {
log.info("cnm*{}", 1000000);
}
});
}
// 将多个实现的方法整合为通用方法
static void consumData(double mondy, Consumer<Double> consumer) {
consumer.accept(mondy);
}
供给型接口
/**
* @author : Lcywings
* @date : 2021/7/19 11:20
* @acl : true
* @description : 供给型接口 - 没有参数,可以返回任意类型的结果
*/
static void testLambdaSupplier() {
Supplier<String> supplier = () -> "数据";
log.info("------ 供给型接口返回:{} ------", supplier.get());
}
断言型接口
/**
* @author : Lcywings
* @date : 2021/7/19 11:20
* @acl : true
* @description : 断言型接口 - 有一个参数,返回一个boolean类型的结果
*/
static void testLambdaPredicate() {
// 有字符串集合,根据不同的业务实现不同的数据返回
List<String> nameList = Arrays.asList("Mark", "Marry", "Jack", "LiLei", "HanMeiMei", "LiSuJie");
// 返回一个集合,将所有姓名中,包含L的姓名返回
List<String> nameContainL = collectNameListByCondition(nameList, name -> name.contains("L"));
log.info("------ 所有姓名中,包含L的姓名:{} ------", nameContainL);
// 返回一个集合,将所有姓名中,包含r或者n的姓名返回
List<String> nameContainrn = collectNameListByCondition(nameList, name -> name.contains("r") || name.contains("n"));
log.info("------ 所有姓名中,包含r或者n的姓名:{} ------", nameContainrn);
}
static List<String> collectNameListByCondition(List<String> names, Predicate<String> predicate) {
//定义返回集合
List<String> newNameList = new ArrayList<>();
//借助断言型接口,指定条件规则(注意;规则是在方法调用出指定的)
names.forEach(name -> {
// 判断姓名是否符合规则,使用断言型接口的test方法
if (predicate.test(name)) {
newNameList.add(name);
}
});
return newNameList;
}
函数型接口
函数型接口
/** * @author : Lcywings * @date : 2021/7/19 12:08 * @acl : true * @description : 函数型接口 - */ static void testLambdaFunction() { Function<Double, Long> function = Math::round; Long result = function.apply(123.55); log.info("------ 函数型接口处理数据,返回结果:{} ------", result); }
自定义函数型接口
/** * @author : Lcywings * @date : 2021/7/19 12:08 * @acl : true * @description : 自定义函数型接口 - */ static void testMyLambdaFunction(MyFunctionInterface<String> myFunctionInterface, String msg) { myFunctionInterface.hello(msg); myFunctionInterface.hi(); }
MyFunctionInterface.class
接口默认方法,可以有实现,Java1.8新更新内容
/** * Created on 2021/7/19. * <p> * Author : Lcywings * <p> * Description : 自定义函数式接口 * <p> * 定义:如果一个接口中,只用一个抽象方法,那么这个接口就是函数式接口 * 注意:@FunctionalInterface注解标注的接口必须是函数式接口否则接口会报错,但是:1.8支持在接口中声明默认实现方法 */ @FunctionalInterface public interface MyFunctionInterface<T> { void hello(T t); default void hi() { System.out.println("这个是默认方法可以直接从接口调用"); } }
调用方法
// 普通lowB调用 // testMyLambdaFunction(str -> System.out.println(str), "自定义函数式接口"); // 高↑级↓调用 testMyLambdaFunction(System.out::println, "自定义函数式接口");
Stream
生成流
生成流1-无中生有
1 of
// 1-5每个数字的平方
Stream.of(1, 2, 3, 4, 5).forEach(n -> System.out.print(n * n + ","));
2 iterate
// 获取从1开始前十个偶数,创建无限流
Stream.iterate(0, m -> m + 2).limit(10).forEach(y -> System.out.print(y + ","));
3 generate
// 获取10个随机数
Stream.generate(Math::random).limit(5).forEach(y -> System.out.print(y + ","));
生成流2-数组/集合创建流
1 操作数组
// 已知数列,获取最大值
int[] nums = {13, 23, 11, 45, 67, 34, 78, 56, 52};
// 数组没法直接进行流操作,先转换为流再操作
int maxNum = Arrays.stream(nums).max().getAsInt();
log.info("------ 数组最大值:{} ------", maxNum);
2 操作用户对象集合
// 操作用户对象集合,获取账户余额大于6000的用户信息
Stream<User> userStream = UserUtil.users.stream();
UserUtil.users.stream().forEach(user -> {
if (user.getLeftMoney() > 6000)
log.info("****** 余额大于6000的用户信息:{} ******", user);
});
流的中间操作
1-简单基本操作
1 筛选
// 筛选:filter,账户余额大于5000,且性别是男(1)的用户信息
UserUtil.users.stream().filter(user -> user.getLeftMoney() >= 5000).filter(user -> user.getGender() == 1).forEach(System.out::println);
2 截断
// 截断:limit,指定截断(最大)数量,返回对应数量的元素
UserUtil.users.stream().limit(5).forEach(System.out::println);
3 过滤
// 过滤:ship,过滤掉前面指定数量的元素,从后面元素开始
UserUtil.users.stream().skip(2).forEach(System.out::println);
4 去重
// 去重:distinct
Stream.of(11, 22, 33, 44, 22, 33, 11, 55).distinct().forEach(System.out::println);
简单流式操作练习(实现分页)
// 使用截断和过滤实现分页(每页展示三条数据,获取第二页数据)
UserUtil.users.stream().skip(3).limit(3).forEach(System.out::println);
复杂流式操作练习
2-映射操作
1 映射:map,将集合元素转换格式或者提取元素信息,会自动将映射规则作用到集合中的每个元素中,形成新的元素集合
List<String> strs = Arrays.asList("mark", "tom", "jack", "chouli");
strs.stream().map(String::toUpperCase).forEach(System.out::println);
2 将用户集合中,提取用户名并判断用户名长度大于5的用户的用户名信息
UserUtil.users.stream().map(User::getUserName).filter(uname -> uname.length() > 5).forEach(System.out::println);
3 mapToInt.规则作用到每个元素上,产生一个IntStream流,获取所有用户的用户名长度信息
UserUtil.users.stream().mapToInt(user -> user.getUserName().length()).forEach(System.out::println);
4 练习:获取strs中所有字符串的所有字符
strs.stream().map(str -> {
List<Character> characters = new ArrayList<>();
for (Character c : str.toCharArray()) {
characters.add(c);
}
return characters.stream();
}).forEach(System.out::println);
// 简化
strs.stream().map(a -> Arrays.stream(a.split(""))).forEach(System.out::println);
基本操作3-排序操作
1 排序: sorted,默认是使用元素的自然排序规则
List<String> strs = Arrays.asList("Mark", "Tom", "Jack", "Chouli");
strs.stream().sorted().forEach(System.out::println);
2 定制化排序,指定排序规则:先按照性别排序,再按照余额排序
UserUtil.users.stream().sorted((u1, u2) -> {
int result = Integer.compare(u1.getGender(), u2.getGender());
if (result == 0) {
// 如果性别相同,按照余额排序
return Double.compare(u1.getLeftMoney(), u2.getLeftMoney());
}
return result;
}).forEach(System.out::println);
基本操作4-匹配和查找操作
1 anyMatch():至少匹配一个,返回true,否则返回false(查找用户集合中,有没有人余额小于4000)
if (UserUtil.users.stream().anyMatch(user -> user.getLeftMoney() < 4000)) {
log.info("------ 用户中有人的余额小于4000 ------");
} else {
log.info("------ 所有用户的余额大于于4000 ------");
}
2 allMatch():全匹配,所有的都满足条件,返回true,否则返回false(查找用户集合中,是不是所有人姓名长度都大于4)
if (UserUtil.users.stream().allMatch(user -> user.getUserName().length() > 4)) {
log.info("------ 用户中所有人的姓名长度大于4 ------");
} else {
log.info("------ 用户中有人的姓名长度小于4 ------");
}
3 noneMatch():没有一个满足条件,返回true,否则返回false(查找用户集合中,是否有人的余额大于一万)
if (UserUtil.users.stream().noneMatch(user -> user.getLeftMoney() > 10000)) {
log.info("------ 用户中所有人的余额小于一万 ------");
} else {
log.info("------ 用户中有人的余额大于一万 ------");
}
4 返回集合中第一个元素
Optional<User> optionalUser1 = UserUtil.users.stream().findFirst();
log.info("------ 第一个用户信息:{} ------", optionalUser1);
5 返回集合中任意一个元素(数据量小,串行情况下,一般直接返回第一个,但是并行就是返回任意一个)
Optional<User> optionalUser2 = UserUtil.users.stream().findAny();
log.info("------ 任意一个用户信息:{} ------", optionalUser2);
6 练习1:获取用户中,最高余额
Optional<Double> maxLeftMoney = UserUtil.users.stream().map(User::getLeftMoney).max(Double::compare);
log.info("------ 获取用户中,最高余额:{} ------", maxLeftMoney);
7 练习2:获取用户中,最低余额的那个用户信息
Optional<User> minUser = UserUtil.users.stream().min(Comparator.comparingDouble(User::getLeftMoney));
log.info("------ 获取用户中,最低余额的那个用户信息:{} ------", minUser);
8 练习3:给所有的用户的用户余额增加500
UserUtil.users.stream().peek(user -> user.setLeftMoney(user.getLeftMoney() + 500)).forEach(System.out::println);
基本操作5-规约
1 reduce:规约,用来统计求和
List<Integer> nums = Arrays.asList(2, 4, 6, 8, 10);
Optional<Integer> sum1 = nums.stream().reduce((x, y) -> x + y);
log.info("------ 集合中数字总和:{} ------", sum1);
Optional<Integer> sum2 = nums.stream().reduce(Integer::sum);
log.info("------ 集合中数字总和:{} ------", sum2);
2 执行原理
Optional<Integer> sum3 = nums.stream().reduce((m, n) -> {
System.out.println("m = " + m);
m += n;
System.out.println("n = " + n);
System.out.println("m_new = " + m);
return m;
});
log.info("------ 集合中数字总和:{} ------", sum3);
3 练习1:计算数值集合中所有数字的平方和
Optional<Integer> numSqSum = nums.stream().map(x -> x * x).reduce(Integer::sum);
log.info("------ 所有数字的平方和:{} ------", numSqSum);
4 练习2:计算所有用户的账户余额总数
Optional<Double> userSum = UserUtil.users.stream().map(User::getLeftMoney).reduce(Double::sum);
log.info("------ 所有用户的账户余额总数:{} ------", userSum);
基本操作6-收集
1 collect:收集可以将流中的结果收集为新的集合,将用户中性别为女(0)的用户存入新的集合中
List<User> users = UserUtil.users.stream().filter(user -> user.getGender() == 0).collect(Collectors.toList());
users.forEach(System.out::println);
2 将用户中编号,姓名新的集合中
Map<Integer, String> userMap = UserUtil.users.stream().collect(Collectors.toMap(User::getUserId, User::getUserName));
3 数值集合,求最大,最小,平均,总和
List<Integer> nums = Arrays.asList(24, 56, 38, 76, 18, 47);
int sum = nums.stream().collect(Collectors.summingInt(Integer::intValue));
Double avg = nums.stream().collect(Collectors.averagingInt(Integer::intValue));
Integer max = nums.stream().collect(Collectors.maxBy(Integer::compare)).get();
Integer min = nums.stream().collect(Collectors.minBy(Integer::compare)).get();
log.info("------ sum={},avg={},max={},min={} ------", sum, avg, max, min);
4 一次性操作
IntSummaryStatistics summaryStatistics = nums.stream().collect(Collectors.summarizingInt(Integer::intValue));
log.info("------ summaryStatistics={} ------", summaryStatistics);
5 根据性别分组
Map<Integer, List<User>> group = UserUtil.users.stream().collect(Collectors.groupingBy(User::getGender));
log.info("------ 根据性别分组={} ------", group);
所有方法总结
- .of()
- .forEach()
- .iterate()
- .generate()
- .max()
- .getAsInt()
- .filter()
- .limit()
- .skip()
- .distinct()
- .map()
- .mapToInt()
- .sorted()
- .anyMatch()
- .allMatch()
- .noneMatch()
- .findFirst()
- .findAny()
- .min()
- .peek()
- .reduce()
- .collect()
扩展用法
Q.E.D.