当前位置: 首页 > news >正文

【java】Java核心知识点与相应面试技巧(九)——异常

Java 异常

上期面试题解析


上文链接:https://blog.csdn.net/weixin_73492487/article/details/146769920


1.为什么局部内部类访问的局部变量必须final?

在 Java 早期版本(Java 8 之前),局部内部类访问的局部变量必须声明为 final。这是因为局部变量是存储在栈上的,当包含局部变量的方法执行完毕后,该局部变量会被销毁。而局部内部类的对象可能会在方法执行结束后仍然存在(比如将局部内部类对象返回给调用者),如果局部内部类可以访问非 final 的局部变量,就可能会访问到已经被销毁的变量
从 Java 8 开始,局部内部类访问的局部变量实际上不必显式声明为 final,但变量的语义仍然是 final 的,即一旦赋值后就不能再重新赋值。这是 Java 8 引入的 “effectively final” 概念。

2.如何避免内部类导致的内存泄漏?

(1)静态内部类:使用静态内部类,因为静态内部类不会持有外部类实例的引用,这样可以避免外部类实例因为内部类的存在而无法被垃圾回收。
(2)及时释放引用:如果内部类持有对外部类的引用,在不再需要内部类对象时,及时将相关引用设置为 null,以便垃圾回收器能够回收相关对象。
(3)弱引用:使用弱引用(WeakReference)来持有外部类的引用,当外部类对象被垃圾回收时,弱引用不会阻止其被回收。

3.以下代码是否正确?

class Outer {
    int val = 10;
    class Inner {
        int val = 20;
        void print() {
            System.out.println(Outer.this.val); 
        }
    }
}

正确
Outer 类定义了一个成员变量 val。
Inner 类是 Outer 的内部类,它也定义了一个成员变量 val。
Inner 类的 print 方法使用 Outer.this.val 来访问外部类 Outer 的 val 变量,这是合法的访问方式。

4.匿名内部类能否实现多接口?

可以

示例:

interface Interface1 {
    void method1();
}

interface Interface2 {
    void method2();
}

public class Main {
    public static void main(String[] args) {
        Object obj = new Interface1() {
            @Override
            public void method1() {
                System.out.println("Implementing method1");
            }
        } & new Interface2() {
            @Override
            public void method2() {
                System.out.println("Implementing method2");
            }
        };

        if (obj instanceof Interface1) {
            ((Interface1) obj).method1();
        }
        if (obj instanceof Interface2) {
            ((Interface2) obj).method2();
        }
    }
}

1. 异常 (Exception)的定义与体系结构

定义:
异常是指程序在运行过程中发生的错误或非预期的行为。在Java中,异常通过继承Throwable类来表示。Throwable类有两个主要的子类:ErrorException

  • Error:表示虚拟机或其他环境相关的严重问题,通常无法通过程序进行恢复。例如,OutOfMemoryError。(内存溢出错误)
  • Exception:是程序能够处理的异常,代表了程序中的问题或错误。程序可以通过捕捉异常来恢复执行。Exception进一步分为检查异常 (Checked Exception)非检查异常 (Unchecked Exception)

体系结构:

Throwable (顶级父类)
├─ Error (系统级错误)
│   ├─ OutOfMemoryError
│   └─ StackOverflowError
└─ Exception (程序级异常)
    ├─ Checked Exceptions (编译时检查)
    │   ├─ IOException
    │   └─ SQLException
    └─ Unchecked Exceptions (RuntimeException及其子类)
        ├─ NullPointerException
        └─ IllegalArgumentException

2. Java异常的分类

2.1 检查异常 (Checked Exception)

检查异常是指在 编译时必须被处理 的异常。编译器会检查这些异常是否被正确捕捉或声明。如果不处理,代码无法通过编译。

  • 例如:
    • IOException:表示输入输出操作异常。
    • SQLException:数据库操作异常。

这些异常通常是可以预见的,并且可以通过编程来处理

2.2 非检查异常 (Unchecked Exception)

非检查异常是指在 运行时才会抛出的异常 。通常表示程序的逻辑错误,这些异常不需要在编译时强制处理。

  • 例如:
    • NullPointerException:表示访问了一个空对象。
    • ArrayIndexOutOfBoundsException:表示访问了数组的无效索引。
    • ArithmeticException:算术错误,例如除以零。

