八、Stream、异常体系
需要学会什么?
- 不可变集合:有些业务场景下需要有不可变集合对象,Java如何得到不可变集合对象。
- Stream流:集合自己提供的API非常繁琐。JDK8开始,得益于Lambda,提供了操作集合、数组更好用的技术:Stream。
- 认识异常体系:程序一旦出现了bug则会终止,如何经理避免程序出现异常,出现异常如何进行处理让程序更稳健。
- 日志框架:系统在开发阶段或者上线后,一旦业务出现问题,需要有信息去定位,如何记录程序的运行情况?
1.创建不可变集合
什么是不可变集合?
- 不可变集合,就是不可被修改的集合。
- 集合的数据项在创建的时候提供,并且在整个生命周期都不可改变,否则报错。
为什么要创建不可变集合?
- 如果要某个数据不能被修改,把它防御性地拷贝到不可变集合中是个很好的实践。
- 或者当集合对象被不信任的库调用时,不可变形式是安全的。
如何创建不可变集合?
- 在List、Set、Map接口中,都存在of方法,可以创建一个不可变的集合。
- 这个集合不能添加,不能删除,不能修改。
方法名称 | 说明 |
---|---|
static List of(E…elements) | 创建一个具有指定元素的List集合对象。 |
static Set of(E…elements) | 创建一个具有指定元素的Set集合对象。 |
static <K, V> Map <K, V> of(E…elements) | 创建一个具有指定元素的Map集合对象。 |
Test.java
import java.util.List;
import java.util.Map;
import java.util.Set;
public class Test {
public static void main(String[] args) {
// 不可变List集合 不能增加/删除/修改
List<Double> list = List.of(569.9, 700.9, 499.9, 600.0, 500.0);
// 700.9
System.out.println(list.get(1));
// [569.9, 700.9, 499.9, 600.0, 500.0]
System.out.println(list);
// 不可变Set集合 不能增加/删除/修改 不允许元素重复
Set<String> set = Set.of("白子画", "花千骨", "霓漫天");
// [花千骨, 白子画, 霓漫天]
System.out.println(set);
// 不可变Map集合 不能增加/删除/修改 不允许键重复
Map<String, Integer> map = Map.of("显示屏", 2, "主机", 1, "键盘", 1, "鼠标", 1);
// {键盘=1, 显示屏=2, 主机=1, 鼠标=1}
System.out.println(map);
}
}
2.Stream流
a.Stream流的概述
什么是Stream流?
- 在Java8中,得益于Lambda所带来的函数式编程,引入了一个全新的Stream流概念。
- 目的:用于简化集合和数组操作的API。
案例:体验Stream流的作用。
需求:
创建一个集合,存储多个字符串元素。
List<String> list = new ArrayList<>(); list.add("张无忌"); list.add("赵敏"); list.add("周芷若"); list.add("小昭"); list.add("殷离"); list.add("张翠山"); list.add("谢逊"); list.add("灭绝师太"); list.add("张三丰"); list.add("张中"); list.add("张士诚");
把集合中所有以”张“开头的元素存储到一个新的集合。
把”张“开头的集合中的长度为3的元素存储到一个新的集合。
遍历上一步得到的集合中的元素输出。
Test.java
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class Test {
public static void main(String[] args) {
// 创建集合
List<String> names = new ArrayList<>();
Collections.addAll(names, "张无忌", "赵敏", "周芷若", "小昭", "殷离", "张翠山", "谢逊", "灭绝师太", "张三丰", "张中", "张士诚");
// [张无忌, 赵敏, 周芷若, 小昭, 殷离, 张翠山, 谢逊, 灭绝师太, 张三丰, 张中, 张士诚]
System.out.println(names);
// 从集合中找出姓"张"的放到新集合
List<String> zhangList = new ArrayList<>();
for (String name : names) {
if (name.startsWith("张")) {
zhangList.add(name);
}
}
// [张无忌, 张翠山, 张三丰, 张中, 张士诚]
System.out.println(zhangList);
// 把"张"开头的集合中的长度为3的元素存储到一个新的集合
List<String> zhangThreeList = new ArrayList<>();
for (String name : zhangList) {
if (name.length() == 3) {
zhangThreeList.add(name);
}
}
// [张无忌, 张翠山, 张三丰, 张士诚]
System.out.println(zhangThreeList);
/* Stream实现*/
names.stream().filter(s -> s.startsWith("张")).filter(s -> s.length() == 3).forEach(System.out::println);
names.stream().filter(s -> s.startsWith("张") && s.length() == 3).forEach(System.out::println);
}
}
Stream流式思想的核心:
- 先得到集合或数组的Stream流(就是一根传送带)。
- 把元素放上去。
- 然后就用这个Strem流简化的API来方便的操作元素。
总结:
- Stream流的作用是什么,结合了什么技术?
- 简化集合、数组操作的API。结合了Lambda表达式。
b.Stream流的获取
Stream流的三类方法:
- 获取Stream流。
- 创建一条流水线,并把数据放到流水线上准备进行操作。
- 中间方法。
- 流水线上的操作。依次操作完毕之后,还可以继续进行其他操作。
- 终结方法。
- 一个Stream流只能由一个终结方法,是流水线上的最后一个操作。
Stream操作集合或者数组的第一步是先得到Stream流,然后才能使用流的功能。
集合获取Stream流的方式:
- 可以使用Collection接口中的默认方法stream()生成流。
名称 | 说明 |
---|---|
defalut Stream stream() | 获取当前集合对象的Stream流。 |
数组获取Stream流的方式:
名称 | 说明 |
---|---|
public static Stream steam(T[] array) | 获取当前数组的Stream流。 |
public static Stream of(T…values) | 获取当前数组/可变数据的Stream流。 |
Test.java
import java.util.*;
import java.util.stream.Stream;
public class Test {
public static void main(String[] args) {
/* Collection集合获取流 */
Collection<String> list = new ArrayList<>();
Stream<String> listStream = list.stream();
/* Map集合获取流 */
Map<String, Integer> map = new HashMap<>();
// 键流
Stream<String> keyStream = map.keySet().stream();
// 值流
Stream<Integer> valueStream = map.values().stream();
// 键值对流(拿整体)
Stream<Map.Entry<String, Integer>> keyValueStream = map.entrySet().stream();
/* 数组获取流 */
String[] names = {"白子画", "花千骨", "杀阡陌"};
Stream<String> stringStream = Arrays.stream(names);
Stream<String> stringStream1 = Stream.of(names);
}
}
c.Stream流的常用API
Stream流的常用API(中间操作方法):
名称 | 说明 |
---|---|
Stream filter(Predicate<? super T> predicate) | 用于对流中的数据进行过滤。 |
Stream limit(long maxSize) | 获取前几个元素。 |
Stream skip(long n) | 跳过前几个元素。 |
Stream distinct() | 去除流中重复的元素。依赖(hashCode()和equals()) |
static Stream concat(Stream a, Stream b) | 合并a和b两个流为一个流。 |
注意:
- 中间方法也称为非终结方法,调用完成后返回新的Stream流可以继续使用,支持链式编程。
- 在Stream流中无法直接修改集合、数组中的数据。
Test.java
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Stream;
public class Test {
public static void main(String[] args) {
// 创建集合
List<String> list = new ArrayList<>();
Collections.addAll(list, "张无忌", "赵敏", "周芷若", "小昭", "殷离", "张翠山", "谢逊", "灭绝师太", "张三丰", "张中", "张士诚");
/* 对流中的数据进行过滤 */
// Stream<T> filter(Predicate<? super T> var1)
list.stream().filter(new Predicate<String>() {
@Override
public boolean test(String s) {
return s.startsWith("张");
}
});
list.stream().filter(s -> s.startsWith("张")).forEach(System.out::println);
/* 统计元素个数 */
long size = list.stream().filter(s -> s.length() == 3).count();
System.out.println(size);
/* 获取前几个元素 */
list.stream().filter(s -> s.startsWith("张")).limit(2).forEach(System.out::println);
/* 跳过前几个元素 */
list.stream().filter(s -> s.startsWith("张")).skip(2).forEach(System.out::println);
/* Map加工方法 */
// 给集合元素的前面都加上一个标识
list.stream().map(s -> "加工" + s).forEach(System.out::println);
// 把所有名字都加工成一个学生对象
list.stream().map(s -> new Student(s)).forEach(System.out::println);
list.stream().map(Student::new).forEach(System.out::println);
/* 合并流 */
Stream<String> stringStream = list.stream().filter(s -> s.startsWith("张"));
Stream<String> stringStream1 = Stream.of("Java1", "Java2");
// 合并
// Stream<String> stringStreamConcat = Stream.concat(stringStream, stringStream1);
// stringStreamConcat.forEach(System.out::println);
// 合并两个不同类型的流 要用两个类型的共同父类来接
Stream<Integer> stringStream2 = Stream.of(99, 100);
Stream<Object> stringStreamConcat1 = Stream.concat(stringStream, stringStream2);
stringStreamConcat1.forEach(System.out::println);
}
}
Stream流的常见终结操作方法:
名称 | 说明 |
---|---|
void forEach(Consumer action) | 对此流的每个元素执行遍历操作。 |
long count() | 返回此流中的元素数。 |
**注意:**终结操作方法,调用完成后流就无法继续使用了,原因是不会返回Stream了。
总结:
- 终结和非终结方法的含义是什么?
- 终结方法后流不可以继续使用,非终结方法会返回新的流,支持链式编程。
d.Stream流的综合应用
需求:
- 某个公司的研发部门,分为开发一部和二部,现在需要进行年终数据结算。
分析:
- 员工信息至少包含了(姓名、性别、工资、奖金、处罚记录)。
- 开发一部有4名员工、开发二部有5名员工。
- 分别筛选处2个部门的最高工资的员工信息,封装成优秀员工对象TopPerformer。
- 分别统计出2个部门的平均月收入,要求去掉最高和最低工资。
- 统计2个开发部门整体的平均工资,要求去掉最高和最低工资。
Employee.java
import java.util.List;
public class Employee {
// 姓名
private String name;
// 性别
private char sex;
// 月薪
private double salary;
// 奖金
private double bonus;
// 处罚
private String punish;
public Employee() {
}
public Employee(String name, char sex, double salary, double bonus, String punish) {
this.name = name;
this.sex = sex;
this.salary = salary;
this.bonus = bonus;
this.punish = punish;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public char getSex() {
return sex;
}
public void setSex(char sex) {
this.sex = sex;
}
public double getSalary() {
return salary;
}
public void setSalary(double salary) {
this.salary = salary;
}
public double getBonus() {
return bonus;
}
public void setBonus(double bonus) {
this.bonus = bonus;
}
public String getPunish() {
return punish;
}
public void setPunish(String punish) {
this.punish = punish;
}
@Override
public String toString() {
return "Employee{" +
"name='" + name + '\'' +
", sex=" + sex +
", salary=" + salary +
", bonus=" + bonus +
", punish='" + punish + '\'' +
'}';
}
}
TopPerformer.java
public class TopPerformer {
// 姓名
private String name;
// 月薪
private double salary;
public TopPerformer() {
}
public TopPerformer(String name, double salary) {
this.name = name;
this.salary = salary;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public double getSalary() {
return salary;
}
public void setSalary(double salary) {
this.salary = salary;
}
@Override
public String toString() {
return "TopPerformer{" +
"name='" + name + '\'' +
", salary=" + salary +
'}';
}
}
Test.java
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.stream.Stream;
public class Test {
// 各部门所有月收入
public static double allMoney = 0;
// 两个部门所有月收入
public static double allMoneyAll = 0;
public static void main(String[] args) {
// 开发一部
List<Employee> employeeList1 = new ArrayList<>();
employeeList1.add(new Employee("白子画", '男', 10000, 200000000, null));
employeeList1.add(new Employee("花千骨", '女', 10000, 2000, "顶撞师父"));
employeeList1.add(new Employee("杀阡陌", '男', 10000, 200000, null));
// 开发二部
List<Employee> employeeList2 = new ArrayList<>();
employeeList2.add(new Employee("张无忌", '男', 30000, 2000000, "错放陈友谅"));
employeeList2.add(new Employee("赵敏", '女', 10000, 20000, null));
employeeList2.add(new Employee("周芷若", '女', 10000, 2000, "反复无常"));
employeeList2.add(new Employee("小昭", '女', 10000, 20000, null));
employeeList2.add(new Employee("殷离", '女', 10000, 10000, null));
employeeList2.add(new Employee("张翠山", '男', 10000, 500, "冲动易怒惹人厌"));
// 最高工资的员工
// Employee topSalaryEmployee1 = employeeList1.stream().max((employee1, employee2) -> {
// return Double.compare(employee1.getSalary() * 12 + employee1.getBonus(), employee2.getSalary() * 12 + employee2.getBonus());
// }).get();
// System.out.println(topSalaryEmployee1);
//
// Employee topSalaryEmployee2 = employeeList2.stream().max((employee1, employee2) -> {
// return Double.compare(employee1.getSalary() * 12 + employee1.getBonus(), employee2.getSalary() * 12 + employee2.getBonus());
// }).get();
// System.out.println(topSalaryEmployee2);
/* 封装成优秀员工对象 */
// 封装成优秀员工对象 开发一部
TopPerformer topPerformer1 = employeeList1.stream().max(new Comparator<Employee>() {
@Override
public int compare(Employee employee1, Employee employee2) {
return 0;
}
}).map(employee -> new TopPerformer(employee.getName(), employee.getSalary() * 12 + employee.getBonus())).get();
System.out.println(topPerformer1);
// 封装成优秀员工对象 开发二部
TopPerformer topPerformer2 = employeeList2.stream().max(new Comparator<Employee>() {
@Override
public int compare(Employee employee1, Employee employee2) {
return 0;
}
}).map(employee -> new TopPerformer(employee.getName(), employee.getSalary() * 12 + employee.getBonus())).get();
System.out.println(topPerformer2);
/* 统计平均月收入 要求去掉最高和最低工资 */
// 统计平均月收入 要求去掉最高和最低工资 开发一部
employeeList1.stream().sorted(new Comparator<Employee>() {
@Override
public int compare(Employee employee1, Employee employee2) {
return Double.compare(employee1.getSalary() * 12 + employee1.getBonus(), employee2.getSalary() * 12 + employee2.getBonus());
}
}).skip(1).limit(employeeList1.size() - 2).forEach(employee -> {
// 去掉最高和最低工资后的工资总和
allMoney += (employee.getSalary() * 12 + employee.getBonus());
});
System.out.println("开发一部的平均工资是:" + allMoney / (employeeList1.size() - 2));
// 统计平均月收入 要求去掉最高和最低工资 开发二部
allMoney = 0;
employeeList2.stream().sorted(new Comparator<Employee>() {
@Override
public int compare(Employee employee1, Employee employee2) {
return Double.compare(employee1.getSalary() * 12 + employee1.getBonus(), employee2.getSalary() * 12 + employee2.getBonus());
}
}).skip(1).limit(employeeList2.size() - 2).forEach(employee -> {
// 去掉最高和最低工资后的工资总和
allMoney += (employee.getSalary() * 12 + employee.getBonus());
});
System.out.println("开发二部的平均工资是:" + allMoney / (employeeList2.size() - 2));
/* 统计2个开发部门整体的平均工资,要求去掉最高和最低工资 */
Stream<Employee> employeeStream1 = employeeList1.stream();
Stream<Employee> employeeStream2 = employeeList2.stream();
Stream<Employee> employeeStreamAll = Stream.concat(employeeStream1, employeeStream2);
employeeStreamAll.sorted(new Comparator<Employee>() {
@Override
public int compare(Employee employee1, Employee employee2) {
return Double.compare(employee1.getSalary() * 12 + employee1.getBonus(), employee2.getSalary() * 12 + employee2.getBonus());
}
}).skip(1).limit(employeeList1.size() + employeeList2.size() - 2).forEach(employee -> {
allMoneyAll += (employee.getSalary() * 12 + employee.getBonus());
});
System.out.println("2个开发部门整体的平均工资是:" + allMoneyAll / (employeeList1.size() + employeeList1.size() - 2));
}
}
e.收集Stream流
Stream流的收集操作:
- 收集Stream流的含义:就是把Stream流操作后的结果数据转回到集合或者数组中去。
- Stream流:方便操作集合/数组的手段。
- 集合/数组:才是开发中的目的。
Stream流的收集方法:
名称 | 说明 |
---|---|
R collect(Collector collector) | 开始收集Stream流,指定收集器。 |
Collectors工具类提供了具体的收集方式:
名称 | 说明 |
---|---|
public static Collector toList() | 把元素收集到List集合中。 |
public static Collector toSet() | 把元素收集到Set集合中。 |
public static Collector toMap(Function keyMapper, Function valueMapper) | 把元素收集到Map集合中。 |
Test.java
import java.util.*;
import java.util.function.IntFunction;
import java.util.stream.Collectors;
import java.util.stream.Stream;
/**
* 目标:收集Stream流的数据到 集合或数组中去。
*/
public class Test {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
Collections.addAll(list, "张无忌", "赵敏", "周芷若", "小昭", "殷离", "张翠山", "谢逊", "灭绝师太", "张三丰", "张中", "张士诚");
System.out.println(list);
Stream<String> stringStream1 = list.stream().filter(s -> s.startsWith("张"));
// 得到不可变集合
// System.out.println(stringStream1.toList());
// 把元素收集到List集合中
List<String> zhangList = stringStream1.collect(Collectors.toList());
// [张无忌, 张翠山, 张三丰, 张中, 张士诚]
System.out.println(zhangList);
Stream<String> stringStream2 = list.stream().filter(s -> s.startsWith("张"));
// 把元素收集到Set集合中
Set<String> zhangSet = stringStream2.collect(Collectors.toSet());
// [张翠山, 张中, 张三丰, 张无忌, 张士诚]
System.out.println(zhangSet);
Stream<String> stringStream3 = list.stream().filter(s -> s.startsWith("张"));
// 把元素收集到数组中
Object[] array = stringStream3.toArray();
// 拓展
String[] stringArray = stringStream3.toArray(new IntFunction<String[]>() {
@Override
public String[] apply(int i) {
return new String[i];
}
});
// [张无忌, 张翠山, 张三丰, 张中, 张士诚]
System.out.println(Arrays.toString(array));
}
}
总结:
- 收集Stream流的作用?
- Stream流是操作集合/数组的手段。
- 操作的结果数据最终要恢复到集合或者数组中去。
3.异常处理
a.异常概述、体系
什么是异常?
- 异常是程序在“编译”或者“执行”的过程中可能出现的问题。(注意:语法错误不算在异常体系中。)
- 比如:数组索引越界、空指针异常、日期格式化异常,等…。
为什么要学习异常?
- 异常一旦出现了,如果没有提前处理,程序就会退出JVM虚拟机而终止。
- 研究异常并且避免异常,然后提前处理异常,体现的是程序的安全性、健壮性。
异常体系:
Error:
- 系统级别问题,JVM退出等,代码无法控制。
Exception:java.lang包下,称为异常类,它代表程序本身可以处理的问题。
- RuntimException及其子类:运行时异常,编译阶段不会报错。(如:空指针异常、数组索引越界异常等。)
- 除RuntimeException之外所有的异常:编译时异常,编译期必须处理的,否则程序不能通过编译。(日期格式化异常。)
编译时异常和运行时异常:
- 编译时异常就是编译的时候出现的异常。
- 运行时异常就是在运行时出现的异常。
总结:
- 异常是什么?
- 异常就是代码在编译或者执行的过程中可能出现的错误。
- 异常分为几类?
- 编译时异常、运行时异常。
- 编译时异常:没有继承RuntimeException的异常,编译阶段就会出错。
- 运行时异常:继承自RuntimeException的异常或其子类,编译阶段不报错,运行可能报错。
- 学习异常的目的?
- 避免异常的出现,同时处理可能出现的异常,让代码更稳健。
b.常见运行时异常
运行时异常示例:
- 数组索引越界异常:ArrayIndexOutOfBoundsException。
- 空指针异常:NullPointerException,直接输出没有问题,但是调用空指针的变量的功能就会报错。
- 数字操作异常:arithmeticException。
- 类型转换异常:ClassCastException。
- 数字转换异常:NumberFormatException。
运行时异常:一般是程序员业务没有考虑好或者是编程逻辑不严谨引起的程序错误。
Test.java
public class Test {
public static void main(String[] args) {
/* 数组索引越界异常:ArrayIndexOutOfBoundsException */
int[] array = {10, 20, 30};
System.out.println(array[2]);
// 日志栈 先执行的日志在下面
// Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: Index 3 out of bounds for length 3
// at com.javase.exceptiondemo.Test1.main(Test1.java:11)
// System.out.println(array[3]);
/* 空指针异常:NullPointerException,直接输出没有问题,但是调用空指针的变量的功能就会报错 */
String name = null;
System.out.println(name);
// System.out.println(name.length());
/* 数字操作异常:arithmeticException */
// int c = 10 / 0;
/* 类型转换异常:ClassCastException */
Object o = 23;
// String s = (String) o;
/* 数字转换异常:NumberFormatException */
String number = "23A";
Integer it = Integer.valueOf(number);
System.out.println(it + 1);
System.out.println("程序结束");
}
}
c.常见编译时异常
编译时异常:
- 不是RuntimeException或者其子类的异常,编译阶段就报错,必须处理,否则代码不通过。
编译时异常示例:
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
public class Test {
public static void main(String[] args) throws ParseException {
String dateString = "2022-07-23 16:00:00";
// 创建简单日期格式化类对象:解析字符串时间成为日期对象
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
Date date = simpleDateFormat.parse(dateString);
System.out.println(date);
}
}
编译时异常的作用是什么:
- 是担心程序员的技术不行,在编译阶段就爆出一个错误,目的在于提醒不要出错。
- 编译时异常是可遇不可求。
d.异常的默认处理流程
- 默认会在出现异常的代码那里自动的创建一个异常对象:ArithmeticException。
- 异常会从方法中出现的点这里抛出给调用者,调用者最终抛出给JVM虚拟机。
- 虚拟机接收到异常对象后,先在控制台直接输出异常栈信息数据。
- 直接从当前执行的异常点干掉当前程序。
- 后续代码没有机会执行了,因为程序已经死亡。
Test.java
public class Test {
public static void main(String[] args) {
System.out.println("---程序开始---");
division(10, 5);
division(10, 0);
System.out.println("---程序结束---");
}
/**
* 除法
* @param a 被除数
* @param b 除数
*/
public static void division(int a, int b) {
System.out.println(a);
System.out.println(b);
int c = a / b;
System.out.println(c);
}
}
总结:
- 默认异常处理机制。
- 默认的异常处理机制并不好,一旦出现异常,程序立即死亡。
e.编译时异常的处理机制
编译时异常的处理形式有三种:
- 出现异常直接抛出去给调用者,调用者也继续抛出去。
- 出现异常自己捕获处理,不麻烦别人。
- 前两者结合,出现异常直接抛出去给调用者,调用者捕获处理。
异常处理方式一:throws
- throws:用在方法上,可以将方法内部出现的异常抛出去给本方法的调用者处理。
- 这种方式并不好,发生异常的方法自己不处理异常,如果异常最终抛出去给虚拟机将引起程序死亡。
抛出异常格式:
方法 throws 异常1, 异常2, 异常3 ... {
}
规范做法:
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
public class Test {
public static void main(String[] args) throws ParseException {
System.out.println("---程序开始---");
parseTime("2022-07-23 18:00:00");
System.out.println("---程序结束---");
}
private static void parseTime(String s) throws ParseException {
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
Date date = simpleDateFormat.parse(s);
System.out.println(date);
}
}
异常处理方式2:try…catch…
- 监视捕获异常,用在方法内部,可以将方法内部出现的异常直接捕获处理。
- 方式异常的方法自己独立完成异常的处理,程序可以继续往下执行。
格式:
try {
// 监视可能出现
} catch(异常类型1 变量) {
// 处理异常
} catch(异常类型2 变量) {
// 处理异常
} ...
建议格式:
try {
// 监视可能出现
} catch(Exception e) {
// 打印异常栈信息
e.printStackTrace();
}
// Exception可以捕获处理一切异常类型!
Test.java
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.InputStream;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
public class Test {
public static void main(String[] args){
System.out.println("---程序开始---");
parseTime("2022-07-23 18:00:00");
System.out.println("---程序结束---");
}
private static void parseTime(String s) {
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
Date date = null;
// 整体捕获异常
try {
date = simpleDateFormat.parse(s);
InputStream inputStream = new FileInputStream("a.jpg");
System.out.println(inputStream);
} catch (ParseException | FileNotFoundException e) {
e.printStackTrace();
}
System.out.println(date);
// 分开捕获异常
// try {
// date = simpleDateFormat.parse(s);
// } catch (ParseException e) {
// // 解析出现问题
// System.out.println("解析时间出现异常!");
// e.printStackTrace();
// // throw new RuntimeException(e);
// }
// System.out.println(date);
//
// try {
// InputStream inputStream = new FileInputStream("a.jpg");
// System.out.println(inputStream);
//
// } catch (FileNotFoundException e) {
// System.out.println("文件不存在!");
// e.printStackTrace();
// }
}
}
异常处理方式3:前两者结合
- 方法直接将异常通过throws抛出去给调用者。
- 调用者收到异常后直接捕获处理。
Test.java
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
public class Test {
public static void main(String[] args) {
System.out.println("---程序开始---");
try {
parseTime("2022-07-24 18:00:00");
System.out.println("操作成功!");
} catch (Exception e) {
e.printStackTrace();
System.out.println("操作失败!");
}
System.out.println("---程序结束---");
}
private static void parseTime(String s) throws ParseException {
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
Date date = simpleDateFormat.parse(s);
System.out.println(date);
}
}
总结:
- 在开发中按照规范来说第三种方式是最好的:底层的异常抛出去给最外层,最外层集中捕获处理。
- 实际应用中,只要代码能够编译通过,并且功能能完成,那么每一种异常处理方式都是可以的。
f.运行时异常的处理机制
运行时异常的处理形式:
- 运行时异常编译阶段不会出错,是运行时才可能出错的,所以编译阶段不处理也可以。
- 按照规范建议还是处理:建议在最外层调用处集中捕获处理即可。
Test.java
public class Test {
public static void main(String[] args) {
System.out.println("---程序开始---");
try {
division(10, 0);
} catch (Exception e) {
throw new RuntimeException(e);
}
System.out.println("---程序结束---");
}
public static void division(int a, int b) {
System.out.println(a);
System.out.println(b);
int c = a / b;
System.out.println(c);
}
}
g.异常处理使代码更稳健的案例
需求:
- 键盘录入一个合理的价格为止(必须是数值,值必须大于0)。
分析:
- 定义一个死循环,让用户不断的输入价格。
Test.java
import java.util.Scanner;
public class Test {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
while (true) {
try {
System.out.println("请您输入合法的价格:");
String priceStr = scanner.nextLine();
// 转成double类型的价格
double price = Double.parseDouble(priceStr);
if (price > 0) {
System.out.println("定价:" + price);
break;
} else {
System.out.println("价格必须是正数!");
}
} catch (NumberFormatException e) {
System.out.println("输入的数据不正确,请输入合法的数值!");
}
}
}
}
h.自定义异常
自定义异常的必要?
- Java无法为这个世界上全部的问题提供异常类。
- 如果企业想通过异常的方法来管理自己的某个业务问题,就需要自定义异常类。
自定义异常的好处:
- 可以使用异常的机制管理业务问题,如提醒程序员注意。
- 同时一旦出现bug,可以用异常的形式清晰的指出出错的地方。
自定义异常的分类:
自定义编译时的异常:提醒强烈,一定要处理。
- 定义一个异常类继承Exception。
- 重写构造器。
- 在出现异常的地方用throw new 自定义对象抛出。
AgeIllegalException.java
/** * 年龄非法异常 */ public class AgeIllegalException extends Exception{ public AgeIllegalException() { } public AgeIllegalException(String message) { super(message); } }
Test.java
/** * 需求:认为年龄小于0岁,大于200岁就是一个异常 * */ public class Test { public static void main(String[] args) { try { checkAge(201); } catch (AgeIllegalException e) { e.printStackTrace(); } } public static void checkAge(int age) throws AgeIllegalException { if (age < 0 || age > 200) { // 抛出一个异常给调用者 // throw:在方法内部直接创建一个异常对象 并从此点抛出 // throws:用在方法申明上的 抛出方法内部的异常 throw new AgeIllegalException(age + " is illegal!"); } else { System.out.println("年龄合法:推荐商品给其购买"); } } }
自定义运行时异常:提醒不强烈,编译阶段不报错,运行时才可能出现。
- 定义一个异常类继承RuntimeException。
- 重写构造器。
- 在出现异常的地方用throw new自定义对象抛出!
AgeIllegalRuntimeException.java
package com.javase.exceptiondemo2; public class AgeIllegalRuntimeException extends RuntimeException{ public AgeIllegalRuntimeException() { } public AgeIllegalRuntimeException(String message) { super(message); } }
Test.java
/** * 需求:认为年龄小于0岁,大于200岁就是一个异常 * */ public class Test { public static void main(String[] args) { try { checkAgeRuntime(0); } catch (Exception e) { e.printStackTrace(); } } public static void checkAgeRuntime(int age) { if (age < 0 || age > 200) { // 抛出一个异常给调用者 // throw:在方法内部直接创建一个异常对象 并从此点抛出 // throws:用在方法申明上的 抛出方法内部的异常 throw new AgeIllegalRuntimeException(age + " is illegal!"); } else { System.out.println("年龄合法:推荐商品给其购买"); } } }