异常概述
在使用计算机语言进行项目开发的过程中,即使程序员把代码写的尽善尽美,在系统的运行过程中仍然会遇到一些问题,因为很多问题不是考代码就能避免的,比如:客户输入数据的格式,读取文件是否存在,网络是否始终保持通畅等。
异常:在Java语言中,将程序执行中发生的不正常的情况称为“异常”(开发过程中的语法错误与逻辑错误不叫异常)。
Java程序在执行中出现的异常分两类:
- Error:Java虚拟机无法解决的严重问题,例如:JVM内部错误,资源耗尽等严重情况。一般不编写针对性的代码进行修复
- 举两个例子
- java.lang.StackOverflowError:栈溢出
- java.lang.OutOfMemoryError:堆溢出
- Exception:其他因编程错误或偶然的外在因素导致的一般性问题,可以使用针对性的代码进行处理。例如:空指针访问,视图读取不存在的文件,网络连接中断,越界等等。
异常举例
import java.util.Date;
import org.junit.Test;
import java.util.Scanner;
/**
* java.lang.Throwable:
* |
* |------java.lang.Error:一般不编写针对性的代码进行处理
* |
* |------java.lang.Exception:可以进行异常的处理
* |
* |------编译时异常(checked)
* | |
* | |------IOException :IO异常
* | |
* | |------FileNotFoundException:文件或目录不存在
* |
* |------运行时异常(unchecked)
* |
* |------NullPointerException : 空指针异常
* |
* |------ArrayIndexOutOfBoundsException:数组越界
* |
* |------ClassCastException : 类型转换异常
* |
* |------NumberFormatException:数字格式化异常
* |
* |------InputMismatchException: 输入异常
* |
* |------ArithmeticException : 算数异常
*
*/
public class ExceptionTest {
// 空指针异常
@Test
public void test1(){
int[] arr = null;
System.out.println(arr[0]);
}
// 越界
@Test
public void test2(){
int[] arr = new int[3];
System.out.println(arr[4]);
}
// 类型转换异常
@Test
public void test3(){
Object obj = new Date();
String str = (String) obj;
System.out.println(str);
}
// 数字格式化异常
@Test
public void test4(){
String str = "abc";
int num = Integer.parseInt(str);
System.out.println(num);
}
// 输入异常
@Test
public void test5(){
Scanner scanner = new Scanner(System.in);
int a = scanner.nextInt();
// 如果这里输入一个非int就会报异常
System.out.println(a);
scanner.close();
}
// 算数异常
@Test
public void test6(){
int a = 10;
int b = 0;
System.out.println(a / b);
}
}
异常处理
Java提供的异常处理是抓抛模型。
Java程序的执行过程中如出现异常,会生成一个异常类对象,该异常对象将被提交给Java运行系统,这个过程叫做抛出异常。
异常对象的生成分为两种,第一种是虚拟机自动生成,在程序运行过程中,虚拟机检测到程序发生问题,如果在当前代码中没有找到对应的处理程序,就由后台自动创建一个对应异常的实例对象抛出;第二种是由开发人员手动创建,如果不抛出则对程序没有任何影响。
try-catch-finally
语法:
try{
// 可能出现异常的代码
}catch(异常类型1 变量名1){
// 处理异常1
}catch(异常类型2 变量名2){
// 处理异常2
}finally{
// 一定会执行的代码
}
例子:
public class TryTest {
public static void main(String[] args) {
/**
* finally 是可选的
*
* 使用try将可能出现异常代码包装起来,在执行过程中,一旦出现异常,就会生成一个对应的异常对象,
* 然后去catch中匹配异常,如果匹配成功就执行catch块中的代码,如果匹配失败则抛出异常。
*
* try-catch-finally结构运行完成后,继续执行后面的代码,如果catch中报错,只能执行finally中的代码
*
* 优先度高的异常应该在优先度低的异常的后面(子类异常必须在父类异常之前)
*
* 常用的异常对象处理方式:①String getMessage() ②printStackTrace()
*
* try-catch-finally结构内的变量是局部变量
*
* finally中的代码一定执行
*
* 一般会将数据库连接、输入输出流、socket等资源的释放操作放到finally中
*
* try-catch-finally结构可以相互嵌套
*/
try{
String str = "abc";
int num = Integer.parseInt(str);
System.out.println(num);
}catch(NumberFormatException e){
System.out.println(e.getMessage());
}catch(Exception e){
e.printStackTrace();
}finally{
System.out.println("执行成功");
}
}
}
体会1:使用try-catch-finally结构
处理编译时异常,使得程序在编译时就不再报错,但是运行时仍可能报错,相当于我们使用try-catch-finally结构
将一个编译时出现的异常延迟到运行时出现。
体会2:开发时,因为运行时异常比较常见,所以我们通常就不针对运行时异常编写try-catch-finally结构
,针对编译时异常,一定要考虑异常的处理。
throws
throws一般用于方法的定义中,表示抛出异常,谁调用这个方法就让谁来解决这个异常。
public class ThrowsTest {
// 这里调用了test方法,所以他接收异常来处理,而且他是main方法,必须处理异常。
public static void main(String[] args) {
try{
test();
}catch(ArithmeticException e){
e.printStackTrace();
}
// 因为test2内已经处理了异常,所以main可以直接调用
test2();
// 方法重写的抛出异常
test3(new SubClass());
}
public static void test3(SuperClass s){
try{
// 如果subclass的method方法抛出的异常比superclass抛出的异常还大,有可能造成子类的异常无法被处理
s.method();
}catch(ArithmeticException e){
e.printStackTrace();
}
}
public static void test2(){
try{
test();
}catch(ArithmeticException e){
e.printStackTrace();
}
}
// 报异常后不自己处理,抛出异常
public static void test() throws ArithmeticException{
int a = 10;
int b = 0;
System.out.println(a / b);
}
}
class SuperClass {
public void method() throws ArithmeticException{
}
}
class SubClass extends SuperClass{
@Override
// 可以声明 ArithmeticException 同级别的异常,但是不能声明他的父类级别的异常 例如 Exception
public void method() throws ArithmeticException{
}
}
手动抛出异常
public class StudentTest {
public static void main(String[] args) {
Student s = new Student();
try{
// 传入负数,程序抛出自定义异常。
// 传入正数,无异常
s.regist(-100);
System.out.println(s);
}catch(Exception e){
// 处理异常
System.out.println(e.getMessage());
}
}
}
class Student{
private int id;
public void regist(int id){
if(id > 0){
this.id = id;
}else{
// 手动抛出异常
throw new RuntimeException("输入的数据非法");
}
}
@Override
public String toString() {
return "Student [id=" + id + "]";
}
}
自定义异常
我们使用的异常,哪怕是手动抛出的异常,都是Java为我们提供好的,那么我们能不能自定义异常呢?
/**
* 首先继承与现有的异常结构
* 必须提供全局变量serialVersionUID
* 提供重载的构造器
*/
public class MyException extends RuntimeException{
// 自动导入的一个静态属性
private static final long serialVersionUID = -7034897196939L;
public MyException(){
}
public MyException(String msg){
super(msg);
}
}
这样就算自定义了一个异常,然后把他放到上一个例子里。
public class StudentTest {
public static void main(String[] args) {
Student s = new Student();
try{
s.regist(-100);
System.out.println(s);
}catch(RuntimeException e){
// 处理异常
System.out.println(e.getMessage());
}
}
}
class Student{
private int id;
public void regist(int id) throws Exception{
if(id > 0){
this.id = id;
}else{
// 手动抛出异常
throw new MyException("输入的数据非法");
}
}
@Override
public String toString() {
return "Student [id=" + id + "]";
}
}
练习
判断运行结果
public class ReturnExceptionDemo {
static void methodA() {
try {
System.out.println("进入方法A");
throw new RuntimeException("制造异常");
}finally {
System.out.println("用A方法的finally");
}
}
static void methodB() {
try {
System.out.println("进入方法B");
return;
} finally {
System.out.println("调用B方法的finally");
}
}
public static void main(String[] args) {
try {
methodA();
} catch (Exception e) {
System.out.println(e.getMessage());
}
methodB();
}
}
----------
进入方法A
用A方法的finally
制造异常
进入方法B
调用B方法的finally
编写应用程序EcmDef.java,接收命令行的两个参数,要求不能输入负数,计算
两数相除。
对 数 据 类 型 不 一 致 (NumberFormatException)
缺 少 命 令 行 参 数(ArrayIndexOutOfBoundsException)
除0(ArithmeticException)
输入负数(EcDef 自定义的异常)进行异常处理。提示:
(1)在主类(EcmDef)中定义异常方法(ecm)完成两数相除功能。
(2)在main()方法中使用异常处理语句进行异常处理。
(3)在程序中,自定义对应输入负数的异常类(EcDef)。
(4)运行时接受参数 java EcmDef 20 10 //args[0]=“20” args[1]=“10”
(5)Interger类的static方法parseInt(String s)将s转换成对应的int值。 如:int a=Interger.parseInt(“314”); //a=314;
/**
* 编写应用程序EcmDef.java,接收命令行的两个参数,要求不能输入负数,计算
* 两数相除。
* 对数据类型不一致(NumberFormatException)
* 缺少命令行参数(ArrayIndexOutOfBoundsException)
* 除0(ArithmeticException)
* 输入负数(EcDef 自定义的异常)进行异常处理。
* 提示:
* (1)在主类(EcmDef)中定义异常方法(ecm)完成两数相除功能。
* (2)在main()方法中使用异常处理语句进行异常处理。
* (3)在程序中,自定义对应输入负数的异常类(EcDef)。
* (4)运行时接受参数 java EcmDef 20 10 //args[0]=“20” args[1]=“10”
* (5)Interger类的static方法parseInt(String s)将s转换成对应的int值。 如:int a=Interger.parseInt(“314”); //a=314;
*/
public class EcmDef {
public static void main(final String[] args) {
try{
final int i = Integer.parseInt(args[0]);
final int j = Integer.parseInt(args[1]);
final int result = ecm(i, j);
System.out.println(result);
}catch(NumberFormatException e){
System.out.println("数据类型不一致");
}catch(ArrayIndexOutOfBoundsException e){
System.out.println("缺少命令行参数");
}catch(ArithmeticException e){
System.out.println("除0");
}catch(EcDef e){
System.out.print(e.getMessage());
}
}
public static int ecm(int i, int j) throws EcDef {
if(i < 0 || j < 0){
throw new EcDef("分子或分母是负数了");
}
return i / j ;
}
}
class EcDef extends Exception {
private static final long serialVersionUID = -3387516993124229948L;
public EcDef(){
}
public EcDef(String msg){
super(msg);
}
}