JAVASE学习笔记 (十)(异常)

异常体系结构

Java中把异常当成对象来处理,并且定义了一个基类java.long.Throwable作为所有异常的超类

在Java API中已经定义了许多异常类.这些异常分为两大类:

  1. 错误:Error 主要是jvmVirtual MachineError)错误和awt错误(GUI编程)
  2. 异常:Exception IO异常,Runtime异常等等
    在这里插入图片描述

Error

  • Error类对象由Java虚拟机生成并抛出,大多数错误与代码编写者所执行的操作无关。
  • Java虚拟机运行错误(Virtual MachineError),当JVM不再有继续执行操作所需的内存资源时,将出现OutOfMemoryError。这些异常发生时,Java虚拟机(JVM) 一般会选择线程终止;
  • 还有发生在虚拟机试图执行应用时,如类定义错误(NoClassDefFoundError) 、链接错误(LinkageError)。这些错误是不可查的,因为它们在应用程序的控制和处理能力之外,而且绝大多数是程序运行时不允许出现的状况。

error包括堆溢出,栈溢出等,下文有演示

Exception

  • 在Exception分支中有一个重要的子类RuntimeException (运行时异常):
    • ArraylndexOutOfBoundsException (数组下标越界)
    • NullPointerException (空指针异常)
    • ArithmeticException (算术异常)
    • MissingResourceException (丢失资源)
    • ClassNotFoundException (找不到类)等异常,这些异常是不检查异常,程序中可以选择捕获处理,也可以不处理。
  • ◆这些异常- -般是由程序逻辑错误引起的,程序应该从逻辑角度尽可能避免这类异常的发生;

Error和Exception的区别:

Error通常是 灾难性的致命的错误,是程序无法控制和处理的,当出现这些异常时,Java虚拟机(JVM) - -般会选择终止线程; Exception通常情况下是可以被程序处理的,并且在程序中应该尽可能的去处理这些异常。

异常的演示

  • Error的演示,我们的代码是正确,但是由于硬件或者其他不可控的原因,莫名其妙出现的错误

    • 编译的时候没有出错,运行的时候也没有出错,在运行一段时间后,因为程序员不可控的情况,程序产生了错误

    • 堆内存(Heap)溢出

      import java.util.*;
      public class Heap{
          public static void main(String[] args){
              int count =1;
              List<Heap> list = new ArrayList<>(2000000000);
              while(true){
                  System.out.println(count++);
                  list.add(new Heap());
              }
          }
      }
      

      java.lang.OutOfMemoryError: Java heap space 堆内存不够用,我们无法在程序中进行控制,只能通过跳转Java中分配的内存进行控制

    • 栈内存(Stack)溢出:运行的是局部变量和调用的犯法

      import java.util.*;
      public class Stack{
          static int count =1;
          public static void main(String[] args){
              new Stack().test01();
          }
          
          public void test01(){
              System.out.println(count++);
              //递归调用
              test01();
          }
      }
      

      Exception in thread “main” java.lang.StackOverflowError 栈内存溢出,因为递归调用

  • 受检查的异常:编译时异常:checked:在编译期间就检查代码是否有可能出现错误,需要你进行处理,如果不处理编译无法进行,强制进行处理

import java.util.*;
import java.io.*;
public class CheckedException{
    public static void main(String[] args){
        //错误: 未报告的异常错误FileNotFoundException; 必须对其进行捕获或声明以便抛出
        
        //下面的代码,可能会出现错误,你必须要对这种错误进行处理,如果不处理编译无法通过
        InputStream is = new FileInputStream("D:/a.txt");
    }
}
  • 不受检查的异常:运行时异常:unchecked:在编译期间,你的代码虽然在逻辑可能出错,但是语法是正确,编译可以通过,但是运行的时候就报错。

其根类为 RuntimeException

import java.util.*;
import java.io.*;
public class UnCheckedException{
    public static void main(String[] args){
        //运行时报错:java.lang.ArithmeticException: / by zero
        int i = 10/0;
    }
}

异常处理机制(无法处理Error)

  1. 抛出异常
  2. 捕获异常

