java基础之异常练习题
**1.**概念填空
java 中所有的错误/异常都继承自 Throwable类;在该类的子类中, Error 类表示严重的底层错误, 对于这类错误一般处理的方式是 直接报告并终止程序 ; Exception 类表示异常。
在 Java 中,所有的错误(Errors)和异常(Exceptions)都直接或间接继承自
java.lang.Throwable
类。
- Throwable 类: 所有的错误和异常都是
Throwable
类的子类。Throwable
有两个主要的子类:Error
和Exception
。
Error 类:
Error
类表示严重的底层错误,通常由 JVM 报告。这些错误通常是无法被程序员合理地处理的,而且它们往往表示虚拟机运行环境的严重问题,例如内存不足、栈溢出等。常见的Error
子类包括OutOfMemoryError
、StackOverflowError
等。Exception 类:
Exception
类表示可被程序员合理地处理的异常情况。Exception
又分为两类:检查异常(Checked Exception)和非检查异常(Unchecked Exception)。检查异常: 通常表示程序可能能够恢复的错误,这些异常在编译时必须要处理或声明抛出。例如,
IOException
、SQLException
等都是检查异常。非检查异常: 也称为运行时异常(Runtime Exception),通常表示程序员在编写代码时犯下的错误,这些异常在编译时不受检查,程序员可以选择是否处理它们。例如,
NullPointerException
、ArrayIndexOutOfBoundsException
等都是非检查异常。处理方式:
对于 Error 类及其子类: 由于
Error
表示严重的底层错误,通常无法通过程序来合理处理。程序员很难或无法通过代码来修复这类错误。一般情况下,不建议捕获或处理Error
,而是尽量让程序终止并让 JVM 报告错误,以便进行适当的诊断和修复。对于 Exception 类及其子类: 这是程序员可以预测和处理的异常。具体的处理方式取决于异常的类型和业务逻辑。检查异常通常需要显式处理,而非检查异常可以选择捕获和处理,也可以让其在运行时抛出。
在处理异常时,可以使用
try-catch
块捕获异常,使用throws
在方法签名中声明可能抛出的异常,或者使用其他处理机制,如finally
块。
2.查阅API,完成以下填空:
(1)异常类 java.rmi.AlreadyBoundException,从分类上说,该类属于 已检查 (已检查|运行时)异常, 从处理方式上说,对这种异常 抛出 处理。
java.rmi.AlreadyBoundException
属于检查异常(Checked Exception)。在Java中,异常分为两大类:检查异常(Checked Exception)和非检查异常(Unchecked Exception)。
检查异常(Checked Exception): 这是在编译时期强制检查的异常,程序必须显式地处理或声明抛出。例如,
IOException
和SQLException
都是检查异常的例子。非检查异常(Unchecked Exception): 也称为运行时异常(Runtime Exception),这些异常在编译时不受检查,程序员可以选择是否处理它们。
NullPointerException
和ArrayIndexOutOfBoundsException
是非检查异常的例子。
AlreadyBoundException
表示在尝试将对象绑定到Registry
(RMI注册表)时,发现已经有相同名称的对象已经绑定。因为它是java.rmi
包下的异常,并且在RMI(远程方法调用)中使用,所以它被归类为检查异常。
(2)异常类java.util.regex.PatternSyntaxException,从分类上说,该类属于 运行时 (已检查|运行时) 异常,从处理方式上说,对这种异常 try catch处理。
java.util.regex.PatternSyntaxException
属于运行时异常(Runtime Exception)。在 Java 中,正则表达式的语法异常通常在运行时抛出,而不是在编译时。因此,PatternSyntaxException
是一种运行时异常,它表示在正则表达式模式的语法中存在错误。处理这种异常的方式通常是使用
try-catch
块来捕获异常,因为它是运行时异常,可以选择性地处理或传播。try { // 使用正则表达式的代码 // ... } catch (PatternSyntaxException e) { // 处理 PatternSyntaxException 异常 // 可以输出日志、提供用户友好的错误消息等 System.err.println("正则表达式语法错误: " + e.getMessage()); }
在上述代码中,如果在正则表达式的处理过程中发生了语法错误,就会捕获
PatternSyntaxException
异常,并执行相应的处理逻辑。处理方式可以根据具体的应用需求而定,可能包括记录错误日志、向用户显示错误消息等。
虽然一般来说,已检查异常(Checked Exception)通常被抛出处理,而运行时异常(Runtime Exception)通常通过
try-catch
处理,但这并不是绝对的规则。在 Java 中,处理异常的方式是根据具体的需求和情况而定的。
- 已检查异常:
- 通常情况下,已检查异常是在编译时期被强制要求处理或者通过
throws
关键字声明抛出的。这是为了确保程序员在使用可能会引发异常的代码时,要么显式地处理异常,要么声明方法可能抛出的异常。
- 运行时异常:
- 运行时异常通常是由程序员在编写代码时犯下的错误,因此它们在编译时不受检查,程序员可以选择是否处理它们。
- 虽然可以使用
try-catch
块捕获运行时异常,但通常更倾向于在代码中避免这些异常的发生。因为运行时异常通常表示程序员在代码中犯了错误,更好的做法是在开发和测试阶段发现并修复这些错误,而不是通过异常处理来掩盖问题。
3.Java 中用来抛出异常的关键字是©
A. try B. catch C. throw D. finally
4.在异常处理中,释放资源、关闭文件等应由处理©
A. try 语句 B. catch 语句 C. finally 语句 D. throw 语句
5.finally 语句块中的代码(A)
A.总是被执行
B.当try 语句块后面没有catch 时,finally 中的代码才会被执行
C.异常发生时才被执行
D.异常没有发生时才被执行
6.自定义异常类时,可以继承的类是©
A. Error B. ArrayList C. Exception 不行D. NullPointerException
A. Error:
Error
类表示严重的底层错误,通常是由 JVM 报告,而不是由程序员捕获和处理的。自定义异常通常是为了处理程序中可能发生的一般异常情况,而不是用来处理 JVM 报告的底层错误。B. ArrayList:
ArrayList
是 Java 集合框架中的一个类,它与异常类无关。它用于存储和操作动态数组元素。继承ArrayList
类与自定义异常没有直接关系。**C. Exception:**在自定义异常类时,通常应该继承自
Exception
类或其子类,因为自定义异常通常是为了表示一种在程序运行时可能发生的错误或异常情况,而Exception
是 Java 中用于表示检查异常的基类。D. NullPointerException:
NullPointerException
是一个运行时异常,通常表示在程序试图访问对象的属性或调用对象的方法时,该对象为null
。继承NullPointerException
通常是不合适的,因为这是一个已有的异常类,通常无需自定义。
7.对于 try{ … }catch…语句的排列方式,下列描述正确的是(A)
A.子类异常在前,父类异常在后
B.父类异常在前,子类异常在后
C.只能有子类异常
D.父类异常不能与子类异常同时出现
在
try-catch
语句中,如果有多个catch
块用于处理不同类型的异常,通常子类异常的catch
块应该放在父类异常的catch
块之前。这是因为异常处理是按照代码的顺序匹配的,如果子类异常的catch
块放在父类异常的前面,那么在遇到异常时,会首先匹配到子类异常,而不会执行父类异常的catch
块。例如:
try { // some code that may throw exceptions } catch (ChildException ce) { // handle ChildException } catch (ParentException pe) { // handle ParentException }
这样的排列方式是正确的,子类异常的
catch
块在前,父类异常的catch
块在后。
8.仔细阅读以下代码,将代码补全。
public class TestThrow {
public static void main(String[] args) {
throwException(10);
}
private static void throwException(int n) {
if (n == 0) {
//抛出一个NullPointerException
throw new NullPointerException();
} else {
//抛出一个ClassCastException
//并设定详细信息为“类型转换出错”
try{
throw new ClassCastException("类型转换出错");
}catch(ClassCastException e){
System.out.println(e.getMessage());
}
}
}
}
在Java中,通过
throw
语句抛出异常时,需要提供一个异常对象。new
关键字用于创建一个新的异常对象,因为异常是类的实例。在throw
语句中,new
用于实例化异常类,并将新创建的异常对象抛出。
throw new NullPointerException();
:
- 这是一个抛出
NullPointerException
的语句。NullPointerException
是一种运行时异常,通常表示在试图访问对象的属性或调用对象的方法时,该对象为null
。throw new NullPointerException("自定义详细信息: 对象引用为 null");
这里的
"自定义详细信息: 对象引用为 null"
是一个可选的参数,用于提供异常的详细信息,可以在捕获异常时使用。
System.out.println(e.getMessage());
:
- 这是在捕获异常后输出异常信息的语句。在
catch
块中,e
是捕获到的异常对象,通过e.getMessage()
可以获取异常的详细信息。这个语句将异常信息输出到标准输出(通常是控制台),方便程序员查看异常信息,定位问题。
9.代码改错:仔细阅读以下程序,将错误的代码进行改正。
class MyException{}
public class TestException {
public static void main(String[] args) {
ma();
}
private static int ma() {
try {
m();
return 100;
} catch (Exception e) {
System.out.println("Exception");
} catch (ArithmeticException e) {
System.out.println("ArithmeticException");
}
}
public static void m(){
throw new MyException();
}
}
在 Java 中,catch 块的顺序应该从最具体的异常到最通用的异常。在你的代码中,
catch (Exception e)
放在了catch (ArithmeticException e)
的前面,这样ArithmeticException
永远不会被捕获,因为Exception
包括了ArithmeticException
。以下是修正后的代码:class MyException extends Exception {} public class TestException { public static void main(String[] args) { ma(); } private static int ma() { try { m(); return 100; } catch (ArithmeticException e) { System.out.println("ArithmeticException"); } catch (Exception e) { System.out.println("Exception"); } return 0; } public static void m() throws MyException { throw new MyException(); } }
这样,
ArithmeticException
的 catch 块会在Exception
的前面,确保了异常的正确捕获。
10.仔细阅读以下代码,当读入的 n 分别为 1,2,3,4,5 时,输出的结果分别是什么?
public class TestException {
public static void main(String[] args) {
System.out.println("main1");
int n;
//读入n
ma(n);
System.out.println("main2");
}
public static void ma(int n){
try {
System.out.println("ma1");
mb(n);
System.out.println("ma2");
}catch (EOFException e){
System.out.println("Catch EOFException");
}catch (IOException e){
System.out.println("Catch IOException");
}catch (SQLException e){
System.out.println("Catch SQLException");
}catch (Exception e){
System.out.println("Catch Exception");
}finally {
System.out.println("In finally");
}
}
public static void mb(int n)throws Exception{
System.out.println("mb1");
if(n == 1)throw new EOFException();
if(n == 2)throw new FileNotFoundException();
if(n == 3)throw new SQLException();
if(n == 4)throw new NullPointerException();
System.out.println("mb2");
}
}
n为1时:
main1 ma1 mb1 Catch EOFException In finally main2
n为2时:
main1 ma1 mb1 Catch IOException In finally main2
n为3时:
main1 ma1 mb1 Catch SQLException In finally main2
n为4时:
main1 ma1 mb1 Catch Exception In finally main2
n为5时:
main1 ma1 mb1 mb2 ma2 In finally main2
FileNotFoundException
是IOException
的子类。在 Java 中,FileNotFoundException
继承自IOException
。继承关系如下:
java.lang.Object ↳ java.lang.Throwable ↳ java.lang.Exception ↳ java.io.IOException ↳ java.io.FileNotFoundException
因此,
FileNotFoundException
包含了IOException
的特性,并且更具体地表示在尝试打开文件时找不到文件或文件无法访问的异常情况。
当
n
的值为1、2、3或4时,"ma2"未被打印的原因是在mb
方法中抛出了异常。当异常发生时,程序的控制流直接跳转到调用该方法的ma
方法中的相应catch
块或其周围的catch
块。这意味着在抛出异常的那一行代码之后的任何代码(在这个例子中是mb(n);
之后的代码)都不会被执行,程序会跳转到相应的catch
块或finally
块。以
n
等于1为例:public static void ma(int n){ try { System.out.println("ma1"); mb(n); // 异常在这里被抛出 System.out.println("ma2"); // 异常发生时,这一行会被跳过 } catch (EOFException e){ System.out.println("Catch EOFException"); } catch (IOException e){ System.out.println("Catch IOException"); } catch (SQLException e){ System.out.println("Catch SQLException"); } catch (Exception e){ System.out.println("Catch Exception"); } finally { System.out.println("In finally"); } }
当
mb(n)
抛出EOFException
时,控制流跳转到catch (EOFException e)
块,“ma2"会被跳过。其他值的n
也是同样的逻辑,只有当mb
方法成功完成且没有抛出异常时,才会达到System.out.println("ma2");
这一行,从而打印"ma2”。
11.仔细阅读以下代码:
import java.io.IOException;
class Super{
public void ma() throws IOException{}
}
interface IA{
void mb();
}
public class MySub extends Super implements IA {
public void ma() //1__________{}
public void mb() //2__________{}
}
在//1 处,填入以下 AB 代码可以编译通过,在//2 处,填入 D 代码可以编译通过
A.throws java.io.IOException
B.throws java.io.FileNotFoundException, java.io.EOFException
C.throws java.sql.SQLException
D.不能抛出任何异常
import java.io.IOException; class Super{ public void ma() throws IOException{} } interface IA{ void mb(); } public class MySub extends Super implements IA { public void ma() throws IOException {} //1A public void mb() {} //2() }
在//1 处,填入 A 代码可以编译通过,因为子类方法可以声明抛出与父类方法相同的异常或者其子类异常。在这里,
ma()
方法声明了throws IOException
,与父类Super
中的ma()
方法相同。在//2 处,如果一个接口方法声明了异常,实现类可以不声明异常,但不能声明其他异常。在上述代码中,
IA
接口的mb()
方法没有声明抛出异常,因此在MySub
类中实现mb()
方法时,可以选择不声明抛出异常。
- 接口可以声明抛出异常,他的实现可以不抛出异常,调用的时候也会抛出异常
- 接口没有声明抛出异常,它的实现不能抛出异常
12.仔细阅读以下代码,关于程序描述正确的是(A)
public class TestTryCatch{
public static void main(String args[]){
System.out.println(ma());
}
public static int ma(){
int n;
try{
n = 10/0;
}catch(Exception e){}
return n;
}
}
A. 编译不通过 未初始化变量
B. 编译通过,输出-1
C. 编译通过,输出 0
D. 以上描述都不正确
在这段代码中,
int n;
是一个局部变量,并且在try
块中发生了异常,导致catch
块中捕获异常,但并未为n
赋值。因此,在return n;
处,n
未被初始化就被返回,这将导致编译错误。
13.仔细阅读以下代码,在ma 方法中,当读入的b 为 100 时,输出结果为 100 ,当读入的b 为 0 时,输出结果为 100 。
public class TestFinally{
public static void main(String args[]){
System.out.println(ma());
}
public static int ma(){
int b;
//读入b
try{
int n = 100;
return n/b;
}catch(Exception e){
return 10;
}finally{
return 100;
}
}
}
当
b
为 100 时,return n/b;
的结果为100/100
,即 1。然后finally
块中的return 100;
执行,覆盖了前面的return n/b;
的结果,所以输出结果为 100。当
b
为 0 时,return n/b;
会抛出ArithmeticException
异常,进入catch
块,执行return 10;
,但finally
块中的return 100;
会覆盖catch
块中的返回值。因此,输出结果仍然为 100。
14.仔细阅读以下代码,在ma 方法中,读入整数b。
**如果读入的值为 10,则输出 **
ma1
ma21
In Finally
**如果读入的值为 0,则输出 **
ma1
In Finally
代码如下:
public class TestTryFinally{
public static void main(String args[]){
try{
ma();
}catch(Exception ex1){}
}
public static void ma() throws Exception{
int n = 10;
int b;
//读入一个整数b
try{
System.out.println("ma1");
int result = n/b;
System.out.println("ma2"+result);
}finally{
System.out.println("In Finally");
}
}
}
当
b
为 10 时,int result = n/b;
执行时,由于n
为 10,而b
为 10,所以计算结果为10/10
,即 1。然后输出语句System.out.println("ma2"+result);
打印 “ma2” 和计算结果,输出为 “ma21”。最后,执行finally
块,输出 “In Finally”。当
b
为 0 时,int result = n/b;
执行时,由于除以零,抛出ArithmeticException
异常,try
块内的代码被中断。控制流程直接跳转到finally
块,输出 “In Finally”。
15.仔细阅读以下代码,是否能编译通过?如果不能,应该如何修改?
import java.io.*;
class MySuper{
public void m() throws IOException{}
}
class MySub extends MySuper {
public void m() throws EOFException{}
}
class MySub2 extends MySub {
public void m() throws FileNotFoundException{}
}
当在Java中的子类覆盖(override)父类的方法时,被覆盖的方法不能抛出比父类方法更宽泛的异常。这是因为如果子类的方法允许抛出比父类更泛化的异常,那么在使用父类引用时,我们无法保证不会抛出更具体的异常,从而违反了代码的稳定性和可靠性。
在这个例子中:
MySub
类的m()
方法覆盖了MySuper
类的m()
方法,并抛出了EOFException
异常,这是合法的,因为EOFException
是IOException
的子类。- 但是,
MySub2
类的m()
方法覆盖了MySub
类的m()
方法,并抛出了FileNotFoundException
异常,这是不合法的,因为FileNotFoundException
是IOException
的子类,而MySub
类的m()
方法已经声明了抛出EOFException
异常,FileNotFoundException
不是EOFException
的子类,这样的话,使用父类引用调用子类方法可能会导致更具体的异常,破坏了代码的可靠性。因此,
MySub2
类的m()
方法应该修改为:class MySub2 extends MySub { public void m() throws EOFException{} // 修正为抛出 EOFException }
16.仔细阅读以下代码,关于程序描述正确的是© ?
public class TestException {
public static void main(String args[]) {
try {
System.out.println("main1");
ma();
System.out.println("main2");
}catch (Exception e){
System.out.println("In Catch");
}
}
public static void ma(){
System.out.println("ma1");
throw new NullPointerException();
System.out.println("ma2");
}
}
A. 编译出错
B. 编译正常,输出 main1 ma1 In Catch
C. 编译正常,运行时出错
D. 以上描述都不正确
解答:
这段代码在运行时会抛出
NullPointerException
异常,导致程序终止。原因是throw new NullPointerException();
具体来说,throw new NullPointerException();
会立即抛出异常,而后续的System.out.println("ma2");
语句不会被执行。因此,ma2
不会被打印,程序直接跳到catch
块中。如果希望执行
ma2
,需要将throw new NullPointerException();
语句放在System.out.println("ma2");
语句之前。以下是修改后的代码:
public class TestException { public static void main(String args[]) { try { System.out.println("main1"); ma(); System.out.println("main2"); } catch (Exception e) { System.out.println("In Catch"); } } public static void ma() { System.out.println("ma1"); System.out.println("ma2"); throw new NullPointerException(); } }
这样修改后,
ma2
将在抛出异常之前被执行。运行结果:
![]()
17.仔细阅读以下程序,下面哪些代码放在/1/处可以编译通过(AB)
public class TestException {
public static void main(String args[]) {
try {
ma();
}
/*1*/
catch (Exception e) {}
}
public static void ma() throws IOException(){}
}
A. catch(NullPointerException npe){}
B. catch(IOException ioe){}
C. catch(SQLException sqle){}
ma()
方法声明抛出IOException
,而NullPointerException
是RuntimeException
的子类,而RuntimeException
又是Exception
的子类,所以 A 和 B 两个选项都是合法的。
18.简述 final、finlize、finally 的区别。
final
:
final
是一个关键字,可以用来修饰类、方法、变量。- 当用
final
修饰一个类时,该类不能被继承。- 当用
final
修饰一个方法时,该方法不能被子类覆盖。- 当用
final
修饰一个变量时,该变量成为常量,一旦赋值后不可更改。
finalize
:
finalize
是一个方法,属于Object
类的方法,用于垃圾回收。- 在 Java 中,通过重写
finalize
方法可以在对象被垃圾回收之前执行一些清理工作。但是,现代 Java 程序中通常不鼓励过度依赖finalize
方法,而更推荐使用其他资源管理手段,如 try-with-resources 语句、AutoCloseable
接口等。
finally
:
finally
是一个关键字,用于定义在异常处理结构的 try 代码块之后执行的代码块。- 无论是否发生异常,
finally
中的代码块都会被执行,通常用于释放资源、关闭文件或网络连接等。finally
语句块中的代码在异常处理完毕后一定会执行。
19.在 try 里有 return 语句,那 finally 里的语句还会执行么?为什么?
finally
里的语句会执行,即使在try
代码块中有return
语句。无论try
代码块中是否有异常,finally
块都会在方法返回前执行。这是因为在 Java 中,
finally
块用于确保在离开try
代码块之前执行清理或资源释放操作。即使在try
块中有return
语句,finally
块仍然会执行。唯一的例外是在finally
块中出现了System.exit()
或者导致整个程序退出的异常,这样会中断finally
的执行。下面是一个示例:
public class Example { public static void main(String[] args) { System.out.println(testFinally()); // 输出:2 } public static int testFinally() { try { return 1; } finally { return 2; } } }
在这个例子中,
testFinally
方法在try
块中返回了1,但是由于finally
块中有一个返回语句,所以最终返回的结果是2。
20.仔细阅读以下代码,写出程序执行的结果。
public class TestException {
public static void main(String args[]) {
String s = null;
try{
int a = s.length();
System.out.println("Step A");
}catch (NullPointerException e1){
System.out.println("Step B");
}catch (RuntimeException e2){
System.out.println("Step C");
}catch (Exception e3){
System.out.println("Step D");
}finally {
System.out.println("Step E");
}
}
}
在这个代码中,由于
s
被赋值为null
,因此在int a = s.length();
这一行会抛出NullPointerException
。由于catch
块是从上到下依次匹配的,第一个匹配的catch
块会执行,而后续的catch
块将被忽略。因此,程序执行的结果将是:
Step B Step E
解释:
NullPointerException
是RuntimeException
的子类,所以第一个catch
块被匹配执行,输出 “Step B”。- 然后,
finally
块总是会执行,输出 “Step E”。
21.编程:创建两个自定义异常类 MyException1 和 MyException2。
要求如下:
(1) MyException1 为已检查异常,MyException2 为运行时异常;
(2) 这两个异常均具有两个构造函数:一个无参,另一个带字符串参数,参数表示产生异常的信息。
// MyException1 - 已检查异常 class MyException1 extends Exception { public MyException1() { super(); } public MyException1(String message) { super(message); } } // MyException2 - 运行时异常 class MyException2 extends RuntimeException { public MyException2() { super(); } public MyException2(String message) { super(message); } } // 示例用法 public class CustomExceptionExample { public static void main(String[] args) { try { // 抛出 MyException1(已检查异常) throw new MyException1("This is MyException1"); } catch (MyException1 e) { System.out.println("Caught MyException1: " + e.getMessage()); } // 抛出 MyException2(运行时异常) throw new MyException2("This is MyException2"); } }
22.在上一题的基础上,把下面代码补充完整。
下面是对代码的补充:
public class TestMyException { public static void main(String args[]) { int n = 1; // 假设读入的n为1 // 读入n try { m(n); } catch (MyException1 ex1) { // 输出ex1详细的方法调用栈信息 ex1.printStackTrace(); } catch (MyException2 ex2) { // 输出ex2的详细信息 System.out.println(ex2.getMessage()); // 并把ex2重新抛出 throw ex2; } } public static void m(int n) throws MyException1 { if (n == 1) { // 抛出MyException1,并设定其详细信息为“n==1" throw new MyException1("n==1"); } else { // 抛出MyException2,并设定其详细信息为“n==2" throw new MyException2("n==2"); } } }
在这个例子中,假设读入的
n
为 1,然后调用m(n)
方法。如果n
等于 1,就抛出MyException1
,并输出其详细的方法调用栈信息。如果n
不等于 1,则抛出MyException2
,输出其详细信息,然后将MyException2
重新抛出。