异常..

2024-01-07 17:33:08

1.开发过程中的错误

在开发Java程序的过程中 会遇到各种各样的错误 一下是对错误的分类:
1.语法错误
如果产生了语法错误的话 那么就会导致编译失败 程序无法正常运行
2.逻辑错误
比如原本我想要进行加法运算 但是我将加法运算符写成了减法运算符 但是这个错误并不致命 也就是说 不会导致编译失败进而程序无法运行
3.运行时错误
就是程序运行过程中产生的以外 会导致程序终止运行 这个错误在Java中也叫做异常

程序如果产生了异常 一般我们称之为:抛出了异常 如果我们没有主动去处理他的话 那么他就会终止程序的运行

2.异常

上面说了 异常就是运行时抛出的错误 如果没有主动去处理他的话 那么就会导致程序的终止运行
在Java中 异常有很多种
所有的异常都是继承自java.lang.Throwable(虽然Throwable的后缀able写的有点像接口 但是实际上他是一个类)
在这里插入图片描述

如何防止程序抛出异常而导致其终止运行呢?try-catch来捕捉异常

3.异常的种类

我们可以分成两种异常 一种是检查型异常 一种是非检查型异常
前者的话 一般通过修改代码的方式难以避免 并且编译器会检查这个异常(如果开发者没有处理这个异常的话 那么编译器就会报错)
哪些是检查型异常呢?除了RuntimeException和Error以外的异常
后者的话 一般可以避免 并且编译器也不会进行异常的检查(如果开发者没有处理这个异常的话 那么编译器是不会报错的)
哪些是非检查型异常呢?RuntimeException和Error这些异常

1.常见的检查型异常

如果存在潜在的异常的话 那么就需要我们进行处理 处理的方式可以是try-catch 也可以是throws
1.可能会抛出FileNotFoundException异常(如果指定文件不存在的话 就会抛出FileNotFoundException这个异常 并且代码的修改无法左右文件的存在性 可以通过throws的方式处理这个异常)

static void test1() throws FileNotFoundException {
    FileOutputStream fos = new FileOutputStream("D:\1.txt");
}

2.解析日期的时候可能会抛出ParseException异常(如果解析的对象的格式和SimpleDateFormat指定的格式不一致的话 那么就会抛出ParseException这个异常 代码的修改无法解决这个异常 可以通过throws的方式进行处理)
(SimpleDateFormat.format()主要用于对未格式化的Date进行格式化操作 并且返回一个字符串
SimpleDateFormat.parse()主要用于对已格式化的字符串进行解析操作 并返回一个Date)

static void test2() throws ParseException {
    SimpleDateFormat fos = new SimpleDateFormat("yyyy-MM-dd");
    fos.parse("2022年10月11日");
}

3.程序睡眠的代码可能会抛出InterruptedException异常 需要我们主动处理 否则编译器就会检查并且报错

static void test3() throws InterruptedException {
    Thread.sleep(2000);
}

4.根据类名找到指定类 然后创建相应的类实例 其中的话 可能会抛出三个异常 一种是如果不存在指定类名的类的话 那么就会抛出ClassNotFoundException异常 一种是如果指定类中没有无参构造方法的话 那么就会抛出InstantiationException 一种是如果无参构造方法的权限为私有的话 那么外界无法访问 就会抛出IllegalAccessException

public class Dog {
}
static void test4() throws ClassNotFoundException, IllegalAccessException, InstantiationException {
    Class cls = Class.forName("Dog");
    Dog dog = (Dog)cls.newInstance();// 调用Dog中的无参构造方法进行Dog的实例化操作
}

其中的话 我们如果只根据Dog找到指定的类是不现实的 因为如果这个项目中存在多个Dog类的话 那么你应该去找谁呢 不知道 所以只能够写清楚全名 才能够解决ClassNotFoundException异常 并且由于我们的构造方法是公有且无参的 所以现在就不会因为异常而导致程序终止运行

2.常见的非检查型异常-Error

1.程序运行过程中因为堆空间溢出导致抛出OutOfMemoryError异常

static void test1(){
    System.out.println(1);
    for(int i = 0; i < 200; ++i){
        long[] a = new long[10_0000_0000];
    }
    System.out.println(2);
}

2.程序运行过程中因为栈空间溢出导致导致抛出StackOverFlowError异常

static void test2(){
    test2();
}

3.常见的非检查型异常-RuntimeException

1.程序运行过程中因为空值调用导致抛出NullPointerException

static void test3(){
    StringBuilder sb = null;
    sb.append("abc");
}

2.程序运行过程中因为构造方法的格式不对导致抛出NumberFormatException

static void test4(){
    Integer i = new Integer("abc");
}

3.程序运行过程中因为数组越界访问导致ArrayIndexOutOfBoundsException