异常处理5个关键字:
try(监控区域,无异常执行),catch(捕获异常,有异常执行),finally(善后工作,比如关闭IO流,关闭资源等等。无论有无异常都执行),throw,throws
catch(要捕获的类型):Throwable最高

throws

throw概述

throws:声明在方法的后面,方法中可能出现的错误向调用者声明,自己不具有处理该错误的能力,那么让调用者处理更好,调用者需要为可能发送的错误负责。//谁调用谁处理
一级一级往上抛出,若没有执行者能处理,就一直抛到JVM。
throw一般在方法体中使用,throws一般在方法上使用

import java.io.*;
public class Demo01{
    //最终给了Java的虚拟机自行处理,如何处理跟我没有关系
    public static void main(String...args)throws FileNotFoundException{//main调用者
        m1();
    }
    public static void m1()throws FileNotFoundException{
        m2();
    }
    public static void m2()throws FileNotFoundException{
        m3();
    }
    public static void m3()throws FileNotFoundException{//调用者
        //调用m4方法;
        m4();//对可能发送的错误进行复杂
    }
    public static void m4()throws FileNotFoundException{//m4本身没有处理能力,告知调用者可能出现的错误
        InputStream is = new FileInputStream("D:\123.txe");//编译时异常
    }
}

throws关键字的使用规则

  • 在方法的后面可以同时抛出多个异常信息

    public class Demo02{
        public static void m4()throws FileNotFoundException,ClassNotFoundException{
            InputStream is = new FileInputStream("D:\123.txe");//编译时异常
            Class.forName("com.os.model.Student");//编译时异常
        }
    }
    
  • 调用者抛出的异常可以大于等于被调用者抛出的异常

    import java.io.*;
    public class Demo02{
        public static void m2()throws Exception/*大于*/{
            m3();
        }
        public static void m3()throws IOException/*大于*/,ClassNotFoundException/*等于*/{
            m4();
        }
        public static void m4()throws FileNotFoundException,ClassNotFoundException{
            InputStream is = new FileInputStream("D:\123.txe");//编译时异常
            Class.forName("com.os.model.Student");//编译时异常
        }
    }
    
  • 继承关系中,子类重写方法抛出的异常需要小于等于父类抛出的异常

    package ex01;
    import java.io.*;
    class Father{
        public void say()throws IOException {
            System.out.println("父类中的方法");
        }
    }
    class Child extends Father {
        public void say()throws FileNotFoundException{
            System.out.println("子类重写的方法");
        }
    }
    

    什么是方法重写?继承关系

    两同:方法名称相同,形参的列表相同(数据类型和个数)

    两小:

    • 返回值类型(引用数据类型):子类返回值类型可以小于等于父类的返回值类型
    • 异常:子类抛出的异常需要小于等于父类抛出的异常

    一大:子类的访问修饰符需要大于等于父类的访问修饰符

throws方式缺点:容易造成代码之间耦合度比较紧密(调用者和被调用者比较紧密)

try…catch

概述

自己承担,自己可以处理出现错误的后果,可以使调用者不要在错误进行处理。
语法格式

  • 1.一个try可以对应多个catch: try…catch(){}…catch(){}…catch(){}
  • 2.一个try可以对应1个finally: try…finally{}
  • 3.try…catch(){}…catch(){}…catch(){}…finally{}

部分规则

  • 一个try可以对应多个catch
  • finally 块中的 return 返回后方法结束执行,不会再执行 try 块中的 return 语句。总之,会在try的return之前执行finally的内容,如果finally有return,相当于截断了正常的try中的return,提前结束
package ex03;
import java.io.*;
public class Demo05{
    
    public static void main(String...args){//main调用者
        try{
            Class.forName("Demo02");//加载类
            InputStream is = new FileInputStream("D:/123.txe");//编译时异常
            System.out.println("正确");
        }catch(FileNotFoundException e){
            System.out.println("e对象是系统自动创建:"+e);
            System.out.println("文件没有找到");
        }catch(ClassNotFoundException e){
            System.out.println("e对象是系统自动创建:"+e);
            System.out.println("类没有找到");
        }
    }
}
  • 一个try可以对应多个catch,前面的异常捕获的异常类型范围小于后面捕获类型