非检查异常通常是程序中的bug,最好通过代码逻辑来避免

2.3 Error

Error类的子类表示JVM运行时发生的严重错误,通常是无法恢复的。

  • 例如:
    • OutOfMemoryError:表示JVM没有足够的内存。
    • StackOverflowError:表示栈溢出,通常是由于递归调用过深导致的。

这些错误通常不应该被捕捉,因为它们是由环境问题引起的,无法恢复。

2.4 Checked vs Unchecked异常对比
特性Checked ExceptionUnchecked Exception
继承关系Exception直接子类RuntimeException子类
编译器检查必须处理不强制处理
使用场景可恢复错误程序逻辑错误
典型示例IOExceptionNullPointerException
方法签名要求必须声明throws可选声明

3. 异常的基本结构

Java中的异常机制主要有以下几个组件:

  1. 抛出异常Throwing Exception,通过throw关键字显式地手动抛出异常。(throw 必须在方法内部使用)

2.捕捉异常Catching Exception,通过try-catch语句来捕捉异常。
3. 声明异常Declaring Exception,通过throws关键字声明一个方法可能抛出的异常。

3.1 抛出异常 (throw)

使用throw关键字可以手动抛出异常。例如:

throw new NullPointerException("Null value encountered!");
public class ThrowExample {
    public static void main(String[] args) {
        try {
            throw new Exception("This is an exception");
        } catch (Exception e) {
            System.out.println(e.getMessage());
        }
    }
}

3.2 捕捉异常 (try-catch)

try-catch语句用于捕捉和处理异常。try块中的代码是需要执行的代码,如果发生异常,会跳转到catch块进行处理。

try-catch 代码块

try {
    int result = 10 / 0;  // 可能抛出ArithmeticException
} catch (ArithmeticException e) {
    System.out.println("Error: " + e.getMessage());
}

try-catch-finally 代码块

try {
    processData();
} catch (IOException e) {
    logger.error("IO操作异常", e);
    throw new DataProcessException("数据处理失败", e);
} finally {
    cleanResources();
}

(1)try 块:包含可能会抛出异常的代码。
(2)catch 块:用于捕获和处理异常。如果 try 块中抛出了异常,控制会跳转到匹配的 catch 块。你可以有多个 catch 块来处理不同类型的异常。
(3)finally 块:无论是否发生异常,finally 块都会执行。它通常用于清理资源,例如关闭文件流、数据库连接等。即使 try 块中有 return 语句,finally 仍然会在 return 语句之前执行,但如果在 finally 块中发生了异常,并且没有进一步的 catch 块来捕获这个异常,那么它会被抛出,可能会影响程序的正常执行。

try-多个catch 代码块

try {
    int result = 10 / 0;  // 会抛出 ArithmeticException
} catch (ArithmeticException e) {
    System.out.println("ArithmeticException caught");
} catch (Exception e) {
    System.out.println("General exception caught");
} finally {
    System.out.println("Finally block executed");
}

catch 块可以有多个
你可以有多个 catch 块,每个 catch 块捕获不同类型的异常。这样可以针对不同类型的异常做出不同的处理。
catch 块的顺序是重要的,应该从最具体的异常类开始,逐步到最通用的异常类(否则会编译报错)。因为 Java 会从上到下依次检查每个 catch 块,如果一个异常类型已经被捕获,那么后面的 catch 块就不会被执行。

3.3 声明异常 (throws)

如果一个方法可能抛出异常,必须使用throws声明该异常。它不会抛出异常,而是告诉调用者这个方法可能会抛出某种异常,调用者需要处理它(通过 try-catch 或者继续声明)(throws 必须出现在方法声明部分(方法签名的末尾))。例如:

public void readFile(String filePath) throws IOException {
    FileReader file = new FileReader(filePath);
}
public void readFile(String path) throws FileNotFoundException, SecurityException {
    File file = new File(path);
    // 可能抛出FileNotFoundException的代码...
}

如果一个方法声明了异常,调用它的方法必须捕获或声明这个异常。

3.4 区别对照
特性throwthrowstry-catch
作用手动抛出异常对象声明方法可能抛出的异常类型捕获并处理代码块中的异常
使用位置方法内部方法声明处(参数列表后)代码执行区域
处理对象具体的异常实例异常类型列表实际发生的异常对象
异常流向向上层调用者抛出通知调用者需要处理的异常类型在当前作用域内消化异常
语法示例throw new Exception()void method() throws Exceptiontry{…} catch(Exception e)
强制要求必须抛出具体异常实例必须声明Checked Exception非必须(可处理可不处理)

