什么是异常?
程序执行过程中的不正常情况。
异常机制有什么用?
增强程序的健壮性。
package exception;
public class ExceptionTest01 {
public static void main(String[] args) {
//通过“异常类”实例化“异常对象”
NumberFormatException n = new NumberFormatException("数字格式化异常");
System.out.println(n);
}
}
异常在java中以类的形式存在,每一个异常类都可以创建异常对象。实际上,JVM在执行到异常处会new异常对象,并且JVM将new的异常对象抛出打印输出信息到控制台。
异常对应的现实生活中是怎样的?
火灾(异常类):
xxxx年xx月xx日 A家发生火灾(异常对象)
xxxx年xx月xx日 B家发生火灾(异常对象)
xxxx年xx月xx日 C家发生火灾(异常对象)
异常的继承结构。
我们可以使用UML图来描述一下继承结构。(UML是一种建模语言,画UML的工具有例如:Rational Rose,starUML……)
Object
Object下有Throwable(可抛出的)
Throwable下有两个分支:Error(不可处理的,直接退出JVM)和Excepion(可处理的)
Excepion下有两个分支:
Excepion的直接子类:编译时异常。(要求程序员在编写程序阶段必须预先对这些异常进行处理,如果不处理编译器报错, 因此得名编译时异常。)
编译时异常又可以称为受检异常:CheckedException 受控异常
RuntimeExcepion:运行时异常。(在编写程序阶段程序员可以预先处理,也可以不管,都行。)
运行时异常又可以称为未受检异常:UnCheckedException 非受控异常
编译时异常和运行时异常,都是发生在运行阶段。编译阶段异常是不会发生的。
所有异常都是在运行阶段发生的。因为只有程序运行阶段才可以new对象。因为异常的发生就是new异常对象。
编译时异常和运行时异常的区别
编译时异常一般发生的概率比较高。对于一些发生概率较高的异常,需要在运行之前对其进行预处理。
运行时异常一般发生的概率比较低。
假设java中没有对异常进行划分,没有分为:编译时异常和运行时异常,
所有的异常都需要在编写程序阶段对其进行预处理,首先,如果这样的话,程序肯定是绝对的安全的。
但是程序员编写程序太累,代码到处都是处理异常的代码。
package exception;
public class ExceptionTest02 {
public static void main(String[] args) {
/*
程序执行到此处发生了ArithmeticException异常,底层new了一个ArithmeticException异常对象,
然后抛出了,由于是main方法调用了100 / 0,所以这个异常ArithmeticException抛给了main方法,
main方法没有处理,将这个异常自动抛给了JVM。JVM最终终止程序的执行。
ArithmeticException 继承 RuntimeException,属于运行时异常。
在编写程序阶段不需要对这种异常进行预先的处理。
*/
System.out.println(100 / 0);
// 这里的HelloWorld没有输出,没有执行。
System.out.println("Hello World!");
}
}
异常的两种处理方式
第一种方式:在方法声明的位置上,使用throws关键字,抛给上一级。谁调用我,我就抛给谁。抛给上一级。
第二种方式:使用try…catch语句进行异常的捕捉。这件事发生了,谁也不知道,因为我给抓住了。
【举个例子】:
我是某集团的一个销售员,因为我的失误,导致公司损失了1000元,
“损失1000元”这可以看做是一个异常发生了。我有两种处理方式,
第一种方式:我把这件事告诉我的领导【异常上抛】
第二种方式:我自己掏腰包把这个钱补上。【异常的捕捉】
**注意:**异常发生之后,如果我选择了上抛,抛给了我的调用者,调用者需要对这个异常继续处理,那么调用者处理这个异常同样有两种处理方式。Java中异常发生之后如果一直上抛,最终抛给了main方法,main方法继续向上抛,抛给了调用者JVM,JVM知道这个异常发生,只有一个结果。终止java程序的执行。
只要异常没有捕捉,采用上报的方式,此方法的后续代码不会执行。另外需要注意,try语句块中的某一行出现异常,该行后面的代码不会执行,try…catch捕捉异常之后,后续代码可以执行。
try catch的深入
(1)catch后面的小括号中的类型可以是具体的异常类型,也可以是该异常类型的父类。
(2)catch可以写多个。建议catch的时候,精确地一个一个处理。这样有利于程序的调试。
(3)catch写多个的时候,从上到下,必须遵守从小到大。
package exception;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
public class ExceptionTest03 {
public static void main(String[] args) throws Exception, FileNotFoundException, NullPointerException {
try {
//创建输入流
FileInputStream fis = new FileInputStream("D:\\Java\\007-API文档");
//读文件
fis.read();
}catch(FileNotFoundException e) {
System.out.println("文件不存在!");
} catch(IOException e){
System.out.println("读文件报错了!");
}
// 编译报错。
/*
try {
//创建输入流
FileInputStream fis = new FileInputStream("D:\Java\007-API文档");
//读文件
fis.read();
} catch(IOException e){
System.out.println("读文件报错了!");
} catch(FileNotFoundException e) {
System.out.println("文件不存在!");
}*/
}
}
JDK8新特性
package exception;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
public class ExceptionTest04 {
public static void main(String[] args) {
try {
//创建输入流
FileInputStream fis = new FileInputStream("D:\\Java\\007-API文档");
// 进行数学运算
System.out.println(100 / 0); // 这个异常是运行时异常,编写程序时可以处理,也可以不处理。
} catch(FileNotFoundException | ArithmeticException | NullPointerException e) {
System.out.println("文件不存在?数学异常?空指针异常?都有可能!");
}
}
}
上报和捕捉怎么选择
如果希望调用者来处理,选择throws。其它情况使用try…catch
异常对象的常用方法getMessage和printStackTrace();
(1)String msg = exception.getMessage() 获取异常简单的描述信
(2)exception.printStackTrace(); 打印异常追踪的堆栈信息
package exception;
public class ExceptionTest05 {
public static void main(String[] args) {
NullPointerException e = new NullPointerException("空指针异常");
System.out.println(e.getMessage());
e.printStackTrace();
//这行代码会执行 java后台打印异常堆栈追踪信息的时候,采用了异步线程的方式打印的
System.out.println("Hello world!");
}
}
查看异常的追踪信息,从上往下一行一行看,sun公司不用看,看自己写的代码就行。
在finally子句中的代码是最后执行的,并且是一定会执行的,即使try语句块中的代码出现了异常。
finally子句必须和try一起出现,不能单独编写。
finally语句通常在finally语句块中完成资源的释放/关闭。因为finally中的代码比较有保障。
package exception;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
public class ExceptionTest06 {
public static void main(String[] args) {
FileInputStream fis = null;
try {
//创建输入流对象
fis = new FileInputStream("D:\\Java\\007-API文档");
//开始读文件
String s = null;
//这里一定会出现空指针异常
s.toString();
//流使用完需要关闭,因为流是占用资源的
//即使以上程序出现异常,流也必须关闭,在这close也许关不掉,所以不能在这里关闭
//fis.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (NullPointerException e){
e.printStackTrace();
} finally{
//流的关闭放在这里比较保险
if(fis != null){//fis 前面也许new对象不成功 避免空指针异常
try {
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
try和finally没有catch也可以, try不能单独使用。
try finally可以联合使用。
package exception;
public class ExceptionTest07 {
public static void main(String[] args) {
/*
以下代码的执行顺序:
先执行try...
再执行finally...
最后执行 return (return语句只要执行方法必然结束。)
*/
try {
System.out.println("try...");
return;
} finally {
// finally中的语句会执行。能执行到。
System.out.println("finally...");
}
// 这里不能写语句,因为这个代码是无法执行到的。
//System.out.println("Hello World!");
}
}
退出JVM finally语句不执行
package exception;
public class ExceptionTest08 {
public static void main(String[] args) {
try {
System.out.println("try...");
// 退出JVM
System.exit(0); // 退出JVM之后,finally语句中的代码就不执行了!
} finally {
System.out.println("finally...");
}
}
}
finally面试题
package exception;
public class ExceptionTest09 {
public static void main(String[] args) {
int result = m();
System.out.println(result);//思考此时输出结果i的值是多少?
}
public static int m(){
int i = 100;
try{
return i;
}finally {
i++;
}
}
}
以上输出结果是100。
java语法规则:
java中有一条这样的规则:
方法体中的代码必须遵循自上而下顺序依次逐行执行(亘古不变的语法!)
java中还有一条语法规则:
return语句一旦执行,整个方法必须结束(亘古不变的语法!)
return i出现在int i = 100后面,所以最终结果必须返回100.
//反编译之后的效果
public static int m(){
int i = 100;
int j = i;
i++;
return j;
}
final finally finalize的区别
final 关键字
final修饰的类无法继承
final修饰的方法无法覆盖
final修饰的变量不能重新赋值。
finally 关键字
和try一起联合使用。
finally语句块中的代码是必须执行的。
finalize 标识符
是一个Object类中的方法名。
这个方法是由垃圾回收器GC负责调用的。
自定义异常
步骤:
第一步:编写一个类继承Exception或者RuntimeException。
第二步:提供两个构造方法,一个无参数的,一个带有String参数的。
package exception;
public class ExceptionTest10 {
public static void main(String[] args) {
//只是new了异常对象,并没有手动抛出
MyException e = new MyException("用户名为空异常!");
//打印异常堆栈信息
e.printStackTrace();
//获取异常简单描述信息
System.out.println(e.getMessage());
}
}
异常在实际开发中的作用
改良之前的代码
package exception;
public class MyStackOperationException extends Exception{
public MyStackOperationException() {
}
public MyStackOperationException(String s) {
super(s);
}
}
package exception;
public class MyStack {
private Object[] elements;
private int index;
public MyStack() {
this.elements = new Object[10];
this.index = -1;
}
/**
* 压栈的方法
* @param obj 被压入的元素
*/
public void push(Object obj) throws MyStackOperationException {
if(index >= elements.length - 1){
throw new MyStackOperationException("压栈失败,栈已满!");
//在这里不用捕捉的方式解决异常,异常是我们自己抛的,又自己捕捉没有意义,而且这个异常的信息我们需要传递出去让外界知道。
}
index++;
elements[index] = obj;
System.out.println("压栈" + obj + "元素成功,栈帧指向" + index);
}
/**
* 弹栈的方法,从数组中往外取元素。每取出一个元素,栈帧向下移动一位。
* @return
*/
public void pop() throws MyStackOperationException {
if(index < 0){
throw new MyStackOperationException("弹栈失败,栈已空!");
}
System.out.print("弹栈" + elements[index] + "元素成功,");
index--;
System.out.println("栈帧指向" + index);
}
public Object[] getElements() {
return elements;
}
public void setElements(Object[] elements) {
this.elements = elements;
}
public int getIndex() {
return index;
}
public void setIndex(int index) {
this.index = index;
}
}
package exception;
public class MyStackExceptionTest {
public static void main(String[] args) {
//创建栈对象
MyStack stack = new MyStack();
//压栈
try {
stack.push(new Object());
stack.push(new Object());
stack.push(new Object());
stack.push(new Object());
stack.push(new Object());
stack.push(new Object());
stack.push(new Object());
stack.push(new Object());
stack.push(new Object());
stack.push(new Object());
stack.push(new Object());
stack.push(new Object());
} catch (MyStackOperationException e) {
System.out.println(e.getMessage());
}
//弹栈
try {
stack.pop();
stack.pop();
stack.pop();
stack.pop();
stack.pop();
stack.pop();
stack.pop();
stack.pop();
stack.pop();
stack.pop();
stack.pop();
stack.pop();
} catch (MyStackOperationException e) {
System.out.println(e.getMessage());
}
}
}
异常与方法覆盖
重写之后的方法不能比重写之前的方法抛出更多(更宽泛)的异常,可以更少。
异常相关测试题
编写程序模拟用户注册:
①程序开始执行时,提示用户输入“用户名”和“密码”信息。
②输入信息之后,后台java程序模拟用户注册。
③注册时用户名要求长度在[6-14]之间,小于或者大于都表示异常。
注意:
完成注册的方法放到一个单独的类中。
异常类自定义即可。
class UserService {
public void register(String username,String password){
//这个方法中完成注册!
}
}
编写main方法,在main方法中接收用户输入的信息,在main方法
中调用UserService的register方法完成注册。
package exception.Test;
public class UserService {
/**
* 用户注册
* @param username 用户名
* @param password 密码
* @throws MyException2 当用户名为null,或者用户名格式不正确,会出现该异常!
*/
public void register(String username,String password) throws MyException2 {
//null == username 这个判断最好放在所有条件的前面,并且这么写比username == null 更保险
if(null == username || username.length() < 6 || username.length() > 14){
throw new MyException2("用户名不合法,长度必须在6-14之间!");
}
System.out.println("注册成功,欢迎【" + username + "】");
}
}
package exception.Test;
public class MyException2 extends Exception{
public MyException2() {
}
public MyException2(String message) {
super(message);
}
}
package exception.Test;
public class Test2 {
public static void main(String[] args) {
UserService userService = new UserService();
try {
userService.register("jack","123");
} catch (MyException2 e) {
System.out.println(e.getMessage());
}
}
}