package ex03;
import java.io.*;
public class Demo05{
    public static void main(String...args){//main调用者
        try{
            int num = 10/0;
            Class.forName("Demo02");//加载类
            InputStream is = new FileInputStream("D:/123.txe");//编译时异常
            System.out.println("正确");
        }/*catch(IOException e){//IOException是FileNotFoundException的父类,范围大
            错误语法
        }*/catch(FileNotFoundException e){
            System.out.println("e对象是系统自动创建:"+e);
            System.out.println("文件没有找到");
        }catch(ClassNotFoundException e){
            System.out.println("e对象是系统自动创建:"+e);
            System.out.println("类没有找到");
        }catch(Exception e){//IOException是FileNotFoundException的父类,范围大
            System.out.println("e对象是系统自动创建:"+e);
        }
    }
}

偷懒:同时处理多个异常

  • 使用两者共同的父异常
package ex04;
import java.io.*;
public class Demo06{ 
    public static void main(String...args){//main调用者
        try{
            Class.forName("Demo02");//加载类
            InputStream is = new FileInputStream("D:/123.txe");//编译时异常
            System.out.println("正确");
        }catch(Exception e){//Exception范围大,但是它不仅仅处理ClassNotFound和FileNotFound
            System.out.println("e对象是系统自动创建:"+e);
        }
    }
}
  • JDK7提供的方法“(FileNotFoundException | ClassNotFoundException e)”:同时处理这两个异常,都执行
package ex05;
import java.io.*;
public class Demo07{
    public static void main(String...args){//main调用者
        try{
            Class.forName("Demo02");//加载类
            InputStream is = new FileInputStream("D:/123.txe");//编译时异常
            System.out.println("正确");
        }catch(FileNotFoundException | ClassNotFoundException e){
            System.out.println("e对象是系统自动创建:"+e);
        }
    }
}

finally:无论对错都会执行

finally:修饰类不能被继承,修饰方法不能被重写,修饰变量不能改变

注意一:不要在finally中设置return

package ex06;
import java.io.*;
public class Demo08{ 
    public static void main(String...args){//main调用者
        int result = m1(999);
        System.out.println(result);
    }
    
    public static int m1(int num){
            try{
                System.out.println("try...."+num);
                return num+100;
            }finally{
                System.out.println("finally...."+num);
                return 200;
            }
        }
}

结果
在这里插入图片描述

finally的语句会先于try或者catch的返回语句之前执行,如果finally中有return语句,那么try或catch中的return语句会被finally中的return覆盖,不建议在finally中放return

面试题

告知代码运行结果

public class F10 {
    public static void main(String[] args) {
        int i =m1(999);
        System.out.println(i);

    }
    public static int m1(int num){
        try{
            System.out.println("try==="+num);
            return num;
        }finally {
            num = num +1 ;
            System.out.println("finally...."+ num);
        }
    }
}

//输出结果:999


输出“999”原因:

在try中,底层是通过定义一个临时变量来返回值。所以返回temp=999的临时变量,而不是finally中num=1000并没有改变temp的值

 int temp = num;//临时变量
 System.out.println("try...."+temp);
 return temp;

手动抛出异常

就算程序正确也可手动抛出异常

package ex09;
import java.util.*;
public class Demo09{ 
    public static void main(String...args){//main调用者   
        //System.out.println(ex);
        int num = new Random().nextInt(10);
        if(num<4){
             //第一种方式:
             /*
             System.out.println(num+"程序错误");//后面的代码不应该在执行了
             return;//终止方法
             */
             //throw 异常对象;
             //1.手动创建一个异常对象,之前我们遇到都是系统自动创建
             //NullPointerException ex = new NullPointerException("你的程序错误了!");
             //throw ex;
             throw new NullPointerException("你的程序错误了!"+num);
        }  
        System.out.println(num+"程序正确了");
    }
}

手动抛出异常+捕获=流程控制

判断奇数偶数

开发中的方式1(返回boolean 或者 int)

package ex10;
import java.util.*;
public class Demo10{ 
    public static void main(String...args){//main调用者
        Scanner scan = new Scanner(System.in);
        System.out.print("请输入一个整数:");
        int num = scan.nextInt();
        boolean flag = m1(num);
        //显示不同的效果
        if(flag){
            System.out.println("偶数");
        }else{
            System.out.println("奇数");
        }
        System.out.println("===========");
        if(m2(num)==0){
            System.out.println("===偶数");
        }else{
           System.out.println("====奇数"); 
        }
    }
    