4. 深度解析异常处理流程

  • 当程序运行时,出现异常,JVM会查找是否有相应的catch块来捕捉该异常。
  • 如果没有找到匹配的catch块,异常会继续向上抛到调用者。
  • 如果异常最终没有被捕捉到,程序会终止并打印出错误信息。

5. 多重异常捕捉

Java 7引入了多重异常捕捉,可以在同一个catch块中捕捉多种异常类型。

try {
    // 可能抛出多个异常的代码
} catch (IOException | SQLException e) {
    System.out.println("Exception caught: " + e);
}

6. 异常的链式捕捉 (Exception Chaining)

异常链是指将一个异常传递给另一个异常。在Java中,通常可以在一个异常的构造函数中将另一个异常作为参数传递,形成异常链。这样可以保留原始异常的栈跟踪信息。
语法:

try {
    // ...
} catch (LowLevelException cause) {
    throw new HighLevelException("上下文信息", cause);
}
try {
    // 可能会发生异常的代码块
} catch (IOException e) {
    throw new RuntimeException("Error occurred while reading file", e);  // 将IOException 链到RuntimeException
}

7. 自定义异常

你可以自定义异常,通过继承ExceptionRuntimeException类来创建自定义异常。

例一:

public class MyException extends Exception {
    public MyException(String message) {
        super(message);  //构造器,表明异常类型
    }
}

例二:

// 业务异常示例
public class PaymentFailedException extends RuntimeException {
    private String transactionId;
    
    public PaymentFailedException(String message, String transactionId) {
        super(message);
        this.transactionId = transactionId;
    }
    
    // 建议重写toString()包含业务信息
    public String toString(){
      //代码块
    }
}

8. 异常的实践

  • 避免吞噬异常:不要在catch块中捕捉异常后什么都不做,这样会丢失重要的错误信息。

    try {
        // some code
    } catch (Exception e) {
        // do nothing (bad practice)
    }
    
  • 适当的异常类型:对于检查异常,应该根据异常类型来进行捕捉,并处理它。对于非检查异常,应该通过代码逻辑来避免。

  • 使用日志记录异常:对于发生的异常,应该记录日志以便后续分析和调试。

  • 使用特定的异常类型:避免捕捉太广泛的异常(如Exception),应该尽量捕捉更具体的异常,以便于准确处理。


9. 高频面试题

1.什么时候用throw vs throws?

2.try-catch和throws能否共存?

3.构造方法中如何使用throws?

相关文章:

  • PHP回调后门
  • Ubuntu22.04系统离线部署Maxkb【教程】
  • 再见VS Code!Google IDE 正颠覆传统开发体验
  • 探秘中医五色五味:开启饮食养生新智慧
  • Element ui input组件类型为 textarea 时没有 清空按钮
  • [网络_1] 因特网 | 三种交换 | 拥塞 | 差错 | 流量控制
  • Nordic 新一代无线 SoC nRF54L系列介绍
  • Tiny Lexer 一个极简的C语言词法分析器
  • 回溯(子集型):分割回文串
  • 如何在 Windows 上安装与配置 Tomcat
  • 基于PX4和Ardupilot固件下自定义MAVLink消息测试(QGroundControl和Mission Planner)
  • 76. pinctrl和gpio子系统试验
  • 【Easylive】HikariCP 介绍
  • 14:00开始面试,14:08就出来了,问的问题有点变态。。。
  • YOLO霸主地位不保?开源 SOTA 目标检测rf-detr 测评
  • UR机械臂sim2real推荐包
  • CUDA专题8—CUDA L2缓存完全指南:从持久化策略到性能优化实战
  • 代码随想录Day29
  • 学以致用,基于OpenCV的公摊面积估算程序
  • 探秘DeepSeek:开源AI领域的创新先锋
  • 香港服务器做网站/今日要闻10条
  • 做外国的独立网站怎么推广/app推广策略
  • 优化型网站建设/合肥网站推广公司哪家好
  • 网站需求分析报告/搜索排名优化软件
  • wap网站空间/百度站长收录
  • 通信建设资质管理信息系统网站/广东东莞疫情最新情况