static void test5(){
    int[] array = {11, 22, 33};
    System.out.println(array[3]);
}

4.程序运行过程中因为类型之间不能转换导致ClassCastException(java.lang.String不能转换为java.lang.Double 这是因为这两个不是子类和父类的关系)

static void test6(){
    Object o = "123.4";
    Double d = (Double)o;
}

4.异常的处理

程序如果产生了异常 我们一般称之为:抛出了异常

不管抛出的是检查型异常 还是非检查型异常 只要我们没有去处理异常的话 那么程序就会终止运行

异常有两种处理方式:1.try-catch 捕获异常
2.throws 将异常往上抛
第二种处理可以解决编译器的报错 但是不能解决程序的终止运行问题
第一种处理可以解决编译器的报错 也可以解决程序的终止运行问题

1.try-catch

在这里插入图片描述
如果代码2没有抛出异常的话 那么代码1和代码3都可以正常执行 所有的catch语句都不会被执行 代码4也可以正常执行
如果代码2抛出异常的话 那么代码1可以正常执行 但是代码3不可以正常执行 会选择匹配的catch语句进行执行 代码4也可以正常执行

另外值得注意的是:
父类型异常不能写在子类型异常之前(这是因为如果匹配的是子类型异常的话 那么只能执行父类型异常的catch语句 子类型异常的catch语句永远没有执行的机会 其存在也没有任何意义)

1.异常对象的常用方法

public class Main {
    public static void main(String[] args){
        // 典型的非检查型异常 因为你没有主动处理这个异常 编译器也不会报错 但是运行过程中执行到该语句时就会终止运行
        try {
            Integer i = new Integer("abc");
        }catch(NumberFormatException e){
            // 异常描述
            System.out.println(e.getMessage());
            // 异常名称+异常描述
            System.out.println(e);
            // 打印堆栈信息 涉及到函数调用 包括了异常名称+异常描述
            e.printStackTrace();
        }
        System.out.println(2);
    }
}

由于e.printStackTrace突出了函数调用的信息 所以我们可以清晰的看出抛出异常的代码的行数
并且这个方法的底层是调用了System.err
他和System.out在内容较长的情况下是会交织在一起进行打印操作的

2.一个catch可以捕获多种类型的异常

在这里插入图片描述

1.这是从Java7开始的语法 单个catch可以捕获多个异常

2.父类型异常和子类型异常共存的时候 保留父类型异常即可(这是因为如果父类型异常在子类型异常之前的话 并且抛出的是子类型异常的话 那么子类型异常将永远没有机会进行匹配 也就没有存在的意义)

3.异常是隐式final修饰的(也就是说不能对其进行再次赋值操作 传递参数对其赋值已经属于第一次赋值了 因为异常属于什么类型是不确定的 所以不可以对其贸然进行赋值操作)

2.finally

try或者catch执行完以后 一定会执行finally内部的语句
finally可以和try-catch搭配 也可以和try搭配
在finally中 经常编写一些关闭文件释放文件资源的代码

public class Main {
    public static void main(String[] args) {
        PrintWriter pw = null;
        try {
            pw = new PrintWriter("D:\1.txt");
            pw.write("my name is 1");
        } catch (FileNotFoundException e) {
            throw new RuntimeException(e);
        } finally {
            if (pw != null)
                pw.close();
        }
    }
}

这段代码中 针对文件关闭 我们编写了一个finally进行代码的存放 但是由于抛出异常的缘故 导致pw为空 所以我们需要对pw进行判空操作 不然的话 又会触发空指针异常

1.finally的细节

如果jvm退出或者线程被杀死的情况下 finally就可能不会被执行

在try或者catch中使用了continue、break、return等语句的话 那么finally就会在这些语句执行之前得到执行

public class Main {
    public static void main(String[] args) {
        for (int i = 1; i <= 3; ++i) {
            try {
                System.out.println(i + "_try_1");
                if (i == 2) continue;
                System.out.println(i + "_try_2");
            } finally{
                System.out.println(i + "_finally");
            }
        }
    }
}

上述这段代码执行的结果是:
1_try_1
1_try_2
1_finally
2_try_1
2_finally
3_try_1
3_try_2
3_finally

public class Main {
    public static void main(String[] args) {
        for (int i = 1; i <= 3; ++i) {
            try {
                System.out.println(i + "_try_1");
                if (i == 2) break;
                System.out.println(i + "_try_2");
            } finally{
                System.out.println(i + "_finally");
            }
        }
    }
}

上述代码执行的结果是:
1_try_1
1_try_2
1_finally
2_try_1
2_finally

public class Main {
    public static void main(String[] args) {
        System.out.println(get());
    }
    static int get(){
        try{
            new Integer("abc");
            System.out.println(1);
            return 2;
        }catch(Exception e){
            System.out.println(3);
            return 4;
        }finally{
            System.out.println(5);
        }
    }
}

