Java 基础面试题——异常

1.Exception 和 Error 有什么区别?

(1)首先来看看 Java 中的异常层次结构图

在这里插入图片描述

(2)在 Java 中,所有的异常都有一个共同的祖先 java.lang 包中的 Throwable 类,Throwable 类有两个重要的子类:

  • Exception:程序本身可以处理的异常,可以通过 catch 来进行捕获。Exception 又可以分为以下两种异常:
    • Checked Exception:受检查异常,必须处理;
    • Unchecked Exception:不受检查异常,可以不处理;
  • Error:Error 属于程序无法处理的错误 ,例如虚拟机内存溢出错误 (OutOfMemoryError)、类定义错误 (NoClassDefFoundError) 等 。这些异常发生时,Java 虚拟机一般会选择线程终止。
    在这里插入图片描述

上图来自于网络。

2.Throwable 类中常用方法有哪些?

  • String getMessage():返回异常发生时的简要描述。
  • String toString():返回异常发生时的详细信息。
  • String getLocalizedMessage():返回异常对象的本地化信息。使用 Throwable 的子类覆盖这个方法,可以生成本地化信息。如果子类没有覆盖该方法,则该方法返回的信息与 getMessage() 返回的结果相同。
  • void printStackTrace():在控制台上打印 Throwable 对象封装的异常信息。

3.Checked Exception 和 Unchecked Exception 有什么区别?

(1)Checked Exception 即受检查异常,指在编译时强制要求处理的异常,并且调用这个方法时必须处理或者重新抛出这个异常。主要目的是强制开发者在编码过程中处理特定情况下可能发生的异常,以提高代码的健壮性和可靠性。一般情况下,Checked Exception表示程序可能遇到的可预见的、无法自行解决的异常情况。例如下面这段 I/O 操作的代码:

在这里插入图片描述

常见的 Checked Exception 有:

  • IOException:输入/输出异常
  • ClassNotFoundException:类未找到异常
  • SQLException:SQL 异常
  • FileNotFoundException:文件未找到异常

(2)Unchecked Exception 即不受检查异常,指在编译时不强制要求处理的异常。这些异常通常是由程序错误导致的,例如逻辑错误或者运行时错误。由于 Unchecked Exception 是由 RuntimeException 类及其子类派生的,因此也常被称为 RuntimeException。相对于Checked Exception,Unchecked Exception 不要求强制进行异常处理或者重新抛出。常见的 Unchecked Exception 有:

  • ArithmeticException:算术异常
  • ClassCastException :类转换异常
  • IllegalArgumentException :非法参数异常
  • NumberFormatException:字符串转换为数字格式异常,IllegalArgumentException 的子类
  • IndexOutOfBoundsException :下表越界异常
  • NullPointerException :空指针异常
  • SecurityException :安全异常
  • UnsupportedOperationException:不支持的操作错误,例如重复创建同一用户

更多 RuntimeException 的子类可在 IDEA 中进行查看:

在这里插入图片描述

4.throw 和 throws 的区别是什么?

(1)throw

  • 表示方法内抛出某种异常对象(只能是一个);
  • 用于程序员自行产生并抛出异常;
  • 位于方法体内部,可以作为单独语句使用;
  • 如果异常对象是非 RuntimeException,则需要在方法申明时加上该异常的抛出,即需要加上 throws 语句或者在方法体内使用 try catch 处理该异常,否则编译报错;
  • 执行到 throw 语句则后面的语句块不再执行;

(2)throws

  • 方法的定义上使用 throws 表示这个方法可能抛出某些异常(可以有多个);
  • 用于声明在该方法内抛出了异常;
  • 必须跟在方法参数列表的后面,不能单独使用;
  • 需要由方法的调用者进行异常处理;
package test;

import java.io.IOException;

class Solution {
    
    /**
     * 测试 throws 关键字
     * @throws NullPointerException
     */
    public static void testThrows() throws NullPointerException {
        Integer i = null;
        System.out.println(i + 1);
    }
    
    /**
     * 测试 throw 关键字抛出运行时异常
     * @param i
     */
    public static void testThrow(Integer i) {
        if (i == null) {
            //运行时异常不需要在方法上申明
            throw new NullPointerException();
        }
    }
    
