[JAVA]-异常
[JAVA]-异常
[TOC]
1.基础概念
<1>异常处理机制
Java的异常机制能让程序具有良好的容错性,让程序更加健壮,当程序出现意外的情况,系统会自动生成一个Exception对象通知程序进程处理,从而实现业务功能和实现代码的分离。
🔖异常的继承体现
🔖使用try-catch捕获异常
Try试图捕获异常。{}内是捕获异常的范围
Catch是捕获到异常以后如何处理 其中catch代码块可以出现多次。
try {
// 业务逻辑处理
// 可能出现异常的代码;
} catch(异常类名 变量名) {
// 异常的处理代码;
}
参考案例:
public class ExceptionDemo {
public static void main(String[] args) {
/**
* 使用try...catch语句捕获异常
*/
try
{
// System.out.println(9/0);
// Class.forName("java.util.ArrayListhahah");
FileInputStream fis = new FileInputStream("c:/test.txt");
}catch (ArithmeticException e) {
System.out.println("捕获到了数学运算异常的错误...");
} catch (FileNotFoundException e) {
System.out.println("捕获到了文件不存在的异常...");
} catch (Exception e) {
System.out.println("捕获到了未知的异常...");
}
}
}
🔖Java7提供了多异常的捕获
在jdk1.7之前,每个catch只能捕获一种类型的异常,但是从java7以后一个catch可以捕获多个异常。使用一个catch捕获多个异常需要注意以下情况:
捕获多个异常,多种异常之间用|分割
捕获多种类型的异常时,异常变量有隐式的final修饰,所以程序不能对异常变量重新赋值
public class MultiExceptionDemo {
public static void main(String[] args) {
try {
int a = Integer.parseInt(args[0]);
int b = Integer.parseInt(args[1]);
int c = a / b;
System.out.println("当前执行的结果c是:" + c);
/**
* 一次性捕获多个异常需用 |隔开
*/
} catch (IndexOutOfBoundsException|NumberFormatException|ArithmeticException e) {
System.out.println("捕获到了数组越界,数字格式异常,算数异常 等之一的错误...");
/**
* 多异常捕获的变量是默认被final修饰的 所以下方的代码报错...
* e=new ArithmeticException("aa");
* 不能够对final修饰的变量进行二次修改赋值
*/
} catch (Exception e) { //最大的异常始终是放在最后一个捕获
System.out.println("捕获到了未知的异常...");
e=new RuntimeException("aaa");
}
}
}
🔖访问异常信息
public class AccessExceptionDemo {
/**
* 通过相关的方法了解具体捕获到的异常信息
*/
public static void main(String[] args) {
try
{
FileInputStream fis = new FileInputStream("c://a.txt");
}catch(IOException e)
{
System.out.println(e.getClass());
System.out.println(e.getMessage());
System.out.println(e.getStackTrace());
//了解具体的异常信息
e.printStackTrace();
}
}
/**
* 结果显示:
* class java.io.FileNotFoundException
* c:\a.txt (系统找不到指定的文件。)
* [Ljava.lang.StackTraceElement;@6d06d69c
* java.io.FileNotFoundException: c:\a.txt (系统找不到指定的文件。)
* at java.io.FileInputStream.open0(Native Method)
* at java.io.FileInputStream.open(Unknown Source)
* at java.io.FileInputStream.<init>(Unknown Source)
* at java.io.FileInputStream.<init>(Unknown Source)
* at com.exception.base.AccessExceptionDemo.main(AccessExceptionDemo.java:13)
*/
}
🔖使用finally回收资源
有些时候 程序中在try代码块里 打开了一些物理资源(数据库的连接,网络连接,磁盘文件)等 最终要把这些资源进行回收,提高系统的运行能力。
垃圾回收机制只负责回收堆内存中的对象,不会回收任何物理资源(比如数据库连接,网络,IO等)
public class FilallyDemo {
public static void main(String[] args) {
FileInputStream fis = null;
try
{
fis = new FileInputStream("c:\\test.txt");
}catch(IOException e)
{
System.out.println(e.getMessage());
}finally{//finally语句块是无论如何都会执行的语句块
if(fis!=null)
try {
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
System.out.println("finally代码块执行...");
}
}
}
public class FinallyCommonView {
public static void main(String[] args) {
int a=test();
System.out.println(a);
}
public static int test() {
int i=0;
try {
System.out.println(9 / 0);
return i=i+100;
} catch (Exception e) {
System.out.println("捕获到了异常..");
//这里的return是执行的 但是并没有结束整个方法 代码继续往下执行
return i=i+3;
}finally {
System.out.println("无论如何都会执行的代码...");
return i=i+6;
}
}
/**
* 结果分析:
* finally语句块是无论如何都会执行的语句块,因此在上述语句中,分为
* try、catch、finally三个部分,try中的return语句不会执行,
* catch中虽然执行了相应的return语句,但此时并没有结束整个方法,
* 而是继续往下执行,直到执行完filally语句块的内容,再执行return
* 语句结束整个方法因此最终结果输出为3+6=9
*/
}
finally语句块不会执行的情况分析
try语句没有被执行到,如在try语句之前return就返回了,这样finally语句就不会执行。这也说明了finally语句被执行的必要而非充分条件是:相应的try语句一定被执行到。
在try块|catch块中有System.exit(0);
这样的语句。System.exit(0)
是终止Java虚拟机JVM的,连JVM都停止了,所有都结束了,当然finally语句也不会被执行到。
在try-catch-finally中, 当return遇到finally,return对finally无效,即:
在try catch块里return的时候,finally也会被执行。
finally里的return语句会把try catch块里的return语句效果给覆盖掉。
[!attention]
return语句并不一定都是函数的出口,执行return时,只是把return后面的值复制了一份到返回值变量里去了。如果try..catch…finally…三个语句之内没有return语句,则其会调用这三个语句块之外的return语句执行!
<2>Check异常和Runtime异常体系
Java的异常被分为两大类,Check异常(检查期异常)和Runtime异常(编译时异常),所有的RuntimeException以及子类都被称为运行时异常,除了运行时异常的都是检查期异常。
其中运行时异常可以不用捕获,代码仍然能编译通过,检查期必须人工捕获,否则编译不通过
public class ExceptionDemo {
/**
* Java的异常分为两大类:Check异常和Runtime异常
* Check检查期异常必须要进行捕获,否则编译无法通过
* Runtime运行时异常可以不需要进行捕获,编译也可正常通过
*/
public static void main(String[] args) {
System.out.println(9/0);//运行时异常:数学运算错误
try {
//检查期异常:需要人为进行捕获,或者是抛出指定异常,否则编译出错
FileInputStream fis = new FileInputStream("c://test.txt");
} catch (FileNotFoundException e) {
e.printStackTrace();
}
}
}
[!attention]
常见的运行时异常(案例分析如下):
NullPointerException、ClassNotFoundException、ArithmeticException、ArrayIndexOutOfBoundsException、lllegalAccessException、lllegalAccessException、ClassCastException
public class RuntimeExceptionTest {
public static void main(String[] args) {
/**
* 1.java.lang.NullPointerException
* 空指针异常:调用了未经初始化的对象或者是不存在的对象
* 数组的初始化是指对数组分配需要的空间,而初始化后的数组
* 对应的数组元素并没有被初始化(实例化),其依然还是空的,
* 所以调用的时候出现空指针异常
*/
int[] arr = null;
System.out.println(arr[0]);
/**
* 2.java.lang.ClassNotFoundException
* 指定的类不存在
*/
/**
* 3. java.lang.ArithmeticException:
* / by zero -->数学运算异常(算术异常)
*/
System.out.println(1/0);
/**
* 4.java.lang.ArrayIndexOutOfBoundsException
* 数组下标越界异常(数组下标的取值范围为0->length-1)
*/
String[] s = new String[3];
System.out.println(s[3]);
/**
* 5.java.lang.lllegalArgumentException
* 方法参数错误异常
* 调用类库的方法时出现参数传递错误
*/
/**
* 6.java.lang.lllegalAccessException
* 没有访问权限
* 当应用程序要调用一个类,但当前的方法没有对该类的访问权限,则会出现这个异常
*/
/**
* 6.ClassCastException
* 类型转化异常一般在继承和多态场景中比较常见
* 例如继承同一个父类的不同子类之间的强制转化
*/
// Dog、Cat分别继承Animal
Animal a1 = new Dog();
Animal a2 = new Cat();
Dog d1 = (Dog)a1;
Dog d2 = (Dog)a2;// 异常
}
}
🔖使用throws声明抛出异常
public class ThrowsExceptionTest {
public static void main(String[] args) {
/**
* 如果是要调用一个抛出异常的方法,可以有两种处理方式
* 1.直接在本方法中处理异常
* 2.继续向上一级抛出指定异常(throws 异常)
*/
try {
test();
} catch (IOException e) {
System.out.println("捕获到文件不存在异常...");
} catch (SQLException e) {
e.printStackTrace();
}
}
/**
* 在方法声明处抛出异常使用“throws”关键字
* 如果是抛出多个异常则可用 “,”进行分隔
*/
public static void test() throws IOException,SQLException
{
FileInputStream fis = new FileInputStream("c://test.txt");
}
}
子类继承父类
子类重写父类的方法 那么抛出的异常必须比父类更小,更少的异常
两小原则针对的是检查期异常,而运行期异常无关
/**
* 子类在重写父类的方法时要遵循“两小原则”
* 子类抛出的异常要比父类小
* 子类抛出的异常要比父类少
* “两小原则”是针对检查期异常而言的,与运行时异常无关
*/
class Father
{
public static void test()throws IOException,SQLException
{
FileInputStream fis = new FileInputStream("c://test.txt");
}
}
public class OverrideExceptionDemo extends Father{
public static void test()throws IOException,SQLException
{
FileInputStream fis = new FileInputStream("c://test.txt");
}
public static void main(String[] args) {
try {
test();
} catch (IOException e) {
e.printStackTrace();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
🔖使用throw抛出异常
如果需要在程序中自行抛出异常,则使用throw进行抛出异常
public class ThrowExceptionTest {
public static void main(String[] args) {
// testThrow(-1);
try {
testThrow2(-2);
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
public static void testThrow(int i)
{
/**
* 如果参数i满足某些条件则手动利用“throw”关键字抛出异常
* 但在方法声明处需要再次进行声明
*/
if(i<0)
{
//自行抛出RuntimeException或者其子类
throw new RuntimeException("i值小于0,不满足条件...");
}
}
public static void testThrow2(int i) throws Exception
{
if(i<0)
{
//自行抛出Exception,需要在方法声明处使用“throws”关键字再次抛出异常
throw new Exception("i值小于0,不满足条件...");
}
}
}
🔖关键字throw与throws的区别
如果调用一个抛出异常的方法 那么调用者有两种处理方式
1.在本方法内处理
2.继续向上一级抛出 throws IOException
如果需要在程序中自行抛出异常,则使用throw进行抛出异常
throws是方法可能抛出异常的声明。(用在声明方法时,表示该方法可能要抛出异常)
区别:
throws出现在方法函数头;而throw出现在函数体。
throws表示出现异常的一种可能性,并不一定会发生这些异常;throw则是抛出了异常,执行throw则一定抛出了某种异常对象。
两者从某种程度上来说都是消极处理异常的方式,只是抛出或者可能抛出异常,但是不会由函数去处理异常,真正的处理异常由函数的上层调用处理。
<3>自定义异常
在一些特殊的业务场景下,异常的类名通常也包含了该异常的有用信息,所以在抛出异常的时候应该选择合适的异常类,从而明确该异常情况,因此可借助自定义异常构建基于不同业务场景的异常处理机制
自定义异常的步骤如下:
# 基础的自定义异常
1.继承RuntimeException 或者Exception
2.写一个无参的构造函数
3.写一个String类型的构造函数
public class SelfException extends RuntimeException{
/**
* 自定义异常类
* 1、继承RuntimeException或者Exception
* 2、写一个无参的构造函数
* 3、写一个String类型的构造函数
*/
public SelfException()
{
}
public SelfException(String msg)
{
super(msg);
}
}
public class SelfExceptionDemo {
//定义方法f1
public static double f1(int a,int b)
{
//求解两个数相除,要求b不能为负数,否则抛出异常
if(b<0)
throw new SelfException("被除数不能为负数!!");
else if(b==0)
throw new RuntimeException("被除数不能为0");
return a/b;
}
public static void main(String[] args) {
//测试
System.out.println(f1(2,5));
}
}
🚀案例分析:模拟借书系统
需求描述:模拟借书系统
a.定义字符串数组保存图书信息
b.提示用户输入,分别按“书名”、“图书序号”查找图书
c.根据输入信息进行适当的异常处理
- 如果输入类型错误,抛出“错误命令异常”,并提示重新输入
- 如果书名不存在,抛出“图书不存在异常”,并提示重新输入
- 如果图书序号超出字符串数组范围,抛出“图书不存在异常”并提示重新输入
public class BorrowBookException extends Exception {
/**
* 自定义异常类
* 1、继承RuntimeException或者Exception
* 2、写一个无参的构造函数
* 3、写一个String类型的构造函数
*/
public BorrowBookException()
{
}
public BorrowBookException(String str)
{
super(str);
}
}
public class BorrowBookSystem {
public static LinkedHashMap save(String[] name,String[] id)
{
//1.根据定义字符串数组保存图书信息(书名、图书序号)
LinkedHashMap lhm = new LinkedHashMap();
for(int i=0;i<name.length;i++)
lhm.put(id[i], name[i]);
return lhm;
}
public static void main(String[] args) {
String[] name = {"java","语文","数学","英语","物理","化学","生物","政治","高数","Oracle"};
String[] id = {"1","2","3","4","5","6","7","8","9","10"};
Scanner sc = new Scanner(System.in);
LinkedHashMap lhm = save(name,id);
while(true)
{
System.out.println("请选择要进行查找的方式:1.书名 2.图书序号");
int choose = sc.nextInt();
if(!(choose==1||choose==2))
try {
throw new BorrowBookException("输入类型错误,请重新输入");
} catch (BorrowBookException e) {
System.out.println(e.toString());//输出异常提示信息
}
else if(choose==1)
{
System.out.println("请输入要查询的信息(输入exit退出)");
String str = sc.next();
if(!str.equals("exit"))
{
if(!lhm.containsValue(str))
try {
throw new BorrowBookException("图书不存在,请重新输入...");
} catch (BorrowBookException e) {
e.printStackTrace();//输出具体的异常信息
}
else
System.out.println("图书查找成功");
}
else
{
System.exit(0);
}
}
else if(choose==2)
{
System.out.println("请输入要查询的信息(输入exit退出)");
String str = sc.next();
if(!str.equals("exit"))
{
if(Integer.valueOf(str)>lhm.keySet().size())
try {
throw new BorrowBookException("图书不存在异常,请重新输入...");
} catch (BorrowBookException e) {
e.printStackTrace();
}
else
System.out.println("图书查找成功");
}
else
{
System.exit(0);
}
}
}
}
}
🚀扩展案例:java判断输入数据是否为纯数字?
异常除却用作业务逻辑异常处理,还可在一些特殊的场景中应用,例如在java判断输入数据是否为纯数字,可能一开始想到的是循规蹈矩遍历输入的数据信息,随后一个个字符进行校验或者借助工具类、正则表达式等方式实现。
此处换一种思路考虑也可借助异常处理机制去实现,也就是直接将这个数据的字符串信息进行数字转化,如果它可以被转化说明是纯数字,如果出现异常则相应通过try...catch...进行捕获处理
方式1:使用Character.isDigit(char)判断
char num[] = str.toCharArray();//把字符串转换为字符数组
StringBuffer title = new StringBuffer();//用StringBuffer将非数字放入title中
StringBuffer hire = new StringBuffer();//把数字放到hire中
for (int i = 0; i < num.length; i++)
{
// 判断输入的数字是否为数字还是字符
if (Character.isDigit(num[i]))
{
/**
* 把字符串转换为字符,再调用Character.isDigit(char)
* 方法判断是否是数字,是返回True,否则False
*/
hire.append(num[i]);// 如果输入的是数字,把它赋给hire
}
else
{
title.append(num[i]);// 如果输入的是字符,把它赋给title
}
}
方式2:使用类型转换判断
try {
String str="123abc";
}
int num=Integer.valueOf(str);//把字符串强制转换为数字
return true;//如果是数字,返回True
} catch (Exception e) {
return false;//如果抛出异常,返回False
}
方式3:使用正则表达式判断
String str = "";
boolean isNum = str.matches("[0-9]+");
/**
* +表示1个或多个(如"3"或"225")
* *表示0个或多个([0-9]*)(如""或"1"或"22")
* ?表示0个或1个([0-9]?)(如""或"7")
* ps:这个方法只能用于判断是否是正整数
*/
<4>Java的异常跟踪栈及异常的使用规则
🔖Java的异常跟踪栈
读取异常栈信息 ,根据异常栈信息 寻找出错的原因和错误所在代码,然后可以阅读自己的业务逻辑,也可以通过断点追踪进行调试。
public class PrintStackTest {
public static void main(String[] args) {
System.out.println("滴滴滴..");
f1();
}
public static void f1() {
// ....业务逻辑代码....
System.out.println("hello");
f2();
// ....业务逻辑代码....
System.out.println("world");
}
public static void f2() {
// ....业务逻辑代码....
f3();
// ....业务逻辑代码....
}
public static void f3() {
// ....业务逻辑代码....
f4();
// ....业务逻辑代码....
}
public static void f4() {
// ....业务逻辑代码....
System.out.println("业务测试...");
throw new SelfException("自定义异常触发....");
// ....业务逻辑代码....
}
}
输出结果显示:
滴滴滴..
hello
业务测试...
Exception in thread "main" com.myexception.SelfException: 自定义异常触发....
at com.exception.base.PrintStackTest.f4(PrintStackTest.java:34)
at com.exception.base.PrintStackTest.f3(PrintStackTest.java:27)
at com.exception.base.PrintStackTest.f2(PrintStackTest.java:21)
at com.exception.base.PrintStackTest.f1(PrintStackTest.java:14)
at com.exception.base.PrintStackTest.main(PrintStackTest.java:8)
🔖异常的使用规则
不可否认的是Java的异常机制很方便,但是滥用异常机制也会造成负面影响。
主要包含以下两个方面:
把异常和普通的代码混淆在一起,不编写任何错误处理代码
不要把异常替代流程控制
注意事项:
不要过度使用异常
不要使用过于庞大的try块
避免使用catch all 语句,要业务逻辑详细的描述异常
不要忽略掉捕获到的异常
[!attention]
在实际的业务场景中,往往借助框架提供的入口构建自身业务的异常处理机制,从而实现对代码的规范,便于跟踪代码逻辑和异常情况分析,但不管是处于何种场景,更多的是希望异常机制给我们提供更多的程序运行相关的信息,从而辅助开发和运维
2.Optional
Optional概述
可能包含或不包含非null值的容器对象
常用方法
方法名 | 说明 |
---|---|
static <T> Optional<T> of(T value) | 获取一个Optional对象,封装的是非null值的对象 |
static <T> Optional<T> ofNullable(T value) | 获取一个Optional对象,Optional封装的值对象可以是null也可以不是null |
参考案例:使用Optional封装对象
class Student {
String name;
Integer age;
public Student(String name, Integer age) {
this.name = name;
this.age = age;
}
}
public class OptionalDemo1 {
public static void main(String[] args) {
Student stu1 = null;
//ofNullable方法:获取一个Optional对象,Optional封装的值对象可以是null也可以不是null
Optional<Student> os1 = Optional.ofNullable(stu1);
System.out.println(os1); // Optional.empty
// of方法:获取一个Optional对象,封装的是非null值的对象
Student stu2 = new Student("h",18);
Optional<Student> os2 = Optional.of(stu2);
System.out.println(os2);// Optional[com.noob.demo.optional.Student@50cbc42f]
// of方法:如果封装一个null值对象,则抛出空指针异常
Optional<Student> os3 = Optional.of(stu1); // stu1为null,抛出空指针异常
}
}
参考案例:常用方法
public class OptionalDemo2 {
public static void main(String[] args) {
// Student s = new Student("h",18);
Student s = null;
// 且如果封装的是一个null,那么通过get方法再次获取会抛出NoSuchElementException
Optional<Student> optional = Optional.ofNullable(s);
// 判断Optional所封装的对象是否不为空,如果不为空返回true,否则返回false
if(optional.isPresent()){
// get() 如果存在值,返回值,否则抛出NoSuchElementException
Student student = optional.get();
System.out.println(student);
}else{
System.out.println("Optional封装的对象为空");
}
}
}
参考案例:处理空指针
public class OptionalDemo3 {
public static void main(String[] args) {
// method1();
// method2();
// method3();
}
private static void method3() {
//Student s = new Student("h",18);
Student s = null;
Optional<Student> optional = Optional.ofNullable(s);
//ifPresent (Consumer<? super T> action):如果不为空,则使用该值执行给定的操作,否则不执行任何操作
optional.ifPresent(student -> System.out.println(student));
}
private static void method2() {
Student s = new Student("h",18);
//Student s = null;
Optional<Student> optional = Optional.ofNullable(s);
//orElseGet(Supplier<? extends T> supplier):如果不为空,则返回具体的值,否则返回由括号中函数产生的结果
Student student = optional.orElseGet(()-> new Student("xx" , 24));
System.out.println(student);
}
private static void method1() {
//Student s = new Student("h",18);
Student s = null;
Optional<Student> optional = Optional.ofNullable(s);
//orElse(T other):如果不为空,则返回具体的值,否则返回参数中的值
Student student = optional.orElse(new Student("xx", 24));
System.out.println(student);
}
}