目录
什么是异常
-
软件程序在运行过程中,可能遇到各种意外,我们称为异常(Exception),让我们写的程序作出合理的处理,而不至于崩溃。
-
异常发生在程序运行期间出现的不期而至的各种状况,影响正常的程序执行流程。如:文件缺失、网络连接失败、非法参数等。
- 要理解Java异常处理是如何工作的,你需要掌握以下三种类型异常:
-
检查性(编译时)异常(CheckedException):最具代表性的检查性异常是用户错误或问题引起的异常,如要打开一个不存在文件时,异常就发生了,这些异常在编译时不能被简单的忽略。
-
运行时异常(RuntimeException):与检查性异常相反,运行时异常可以在编译时容易被忽略,只有在运行时才能看到。
-
错误(ERROR):错误不是异常,而是脱离程序员控制的问题。错误在代码中通常被忽略,如:栈溢出时,一个错误就发生了,它们在编译时无法被检测。
异常体系结构
-
Java把异常当做对象来处理,并定义了一个基类java.lang.Throwable(可抛出的)作为所有异常的超类。
-
在Java API中已经定义了许多异常类,这些异常类分为两大类,错误Error和异常Exception。
错误(Error)
-
Error类对象由 Java 虚拟机生成并抛出,大多数错误与代码编写者所执行的操作无关。
-
Java运行错误(Virtual MachineError),当JVM不再有继续执行操作所需的内存资源时,将出现OutOfMemoryError。这些异常发生时候,Java虚拟机(JVM)一般会选择线程终止;
-
还有发生在虚拟机试图执行应用时,如类定义错误(NoClassDefFoundError)、链接错误(LinkageError)。这些错误是不可查的,因为他们在应用程序的控制和处理能力之外,而且绝大多数是程序运行时不允许出现的状况。
异常(Exception)
-
在Exception分支中有一个重要的子类RunTimeException(运行时异常)
-
ArrayLndexOutOfBoundsExecption(数组下标越界)
-
NullPointerExeception(空指针异常)
-
ArtithmeticException(算数异常)
-
MissingResourceException(资源丢失)
-
ClassNotFoundException(找不到类)
这些异常是不检查异常,程序中可以选择捕获处理,也可以不处理。
-
-
这些异常一般是由程序员逻辑错误引起的,程序应该从逻辑角度尽可能避免这类异常的发生;
-
Error和Exception的区别:Error通常是灾难性的致命的错误,是程序无法控制和处理的,当出现这些异常时,Java虚拟机(JVM)一般会选择终止线程:Exception通常是可以处理的,并且在程序中应该尽可能的处理这些异常。
异常处理机制
-
异常机制使程序更健壮,因为捕捉异常后程序不会停止!
- 对于异常的处理可使用两种方式
-
在方法声明的位置上,使用throws关键字,抛给上一级。
-
使用try...catch语句进行异常的捕捉。
-
-
异常处理的五个关键字
-
try、catch、finally ; throw、throws
-
- 通常在finally语句中完成资源的释放/关闭。
捕获异常(try...catch...finally)
public static void main(String[] args) {
int a = 1;
int b = 0;
//System.out.println(a/b); //ArithmeticException算数异常
try { //try 监控区域
System.out.println(a/b);
//e引用保存内存地址 指向new出来的异常对象
} catch (ArithmeticException a) { //catch(想要捕获的异常类型)
System.out.println("Exception:程序出现异常,变量b不能为0");
} catch (Exception e){ //多态:Exception e = new ArithmeticException();
//可以捕获多个异常,需要从小到大捕获。建议精确,有利于调试
//ArithmeticException< Exception< Tgriwable
System.out.println("Throwable");
}finally { //无论是否出现异常finally都会被执行
System.out.println("无论是否出现异常finally都会被执行");
}
//finally 如IO 流使用完因为占用资源需要关闭,可写在finally中 close();
//快捷键:选中代码后Ctrl Alt+T
}
-
捕捉相当于将异常拦下,异常真正的处理了。
-
在多重catch块最后面,可以加一个catch(Exception)来处理可能会被遗漏的异常。
-
try...finally可以联用
public static void main(String[] args) {
try {
System.out.println("try...");
return; //结束
} finally { //finall语句块内是一定被执行的
System.out.println("finally...");
}
//代码无法被执行到
//System.out.println("HelloWorld"); //报错:Unreachable statement
}
抛出异常(throw、throws)
实际项目时,底层异常一般上抛到某一层面统一进行处理。
- 如果希望使用者来处理,选择throws(上抛)。(比如 FileNotFoundException文件丢失异常)
注:子类继承父类,不能比父类抛出更多的异常!
public static void main(String[] args) {
new Test().test(1,0);
}
//假设方法中处理不了这个异常,就在方法上抛出异常 若调用这个方法就需要处理这个异常
public void test(int a,int b) throws ArithmeticException{
if(b==0){ //区分 throw throws
throw new ArithmeticException();//主动抛出异常,一般在方法中使用
}
}
上抛类似于甩锅,继续把异常异常传递给调用者。
public static void main(String[] args) {
//编译时异常不进行处理,就会报错
doSome(); //Unhandled exception: java.lang.ClassNotFoundException
}
public static void doSome()throws ClassNotFoundException{
System.out.println("doSome");
}
关于finally的面试题
//面试题1:finally什么情况不会被执行?
//1.System.exit()中止当前虚拟机(JVM)时。
//2.守护(daemon)线程被中止时
//面试题2:以下代码将输出?
public static void main(String[] args) {
System.out.println(m()); //100
}
public static int m(){
int i = 100;
try{
return i;
}finally{ //finally语句块是一定最后被执行的
i++;
}
}
/*
Java语法规则(有一些规则是不能破坏的)
方法体重的代码必须遵循自上而下的顺序依次执行。
return语句一旦执行,整个方法必须结束。
反编译之后的效果
public static int m (){
int i = 100;
int j = i;
i++;
return j;
}*/
//笔试题3:final finally finalize 有什么区别?
public static void main(String[] args) {
//1.final是一个关键字。表示最终的。
final int i = 100;
//修饰的类无法继承、方法无法覆盖、变量无法重新赋值。
//2.finally也是一个关键字,和try联合使用,在异常处理机制中。
//finally语句块内的代码是一定会被执行的。
try{
}finally{
System.out.println("finally...");
}
//3.finalize()是Object类中的一个方法,已过时。
//该方法是JVM的GC垃圾回收期负责调用。
//finalize作为方法名出现,所以finalize是标识符。
*/
//
异常对象的常用方法
-
获取异常简单的描述信息: (实际上是异常构造方法上面String参数)
-
String msg = exception.getMessoge();
-
NullPointerException e = new NullPointerException("空指针异常!");
System.out.println(e.getMessage()); //空指针异常!
-
打印异常追踪的堆栈信息:
-
exception.printStackTrace();
-
NullPointerException e = new NullPointerException("空指针异常!");
e.printStackTrace(); //采用了异步线程的方式打印。(多线程)
//实际开发中,建议使用。养好好习惯,不然出了问题你也不知道!
注:查看异常追踪信息时由,自己写的代码开始从上往下看。
自定义异常(重点)
SUN提供的JDK内置异常肯定是不够用的。在实际开发中,有很多业务出现异常中,JDK中都是没有的,也和业务挂钩的,那么我们便需要自定义异常。
-
如何自定义异常? (灵感来自于SUN公司)
-
编写一个类继承Exception或RuntimeException
-
提供两个构造方法,一个无参,一个有参。
-
public class MyException extends Exception { //编译时异常 发生概率高
public MyException(){ } //无参构造
public MyException(String s){ //有参构造
super(s);
}
}
public class MyException extends RuntimeException { //运行时异常 发生概率低
public MyException(){ }
public MyException(String s){
super(s);
}
- 自定义异常的应用
public class test2 {
public static void main(String[] args) {
//创建异常对象 只new了对象,未抛出
MyException e = new MyException("用户名不能为空!");
//打印异常堆栈信息
e.printStackTrace();
//获取异常简单描述信息
String msg = e.getMessage(); //用户名不能为空!
}
}
习题
编写程序模拟用户注册:
1、程序开始执行时,提示用户输入“用户名”和“密码”信息。
2、输入信息之后,后台java程序模拟用户注册。
3、注册时用户名要求长度在[6-14]之间,小于或者大于都表示异常。
注意:
完成注册的方法放到一个单独的类中。
异常类自定义即可。
class UserService {
public void register(String username,String password){
//这个方法中完成注册!
}
}
编写main方法,在main方法中接收用户输入的信息,在main方法
中调用UserService的register方法完成注册。
public class UserService {
public void register(String username,String password)throws UserServiceException{
//如果用户名越界
if (username.length()<6 || username.length()>16){
//抛出新建的异常,输入简单信息
throw new UserServiceException("用户名越界,请重新输入");
}
}
}
//照葫芦画瓢,创建新异常,用户服务异常
public class UserServiceException extends Exception{
public UserServiceException(){}
public UserServiceException(String s){
super(s);
}
}
public class TestUser {
public static void main(String[] args) {
UserService a = new UserService();
Scanner scanner = new Scanner(System.in);
System.out.println("欢迎使用注册系统,请输入用户名!");
String u = scanner.next();
System.out.println("用户名为"+u+",请输入密码!");
String p = scanner.next();
boolean flag = false; //用于判断用用户是否创建成功
do{
try{
a.register(u,p);
flag = true; //跳出循环
System.out.println("注册成功"); //如果失败,抛出异常,注册失败。
} catch (UserServiceException e) {
System.out.println(e.getMessage());
u = scanner.next(); //重新输入用户名
}
}while(flag = false);
}
}