一、Stream流
1.概述
- JDK8中,得益于Lambda的函数式编程,引入Stream;
- 目的:用于简化集合和数组操作的API;
2.使用步骤
- 先得到集合或者数组的Stream流
- 元素放上去,进过一次次过筛,留下想要的元素;
- Stream流简化的API来操作元素
/**
* 使用Stream流:
* 寻找集合中“张”开头且长度为3的名字
*/
public class StreamDemo01 {
public static void main(String[] args) {
ArrayList<String> names = new ArrayList<>();
Collections.addAll(names,"张36","张三","王海峰","张23","张风风风","张九六");
//使用Stream流
names.stream().filter(s -> s.startsWith("张")).filter(s -> s.length() == 3).forEach(s -> System.out.println(s));
/**
* 张36
* 张23
* 张九六
*/
}
}
3.Stream流的三种方法
- 获取Stream流
- 创建流水线,数据放上,准备操作;
- 集合:
- 直接使用stream()方法
- 数组:
- 使用静态的方法Arrays.stream()
- Stream.of()
- 中间方法
- 流水线上操作,一次操作后,还可以其他操作
- 终结方法
- 一个Stream流只能有一个终结方法,是流水线最后一个操作;
/**
* 集合和数组
* 创建Stream流的方式
*/
public class StreamDemo02 {
public static void main(String[] args) {
/**
* 集合
*/
Collection<String> lists = new ArrayList<>();
Stream<String> ls = lists.stream();
HashMap<String, Integer> maps = new HashMap<>();
//键流
Stream<String> keySets = maps.keySet().stream();
//值流
Stream<Integer> vs = maps.values().stream();
//键值对流
Stream<Map.Entry<String, Integer>> entryStream = maps.entrySet().stream();
/**
* 数组
*/
String[] names = {"完美","结论","五项","全能"};
Stream<String> namesStream = Arrays.stream(names);
Stream<String> namesStream2 = Stream.of(names);
}
}
4. Stream流的常用API
- 注意:
- 中间方法也称为非终结方法,调用完成后返回新的Stream流可以继续使用,支持链式编程。
- Stream流中无法直接修改集合和数组
/**
* Stream流的API
* 过滤;计数;取前面几个值;取之(n)后的值
* 去重;Map加工方法(修改值,变换类型,封入对象)
* 合并流;
*/
public class StreamDemo03 {
public static void main(String[] args) {
ArrayList<String> names = new ArrayList<>();
Collections.addAll(names,"张36","张三","王海峰","张23","张风风风","张九六");
//Stream<T> filter(Predicate<? super T> predicate);
names.stream().filter(s -> s.startsWith("张"));
//过滤条件,遍历集合中的每一个值,把符合条件的值返回
names.stream().filter(new Predicate<String>() {
@Override
public boolean test(String s) {
//return false;
return s.startsWith("张");
}
});
//终结:返回符合条件的值个数
long count = names.stream().filter(s -> s.length() == 3).count();
System.out.println(count);
//取前两个值输出
names.stream().limit(2).forEach(s -> System.out.println(s));
System.out.println("前=============后面的:");
//跳过前两个
names.stream().skip(2).forEach(s -> System.out.println(s));
//去重
names.stream().distinct().forEach(System.out::println);
//Map加工方法
//new Function<String, Strin> 前一个是集合内值得类型,后一个是加工后值得类型
//集合所有元素前+妈的
names.stream().map(new Function<String, String>() {
@Override
public String apply(String s) {
return "妈的"+ s;
}
});
names.stream().map(s -> "妈的2"+ s);
//需求:把上述名字加工成Teacher对象
//循环输出,对象都是s,可以直接System.out::println
names.stream().map(s -> new Teacher(s)).forEach(System.out::println);
//简化:构造器引用,方法引用
names.stream().map(Teacher::new).forEach(System.out::println);
//合并流:合并后的类型必须是两个的父类,或者相同类
Stream<String> ss1 = names.stream().filter(s -> s.startsWith("张"));
Stream<Integer> ss2 = Stream.of(55,66);
Stream<Object> ss3 = Stream.concat(ss1,ss2);
ss3.forEach(System.out::println);
}
}
5. Stream流的常见终结方法
- 终结操作方法,调用完成后,流无法继续使用,不会再返回Stream了
- forEach()
- count()
6. Stream流综合应用
/**
* Stream流的案例
* 1.求每个部门工资最高的员工,封入优秀员工表
* 2.取平均工资,去掉最高最低
* 3.统计两个部门的平均工资,去掉最高最低 两个流,合并
*/
public class StreamDemo04 {
public static double allMoney = 0;
public static double allTwoMoney = 0;
public static void main(String[] args) {
ArrayList<Employee> one = new ArrayList<>();
one.add(new Employee("A",'男',3000,2000,null));
one.add(new Employee("B",'男',3600,2000,null));
one.add(new Employee("C",'男',9000,1000,null));
one.add(new Employee("D",'男',8800,2000,null));
ArrayList<Employee> two = new ArrayList<>();
two.add(new Employee("AA",'男',6600,2000,null));
two.add(new Employee("BB",'男',7800,2500,"键"));
two.add(new Employee("CC",'男',6900,2000,null));
two.add(new Employee("DD",'男',3600,4000,"欠"));
two.add(new Employee("EE",'男',3000,6000,null));
//1.每个部门工资最高的员工
//选出一个值,直接get取到
// Employee eMax1 = one.stream().max((e1, e2) -> Double.compare(e1.getSalary() + e1.getBonus(), e2.getSalary() + e2.getBonus())).get();
TopperFormer topperFormerOne = one.stream().max((e1, e2) -> Double.compare(e1.getSalary() + e1.getBonus(), e2.getSalary() + e2.getBonus()))
.map(e -> new TopperFormer(e.getName(), e.getSalary() + e.getBonus())).get();
System.out.println(topperFormerOne);
//2.取平均工资,去掉最高最低
one.stream().sorted((e1, e2) -> Double.compare(e1.getSalary() + e1.getBonus(), e2.getSalary() + e2.getBonus()))
.skip(1).limit(one.size()-2)
.forEach(e ->{
//累计工资和;独立方法栈,变量必须取在main外的静态变量
allMoney+=(e.getSalary()+e.getBonus());
});
//一部门平均工资:7800.0
System.out.println("一部门平均工资:"+allMoney/(one.size()-2));
//3.统计两个部门的平均工资,去掉最高最低
//两个流,合并
Stream<Employee> oneStream = one.stream();
Stream<Employee> twoStream = two.stream();
Stream<Employee> concatStream = Stream.concat(oneStream, twoStream);
concatStream.sorted((e1, e2) -> Double.compare(e1.getSalary() + e1.getBonus(), e2.getSalary() + e2.getBonus()))
.skip(1).limit(one.size()+two.size()-2)
.forEach(e ->{
//累计工资和;独立方法栈,变量必须取在main外的静态变量
allTwoMoney+=(e.getSalary()+e.getBonus());
});
//处理精度问题
BigDecimal a = BigDecimal.valueOf(allTwoMoney);
BigDecimal b = BigDecimal.valueOf(one.size()+two.size()-2);
//保留两位小数,四舍五入
//两部门平均工资:8571.43 8571.42857142857
System.out.println("两部门平均工资:"+a.divide(b,2, RoundingMode.HALF_UP));
}
}
7.收集Stream流
- 收集含义:流操作后的结果传回集合或数组中。
- Stream流:方便操作集合、数组手段;
- 集合、数组:才是开发目的;
/**
* 收集Stream流
*/
public class StreamDemo05 {
public static void main(String[] args) {
ArrayList<String> names = new ArrayList<>();
Collections.addAll(names,"张36","张三","王海峰","张23","张风风风","张九六");
Stream<String> stream = names.stream().filter(s -> s.startsWith("张"));
//收集流打成list
//Collectors.toList() new ArrayList 循环,将流内的数据添加
List<String> lists = stream.collect(Collectors.toList());
//下方的最新API的集合还是不可变集合,不可修改添加数据
//List<String> lists = stream.toList();
/**
* 注意:
* 流只能使用一次,上面用过就关闭了
*/
//HashSet::new
Set<String> sets = stream.collect(Collectors.toSet());
//直接收集为数组;默认为Object类型
Object[] objects = stream.toArray();
//自定义收集数组类型;根据流的长度创建数组
String[] obj = stream.toArray(value -> new String[value]);
String[] obj2 = stream.toArray(String[]::new);
}
}
二、异常
1.什么是异常
- 程序在编译或者执行中出现的问题;
- 如:数组索引越界、空指针异常、日期格式化异常;
- 注意:
- 异常没有提前处理,程序会退出JVM虚拟机而终止;
- 研究异常,避免异常,提前处理异常、体现的是程序的安全健壮性
2.异常体系
- Throwable:(抛出异常)
- Error 错误
- 系统级别问题,JVM退出等,代码无法干预
- Exception 异常
- RuntimeException及其子类:
- 运行时异常,编译时不会报错
- 编译成class文件不需要处理,运行字节码文件可能出现异常
- 数组索引越界、空指针异常
- 除了RuntimeException之外所有异常
- 编译时异常(又称 受检异常),编译期必须处理,否则程序不能通过编译
- 日期格式化异常
- RuntimeException及其子类:
- Error 错误
3.运行时异常
一般是编程逻辑不严谨导致的程序错误;
| 数组索引越界异常: | ArrayIndexOutOfBoundsException |
|---|---|
| 空指针异常: | Null Pointer Exception |
| 数字操作异常: | Arithmetic Exception |
| 类型转换异常: | ClassCastException |
| 数字转换异常: | NumberFormatException |
public class ExceptionDemo01 {
public static void main(String[] args) {
String a = "123456";
Integer aa = Integer.valueOf(a);
Integer b = 123456;
String bb = b.toString();
String bbb = String.valueOf(b);
String c = "123456abc";
//运行时异常,非法参数
Integer cc = Integer.valueOf(c);
}
}
4.编译时异常
- 写代码时就报错,不处理,跑不起来。
- 在容易出错的地方,提醒程序员
public static void main(String[] args) throws ParseException {
String data = "2022-08-03 23:30:30";
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
Date date = sdf.parse(data);
System.out.println(data);
}
5.异常默认处理流程
- 默认在异常代码处自动创建异常对象:
Arithmetic Exception - 异常会从方法中出现位置抛给调用者,调用者抛给虚拟机
- JVM接收到异常对象后,控制台输出异常栈信息数据
- 直接从当前执行的异常点干掉当前程序;
- 程序死亡,后续代码不执行
6.编译时异常处理形式
- 三种处理形式:
- 异常直接抛给调用者,调用者继续抛出
- throws(异常1,异常2,...):用在方法上,异常抛给调用者处理
- throws Exception 抛出一切异常,不用单独列出
- 不好:不处理异常,如果异常最终传递到虚拟机,程序死亡
- 自己捕获异常处理
- try catch
- 可以有多个异常放在一个try 放多个catch分别处理各个异常
- 建议用一个catch ;Exception打印出一切异常
- 监视捕获异常,方法内部,直接处理
- 好处:独立完成异常处理,程序可以继续往下执行。
- try catch
- 二者结合,异常抛给调用者,调用者捕获处理
- 好处:上层调用者可以知道底层失败原因,决策下一步怎么走
- 该种处理最符合开发规范
- 异常直接抛给调用者,调用者继续抛出
7.运行时异常处理
建议在最外层调用处同一集中处理;
8.异常处理案例
public class RuntimeDemo01 {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
while (true){
try {
System.out.println("输入合法价格:");
String s = sc.nextLine();
Double price = Double.valueOf(s);
if (price > 0){
System.out.println("价格是:"+price);
break;
}else {
System.out.println("请输入正数");
}
} catch (Exception e) {
System.out.println("请检查输入值");
}
}
}
}
9.自定义异常
9.1 自定义异常必要性
- java无法为所有的问题提供异常;
- 可以通过异常来管理某个业务问题;
9.2好处
- 可以使用异常的机制管理业务问题,提醒程序员注意
- 出现bug,异常形式,清晰指出出错地方;
9.3 自定义异常分类
1.自定义编译时异常
- 定义异常类继承Exception;
- 重写构造器;
- 出现异常的地方用throw new 自定义对象抛出;
- 作用:提醒强烈!
/**
* 自定义年龄非法异常
* 1.继承Exception
* 2.重写构造器
*/
public class AgeIlleagaException extends Exception{
public AgeIlleagaException() {
}
//返回异常信息
public AgeIlleagaException(String message) {
super(message);
}
}
public class ExceptionDemo01 {
public static void main(String[] args) {
try {
checkAge(-99);
} catch (AgeIlleagaException e) {
e.printStackTrace();
}
}
public static void checkAge(int age) throws AgeIlleagaException {
if (age<0 || age>200){
//throws 用在方法上,抛出异常
//throw 方法内部直接创建一个异常对象,并从此点抛出
throw new AgeIlleagaException(age+"年龄非法");
}else {
System.out.println("合法");
}
}
}
2.自定义运行时异常
- 定义异常类继承RuntimeException;
- 重写构造器;
- 出现异常的地方用throw new 自定义对象抛出;
- 提醒不强烈;
三、日志
1.日志概述
用来记录程序运行过程中的信息,并可以进行永久存储;
1.1日志技术具备的优势
- 将系统执行的信息选择性记录到指定位置(控制台、文件、数据库);
- 随时以开关形式控制是否记录日志,无需修改源代码;
- 性能较好;多线程,同步执行,避开业务记录日志
2.日志技术体系
- 体系结构:(日志规范接口)
Commons Logging简称JCLSimple Logging Facade for Java简称 slf4j- Log4j
- JUL
- Logback
- 日志规范:
- 一些接口,提供给日志实现框架设计的标准
- 日志框架
- 第三方做好的日志记录实现代码,直接拿去用
3. Logback日志框架
- 性能比log4j好
- 官方网站:
3.1框架介绍
| 模块 | ||
|---|---|---|
| logback-core | 为其他两个模块奠定了基础。 | |
| logback-classic | log4j 的一个改良的版本,实现了slf4j API | logback-classic原生实现了SLF4J API 可以在logback和其他日志框架(如log4j 或java.util.logging (JUL ))之间来回切换。 |
| logback-access | 集成了Servlet容器,如Tomcat和Jetty,以提供HTTP访问日志功能。 | 注意,可以在logback-core的基础上轻松构建自己的模块。 |
3.2 Logback使用哪几个模块,各自作用
| 模块 | 作用 |
|---|---|
| slf4j-api | 日志规范 |
| logback-core | 基础模块 |
| logback-classic | log4j的改良版本,完整实现了slf4j-api |
4.使用Logback框架开发步骤
- 项目下新建lib文件,相关jar包导入,并添加到项目库中
- 核心配置文件logback.xml放到src目录下
- 代码中获取日志对象;
- 使用日志对象输出日志信息;
5.日志级别
- 级别程度依次:TRACE(路径跟踪)<DEBUG(调试)<INFO(关键信息)<WARN(警告)<ERROR(错误)
- 默认级别DEBUG
- 作用:用于控制日志输出级别,只输出不低于设定级别的日志
- ALL和OFF分别是打印全部日志,关闭全部日志
6.logback.xml配置详解
<?xml version="1.0" encoding="UTF-8"?>
<!-- 配置文件修改时重新加载,默认true -->
<configuration scan="true">
<!-- 控制台输出 -->
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<!--System.err打印字体红色-->
<target>System.out</target>
<encoder charset="UTF-8">
<!-- 输出日志记录格式 -->
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<!-- FILE输出方向通向文件 -->
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<encoder charset="UTF-8">
<!-- 输出日志记录格式 -->
<!-- %thread线程名, %-5level级别从左显示五个字符宽度
%msg日志消息 %n换行符 -->
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
<charset>utf-8</charset>
</encoder>
<!--日志输出路径-->
<file>F:/work/data/data.log</file>
<!-- 指定日志文件拆分和压缩规则-->
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<!--通过指定压缩文件名称,来确定分割文件方式-->
<fileNamePattern>F:/work/data/data-%d{yyyyMMdd}.log%i.gz</fileNamePattern>
<!--文件拆分大小-->
<maxFileSize>1MB</maxFileSize>
</rollingPolicy>
</appender>
<!-- 设置日志输出级别 -->
<root level="ALL">
<!--控制台和文件的控制开关,注释后不再打印日志-->
<appender-ref ref="CONSOLE" />
<appender-ref ref="FILE" />
</root>
</configuration>
RecordDate:2021/08/14