ZetCode

Java RuntimeException 类

最后修改时间:2025 年 4 月 13 日

java.lang.RuntimeException 类是 Java 中所有未检查异常的超类。与已检查异常不同,RuntimeException 不需要在使用它们的方法签名中声明或显式捕获。

RuntimeException 通常指示应该修复而不是捕获的编程错误。常见示例包括 NullPointerException、ArrayIndexOutOfBoundsException 和 IllegalArgumentException。理解 RuntimeException 对于编写健壮的 Java 程序至关重要。

RuntimeException 基础

RuntimeException 是 Exception 的一个子类,但不同之处在于它是未检查的。 这意味着编译器不会强制处理或声明这些异常。 它们通常代表可以避免的问题。

public class RuntimeException extends Exception {
    public RuntimeException() {...}
    public RuntimeException(String message) {...}
    public RuntimeException(String message, Throwable cause) {...}
    public RuntimeException(Throwable cause) {...}
    protected RuntimeException(String message, Throwable cause,
        boolean enableSuppression, boolean writableStackTrace) {...}
}

上面的代码显示了 RuntimeException 中可用的构造函数。 这些构造函数允许创建带有消息、原因以及控制堆栈跟踪行为的异常。

基本 RuntimeException 示例

此示例演示了创建和抛出一个基本的 RuntimeException。 当在简单的验证方法中检测到无效输入时,我们会抛出它。

Main.java
package com.zetcode;

public class Main {
    public static void validateAge(int age) {
        if (age < 0) {
            throw new RuntimeException("Age cannot be negative");
        }
        System.out.println("Valid age: " + age);
    }

    public static void main(String[] args) {
        try {
            validateAge(-5); // This will throw RuntimeException
        } catch (RuntimeException e) {
            System.out.println("Caught exception: " + e.getMessage());
        }
        
        validateAge(25); // This will pass validation
    }
}

在此示例中,当提供负年龄时,我们会抛出一个 RuntimeException。 虽然我们在 main 中捕获了它,但请注意,捕获 RuntimeException 是可选的。 第二次调用显示了使用正年龄的成功验证。

自定义 RuntimeException

创建自定义 RuntimeException 允许进行更具体的错误处理。 此示例显示了用于无效帐户操作的自定义异常。

Main.java
package com.zetcode;

class InsufficientFundsException extends RuntimeException {
    public InsufficientFundsException(String message) {
        super(message);
    }
}

class BankAccount {
    private double balance;

    public BankAccount(double initialBalance) {
        this.balance = initialBalance;
    }

    public void withdraw(double amount) {
        if (amount > balance) {
            throw new InsufficientFundsException(
                "Only " + balance + " available. Tried to withdraw " + amount);
        }
        balance -= amount;
    }
}

public class Main {

    public static void main(String[] args) {
        BankAccount account = new BankAccount(100);
        
        try {
            account.withdraw(150);
        } catch (InsufficientFundsException e) {
            System.out.println("Error: " + e.getMessage());
        }
    }
}

在这里,我们定义了扩展 RuntimeException 的 InsufficientFundsException。 当提款超过余额时,BankAccount 会抛出它。 这提供了清晰的、特定于域的错误信息,同时保持未检查状态。

带有 Cause 的 RuntimeException

RuntimeException 可以使用 cause 参数包装其他异常。 当在保留原始错误的同时将已检查的异常转换为未检查的异常时,这非常有用。

Main.java
package com.zetcode;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;

class FileReadException extends RuntimeException {
    public FileReadException(String message, Throwable cause) {
        super(message, cause);
    }
}

public class Main {
    public static String readFile(String path) {
        try {
            return new String(Files.readAllBytes(Paths.get(path)));
        } catch (IOException e) {
            throw new FileReadException("Failed to read file: " + path, e);
        }
    }

    public static void main(String[] args) {
        try {
            String content = readFile("nonexistent.txt");
            System.out.println(content);
        } catch (FileReadException e) {
            System.out.println("Error: " + e.getMessage());
            System.out.println("Original cause: " + e.getCause().getMessage());
        }
    }
}

此示例显示了如何将 IOException 包装在我们自定义的 FileReadException 中。 原始异常作为原因被保留,在避免已检查异常要求的同时提供完整的错误上下文。

ArrayIndexOutOfBoundsException

