异常简介:
1、很多事情你很难能够和正常情况下一样,比如出去旅游。
出发-到达目的地-旅游-烧烤-回家,但是当你准备烧烤时来个倾盆大雨----这就是异常
2、所以程序野有异常:表示在程序运行时的一种情况。
3、所以java中的异常会终端执行的程序,所以为了代码健壮性,我们需要异常类处理。
4、java发生异常的三种原因:
a、java内部错误发生异常,也就是虚拟机产生的异常。
b、编写程序产生的异常,如数组越界、空指针
c、通过throw手动产生异常来告知调用者必要信息。
5、 java是通过面向对象的方法来处理异常,在一个方法运行时发生了异常,则这个方法会产生一个异常
的对象,交给运行时系统处理。
a、交给运行时系统过程叫做抛出(throw)异常
b、运行时系统通过调用栈找到该异常的的对象,叫做捕获(catch)异常
看个例子:
public class Test01{
public static void main(String[] args){
System.out.println("请输入1~3之间的整数");
Scanner input = new Scanner(System.in);
int num = input.nextInt();
switch(num){
case 1:
System.out.println("one"); break;
case 2:
System.out.println("two");break;
case 3:
System.out.println("three");break;
default:
System.out.println("error");break;
}
}
}
当你输入一个非1~3的数时,如a.---那么会报Exception in thread "main" java.util.InputMismatchException
6、异常类型都是java内置类Throwable的子类:如下图
7、根据图分析:我们可以将异常分为Error和Exception异常
a、Error是灾难性的,一般是非程序可以控制的,一般JVM错误产生,如堆栈溢出
b、Exception是程序出现的错,分为运行时异常和非运行时异常。
也可以叫做不检查异常和检查异常。
运行时异常:RuntimeException. 如:ArithmeticException(算数异常)---可捕获处理也可不捕获处理
非运行时异常:Exception子类非RuntimeException.如:ClassNotFoundException--必须捕获处理
如下图:
如下是常见的 Error 和 Exception:
1)运行时异常(RuntimeException):
NullPropagation:空指针异常;
ClassCastException:类型强制转换异常
IllegalArgumentException:传递非法参数异常
IndexOutOfBoundsException:下标越界异常
NumberFormatException:数字格式异常
2)非运行时异常:
ClassNotFoundException:找不到指定 class 的异常
IOException:IO 操作异常
3)错误(Error):
NoClassDefFoundError:找不到 class 定义异常
StackOverflowError:深递归导致栈被耗尽而抛出的异常
OutOfMemoryError:内存溢出异常
例
下面代码会导致 Java 堆栈溢出错误
/ 通过无限递归演示堆栈溢出错误
class StackOverflow {
public static void test(int i) {
if (i == 0) {
return;
} else {
test(i++);
}
}
}
public class ErrorEg {
public static void main(String[] args) {
// 执行StackOverflow方法
StackOverflow.test(5);
}
}
=========================================
==========================================
Java 的异常处理通过 5 个关键字来实现:try、catch、throw、throws 和 finally。try catch 语句用于捕获并处理异常,finally 语句用于在任何情况下(除特殊情况外)都必须执行的代码,throw 语句用于拋出异常,throws 语句用于声明可能会出现的异常
catch可以多个,throw抛出提示,throws向上抛出处理不来的异常
public class FiveKeyWord {
public static void main(String[] args) throws Exception {
test(9);
}
private static void test(int i){
int a;
try{
a=i/0;
}catch(ArithmeticException e){
System.out.println("算数异常");
}catch(Exception e){
throw(e);//向外抛出异常
}finally {
System.out.println("释放资源");
}
}
}
============================
==================
异常结构体一:
try{发生异常位置}catch(ExceptionType e){处理异常}
{}不能省略,try里没有异常程序就会跳过catch,有的话就跳到catch里执行。
知道有三个输出异常方法就可以:
- printStackTrace() 方法:指出异常的类型、性质、栈层次及出现在程序中的位置。
- getMessage() 方法:输出错误的性质。---什么错误,需要输出流打印syso(e.getMessae())
- toString() 方法:给出异常的类型与性质。---需要输出流打印syso(e.getMessae())
异常结构体二:
注意:a、当捕获的多个异常类之间存在父子关系时,捕获异常时一般先捕获子类,再捕获父类。所以子类异常必须在父类异常的前面,否则子类捕获不到。
b、执行和上面一样。在多个 catch 代码块的情况下,当一个 catch 代码块捕获到一个异常时,其它的 catch 代码块就不再进行匹配。
- try {
- // 可能会发生异常的语句
- } catch(ExceptionType e) {
- // 处理异常语句
- } catch(ExceptionType e) {
- // 处理异常语句
- }
例子:利用异常,写输入班级总人数,总分数,计算平均分
public class ExceptionAvgScore {
public static void main(String[] args) {
Scanner input =new Scanner(System.in);
try{
System.out.println("请输入班级总人数:");
int count = input.nextInt();
System.out.println("请输入班级总成绩");
int score = input.nextInt();
int avg = score/count;//获取平均分
System.out.println(avg);
}catch(InputMismatchException e1){
System.out.println("输入数值错误");
}catch(ArithmeticException e2){
System.out.println("计算总是不能为0");
}catch(Exception e3){
e3.printStackTrace();
System.out.println("发生错误|"+e3.getMessage());
}
}
}
异常结构三
- try {
- // 可能会发生异常的语句
- } catch(ExceptionType e) {
- // 处理异常语句
- } finally {
- // 清理代码块
- try {
- // 逻辑代码块
- } finally {
- // 清理代码块
- }
在实际开发中,根据 try catch 语句的执行过程,try 语句块和 catch 语句块有可能不被完全执行,而有些处理代码则要求必须执行。例如,程序在 try 块里打开了一些物理资源(如数据库连接、网络连接和磁盘文件等),这些物理资源都必须显式回收。
Java的垃圾回收机制不会回收任何物理资源,垃圾回收机制只回收堆内存中对象所占用的内存
所以为了确保一定能回收 try 块中打开的物理资源,异常处理机制提供了 finally 代码块,并且 Java 7 之后提供了自动资源管理。
try必须一个,catch非必需可多个,finally非必需可多个。执行顺序t-c-f.
===============================================
============================================
try-return---注意下面两种类型,基本类型和引用类型。
private int testReturn1() { 2 int i = 1; 3 try { 4 i++; 5 System.out.println("try:" + i); 6 return i; 7 } catch (Exception e) { 8 i++; 9 System.out.println("catch:" + i); 10 } finally { 11 i++; 12 System.out.println("finally:" + i); 13 } 14 return i; 15 }
输出:try:2
finally:3
2
private List<Integer> testReturn2() { 2 List<Integer> list = new ArrayList<>(); 3 try { 4 list.add(1); 5 System.out.println("try:" + list); 6 return list; 7 } catch (Exception e) { 8 list.add(2); 9 System.out.println("catch:" + list); 10 } finally { 11 list.add(3); 12 System.out.println("finally:" + list); 13 } 14 return list; 15 }
输出:try:[1]
finally:[1, 3]
[1, 3]
二、catch中带有return
1 private int testReturn3() { 2 int i = 1; 3 try { 4 i++; 5 System.out.println("try:" + i); 6 int x = i / 0 ; 7 } catch (Exception e) { 8 i++; 9 System.out.println("catch:" + i); 10 return i; 11 } finally { 12 i++; 13 System.out.println("finally:" + i); 14 } 15 return i; 16 }
输出:try:2
catch:3
finally:4
3
三、finally中带有return
1 private int testReturn4() { 2 int i = 1; 3 try { 4 i++; 5 System.out.println("try:" + i); 6 return i; 7 } catch (Exception e) { 8 i++; 9 System.out.println("catch:" + i); 10 return i; 11 } finally { 12 i++; 13 System.out.println("finally:" + i); 14 return i; 15 } 16 }
输出:try:2
finally:3
3
======================================
========================================================
Java 9增强的自动资源管理
自动关闭资源的 try 语句相当于包含了隐式的 finally 块(这个 finally 块用于关闭资源),因此这个 try 语句可以既没有 catch 块,也没有 finally 块。
java7之前:当程序使用 finally 块关闭资源时,程序会显得异常臃肿
- public static void main(String[] args) {
- FileInputStream fis = null;
- try {
- fis = new FileInputStream("a.txt");
- } catch (FileNotFoundException e) {
- e.printStackTrace();
- } finally {
- // 关闭磁盘文件,回收资源
- if (fis != null) {
- try {
- fis.close();
- } catch (IOException e) {
- e.printStackTrace();
- }
java7开始:Java 7 增加了一个新特性,该特性提供了另外一种管理资源的方式,这种方式能自动关闭文件,被称为自动资源管理(Automatic Resource Management)。该特性是在 try 语句上的扩展,主要释放不再需要的文件或其他资源。
当 try 代码块结束时,自动释放资源。不再需要显式的调用 close() 方法,该形式也称为“带资源的 try 语句”。
注意:
- try 语句中声明的资源被隐式声明为 final,资源的作用局限于带资源的 try 语句。
- 可以在一条 try 语句中声明或初始化多个资源,每个资源以
;
隔开即可。 - 需要关闭的资源必须实现了 AutoCloseable 或 Closeable 接口。
Closeable 是 AutoCloseable 的子接口,Closeable 接口里的 close() 方法声明抛出了 IOException,
因此它的实现类在实现 close() 方法时只能声明抛出 IOException 或其子类;
AutoCloseable 接口里的 close() 方法声明抛出了 Exception,
因此它的实现类在实现 close() 方法时可以声明抛出任何异常。
- public class AutoCloseTest {
- public static void main(String[] args) throws IOException {
- try (
- // 声明、初始化两个可关闭的资源
- // try语句会自动关闭这两个资源
- BufferedReader br = new BufferedReader(new FileReader("AutoCloseTest.java"));
- PrintStream ps = new PrintStream(new FileOutputStream("a.txt"))) {
- // 使用两个资源
- System.out.println(br.readLine());
- ps.println("C语言中文网");
- }
- }
- }
java9:不要求在 try 后的圆括号内声明并创建资源,只需要自动关闭的资源有 final 修饰或者是有效的 final
Java 7 几乎把所有的“资源类”(包括文件 IO 的各种类、JDBC 编程的 Connection 和 Statement 等接口)进行了改写,改写后的资源类都实现了 AutoCloseable 或 Closeable 接口。
- public class AutoCloseTest {
- public static void main(String[] args) throws IOException {
- // 有final修饰的资源
- final BufferedReader br = new BufferedReader(new FileReader("AutoCloseTest.java"));
- // 没有显式使用final修饰,但只要不对该变量重新赋值,该变量就是有效的
- final PrintStream ps = new PrintStream(new FileOutputStream("a. txt"));
- // 只要将两个资源放在try后的圆括号内即可
- try (br; ps) {
- // 使用两个资源
- System.out.println(br.readLine());
- ps.println("C语言中文网");
- }
- }
- }
===============================================
==========================================================
throws 声明异常
throws Exception 1,Exception2,…{…}
使用 throws 声明抛出异常的思路是,当前方法不知道如何处理这种类型的异常,该异常应该由向上一级的调用者处理;
如果 main 方法也不知道如何处理这种类型的异常,也可以使用 throws 声明抛出异常,该异常将交给 JVM 处理。
JVM 对异常的处理方法是,打印异常的跟踪栈信息,并中止程序运行,这就是前面程序在遇到异常后自动结束的原因。
方法---main---JVM,停止运行。-----------一直网上知道解决为止。
public void readFile() throws IOException,C,D... {}
子类不能大于父类,不然编译失败如下:
- public class OverrideThrows {
- public void test() throws IOException {
- FileInputStream fis = new FileInputStream("a.txt");
- }
- }
- class Sub extends OverrideThrows {
- // 子类方法声明抛出了比父类方法更大的异常
- // 所以下面方法出错
- public void test() throws Exception {
- }
- }
throw 拋出异常
ExceptionObject 必须是 Throwable 类或其子类的对象。如果是自定义异常类,也必须是 Throwable 的直接或间接子类
如果不是就会编译失败:throw new String("拋出异常"); // String类不是Throwable类的子类
public class Throw {
public static void main(String[] args) {
Scanner input = new Scanner(System.in);
System.out.println("请输入用户名: ");
String username = input.next();
boolean con ;
try{
con = validateUserName(username);
if(con){
System.out.println("用户名输入正确");
}
}catch(IllegalArgumentException e){
System.out.println(e);
}
}
private static boolean validateUserName(String username) {
boolean flag =false;
if(username.length()>8){
for(int i=0;i<username.length();i++){
char ch = username.charAt(i);
if((ch>='0' &&ch<='9') ||(ch>='a'&&ch<='z')||(ch>='A'&& ch<='Z')){
flag=true;
}else{
flag=false;
throw new IllegalArgumentException("用户名只能由字母或数字组成");
}
}
}else{
throw new IllegalArgumentException("用户名必须大于8为");
}
return flag;
}
}
throws 与 throw区别:未必有异常,后者一定抛出了异常对象;
作用在方法或类上,方法或类内;
解决不了可以往上抛,只能自己解决。
====================================================================
==================================================
java7提供了多异常捕获,catch (IndexOutOfBoundsException | NumberFormatException | ArithmeticException e),
这样就不用写多个catch了.但是还是要遵循先子后父的规律。
==================
==============
自定义异常:
<class><自定义异常名><extends><Exception>
- public class MyException extends Exception {
- public MyException() {
- super();
- }
- public MyException(String str) {
- super(str);
- }
- }
- public class Test07 {
- public static void main(String[] args) {
- int age;
- Scanner input = new Scanner(System.in);
- System.out.println("请输入您的年龄:");
- try {
- age = input.nextInt(); // 获取年龄
- if(age < 0) {
- throw new MyException("您输入的年龄为负数!输入有误!");
- } else if(age > 100) {
- throw new MyException("您输入的年龄大于100!输入有误!");
- } else {
- System.out.println("您的年龄为:"+age);
- }
- } catch(InputMismatchException e1) {
- System.out.println("输入的年龄不是数字!");
- } catch(MyException e2) {
- System.out.println(e2.getMessage());
- }
- }
- }
============================
====================
写个用户名和密码验证
假设在某仓库管理系统的登录界面中需要输入用户名和密码,其中用户名只能由 6~10 位数字组成,密码只能有 6 位,任何不符合用户名或者密码要求的情况都视为异常,并且需要捕获并处理该异常。
- public class LoginException extends Exception {
- public LoginException() {
- super();
- }
- public LoginException(String msg) {
- super(msg);
- }
- }
- public static void main(String[] args) {
- Scanner input = new Scanner(System.in);
- System.out.println("用户名:");
- String username = input.next();
- System.out.println("密码:");
- String password = input.next();
- Test08 lt = new Test08 ();
- boolean con = lt.validateLogin(username,password); // 调用 validateLogin() 方法
- if (con) {
- System.out.println("登录成功!");
- }
- }
- public boolean validateLogin(String username,String pwd) {
- boolean con = false; // 用户名和密码是否正确
- boolean conUname = false; // 用户名格式是否正确
- try {
- if (username.length() >= 6 && username.length() <= 10) {
- for (int i = 0;i < username.length();i++) {
- char ch = username.charAt(i); // 获取每一个字符
- if (ch >= '0' && ch <= '9') { // 判断字符是否为0~9的数字
- conUname = true; // 设置 conUname 变量值为 true
- } else {
- // 如果字符不是0~9的数字,则拋出LoginException异常
- conUname = false;
- throw new LoginException("用户名中包含有非数字的字符!");
- }
- }
- } else {
- // 如果用户名长度不在6~10位之间,拋出异常
- throw new LoginException("用户名长度必须在6〜10位之间!");
- }
- if (conUname) { // 如果用户名格式正确,判断密码长度
- if (pwd.length() == 6) {
- // 如果密码长度等于6
- con=true; // 设置con变量的值为true,表示登录信息符合要求
- } else { // 如果密码长度不等于6,拋出异常
- con = false;
- throw new LoginException("密码长度必须为 6 位!");
- }
- }
- } catch(LoginException e) {
- // 捕获 LoginException 异常
- System.out.println(e.getMessage());
- }
- return con;
- }
=====================
==============================
Java完善除法运算的错误信息
- public class Compute {
- private int[] num = new int[2];
- public int[] getNum() {
- return num;
- }
- public void setNum(int[] num) {
- this.num = num;
- }
- }
- public class Test04 {
- public static void main(String[] args) {
- Compute c = new Compute();
- int array[] = c.getNum();
- int res = 0;
- String YorN = null;
- Scanner in = new Scanner(System.in);
- try {
- System.out.println("请输入第一个整数:");
- array[0] = in.nextInt();
- System.out.println("请输入第二个整数:");
- array[1] = in.nextInt();
- res = array[0] / array[1];
- System.out.println("是否保存结果请输入Y或者N");
- YorN = in.next();
- if (YorN.equals("Y")) {
- array[2] = res;
- }
- System.out.println(array[0] + "除以" + array[1] + "的结果是:" + res);
- }
- }
- }
- catch (ArrayIndexOutOfBoundsException e) {
- System.out.println("出现数组越界错误,下标过大或者过小。");
- } catch(ArithmeticException e) {
- System.out.println("出现算术运算错误,被除数不能为0。");
- } catch(InputMismatchException e) {
- System.out.println("输入的数据类型不匹配,只能输入数字。");
- } catch(Exception e) {
- System.out.println("发生未知错误。");
- }
=======================
====================
异常使用注意点:
1、不要自定义继承RuntimException,直接if判断就可以提高代码健壮性。
2、try-catch:不要直接Exception,而是要一个一个从小到大写出所有异常。
3、异常捕捉处理:一个一个写出来,不要直接用一个模板写出抛给用户提示。类似上面直接Exception
4、事务:a、try在事物中,要手动提交事物;b、在catch要使事物有效,必须抛出
5、异常与finally:比如io,要从里层到外层关闭---------------------------JDK 7 以上可以使用try-with-resources 方式。
6、finally有return:执行完finally的return,方法结束
7、抛出异常需要精确捕获,就像抛绣球也要看着人抛
8、程序员的基本修养 & NPE(现有代码中出现率较高)---nullpointexception
1.方法(接口)的返回值可以为 null ,但不推荐返回空集合,或者空对象等,必须添加注释充分说明什么情况下会返回 null 值。调用方需要进行 null 判断防止 NPE 问题。
2、防止 NPE ,是程序员的基本修养,注意 NPE 产生的场景。
a.查询数据库返回null ,包括null 对象和null 集合。
b.集合内元素有null 对象。
========================
=====================================
Java的异常跟踪栈
printStack();
xception in thread "main" Test.SelfException: 自定义异常信息
at Test.PrintStackTraceTest.thirdMethod(PrintStackTraceTest.java:26)
at Test.PrintStackTraceTest.secondMethod(PrintStackTraceTest.java:22)
at Test.PrintStackTraceTest.firstMethod(PrintStackTraceTest.java:18)
at Test.PrintStackTraceTest.main(PrintStackTraceTest.java:14)
Java.util.logging:JDK自带记录日志类
日志用来记录程序的运行轨迹,方便查找关键信息,也方便快速定位解决问题。
Logger.getGlobal().info("打印信息");
Logger 的默认级别是 INFO,比 INFO 级别低的日志将不显示。Logger 的默认级别定义在 jre 安装目录的 lib 下面。
# Limit the message that are printed on the console to INFO and above.
java.util.logging.ConsoleHandler.level = INFO
所以在默认情况下,日志只显示前三个级别,对于所有的级别有下面几种记录方法:
logger.warning(message);
logger.fine(message);
或者指定级别
logger.log(Level.FINE, message);
- log.info("info");
- log.warning("warning");
- log.severe("server");
十一月 27, 2019 5:13:05 下午 Test.Test main
信息: info
十一月 27, 2019 5:13:05 下午 Test.Test main
警告: warning
十一月 27, 2019 5:13:05 下午 Test.Test main
严重: server
修改日志管理器配置
可以通过编辑配置文件来修改日志系统的各种属性。在默认情况下,配置文件存在于 jre 安装目录下“jre/lib/logging.properties”
java -Djava.util.logging.config.file = configFile MainClass
.level=INFO
Test.Test.level=FINE
java.util.logging.ConsoleHandler.level=FINE
注意:在日志管理器配置的属性设置不是系统属性,因此,用-Dcom.mycompany.myapp.level=FINE
启动应用程序不会对日志记录器产生任何影响。