异常:•在Java语言中,将程序执行中发生的不正常情况称为“异常”。
Java程序运行过程中所发生的异常事件可分为两类:
- Error: (不可查异常) JVM系统内部错误、资源耗尽等严重情况.如果程序出现了Error那么将无法恢复,只能重新启动程序,最典型的Error的异常是:OutOfMemoryError
- RuntimeException(不可差异) ,是RuntimeException类及其子类异常,如NullPointerException(空指针异常),被0除异常,此种异常可以不用显示的处理,java没有要求我们一定要处理。程序中可以选择捕获处理,也可以不处理.
- 一般性异常(可查异常):出现了这种异常必须在程序里面显示的处理(try-catch语句捕获它,要么用throws子句声明抛出它),否则程序无法编译通过
异常一般分为Error和程序本身可以处理的异常(RuntimeException和一般性异常),一般性异常指Exception子类中初RuntimeException类及其子类外的其他类及其子类.
//RuntimeError
public class ExceptionTest1 {
public static void main(String[] args) {
Person p=new Person("Li",15);
// p=null; //编译时没问题,运行时报错 java.lang.NullPointerException
System.out.println(p.name);//异常发生后,后面的程序不再执行
int a=19;
int b=0;
// System.out.println(a/b);//编译通过,运行报错, java.lang.ArithmeticException: / by zero
}
}
class Person{
private int age;
String name;
public Person(String name,int age) {
this.age=age;
this.name=name;
}
}
jvm处理异常的方式
如果在main方法中可以处理该异常信息,比如捕获或者抛出,然后继续运行,如果不处理该异常信息,则调用main的jvm有默认的异常处理机制,比如在控制台打印出异常信息.
可以在出现异常的地方进行处理,捕获或者向上抛出(向调用它的方法抛出),可以多层抛出,如果都没有处理则最终会抛出到main方法,main方法不处理,则调用main的jvm默认处理,(源码里定义了处理方式)
总结如下:Java程序的执行过程中如出现异常,会自动生成一个异常类对象,该异常对象将被提交给Java运行时系统,这个过程称为抛出(throw)异常。如果一个方法内抛出异常,该异常会被抛到调用方法中。如果异常没有在调用方法中处理,它继续被抛给这个调用方法的调用者。这个过程将一直继续下去,直到异常被处理。这一过程称为捕获(catch)异常。如果一个异常回到main()方法,并且main()也不处理,则程序运行终止。程序员通常只能处理Exception,而对Error无能为力。
异常的作用:通过异常信息,可以快速定位程序问题,以便完善程序
throws关键字抛出异常
throws 关键字声明 抛出异常,在方法声明的位置上使用throws关键字向上抛出异常的列表,可以是方法中产生的异常类型,也可以是其父类,也可以是多个异常,用逗号隔开.
如果一个方法(中的语句执行时)可能生成某种异常,但是并不能确定如何处理这种异常,则此方法应显式地声明抛出异常,使用throws关键字抛出异常并不是真正的处理异常,而由该方法的调用者去处理。
import java.io.FileInputStream;
import java.io.FileNotFoundException;
public class ExceptionTest2 {
//在方法名后面加throws关键字抛出异常,抛出异常的种类可以通过调用类的api查,也可以是其父类
public static void main(String[] args) throws FileNotFoundException {
FileInputStream f =new FileInputStream("test.txt");
}
}
out:
Exception in thread "main" java.io.FileNotFoundException: test.txt (系统找不到指定的文件。)
at java.base/java.io.FileInputStream.open0(Native Method)
at java.base/java.io.FileInputStream.open(FileInputStream.java:219)
at java.base/java.io.FileInputStream.<init>(FileInputStream.java:157)
at java.base/java.io.FileInputStream.<init>(FileInputStream.java:112)
抛出异常的父类:
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
public class ExceptionTest2 {
public static void main(String[] args) throws IOException {
FileInputStream f =new FileInputStream("test.txt");
}
}
out:
Exception in thread "main" java.io.FileNotFoundException: test.txt (系统找不到指定的文件。)
at java.base/java.io.FileInputStream.open0(Native Method)
at java.base/java.io.FileInputStream.open(FileInputStream.java:219)
at java.base/java.io.FileInputStream.<init>(FileInputStream.java:157)
at java.base/java.io.FileInputStream.<init>(FileInputStream.java:112)
多层向上抛出
public class ExceptionTest2 {
//main方法调用Student的test1方法,不处理,抛出,调用main的JVM去处理
public static void main(String[] args) throws FileNotFoundException {
Student s=new Student();
s.test1();
}
}
class Student{
//test1方法调用test2方法,test1也不处理,抛出
public static void test1() throws FileNotFoundException {
test2();
}
//test2方法调用test3方法,test2方法不处理,向上抛出
public static void test2() throws FileNotFoundException {
test3();
}
//在test3方法中出现异常,暂不处理,抛出
public static void test3() throws FileNotFoundException {
FileInputStream f=new FileInputStream("test.txt");
}
}
out:
Exception in thread "main" java.io.FileNotFoundException: test.txt (系统找不到指定的文件。)
at java.base/java.io.FileInputStream.open0(Native Method)
at java.base/java.io.FileInputStream.open(FileInputStream.java:219)
at java.base/java.io.FileInputStream.<init>(FileInputStream.java:157)
at java.base/java.io.FileInputStream.<init>(FileInputStream.java:112)
at com.chen.day6.Student.test3(ExceptionTest2.java:27)
at com.chen.day6.Student.test2(ExceptionTest2.java:23)
at com.chen.day6.Student.test1(ExceptionTest2.java:19)
at com.chen.day6.ExceptionTest2.main(ExceptionTest2.java:12)
try-catch捕获异常
try捕获异常的首先用try{…}语句块选定捕获异常的范围,将可能出现异常的代码放在try语句块中。
catch (Exceptiontype e)当try语句块出现异常时,catch语句块才执行,在catch语句块中是对异常对象进行处理的代码。catch中的异常类型也要跟产生异常的类型相同,或者是其父类,如果是无关的异常类型,则不执行
try语句块后可以跟多个catch语句块,用于处理可能产生的不同类型的异常对象,但是catch异常类型只能从小到大.
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
public class ExceptionTest3 {
public static void main(String[] args) {
//try后面可以有多个catch(异常种类 e),异常种类的顺序要从小到大,先子类后父类
try {
FileInputStream f=new FileInputStream("test.txt");
f.read();
} catch (FileNotFoundException e) {
//catch后面括号的内容是系统创建了一个FileNotFoundException对象,
//相当于FileNotFoundException e=new FileNotFoundException();
e.printStackTrace();
String msg=e.getMessage();
// System.out.println(msg);
} catch (IOException e) {
e.printStackTrace();
}catch(Exception e) {
e.printStackTrace();
}
}
}
out:
java.io.FileNotFoundException: test.txt (系统找不到指定的文件。)
at java.base/java.io.FileInputStream.open0(Native Method)
at java.base/java.io.FileInputStream.open(FileInputStream.java:219)
at java.base/java.io.FileInputStream.<init>(FileInputStream.java:157)
at java.base/java.io.FileInputStream.<init>(FileInputStream.java:112)
at com.chen.day6.ExceptionTest3.main(ExceptionTest3.java:10)
catch异常后,创建了一个异常对象,可以访问异常对象的成员变量或调用它的方法。getMessage( ) 方法,用来得到有关异常事件的信息,printStackTrace( )用来跟踪异常事件发生时执行堆栈的内容。
finally
finally语句块使用方法:try….finally…(不常见),try…catch….finally,finally语句块可以不写,但写了之后不管try语句块是否发生异常,finally块中的语句一定会被执行。为异常处理提供一个统一的出口
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
public class ExceptionTest3 {
public static void main(String[] args) {
//try后面可以有多个catch(异常种类 e),异常种类的顺序要从小到大,先子类后父类
try {
FileInputStream f=new FileInputStream("test.txt");
f.read();
} catch (FileNotFoundException e) {
//catch后面括号的内容是系统创建了一个FileNotFoundException对象,
//相当于FileNotFoundException e=new FileNotFoundException();
e.printStackTrace();
String msg=e.getMessage();
// System.out.println(msg);
} catch (IOException e) {
e.printStackTrace();
}catch(Exception e) {
e.printStackTrace();
}finally {
System.out.println("捕获到了异常,程序继续执行");
}
}
}
out:
java.io.FileNotFoundException: test.txt (系统找不到指定的文件。)
at java.base/java.io.FileInputStream.open0(Native Method)
at java.base/java.io.FileInputStream.open(FileInputStream.java:219)
at java.base/java.io.FileInputStream.<init>(FileInputStream.java:157)
at java.base/java.io.FileInputStream.<init>(FileInputStream.java:112)
捕获到了异常,程序继续执行
try语句块中里面执行了return,finally中的代码也会执行,并且先执行finally语句块,才执行return.
public class FinallyTest {
public static void main(String[] args) {
Student1 person2=new Student1();
int j=person2.test1();
System.out.println(j);
}
}
class Student1{
public static int test1() {
try{
int i =10;//try里定义的变量在外部无法访问,相当于语句块
return i;
}catch (Exception e) {
e.printStackTrace();
}finally {
System.out.println("finally 中语句块");
}
return 0;//当去掉catch语句块的时候可以直接执行,猜测有catch语句块的时候,
//系统认为可能发生异常,执行不到return i这步,所以要求后面加个return 0,确保有返回值
}
public int test2() {
try{
int i =10;//try里定义的变量在外部无法访问,相当于语句块
return i;
}finally {
System.out.println("finally 中语句块");
}
// return 0;//报错,不会被访问的语句,
}
}
try-catch-finally的执行顺序
不管有没有出现异常,finally块中代码都会执行
如果try、catch中有return语句,finally中没有return,代码从try到finally顺序执行,因为finally语句块肯定执行,所以try里的语句并不会直接返回。在try语句的return语句中,return返回的引用变量并不是try语句外定义的引用变量i,而是系统重新定义了一个局部引用i’,这个引用指向了引用i对应的值,即使在finally语句中把引用i指向了新的值,但是return返回的引用是i',所以引用i的值和try语句中的返回值无关了。对于基本数据类型,静态变量,全局变量,引用数据类型,finally语句块不会改变其值,但是包装类型会改变其值,如ArrayList(下例3)
finally中有return语句,会屏蔽掉try - catch中的return,也会忽略掉try -catch中的异常,从finally中返回
如果finally中发生异常,代码执行将会抛出finally中的异常信息,try、catch中的异常将被忽略,所以finally尽量不要出现异常
public class FianllyTest2 {
public static void main(String[] args) {
Student2 student2=new Student2();
int j=student2.test1();
System.out.println(j);
}
}
class Student2{
public static int test1() {
int k=20;
try{
return k;
}finally {
k++;
System.out.println("finally 中的"+k);
}
}
}
out:
finally 中的21
20
分析:执行到try语句块的return k时,先将k赋值给一个变量temp,检查后面是否有finall语句,有的话先执行finally语句块内容,再执行将这个变量temp返回的操作,(也就是temp指针指向原来k指的地方,在finally中k指向了新的地方),如果finally语句中有return语句,那就不执行try中的finally语句了
public class FianllyTest2 {
public static void main(String[] args) {
Student2 student2=new Student2();
int age=student2.test1();
System.out.println(age);
}
}
class Student2{
public static int test1() {
Student3 s3=new Student3("zhang", 15);
try{
return s3.getAge();
}finally {
s3.setAge(28);
System.out.println("finally 中的"+s3.getAge());
}
}
}
class Student3{
private int age;
private String name;
public Student3(String name,int age) {
this.name=name;
this.age=age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
out://说明引用对象类型,经过finally后也不会改变其值
finally 中的28
15
例3,包装类型
public class FinallyTest4 {
public static void main(String[] args) {
ArrayList o=testWrap();
System.out.println(o);
}
public static ArrayList<Object> testWrap(){
ArrayList<Object> list = new ArrayList<>();
try{
list.add("hello");
System.out.println("try hello ");
return list;
}catch(Exception e){
list.add("catch");
System.out.println("catch block");
return list;
}finally{
list.add("world");
System.out.println(" finally hello world ");
}
}
}
out:
try hello
finally hello world
[hello, world]
自定义异常
当java里面的异常始终是有限的,无法满足开发者的需求时(比如没有用户不存在异常),可以自定义异常
通过查看异常类的源码,一般都是一个序列号和几个构造方法.
如果自定义异常是RuntimeException类型的,那就直接继承RuntimeException即可否则就继承Exception。继承之后一般提供两个构造方法.
package com.chen.day6;
import java.util.Arrays;
public class UserNotExistException extends RuntimeException {
private static final long serialVersionUID = 1L;
// 提供两个构造方法
public UserNotExistException() {
}
public UserNotExistException(String msg) {
super(msg);
}
}
//提供一个类,作为返回值对象
class User {
}
测试类
class TestUserNotExistException {
public static void main(String[] args) {
String user = "AA";
Object result = getUser(user);
System.out.println(result);
}
public static User getUser(String user) {
java.util.List<String> users = Arrays.asList("aa", "bb", "cc");
if (users.contains(user)) {
return new User();
} else {
throw new UserNotExistException("User is Not Exist");
}
}
}
out:
Exception in thread "main" com.chen.day6.UserNotExistException: User is Not Exist
at com.chen.day6.TestUserNotExistException.getUser(UserNotExistException.java:34)
at com.chen.day6.TestUserNotExistException.main(UserNotExistException.java:25)
throw,在方法内部出现异常情况,程序不能继续执行,就用throw把异常对象抛出
throw和throws区别:
- throws,写在方法声明后面,跟的是异常类型名字,如果有多个异常类型,用逗号隔开,本身不处理异常,由方法调用者处理
- throw 用在方法体内,跟的是异常对象名字,只能抛出一个异常对象名,能抛出一个异常对象名,本身抛出异常,由方法体内的语句处理
例子:
编写应用程序EcmDef.java,接收命令行的两个参数,要求不能输入负数,计算两数相除。 对缺少命令行参数(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;
package com.chen.day6;
public class Ecmdef {
public static void main(String[] args) {
//多个异常只能catch一个
try {
int i = Integer.parseInt(args[0]);
int j = Integer.parseInt(args[1]);
int result = ecm(i, j);
System.out.println("结果是: " + result);
} catch (NumberFormatException e) {
System.out.println("非法输入:输入参数不能转换成int型");
} catch (ArrayIndexOutOfBoundsException e) {
System.out.println("输入参数个数不足");
} catch (ArithmeticException e) {
System.out.println("不能被0除,除数不能是0");
} catch (EcDef e) {
System.out.println("不能输入负数");
//e.printStackTrace();
}
}
public static int ecm(int i, int j) {
if (i < 0 || j < 0) {
throw new EcDef(" 输入不能是负数");
} else {
return i / j;
}
}
}
class EcDef extends RuntimeException {
//自定义异常
public EcDef() {
}
public EcDef(String msg) {
super(msg);
}
}
参考http://www.atguigu.com/online.shtml