上述代码的执行结果为:3 5 4

3.throws

throws的作用就是将异常抛给上层方法

public class Main {
    public static void main(String[] args) throws FileNotFoundException, ClassNotFoundException {
        test();
    }
    static void test() throws FileNotFoundException, ClassNotFoundException {
        PrintWriter pw = new PrintWriter("D:/1.txt");
        Class cls = Class.forName("Dog");
    }
}

从上述代码可以看出:test方法中将产生的异常抛给了main方法 然后main方法也将异常抛给了上层 至于上层是什么 等会会讲到

如果throws同时抛出了子类型异常和父类型异常的话 那么只要保留父类型异常即可(这是因为如果同时保留两个异常 并且父类型写在子类型之前的话 那么子类型异常永远得不到匹配 那么也就没有存在的必要了)

当然的话 对于异常的处理方式不止有throws一种解决途径 你还可以通过try-catch的方式对异常进行处理 并且不仅仅可以全部通过throws或者try-catch进行处理 而且可以一部分使用throws 一部分使用try-catch进行处理

public class Main {
    public static void main(String[] args) throws FileNotFoundException{
        PrintWriter pw = new PrintWriter("D:/1.txt");
        try {
            Class cls = Class.forName("Dog");
        } catch (ClassNotFoundException e) {
            throw new RuntimeException(e);
        }
    }
}

1.throws的流程

public class Main {
    public static void main(String[] args) throws FileNotFoundException {
        method1();
    }
    static void method1() throws FileNotFoundException {
        method2();
    }

    private static void method2() throws FileNotFoundException {
        method3();
    }

    private static void method3() throws FileNotFoundException {
        PrintWriter pw = new PrintWriter("D:/1.txt");
    }
}

在这里插入图片描述
从上述的代码和配图我们可以知道 异常是一路像上传 最终传给了jvm 然后最后就会使得程序终止运行

2.throws的细节

如果子类重写父类的方法时
如果父类的方法没有throws异常的话 那么子类也不可以throws异常
如果父类的方法throws了异常的话 那么子类可以throws异常 也可以不throws异常
当子类选择throws异常的时候 它可以throws父类异常的同类型异常 也可以是子类型异常
在这里插入图片描述

5.异常的实例

public class Main {
    public static void main(String[] args) throws FileNotFoundException {
        System.out.println(1);
        Integer i1 = new Integer("123");
        System.out.println(2);
        Integer i2 = new Integer("abc");
        System.out.println(3);
    }
}

上述代码中 打印结果为:1 2 不会打印3 这是因为Integer i2 = new Integer(“abc”);抛出了NumberFormatException异常
2.

public class Main {
    public static void main(String[] args) throws FileNotFoundException {
        System.out.println(1);
        Integer i = new Integer("1234");
        Object o = "3.14";
        Double d = (Double)o;
        System.out.println(2);
    }
}

上述代码中 打印了1 但是不会打印2 这是因为Double d = (Double)o;抛出了ClassCastException异常
3.

public class Main {
    public static void main(String[] args) throws FileNotFoundException {
        Integer[] nums = {11, null, 22};
        for(int num: nums){
            System.out.println(num);
        }
    }
}

上述代码中 会打印11 但是不会打印null和22 这是因为包装类型转换成对应的基本类型Java会自动调用xxxvalue进行转换操作 但是空值是不可以调用方法 所以就抛出了NullPointerException异常
而且如果数组元素是引用类型的话 遍历推荐的写法为引用类型 并非对应的基本类型

1.打印的细节

public class Dog {
    @Override
    public String toString() {
        return "Dog-666";
    }
}
public class Main {
    public static void main(String[] args) throws FileNotFoundException {
        Dog dog1 = new Dog();
        System.out.println(dog1);// Dog-666
        Dog dog2 = null;
        System.out.println(dog2);// null
    }
}

上述代码的结果分别是:Dog-666和null
打印一个对象的本质是调用这个对象中的toString方法 但是空值调用显然是会抛出异常的 但是他却正常打印了 原因在于println方法的底层实现
其实对于println方法来说 如果参数为空的话 他会返回一个"null" 如果不空 她才会选择调用对象的toString方法

public class Main {
    public static void main(String[] args) throws FileNotFoundException {
        System.out.println(1);
        try{
            System.out.println(2);
            Integer i = new Integer("abc");
            System.out.println(3);
        }catch(NumberFormatException e){
            System.out.println(4);
        }
        System.out.println(5);
    }
}

上述代码中 打印结果为1 2 4 5 不会打印3 这是因为Integer i = new Integer(“abc”);抛出了NumberFormatException异常

文章来源:https://blog.csdn.net/m0_71299382/article/details/135426436
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。