例外
-
首先是上期的String物件一共生成了11个(包括底层生成的存于哈希表中的那个哈~!)
-
首先要知道例外有两大类,一个是编译期例外、一个是运行时例外
-
编译器例外:如System.out.println写成system.out.println,此时编译时就会报错,或者说在你的IDEA里此代码当场报红
-
运行时例外:当程序跑起来了,才在IDEA底下的结果视窗中弹出例外的信息,所以说此时代码是可以通过编译的
- 使用例外的好处:可以让我们的例外都统一放在一起,方便管理,看着也舒服;要不然采用我们平时写代码的习惯,每个都用个if陈述句去判断,可能造成正常运行的代码和出现例外的代码都混在一起的现象,
例外的基本用法
try{
有可能出现例外的陈述句都丢在这;
}catch(例外型别 例外物件){
处理例外的陈述句;
}finally{
例外的出口;//这个finally的部分可以不写,写了的话,这里面的陈述句是一定会被执行的,即使try陈述句里有return,finally里的陈述句都要先执行!
}
-
代码执行到有例外的陈述句后,如果我们自己不去处理,其结果是什么?
结果是这个例外会交给JVM去处理!而JVM处理的结果就是,代码从这个出现例外的陈述句开始往下的代码全部终止!或者说例外终止,或者说奔溃了,
-
那给一个我们主动去处理一个例外的情况看看:
public class Test {
public static void main(String[] args) {
int n=0;
try{
if(n==0){
throw new Exception("抛出例外咯!");//这里new物件的时候呼叫的是内置例外类,这句话一旦执行,这个例外会在结果栏里给出定位(例外代码所在行号,这也是例外使用的好处之一)
// Exception的特定构造方法
}
}catch(Exception e){
e.printStackTrace();//打印例外e,这里就相当于处理例外了
}
System.out.println("hehe");
}
}
/*
结果:
java.lang.Exception: 抛出例外咯!
at Test.main(Test.java:13)
hehe
Process finished with exit code 0
*/
上述代码给我们的启示是:即使有例外出现(被抛出),我们将其处理之后,不影响后续代码的执行,不至于整个程序都崩溃,而如果我们自己不处理,就交由JVM处理,其结果就是程序将终止与第六行代码,
- 其次我们try{}内的陈述句从上往下依次执行时,一旦出现某个陈述句抛出例外,那这个{}内该代码以下的代码不会再执行!
关于例外打印的顺序
- 我们知道JVM处理例外后,将例外信息堆栈的例外都将打印出来,因为这是一个堆栈,所以说先进去的例外信息后出来,后进去的例外信息会先出来,就比如:
public class Test {
public static void main(String[] args) {
Scanner scanner=new Scanner(System.in);
int n=scanner.nextInt();
System.out.println(n);
}
}
当我们从键盘键入一些字母之后,查看最后的例外信息:
sasa
Exception in thread "main" java.util.InputMismatchException
at java.util.Scanner.throwFor(Scanner.java:864)
at java.util.Scanner.next(Scanner.java:1485)
at java.util.Scanner.nextInt(Scanner.java:2117)
at java.util.Scanner.nextInt(Scanner.java:2076)
at Test.main(Test.java:13)
由上面提到的先进后出的例外信息可知,第一个出现的例外是main类的第13行,而上面的例外信息都是内置类的原始码出错!(我们不可能怀疑原始码),所以我们就乖乖去修改第13行代码就可以啦!改完(我们自己可以去用try-catch去包裹它,包裹:surround)即可,
处理多个例外的情况
public class Test {
public static void main(String[] args) {
int[] arr={1,2,3};
try{
arr=null;
System.out.println(arr[5]);
System.out.println(arr.length);
}catch (ArrayIndexOutOfBoundsException e){
e.printStackTrace();
System.out.println("捕捉到了阵列越界例外!");
}catch (NullPointerException e){
e.printStackTrace();
System.out.println("捕捉到了空指标例外!");
}
System.out.println("嘻嘻!");
}
}
/*
结果:
java.lang.NullPointerException
at Test.main(Test.java:15)
捕捉到了空指标例外!
嘻嘻!
*/
还是上面提到的原则:try里第一个抛出例外的陈述句执行完,try内的之后的陈述句都终止执行,
当然我们可以多捕获多个例外的代码换一种写法:
public class Test {
public static void main(String[] args) {
int[] arr={1,2,3};
try{
arr=null;
System.out.println(arr[5]);
System.out.println(arr.length);
}catch (ArrayIndexOutOfBoundsException | NullPointerException e){//例外想写几个写几个,e只能写一个哦
e.printStackTrace();//根据例外信息判断是哪个例外即可
}
System.out.println("嘻嘻!");
}
}
例外的体系结构
- 顶层类Throwable派生出两个而重要子类:Error和Exceptiom
- 其中Error代表java运行时内部错误或者资源耗尽错误,应用程序不会抛出这个例外,这种内部错误一旦出现,除了告知用户并使程序终止之外,无能为力,这种情况很少出现
- Exception是我们程序员所使用的例外类的父类
- 其中Exception有一个子类叫RuntimeException,这里面又派生出许多我们常见的例外类,如:NullpointerException等
-
说上面的是因为:我们用catch去捕捉例外类时是可以用父类例外去捕捉子类例外的,从逻辑上来说,我们可以用Exception去捕捉所有的它的子类例外!但不建议这样去做,因为Exception涵盖的例外类太多,可能把控不好,最好是写我们想捕捉的具体的例外类,尽量不要使用父类例外去捕捉子类例外,但没说不可用哈,
-
若一段代码中既有父类例外去捕获一个例外,也有具体的例外去捕获例外,那书写顺序是:先写子类例外去捕获,然后再父类,写反了,当场报错,
-
当我们不知道一个例外的父类到底是谁时:我们可以左键点击一下例外类,然后右键→Diagram→show就可以看见整个继承关系了!:(社区办可能没有这个功能,想不花钱用旗舰版,那只能破解版了)
-
其次很重要的一点:函式处理例外的时候,本函式抛出例外如果幺有当场解决,这个例外就会自动抛给呼叫这个函式的函式,呼叫者(主函式)如果还不处理这个例外,那就只能交由JVM去处理这个例外了!换句话说就是:例外会沿着例外的信息呼叫堆栈进行传递!!!!
finally的使用
- finally表示最后的善后作业,例如释放资源
public class Test {
public static void main(String[] args) {
Scanner scanner=new Scanner(System.in);//Scanner相当于一个资源!~
try{
int n=scanner.nextInt();
System.out.println(10/n);
}catch(InputMismatchException e){
e.printStackTrace();
System.out.println("捕捉到了输入不匹配例外!");
}catch(ArithmeticException e){
e.printStackTrace();
System.out.println("捕捉到了算术例外,可能除数为0");
}finally {
scanner.close();
}
}
}
针对上述代码,这里有一个小技巧一步到位:
public class Test {
public static void main(String[] args) {
try(Scanner scanner=new Scanner(System.in)){//这里可以做到try里面的陈述句执行完毕,自动呼叫scanner.close()
int n=scanner.nextInt();
System.out.println(10/n);
}catch(InputMismatchException e){
e.printStackTrace();
System.out.println("捕捉到了输入不匹配例外!");
}catch(ArithmeticException e){
e.printStackTrace();
System.out.println("捕捉到了算术例外,可能除数为0");
}
}
}
- 对上述代码:一个函式抛出例外(本函式内部不作处理!)我想把例外传给主函式,要对被呼叫的函式做例外申明:
public class Test {
public static void func()throws Exception{//throws就用于宣告例外啦,所以说throws用在宣告,而throw用在抛出例外,给例外定位!
//哪里写了throw,只要这个陈述句被执行,最后这行代码会被标出来,到时候我们直接过去改就可以了!
throw new Exception();
}
public static void main(String[] args) {
try{
func();
}catch (Exception e){
e.printStackTrace();
System.out.println("捕捉到了被呼叫函式的例外啦!");
}
System.out.println("haha");
}
}
- 针对finally里面的陈述句,我们要知道,即使try{}内有return 的陈述句,我们finally里面的陈述句也会在此之前执行(所以说我们尽量不要在finally里面写return 的陈述句!),如:
public class Test {
public static int func(){
int a=10;
try{
return a;
}catch(ArithmeticException e){
e.printStackTrace();
}finally{
return 20;
}
}
public static void main(String[] args) {
int ret=func();
System.out.println(ret);
}
}//打印结果是什么
//20,因为finally里面的陈述句先执行(因为要在func方法结束之前执行),回传20后,try里面的陈述句没法回传了!
例外的处理流程?
- 程序先执行try中的代码
- 如果try中的代码出现例外,就会结束try中的代码,看catch 中的例外是否匹配
- 如果找到匹配的例外,就会执行对应catch中的代码
- 如果没有找到匹配的catch的例外,就会传递给呼叫者
- 无论是否找到匹配的例外型别,finally里面的代码都会被执行,finally所在方法结束之前执行!
- 如果上层呼叫者没有处理例外,继续往上面传
- 一直穿到了main函式也没有人去处理这个例外,那这个例外将会交由JVM处理,此时程序就会出现例外终止,结果代码是code 1
手动抛出例外(throw)
- 除了java中内置的一些例外类之外,程序员也可以手动抛出某个例外,使用throw关键字来完成这个操作(前述已提及throws做例外宣告),
public class Test{
public static void func(int x){//最好能在这做一个申明
if(x==0){
throw new ArithmeticException("抛出一个例外,你可以在这里描述这个例外")
}
}
public static void main(String[] args){
func(0);
}
}//这里只是抛出例外,我并没有去处理它
//结果
Exception in thread "main" java.lang.ArithmeticException: 抛出一个例外,你可以在这里描述这个例外
at Test.func(Test.java:14)
at Test.main(Test.java:18)
Process finished with exit code 1 //这个1就是因为是JVM处理了这个例外,如果是我们自己处理的就是数字0
受查例外必须显式去处理是什么意思??♂
- 受查例外前面也有所提及,它是指Exception子类中除了RuntimeException之外的类,这些类一旦出现,我们在程序运行之前就可以看到代码报红了,这里意思就是说,你不处理这个例外,你连编译都别想过去,
- 而显式处理这种例外的方法主要有两种,一个是用try-catch进行包裹;一个就是“往上抛”,所谓“往上抛”就是说,当前方法只管用这个类,出不出错我都不管,我就把这个可能的例外抛给呼叫者,呼叫者如果想处理,就去catch它,或者呼叫者也不想处理,呼叫者也将这个可能的例外往上抛,就连我们的main函式都可以这样做,当main函式也将这个可能的例外往上抛时,那就相当于交给JVM处理了,因为中间没有一个人想处理这个“烂摊子”,
举个例子:比如我们常用的界面Clonable(注意事项已写在代码里):
class Person implements Cloneable{
public int age=10;
@Override
protected Object clone() throws CloneNotSupportedException {//下面的clone()属于受查例外,super可能是不允许克隆的
//这里就相当于这事我先干了,例外交给上头去处理,
return super.clone();
}
}
public class Test1 {
public static void main(String[] args) throws CloneNotSupportedException {//这里也是把clone()的可能例外往上抛,如果出现例外
//这里就相当于交给JVM去处理了,
Person person=new Person();
Person person1=(Person)person.clone();
System.out.println(person1.age);
}
}
用try-catch进行包裹,不再赘述,就是前面的例子,就是我们当场就把那个可能的例外给处理了的意思,
什么是自定义例外?怎么使用???
- java中虽然已经内置了丰富的例外类,但是实际场景中我们可能还需要对例外类进行拓展,创建符合我们自己的例外类,
- 那使用自定义例外类是必须继承一个内置的例外类的,内置的例外类无非就是两种:一种是受查例外(Exception)、一种就是非受查例外(RuntimeException),
举例(注意事项已写在代码内部)
class MyException1 extends Exception{
public MyException1(String message) {
super(message);
}
}//自定义受查例外1
class MyException2 extends RuntimeException{
public MyException2(String message) {
super(message);
}
}//自定义受查例外2
public class Test2 {
public static void func1(int x){
try{//使用try-catch包裹显式处理这个受查例外
if(x==0){
throw new MyException1("hehe");//此处子类构造要先帮父类进行构造
}
}catch (MyException1 e){
e.printStackTrace();
}
}
public static void func2(int x)throws MyException2{//“往上抛”
if(x==0){
throw new MyException2("HAHA");//这个也是子类构造前要先帮父类进行构造
}
}
public static void main(String[] args) {
func1(0);
func2(0);
}
}
//结果:
MyException1: hehe
at Test2.func1(Test2.java:22)
at Test2.main(Test2.java:34)
Exception in thread "main" MyException2: HAHA
at Test2.func2(Test2.java:30)
at Test2.main(Test2.java:35)
Process finished with exit code 1//这里是1是因为,func2()往上抛例外,但我main并没有去catch这个可能的例外,所以交由JVM去处理,那就会出现这样的例外终止代码1
- 自定义例外的使用:
class NameException1 extends Exception{
public NameException1(String message) {
super(message);
}
}
class PasswordException2 extends Exception{
public PasswordException2(String message) {
super(message);
}
}
public class Test3 {
private static final String name="hehe";//static final修饰的成员变量必须初始化
private static final String psaaword="123";//同理
public static void login(String name,String password)throws NameException1,PasswordException2{
if(!Test3.name.equals(name)){
throw new NameException1("用户名错误!");//仅抛出例外不行,要显式处理
}
if(!Test3.psaaword.equals(password)){
throw new PasswordException2("密码错误!");
}
}
public static void main(String[] args) {
try{//try-catch包裹进行处理显式例外
login("hehe","1234");
}catch (NameException1 e){
e.printStackTrace();//定位例外
System.out.println("用户名错误!");
}catch(PasswordException2 e){
e.printStackTrace();//定位例外
System.out.println("密码错误!");
}
}
}
//结果:
PasswordException2: 密码错误!
at Test3.login(Test3.java:27)
at Test3.main(Test3.java:33)
密码错误!
Process finished with exit code 0//因为例外是我们自己处理的,所以code 0
一个练习题:使用while回圈建立类似“恢复模型”的例外处理行为,他将不断重复,直到例外不再抛出!下期揭晓答案(答案不唯一,起到类似的效果即可)
0 评论