ArrayIndexOutOfBoundsException 是一种常见的 RuntimeException,当访问无效的数组索引时抛出。 此示例演示了它如何发生。

Main.java
package com.zetcode;

public class Main {

    public static void main(String[] args) {
        int[] numbers = {1, 2, 3};
        
        try {
            System.out.println(numbers[3]); // Invalid index
        } catch (ArrayIndexOutOfBoundsException e) {
            System.out.println("Caught exception: " + e);
            System.out.println("Array length: " + numbers.length);
        }
        
        // Safe access with bounds checking
        int index = 3;
        if (index >= 0 && index < numbers.length) {
            System.out.println(numbers[index]);
        } else {
            System.out.println("Invalid index: " + index);
        }
    }
}

第一次尝试访问一个包含 3 个元素的数组中的索引 3,导致异常。 第二部分显示了正确的边界检查以防止异常。 在访问之前,始终验证数组索引。

NullPointerException

NullPointerException 发生在尝试在需要对象的地方使用 null 时。 此示例显示了常见的情况和预防技术。

Main.java
package com.zetcode;

class Person {
    private String name;
    
    public Person(String name) {
        this.name = name;
    }
    
    public String getName() {
        return name;
    }
}

public class Main {

    public static void main(String[] args) {
        Person person = null;
        
        try {
            System.out.println(person.getName()); // Throws NPE
        } catch (NullPointerException e) {
            System.out.println("Caught NPE: " + e);
        }
        
        // Safe alternatives
        person = new Person("Alice");
        if (person != null) {
            System.out.println(person.getName());
        }
        
        // Java 8+ Optional alternative
        java.util.Optional.ofNullable(person)
            .ifPresent(p -> System.out.println(p.getName()));
    }
}

该示例显示了在 null 上调用方法时出现的 NullPointerException。 然后,我们演示了 null 检查和 Java 8 的 Optional 作为更安全的选择。 在使用之前,始终验证对象是否为 null。

IllegalArgumentException

抛出 IllegalArgumentException 指示非法或不适当的参数。 此示例验证了方法参数。

Main.java
package com.zetcode;

class Calculator {
    public static double divide(double dividend, double divisor) {
        if (divisor == 0) {
            throw new IllegalArgumentException("Divisor cannot be zero");
        }
        return dividend / divisor;
    }
}

public class Main {

    public static void main(String[] args) {
        try {
            double result = Calculator.divide(10, 0);
            System.out.println("Result: " + result);
        } catch (IllegalArgumentException e) {
            System.out.println("Error: " + e.getMessage());
        }
        
        // Valid case
        double validResult = Calculator.divide(10, 2);
        System.out.println("Valid result: " + validResult);
    }
}

Calculator 类抛出 IllegalArgumentException 以表示除以零。 这清楚地将无效输入传达给调用者。 main 方法演示了错误和成功案例。

ConcurrentModificationException

当在迭代集合时修改集合时,会发生 ConcurrentModificationException。 此示例显示了问题和解决方案。

Main.java
package com.zetcode;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

public class Main {

    public static void main(String[] args) {
        List<String> colors = new ArrayList<>();
        colors.add("Red");
        colors.add("Green");
        colors.add("Blue");
        
        try {
            // This will throw ConcurrentModificationException
            for (String color : colors) {
                if (color.equals("Green")) {
                    colors.remove(color);
                }
            }
        } catch (Exception e) {
            System.out.println("Caught: " + e);
        }
        
        // Safe removal using Iterator
        Iterator<String> iterator = colors.iterator();
        while (iterator.hasNext()) {
            String color = iterator.next();
            if (color.equals("Green")) {
                iterator.remove();
            }
        }
        
        System.out.println("Modified list: " + colors);
    }
}

第一次尝试在迭代期间修改列表,导致异常。 第二种方法使用 Iterator 的 remove 方法安全地删除元素。 在迭代期间修改集合时,始终使用正确的技术。

来源

Java RuntimeException 类文档

本教程介绍了 RuntimeException 以及演示常见场景的实际示例。 理解这些异常有助于编写更健壮的 Java 代码,从而正确处理错误条件。

作者

我叫 Jan Bodnar,是一位经验丰富的敬业程序员。 我从 2007 年开始撰写编程文章,至今已创作了超过 1,400 篇文章和 8 本电子书。 凭借超过八年的教学经验,我致力于分享我的知识并帮助他人掌握编程概念。

列出所有Java教程