    /**
     * 测试 throw 关键字抛出非运行时异常,需要方法体需要加 throws 异常抛出申明
     * @param filePath
     */
    public static void testThrow(String filePath) throws IOException {
        if (filePath == null) {
            //非运行时异常,需要方法体需要加 throws 异常抛出申明
            throw new IOException();
        }
    }
    
    public static void main(String[] args) {
        testThrows();
        
        Integer i = null;
        testThrow(i);
        
        String filePath = null;
        try {
            testThrow(filePath);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

上述代码中三个方法对应的异常如下:

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

5.try-catch-finally 如何使用?

(1)try 块:用于捕获异常。其后可接零个或多个 catch 块,如果没有 catch 块,则必须跟一个 finally 块。
(2)catch 块:用于处理 try 捕获到的异常。
(3)finally 块 :无论是否捕获或处理异常,finally 块里的语句都会被执行。但需要注意的是,当在 try 块或 catch 块中遇到 return 语句时,finally 语句块将在方法返回之前被执行。

示例代码如下:

public static void main(String[] args) {
    try {
        System.out.println("Try to do something");
        throw new RuntimeException("RuntimeException");
    } catch (Exception e) {
        System.out.println("Catch Exception -> " + e.getMessage());
    } finally {
        System.out.println("Finally");
    }
}

输出结果如下:

Try to do something
Catch Exception -> RuntimeException
Finally

注意:不要在 finally 语句块中使用 return!当 try 语句和 finally 语句中都有 return 语句时,try 语句块中的 return 语句会被忽略。这是因为 try 语句中的 return 返回值会先被暂存在一个本地变量中,当执行到 finally 语句中的 return 之后,这个本地变量的值就变为了 finally 语句中的 return 返回值。

6.finally 块中的代码一定会执行吗?

(1)finally 块中的代码不一定会执行!例如在执行到 finally 块之前 JVM 就被终止运行的话,finally 中的代码就不会被执行。

public static void main(String[] args) {
    try {
        System.out.println("Try to do something");
        throw new RuntimeException("RuntimeException");
    } catch (Exception e) {
        System.out.println("Catch Exception -> " + e.getMessage());
        // 终止当前正在运行的 JVM
        System.exit(1);
    } finally {
        System.out.println("Finally");
    }
}

输出结果如下:

Try to do something
Catch Exception -> RuntimeException

(2)另外,在以下 2 种特殊情况下,finally 块的代码也不会被执行:程序所在的线程死亡、关闭 CPU。

7.异常使用有哪些需要注意的地方?

(1)不要把异常定义为静态变量,因为这样会导致异常栈信息错乱。
(2)每次手动抛出异常,我们都需要手动 new 一个异常对象抛出。抛出的异常信息一定要有意义。
(3)建议抛出更加具体的异常,例如出现字符串转换为数字格式错误的时候,就应该抛出 NumberFormatException 而不是其父类IllegalArgumentException。
(4)使用日志打印异常之后就不要再抛出异常了(两者不要同时存在一段代码逻辑中)。

8.A 方法调用 B 方法时,如果在 B 方法中出现异常,如何将异常返回给 A 方法?

在 Java 中,如果 A 方法调用 B 方法时,在 B 方法中发生了异常,可以通过以下几种方式将异常返回给 A 方法:

  • 抛出异常:B 方法可以在方法签名中声明抛出某种类型的异常,并在方法体中使用 throw 语句抛出异常。A 方法需要使用 try-catch 块来捕获异常并进行处理。在这种方式下,B 方法需要在方法签名中声明可能抛出的异常类型,使得调用 B 方法的代码可以在编译时进行异常处理。示例代码如下:
public void B() throws SomeException {
    // B 方法中发生异常
    throw new SomeException("Some error message");
}

public void A() {
    try {
        B();
    } catch (SomeException e) {
        // 在 A 方法中捕获异常并进行处理
        System.out.println("Exception caught: " + e.getMessage());
    }
}
  • 异常对象返回:B 方法可以在发生异常时,创建异常对象并返回给 A 方法。A 方法在调用 B 方法后,可以根据返回的异常对象来判断是否发生了异常,并进一步处理。在这种方式下,调用 B 方法后,A 方法可以判断返回的异常对象是否为 null 来确定是否发生了异常,并进一步处理。示例代码如下:
public SomeException B() {
    try {
        // 执行业务逻辑,如果发生异常则创建异常对象
        throw new SomeException("Some error message");
    } catch (SomeException e) {
        // 在 B 方法中捕获异常并返回异常对象
        return e;
    }
}

public void A() {
    SomeException exception = B();
    if (exception != null) {
        // 在 A 方法中判断是否有异常并进行处理
        System.out.println("Exception caught: " + exception.getMessage());
    }
}
  • 自定义异常类/包装异常:B 方法可以定义一种自定义的异常类,将异常信息封装到该自定义异常对象中,并在发生异常时返回该自定义异常对象。A 方法可以捕获该自定义异常对象并进行处理。在这种方式下,可以定义一个自定义的异常类,将异常信息封装到该对象中,并在 B 方法中返回该自定义异常对象。A 方法可以根据自定义异常对象来判断是否发生了异常,并进一步处理。示例代码如下:
public class CustomException extends RuntimeException {
    // 自定义异常类
    public CustomException(String message) {
        super(message);
    }
}

public CustomException B() {
    try {
        // 执行业务逻辑,如果发生异常则创建自定义异常对象
        throw new CustomException("Some error message");
    } catch (CustomException e) {
        // 在 B 方法中捕获异常并返回自定义异常对象
        return e;
    }
}

public void A() {
    CustomException exception = B();
    if (exception != null) {
        // 在 A 方法中捕获自定义异常对象并进行处理
        System.out.println("Exception caught: " + exception.getMessage());
    }
}

通过以上方式之一,可以将在 B 方法中发生的异常传递给 A 方法,并在 A 方法中对异常进行相应的处理。根据实际场景和需求,选择合适的方式来处理异常。

9.什么时候用 assert?如何使用 assert?

(1)assertion(断言)在软件开发中是一种常用的调试方式,很多开发语言中都支持这种机制。一般来说,assertion 用于保证程序最基本、关键的正确性。assertion 检查通常在开发和测试时开启
(2)为了提高性能,在软件发布后, assertion 检查通常是关闭的。在实现中,断言是一个包含布尔表达式的语句,在执行这个语句时假定该表达式为 true;如果表达式计算为 false,那么系统会报告一个 AssertionError。
(3)断言用于调试目的:

assert(a > 0); // throws an AssertionError if a <= 0

(4)断言有以下两种形式:

assert Expr1; 
assert Expr1 : Expr2;

Expr1 应该总是产生一个布尔值。 Expr2 可以是得出一个值的任意表达式;这个值用于生成显示更多调试信息的字符串消息。这两种表达形式本质是一样的,不同在于第二种方式中,可以指定输出错误的信息。

(5)断言在默认情况下是禁用的。
① 要在编译时启用断言,需使用 source 1.4 标记:

javac -source 1.4 Test.java

② 要在运行时启用断言,可使用 -ea 或者 -enableassertions 标记。
③ 要在运行时选择禁用断言,可使用 -da 或者 -disableassertions 标记。
④ 要在系统类中启用断言,可使用 -esa 或者 -dsa 标记。还可以在包的基础上启用或者禁用断言。

(6)可以在预计正常情况下不会到达的任何位置上放置断言。断言可以用于验证传递给私有方法的参数。不过,断言不应该用于验证传递给公有方法 的参数,因为不管是否启用了断言,公有方法都必须检查其参数。不过,既可以 在公有方法中,也可以在非公有方法中利用断言测试后置条件。另外,断言不应该以任何方式改变程序的状态。

10.什么是内存泄漏和内存溢出?

(1)内存泄漏 (Memory Leak):指程序中已动态分配的堆内存由于某种原因程序未释放或无法释放,造成系统内存的浪费,导致程序运行速度减慢甚至系统崩溃等严重后果。

(2)内存溢出 (Out Of Memory,简称 OOM):指应用系统中存在无法回收的内存或使用的内存过多,最终使得程序运行要用到的内存大于能提供的最大内存。常见的解决方案有:

  • 修改 JVM 启动参数,直接增加内存。
  • 检查错误日志,查看 “Out Of Memory” 错误前是否有其它异常或错误。
  • 对代码进行走查和分析,找出可能发生内存溢出的位置。
  • 使用内存查看工具动态查看内存使用情况。

(3)内存泄漏最终会导致内存溢出。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

代码星辰

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

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

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

打赏作者

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

抵扣说明:

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

余额充值