    public static boolean m1(int number){
        if(number%2==0){
            return true;
        }else{
            return false;
        }
    }
    public static int m2(int number){
        return number%2;
    }

}

方式2

package ex11;
import java.util.*;
public class Demo11{ 
    public static void main(String...args){//main调用者
        Scanner scan = new Scanner(System.in);
        System.out.print("请输入一个整数:");
        int num = scan.nextInt();
        try{
            m1(num);
            System.out.println("奇数");
        }catch(RuntimeException e){//系统创建了对象,堆地址
            System.out.println("偶数");
        }    
    }
    
    public static void m1(int number){
        if(number%2==0){
            throw new RuntimeException();
        }
    }
}

有人说后者效率比前者低,原因:

后者因为用了catch,系统自动创建了对象,产生堆地址 前者全在栈中,
但只要不大量使用,后者并没有什么影响

自定义异常

使用Java内置的异常类可以描述在编程时出现的大部分异常情况。除此之外,用户还可以自定义异常。用户自定义异常类,只需继承Exception类(绝大情况下,都继承RuntimeException)即可。

  • 在程序中使用自定义异常类,大体可分为以下几个步骤:
  1. 创建自定义异常类。
  2. 在方法中通过throw关键字抛出异常对象。
  3. 如果在当前抛出异常的方法中处理异常,可以使用try-catch语句捕获并处理;否则在方法的声明处通过throws关键字指明要抛出给方法调用者的异常,继续进行下一步操作。
  4. 在出现异常方法的调用者中捕获并处理异常。
package ex12;
import java.util.*;
public class Demo12{ 
    public static void main(String...args){//main调用者
        Scanner scan = new Scanner(System.in);
        System.out.print("请输入一个整数:");
        int num = scan.nextInt();
        try{
            m1(num);
            System.out.println("奇数");
        }catch(MyException e){//系统创建了对象,堆地址
            System.out.println(e);
            System.out.println("偶数");
        }    
    }
    
    public static void m1(int number){
        if(number%2==0){
            throw new MyException("自定义异常");
        }
    }
}
//绝大部分情况下,我们都是继承的是运行时异常
class MyException extends RuntimeException{
    public MyException(){}
    
    public MyException(String message){
        super(message);//父类中构造方法
    }
}

将编译时异常转换为运行时异常(特别推荐的技巧)

这么做的的原因:

import java.util.Properties;

public class F09 {
    public static void main(String[] args) {
        m1();
    }
        public static void m1(){
            try {
              Class.forName("demo12351");
            } catch (Exception e) {
                System.out.println(e.toString());
            }
            System.out.println("如果程序错误,这段内容不应该输出");
        }
}

运行结果:
在这里插入图片描述
解决方案一:修改catch:

 try {
      Class.forName("demo12351");
 } catch (Exception e) {
      System.out.println(e.toString());
      return;
   }

解决方案二:转换为运行时异常(推荐:解耦)

import java.util.Properties;

public class F09 {
    public static void main(String[] args) {
        m1();
    }
        public static void m1(){
            try {
              Class.forName("demo12351");
            } catch (Exception e) {
                /*程序员记录使用*/
                e.printStackTrace();//控制台输出错误信息,返回错误位置
                System.out.println(e.toString());//获取异常类和异常类信息
               throw new RuntimeException(e.getMessage());//返回的异常信息
            }
            System.out.println("如果程序错误,这段内容不应该输出");
        }
}

结果:
在这里插入图片描述

总结

1.处理运行时异常时,采用逻辑去合理规避同时辅助try-catch处理
2. 在多重catch块后面,可以加一个catch (Exception) 来处理可能会被遗漏的异常
3. 对于不确定的代码,也可以加上try-catch ,处理潜在的异常
4. 尽量去处理异常,切忌只是简单地调用printStackTrace()去打印输出
5. 具体如何处理异常,要根据不同的业务需求和异常类型去决定
6. 尽量添加finally语句块去释放占用的资源

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

月色夜雨

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值