【建议收藏】30个Java异常的知识点,你能撑到第几个?
文章目录
- 问题1:Error 和 Exception 区别是什么?
- 问题2:运行时异常和一般异常(受检异常)区别是什么?
- 问题3:JVM 是如何处理异常的?
- 问题4:throw 和 throws 的区别是什么?
- 问题5:final、finally、finalize 有什么区别?
- 问题6:NoClassDefFoundError 和 ClassNotFoundException 区别?
- 问题7:try-catch-finally 中哪个部分可以省略?
- 问题8:try-catch-finally 中,如果 catch 中 return 了,finally 还会执行吗?
- 问题9:类 ExampleA 继承 Exception,类 ExampleB 继承ExampleA。有如下代码片断:请问执行此段代码的输出是什么?
- 问题10:常见的 RuntimeException 有哪些?
- 问题11:Java常见异常有哪些
- 问题12:Java异常处理最佳实践
- 问题13:在 finally 块中清理资源或者使用 try-with-resource 语句
- 问题14:优先明确的异常
- 问题15:对异常进行文档说明
- 问题16:使用描述性消息抛出异常
- 问题17:优先捕获最具体的异常
- 问题18:不要捕获 Throwable 类
- 问题19:不要忽略异常
- 问题20:不要记录并抛出异常
- 问题21:包装异常时不要抛弃原始的异常
- 问题22:不要使用异常控制程序的流程
- 问题23:使用标准异常
- 问题24:异常会影响性能
- 问题25:如何合理地使用异常处理以避免性能问题
- 问题26:在finally块中避免使用return语句
- 问题27:捕获异常与抛异常必须是完全匹配,或者捕获异常是抛异常的父类
- 问题28:在调用RPC、二方包、或动态生成类的相关方法时,捕捉异常必须使用Throwable类来进行拦截
- 问题29:方法的返回值可以为null,不强制返回空集合,或者空对象等,必须添加注释充分说明什么情况下会返回null值
- 问题30:防止NPE,是程序员的基本修养,注意NPE产生的场景
问题1:Error 和 Exception 区别是什么?
答案:
在Java中,Error
和 Exception
都是 Throwable
类的子类,但它们之间存在着明显的区别,这些区别主要体现在使用场景和处理方式上。
首先,Error
表示JVM在运行期间无法处理的严重问题,通常是JVM自身的问题,比如OutOfMemoryError
和StackOverflowError
。这类错误通常不由应用程序来处理,因为它们表明应用程序的状态已经严重损坏,应用程序无法恢复。Error是不受检异常,编译器不会强制开发者去捕获或抛出这些异常。
与此相反,Exception
表示程序运行中可以被处理的异常情况。Exception
可以细分为两大类:受检异常(checked exceptions)和非受检异常(unchecked exceptions)。受检异常是必须显式处理的异常,例如IOException
和SQLException
,编译器要求开发者通过try-catch
语句块或使用throws
关键字声明来处理这些异常。非受检异常,也就是运行时异常(如NullPointerException
和ArrayIndexOutOfBoundsException
),是不需要强制处理的,编译器不会检查这些异常的处理情况。
在实际开发中,我们通常会捕获并处理Exception
,而对于Error
,由于其严重性和不可控性,我们通常不会在代码中捕获它们。相反,我们会通过代码设计和测试来尽量减少这些错误的发生。
问题2:运行时异常和一般异常(受检异常)区别是什么?
答案:
运行时异常(RuntimeException)和一般异常(受检异常)在Java异常处理中扮演着不同的角色,它们的主要区别在于编译器如何处理它们。
运行时异常是RuntimeException
类及其子类的实例,这些异常代表了编程错误,比如逻辑错误或错误的使用API。它们是不受检异常,这意味着编译器不会强制要求开发者捕获这些异常。相反,它们可以被用来指示那些在正常程序运行中不应该发生的情况,或者那些应该由应用程序逻辑自行处理的问题。例如,NullPointerException
和ArrayIndexOutOfBoundsException
都是运行时异常。
另一方面,一般异常,也就是受检异常,是除了RuntimeException
及其子类之外的所有异常。这些异常通常是由于外部因素引起的,比如文件找不到、数据库连接失败等。受检异常要求开发者在编译时就考虑异常处理,可以通过try-catch
语句块来捕获并处理这些异常,或者通过在方法签名中使用throws
关键字来声明这些异常,告知方法的调用者需要对这些异常进行处理。
在设计API时,如果一个方法的调用者可以预见异常并能够处理,那么就应该将这个异常声明为受检异常。如果异常是由于编程错误导致的,那么它应该是一个运行时异常。这样的设计可以帮助提高代码的可读性和健壮性。
问题3:JVM 是如何处理异常的?
答案:
Java虚拟机(JVM)处理异常的过程是Java异常处理机制的核心。当Java程序运行时,如果发生异常,JVM会采取一系列步骤来处理这个异常。
首先,当异常发生时,JVM会创建一个对应的异常对象。这个异常对象是Throwable
类或其子类的实例,它包含了异常的名称、异常描述以及异常发生时应用程序的状态信息。
接下来,JVM会沿着调用栈(call stack)向上查找,寻找可以处理这个异常的代码块。这个过程从发生异常的方法开始,逐层向上,直到找到匹配的异常处理器。如果在当前线程的调用栈中没有找到合适的异常处理器,JVM会将异常传递给父线程,依此类推。
如果在任何地方都没有找到合适的异常处理器,那么JVM会将异常转交给默认的异常处理器。默认异常处理器是JVM的一部分,它的作用是打印出异常信息并终止应用程序的执行。
此外,如果异常发生在一个方法中,而这个方法通过throws
关键字声明了可能抛出的异常,那么调用这个方法的上层方法也必须处理这个异常,除非它也通过throws
关键字继续向上声明。
值得注意的是,如果一个方法通过try-catch
语句块捕获了异常,那么JVM就不会继续向上抛出这个异常,而是在当前方法中进行处理。如果在catch
块中使用了return
语句或在finally
块中使用了return
语句,那么方法会返回到调用者,并且finally
块中的return
语句会覆盖try
块或catch
块中的return
语句。
总的来说,JVM处理异常的机制确保了Java程序的健壮性和错误处理能力,使得开发者可以更加灵活地控制程序的执行流程和错误处理逻辑。
问题4:throw 和 throws 的区别是什么?
答案:
在Java中,throw
和throws
是处理异常的两个关键字,它们在异常处理中扮演着不同的角色。
throw
是一个语句,用于在代码中主动抛出一个异常实例。它通常用在try
块或catch
块中,用来显式地抛出一个异常。throw
可以抛出任何Throwable
的子类实例,包括错误(Error)和异常(Exception)。使用throw
关键字可以给方法调用者提供一个明确的信号,表明方法内部发生了特定的问题。例如,如果一个方法需要参数有效性检查,而参数不满足要求,就可以使用throw
抛出一个IllegalArgumentException
。
throws
是一个关键字,用于在方法签名中声明该方法可能会抛出的异常。当一个方法可能会产生某种异常,并且调用者需要处理这种异常时,就在方法声明时使用throws
关键字后跟异常类型。这样,调用者就知道必须采取适当的异常处理措施。throws
后面可以跟多个异常类型,用逗号分隔,表明这个方法可能会抛出这些异常中的任何一个。例如,一个读取文件的方法可能会声明抛出IOException
和NullPointerException
。
总结来说,throw
用于实际抛出异常,而throws
用于声明方法可能会抛出的异常。一个方法可以通过throw
抛出一个异常,也可以通过throws
声明可能会抛出的异常,但不能同时使用两者。
问题5:final、finally、finalize 有什么区别?
答案:
在Java中,final
、finally
和finalize
是三个完全不同的概念,它们在编程中有着各自独特的用途和含义。
final
是一个关键字,用于修饰类、方法或变量。当final
修饰一个类时,表明这个类不能被继承,例如String
类。当final
修饰一个方法时,表明这个方法不能被重写。当final
修饰一个变量时,表明这个变量的值在初始化之后不能被改变,即它是一个常量。
finally
是一个块,与try
块一起使用,用于处理异常后的清理工作。无论是否发生异常,finally
块中的代码都会被执行。这通常用于释放资源,如关闭文件流或数据库连接。finally
块在try
块和catch
块之后执行,即使在try
块或catch
块中使用了return
语句,finally
块仍然会执行。
finalize
是一个方法,属于Object
类。它是Java垃圾收集机制的一部分,用于在对象被垃圾收集器回收前进行清理工作。finalize
方法在对象的生命周期结束时被调用,但它的调用时机是不确定的,因此不推荐使用finalize
来执行重要的清理工作。相反,应该使用try
-finally
块或try
-with-resources`语句来确保资源的正确释放。
总的来说,final
用于修饰不可变的对象、方法和类,finally
用于异常处理后的清理工作,而finalize
是一个不推荐使用的方法,用于对象被回收前的清理工作。
问题6:NoClassDefFoundError 和 ClassNotFoundException 区别?
答案:
NoClassDefFoundError
和ClassNotFoundException
都是Java中与类加载和链接相关的问题,但它们出现的场景和处理方式有所不同。
NoClassDefFoundError
是一个错误(Error
),它在程序运行期间发生,表明虚拟机或类加载器尝试加载某个类时,在内存中找不到该类的定义。这种情况通常发生在编译时类存在,但在运行时类被删除或未被包含在类路径中。由于这是一个Error
,它通常不会被应用程序捕获和处理。
ClassNotFoundException
是一个异常(Exception
),它在程序尝试使用Class.forName()
、ClassLoader.loadClass()
或ClassLoader.findSystemClass()
动态加载类时,如果指定的类在类路径中找不到,就会抛出这个异常。与NoClassDefFoundError
不同,ClassNotFoundException
是一个受检异常,可以通过try-catch
语句捕获并处理。
在实际开发中,如果预期在运行时可能会遇到类找不到的情况,应该捕获ClassNotFoundException
并进行适当的处理。而对于NoClassDefFoundError
,通常需要通过确保类路径的正确性和完整性来预防其发生。
问题7:try-catch-finally 中哪个部分可以省略?
答案:
在Java的异常处理结构try-catch-finally
中,catch
块是可以省略的。
问题8:try-catch-finally 中,如果 catch 中 return 了,finally 还会执行吗?
答案:
在Java中,无论catch
块中是否有return
语句,finally
块中的代码总是会被执行。这是因为finally
块用于执行清理工作,如关闭文件流或释放数据库连接等,这些操作无论是否发生异常都需要执行。
当在try
块或catch
块中执行了return
语句时,finally
块仍然会执行,但它执行在方法返回之前。如果在finally
块中修改了返回值,那么这个修改后的值将成为最终的返回值。因此,如果在finally
块中写了return
语句,它将覆盖try
块和catch
块中的return
语句。
例如,考虑以下代码:
public int divide(int numerator, int denominator) {int result;try {result = numerator / denominator;} catch (ArithmeticException e) {result = -1;return result; // 这实际上不会立即返回} finally {System.out.println("Operation finished.");}return result; // 这个方法实际的返回点
}
在上述代码中,如果在catch
块中发生异常,catch
块会返回-1
,但finally
块仍然会执行打印消息。如果finally
块中也有return
语句,那么它将决定方法的最终返回值。
问题9:类 ExampleA 继承 Exception,类 ExampleB 继承ExampleA。有如下代码片断:请问执行此段代码的输出是什么?
答案:
根据里氏代换原则,如果代码中存在对基类(父类)类型的引用,那么可以被其子类类型所替代。在Java异常处理中,这也适用。如果一个方法声明可以抛出ExampleA
类型的异常,那么它实际上也可以抛出ExampleA
的任何子类类型的异常。
考虑以下代码:
try {throw new ExampleB("b");
} catch (ExampleA e) {System.out.println("ExampleA");
} catch (Exception e) {System.out.println("Exception");
}
在这个例子中,ExampleB
是ExampleA
的子类,而ExampleA
又继承自Exception
。当抛出ExampleB
类型的异常时,首先会检查是否有匹配ExampleA
类型的catch
块。由于ExampleB
是ExampleA
的子类,所以这个catch
块会匹配并执行,输出"ExampleA"。因此,执行此段代码的输出是:
ExampleA
问题10:常见的 RuntimeException 有哪些?
答案:
RuntimeException
是Java中一类非常重要的异常,它表示虚拟机在运行期间可能出现的异常情况。以下是一些常见的RuntimeException
类型:
NullPointerException
:当应用程序尝试使用null
的对象引用时抛出。IndexOutOfBoundsException
:当访问数组或集合等的索引超出其范围时抛出。ArithmeticException
:在算术运算中发生错误时抛出,例如,整数除以零。ArrayStoreException
:尝试将不兼容类型的对象存储到一个对象数组中时抛出。ClassCastException
:当尝试将一个对象强制类型转换为不兼容的类时抛出。NegativeArraySizeException
:当应用程序尝试创建一个大小为负数的数组时抛出。IllegalMonitorStateException
:当线程不持有对象的锁定,但尝试释放它时抛出。IllegalThreadStateException
:当线程状态非法时抛出,例如,调用Thread.resume()
或Thread.suspend()
方法。
这些异常通常都是程序逻辑错误导致的,因此它们都是非受检异常,编译器不会强制要求开发者捕获或声明这些异常。开发者应该通过代码审查和测试来避免这些异常的发生。
问题11:Java常见异常有哪些
答案:
Java中除了RuntimeException
外,还有许多其他类型的异常,这些异常通常是受检异常,需要开发者显式地进行处理。以下是一些常见的受检异常和其他重要异常类型:
java.lang.Exception
:所有受检异常的父类。java.io.IOException
:当发生输入输出异常时抛出,如文件未找到、磁盘空间不足等。java.sql.SQLException
:当数据库操作失败时抛出。java.lang.ClassNotFoundException
:当通过类名获取Class对象失败时抛出。java.lang.IllegalArgumentException
:当方法接收到一个不合法或不适当的参数时抛出。java.lang.NoSuchFieldException
:当访问不存在的字段时抛出。java.lang.NoSuchMethodException
:当访问不存在的方法时抛出。java.lang.NumberFormatException
:当字符串不能被解析为数字时抛出,例如,尝试将字符串转换为整数时抛出。javax.xml.parsers.ParserConfigurationException
:当XML解析器配置错误时抛出。org.xml.sax.SAXException
:当XML解析过程中遇到错误时抛出。java.text.ParseException
:当日期格式解析失败时抛出。
这些异常覆盖了文件操作、数据库访问、类加载、XML处理等多个方面。开发者在编写代码时,需要根据这些异常可能发生的场景,通过try-catch
语句或在方法签名中使用throws
关键字来显式处理这些异常。正确处理这些异常对于保证程序的健壮性和可靠性至关重要。
问题12:Java异常处理最佳实践
答案:
在Java中处理异常时,遵循最佳实践是非常重要的。以下是一些关键的最佳实践,可以帮助开发者更有效地处理异常:
-
清理资源:确保在
finally
块中释放资源,或者使用Java 7引入的try-with-resources
语句自动管理资源。这可以防止资源泄露,如未关闭的文件流或数据库连接。 -
明确的异常:抛出的异常应该尽可能具体,以便调用者可以了解发生了什么错误,并据此做出适当的响应。
-
文档说明:在方法上声明抛出的异常时,应通过Javadoc进行文档说明,包括可能抛出的异常类型和条件。
-
描述性消息:在抛出异常时,应提供描述性的错误消息,这有助于调试和监控。
-
捕获最具体的异常:在编写
catch
块时,应优先捕获最具体的异常类型,这有助于精确处理异常情况。 -
避免捕获Throwable:避免捕获
Throwable
类,因为这可能会隐藏程序中的错误。 -
不忽略异常:捕获异常时,应至少记录异常信息,而不是忽略它们。
-
不包装并抛出异常:避免捕获异常、记录日志然后再次抛出相同的异常,这可能会导致重复的日志条目。
-
包装异常时保留原始异常:当将标准异常包装为自定义异常时,应将原始异常作为原因传递,以保留原始的异常堆栈。
-
不使用异常控制流程:避免使用异常来控制程序的正常流程,如使用异常来代替循环或条件语句。
-
使用标准异常:尽可能使用Java API提供的标凈异常类型,而不是定义自己的异常。
-
异常的性能影响:意识到异常处理可能对性能产生影响,因为创建和抛出异常是昂贵的操作。
遵循这些最佳实践可以帮助开发者编写更健壮、更易于维护和更高效的代码。
问题13:在 finally 块中清理资源或者使用 try-with-resource 语句
答案:
在Java中,确保资源正确关闭是异常处理中的一个重要方面。有两种主要方式可以保证资源被清理:使用finally
块或使用Java 7引入的try-with-resources
语句。
使用finally
块:
在try
块中打开的资源,如文件流或数据库连接,必须在finally
块中被关闭。这是因为finally
块无论是否发生异常都会执行,这保证了即使在发生异常的情况下资源也能被正确关闭。例如:
FileInputStream inputStream = null;
try {inputStream = new FileInputStream("file.txt");// 使用 inputStream 进行操作
} catch (IOException e) {// 异常处理
} finally {if (inputStream != null) {try {inputStream.close();} catch (IOException e) {// 异常处理}}
}
使用try-with-resources
语句:
Java 7及以上版本提供了try-with-resources
语句,它允许在try
语句中直接声明资源,这些资源会在try
块执行完毕后自动关闭,无论是否发生异常。这种方式简化了资源管理,减少了代码量,并避免了在finally
块中编写重复的关闭代码。例如:
try (FileInputStream inputStream = new FileInputStream("file.txt")) {// 使用 inputStream 进行操作
} catch (IOException e) {// 异常处理
}
在这个例子中,FileInputStream
实现了AutoCloseable
接口,当try
块执行完毕后,inputStream
的close
方法会自动被调用,从而释放资源。
使用try-with-resources
不仅可以使代码更简洁,还可以避免在finally
块中处理额外的异常,因为如果在关闭资源时抛出异常,它会被添加到原始异常的抑制异常列表中,而不会覆盖原始异常。
问题14:优先明确的异常
答案:
在Java中,抛出的异常应该尽可能明确,这样调用者才能更好地理解发生了什么错误,并据此做出适当的响应。明确的异常可以提供更多的上下文信息,帮助快速定位和解决问题。
当设计API或编写方法时,最好抛出一个具体表示错误情况的异常,而不是使用一般性的异常。例如,如果一个方法因为传入的参数不符合预期格式而失败,那么抛出一个NumberFormatException
或IllegalArgumentException
会比简单地抛出一个RuntimeException
更有意义。
此外,当捕获异常并需要向外抛出时,也应考虑是否有必要将异常包装为一个更具体的异常类型。这可以通过创建一个新的异常类型,并在其构造函数中包含原始异常作为原因(cause)来实现。例如:
public void someMethod() {try {// 可能会抛出 IOException 的操作} catch (IOException e) {throw new MyCustomException("详细错误信息", e);}
}
在这里,MyCustomException
是一个更具体的异常类型,它提供了更多关于错误情况的信息,并且保留了原始的IOException
作为原因,这样调用者可以进一步了解错误的根源。
使用明确的异常可以使代码更容易理解和维护,也有助于提高代码的健壮性和可靠性。
问题15:对异常进行文档说明
答案:
在Java中,对方法可能抛出的异常进行文档说明是非常重要的。这不仅有助于调用者理解方法的行为和预期的错误情况,还可以提高代码的可读性和可维护性。
在方法的Javadoc中使用@throws
标签来文档化可能抛出的异常是一种最佳实践。例如:
/*** 将字符串转换为整数。** @param s 要转换的字符串* @return 转换后的整数* @throws NumberFormatException 如果字符串不能被解析为整数*/
public int parseInt(String s) throws NumberFormatException {return Integer.parseInt(s);
}
在这个例子中,parseInt
方法在Javadoc中明确说明了它可能会抛出NumberFormatException
,并且描述了抛出这个异常的条件。这样,任何调用这个方法的开发者都会知道,如果传入的字符串不是有效的整数格式,就会抛出这个异常。
文档化异常还有助于在团队中共享知识,确保代码的使用者能够正确处理可能的错误情况。这也使得代码的维护变得更加容易,因为新的开发者或维护者可以快速了解方法的行为和预期的错误情况。
问题16:使用描述性消息抛出异常
答案:
在Java中抛出异常时,使用描述性的消息是非常重要的。这可以帮助开发者快速理解异常的原因和上下文,从而更快地定位和解决问题。
当创建和抛出一个异常时,应该提供一个清晰、简洁且具有描述性的错误消息。这个消息应该能够准确地描述导致异常的情况,而不需要过多的上下文信息。例如:
if (input == null) {throw new IllegalArgumentException("输入参数不能为 null");
}
在这个例子中,如果input
参数为null
,则抛出一个IllegalArgumentException
,并附带一个描述性的消息,表明输入参数不能为null
。这个消息足够清晰,可以立即告诉开发者问题所在,而无需进一步的调查。
使用描述性消息抛出异常不仅有助于调试,也有助于在生产环境中记录和监控错误。例如,当异常被记录到日志文件中时,一个清晰的消息可以立即告诉运维人员或开发者发生了什么问题,从而加快问题的响应和解决时间。
问题17:优先捕获最具体的异常
答案:
在Java中处理异常时,优先捕获最具体的异常是一种最佳实践。这样做可以确保异常被最具体和最合适的catch
块处理,从而提供更精确的错误处理逻辑。
当异常被抛出时,JVM会从最具体的异常类型开始寻找匹配的catch
块。因此,如果在catch
块中先捕获了较一般的异常类型,那么具体的异常类型将不会被处理,因为一般的异常类型已经匹配并处理了异常。例如:
try {// 可能会抛出 NumberFormatException 的操作
} catch (NumberFormatException e) {// 处理 NumberFormatException
} catch (IllegalArgumentException e) {// 处理 IllegalArgumentException
}
在这个例子中,如果操作抛出了一个NumberFormatException
,它将被第一个catch
块捕获并处理。如果抛出的是其他类型的IllegalArgumentException
,它将被第二个catch
块捕获并处理。这种顺序确保了最具体的异常类型首先被处理。
优先捕获最具体的异常不仅可以提供更精确的错误处理,还可以避免隐藏更具体的错误情况,从而提高代码的健壮性和可靠性。
问题18:不要捕获 Throwable 类
答案:
在Java中,捕获Throwable
类通常不是一个好的做法。Throwable
是所有异常和错误的超类,捕获Throwable
意味着你可能会捕获到错误(Error
)和异常(Exception
)。
错误通常表示JVM无法处理的严重问题,如OutOfMemoryError
或StackOverflowError
。捕获这些错误通常没有意义,因为它们表明应用程序的状态已经严重损坏,应用程序无法恢复。此外,捕获Throwable
还可能隐藏程序中的错误,使得调试和问题定位变得更加困难。
因此,最佳实践是只捕获那些你能够合理处理的异常类型。如果你不确定如何处理一个异常,最好让异常继续向上抛出,让调用者或更高层的处理逻辑来决定如何处理。例如:
try {// 可能会抛出多种异常的操作
} catch (SpecificException e) {// 处理 SpecificException
} catch (AnotherException e) {// 处理 AnotherException
}
在这个例子中,只有特定的异常类型被捕获和处理,而其他未被捕获的异常将继续向上抛出。这种方式可以确保异常被适当地处理,同时避免了隐藏潜在的错误。
问题19:不要忽略异常
答案:
在Java中,忽略异常通常不是一个好的做法。即使你觉得某个异常在当前上下文中不会发生,简单地忽略它可能会导致程序在将来的某个时刻出现不可预测的行为。
当一个异常被捕获时,至少应该记录异常的信息,这样当问题发生时,你有足够的信息来定位和解决问题。例如:
try {// 可能会抛出异常的操作
} catch (Exception e) {log.error("发生异常", e);
}
在这个例子中,如果发生异常,它将被catch
块捕获,并且异常的信息将被记录到日志中。这样,即使异常被忽略,你仍然可以通过日志来了解发生了什么问题。
忽略异常可能会导致问题的根源被掩盖,使得调试和问题定位变得更加困难。因此,最佳实践是至少记录异常的信息,甚至可能需要将异常包装为一个更具体的异常类型,然后重新抛出。
问题20:不要记录并抛出异常
答案:
在Java中,捕获一个异常、记录它的信息,然后再次抛出相同的异常,通常不是一个好的做法。这种方式可能会导致异常的堆栈跟踪信息丢失,从而使得调试和问题定位变得更加困难。
当一个异常被捕获后,如果需要重新抛出,最好的做法是将原始异常作为新异常的原因(cause)传递。这样,新异常将包含原始异常的所有信息,包括堆栈跟踪。例如:
try {// 可能会抛出异常的操作
} catch (Exception e) {log.error("发生异常", e)
} catch (SpecificException e) {throw new AnotherException("更详细的错误信息", e);
}
在这个例子中,如果发生了SpecificException
异常,它将被捕获并记录,然后抛出一个包含更多上下文信息的AnotherException
异常。新异常包含了原始异常的原因,这样就不会丢失任何堆栈跟踪信息。
重新抛出相同的异常不仅会导致堆栈跟踪信息的丢失,还可能导致调用者难以区分原始异常和重新抛出的异常。因此,最佳实践是使用新的、更具体的异常类型来包装原始异常,并提供额外的错误上下文信息。
问题21:包装异常时不要抛弃原始的异常
答案:
在Java异常处理中,有时需要将捕获的异常包装成另一种异常,以便提供更多的上下文信息或适应调用者的异常处理策略。在这种情况下,重要的是要保留原始异常作为新异常的原因(cause),这样才能在调试时追溯到异常的根源。
当创建一个新的异常实例时,可以将捕获的原始异常作为构造参数传递给新异常,如下所示:
try {// 一些可能会抛出IOException的操作
} catch (IOException e) {throw new CustomException("操作失败", e);
}
在这个例子中,如果发生了IOException
,它将被捕获并用于创建一个CustomException
实例,同时将原始的IOException
作为原因传递。这样,任何处理CustomException
的代码都可以通过getCause
方法访问原始的IOException
。
保留原始异常的原因对于调试和错误跟踪至关重要,因为它提供了从异常发生点到异常被处理点的完整路径。不保留原始异常的原因可能会导致丢失关键的错误信息,使得定位和解决问题变得更加困难。
问题22:不要使用异常控制程序的流程
答案:
在编程中,使用异常来控制程序的正常流程是一种不良实践。异常应该被保留用于处理真正的异常情况,即那些不经常发生且难以预测的错误情况。使用异常来控制流程可能会导致代码难以阅读和维护,同时也可能导致性能问题,因为异常处理通常比普通的流程控制结构更耗费资源。
例如,不应该使用异常来代替循环或条件语句,如下所示的代码是不推荐的:
try {while (!condition) {// 尝试执行某些操作}
} catch (Exception e) {// 处理循环结束的情况
}
在这个例子中,异常被用来控制循环的流程,这是不合适的。正确的做法是使用循环结构来控制流程,如下所示:
while (!condition) {try {// 尝试执行某些操作break; // 成功时退出循环} catch (Exception e) {// 处理异常情况}
}
在这个改进的例子中,异常只在必要时被抛出和捕获,而循环的流程控制则由循环结构本身来管理。这种方式使得代码更加清晰,并且避免了不必要的性能开销。
问题23:使用标准异常
答案:
在Java编程中,推荐使用Java API提供的标凈异常类型,而不是定义自己的异常,除非标准的异常类型不能满足特定的需求。Java API提供了丰富的异常类型,可以覆盖大多数常见的错误情况。
使用标准异常的好处包括:
- 可读性:其他开发者更容易理解标凈异常的含义,这提高了代码的可读性。
- 维护性:标准异常通常经过了良好的设计,它们的含义和用途在开发者社区中有着广泛的共识,这使得代码的维护变得更加容易。
- 一致性:在整个项目或团队中使用标准异常可以保持异常处理的一致性,这有助于减少混淆和错误。
例如,如果需要表示一个参数无效的情况,可以直接使用IllegalArgumentException
,而不是定义一个新的异常类型:
if (invalidArgument) {throw new IllegalArgumentException("参数无效");
}
如果标准的异常类型不能满足需求,比如需要提供特定的错误代码或额外的错误信息,那么可以创建自定义的异常类型。但是,即使是在这种情况下,也应该考虑继承现有的标准异常类型,而不是从头开始定义一个新的Exception
或Error
。
问题24:异常会影响性能
答案:
在Java中,异常处理是一个相对昂贵的操作,因为它涉及到异常的创建、堆栈跟踪的捕获以及异常的传播。这些操作都会消耗额外的计算资源,可能导致性能问题,特别是在对性能要求较高的应用程序中。
异常处理的性能影响主要体现在以下几个方面:
- 异常创建:创建一个异常实例需要分配内存,并填充异常信息,如消息、堆栈跟踪等,这是一个相对耗时的操作。
- 堆栈跟踪:当异常被抛出时,JVM需要捕获当前的堆栈跟踪信息,这涉及到对调用栈的遍历,也是一个耗时的操作。
- 异常传播:异常在调用栈中传播,直到被捕获,这个过程涉及到方法的退出和
catch
块的进入,可能会导致额外的上下文切换和性能开销。
因此,在性能敏感的应用程序中,应该尽量减少异常的使用,特别是在频繁执行的代码路径中。例如,可以使用预检查来避免潜在的异常情况,或者使用非异常机制来处理错误情况,如下所示:
if (object != null) {// 安全地执行操作
} else {// 处理无效对象的情况,而不是抛出异常
}
在不可避免需要使用异常的情况下,也应该确保异常的使用是合理的,并且不会对性能产生负面影响。例如,可以使用自定义的异常类型来表示特定的错误情况,同时提供更多的错误信息,而不是使用通用的异常类型。
问题25:如何合理地使用异常处理以避免性能问题
答案:
异常处理机制虽然强大,但应谨慎使用以避免对应用程序性能造成负面影响。以下是一些避免性能问题的策略:
-
避免不必要的异常处理:仅在真正异常的情况下使用异常处理。对于预期的、正常的流程控制,应使用标准的控制流结构,如循环和条件语句。
-
使用预检查和后检查:在可能的情况下,通过预先检查条件来避免异常的发生,而不是在代码执行过程中捕获异常。
-
减少异常链的深度:避免过深的异常处理链,因为每次异常捕获和处理都可能产生额外的性能开销。
-
使用finally块或try-with-resources来管理资源:确保及时释放资源,避免资源泄露,同时减少异常处理的复杂度。
-
对异常进行适当的分类和处理:不要捕获所有异常,而是捕获那些你真正需要处理的异常。这可以减少不必要的性能开销,并提高代码的可读性。
-
避免在循环中使用异常处理:循环中的异常处理可能会导致性能问题,因为循环可能会执行很多次。相反,应该在循环外部处理异常。
-
使用系统日志记录异常信息:而不是在每个异常捕获块中打印异常信息,使用系统日志可以更有效地记录异常信息,并减少性能开销。
-
考虑异常处理的性能开销:在设计API时,考虑异常处理的性能影响。如果一个方法可能会抛出多个异常,考虑是否可以通过返回特殊值或使用其他机制来避免异常。
-
使用异常处理来提高代码的健壮性:在那些不处理异常可能导致程序崩溃或数据损坏的情况下使用异常处理,即使这可能会带来一些性能开销。
通过遵循这些策略,可以在保持代码健壮性的同时,最小化异常处理对性能的影响。
问题26:在finally块中避免使用return语句
答案:
在Java中,finally
块主要用于执行清理工作,如关闭文件流或数据库连接。finally
块总是会被执行,无论是否发生异常,以及try
块或catch
块中是否有return
语句。
然而,在finally
块中使用return
语句是不推荐的做法,因为它可能会导致代码的行为变得难以预测。当finally
块中有return
语句时,它会覆盖try
块和catch
块中的任何return
语句。这可能会导致调用者收到意外的返回值,从而使得代码的调试和维护变得更加困难。
例如,考虑以下代码:
public int compute() {int result;try {result = 1;return result;} finally {result = 2;return result;}
}
在上述代码中,不管try
块中的return
语句如何,方法最终都会返回2
。这可能不是调用者所期望的行为,因为它违反了正常的程序流程控制。
因此,最佳实践是避免在finally
块中使用return
语句,以保持代码的清晰性和可预测性。如果需要在finally
块中执行清理工作,并且需要返回特定的值,可以考虑使用输出参数或状态标志来传递这些值。
问题27:捕获异常与抛异常必须是完全匹配,或者捕获异常是抛异常的父类
答案:
在Java中,当捕获异常时,捕获的异常类型必须是抛出的异常类型的完全匹配,或者是它的父类。这是因为异常处理机制要求捕获的异常类型能够准确地表示可能被抛出的异常类型。
例如,如果一个方法声明抛出了IOException
,那么在调用这个方法的代码中,捕获的异常类型必须是IOException
或其父类。如果捕获了一个不相关的异常类型,编译器将会报错。
try {// 可能会抛出IOException的操作
} catch (IOException e) {// 正确:捕获了IOException及其子类
} catch (Exception e) {// 正确:捕获了IOException(IOException是Exception的子类)
} catch (RuntimeException e) {// 错误:虽然IOException是RuntimeException的子类,但这不是最具体的匹配
}
在这个例子中,IOException
是Exception
的子类,因此可以被Exception
的catch
块捕获。但是,为了获得最具体的异常处理,最好直接捕获IOException
。
如果一个方法声明抛出了多个异常类型,那么在调用这个方法的代码中,每个catch
块应该对应于声明抛出的异常类型之一。
遵循这个规则可以确保异常处理的准确性和效率,同时避免捕获不必要的或不相关的异常类型,这可能会导致代码的混乱和错误。
问题28:在调用RPC、二方包、或动态生成类的相关方法时,捕捉异常必须使用Throwable类来进行拦截
答案:
在处理远程过程调用(RPC)、二方包(library)或动态生成类的代码时,可能会遇到各种不可预知的异常情况。这些情况可能包括方法找不到、类冲突或其他运行时错误。
为了确保这些场景中的异常能够被正确捕获和处理,可以使用Throwable
类作为catch
块的类型。由于Throwable
是所有异常和错误的超类,因此可以捕获任何类型的异常。
例如,考虑以下代码:
try {// 调用RPC方法或动态生成类的方法
} catch (Throwable t) {// 处理所有可能的异常和错误
}
在这个例子中,Throwable
作为catch
块的类型,可以捕获任何可能被抛出的异常或错误。这确保了代码的健壮性,即使在面对未知的异常情况时也能够正常运行。
然而,需要注意的是,捕获Throwable
可能会隐藏一些不应该被应用程序处理的错误,如OutOfMemoryError
。因此,在实际开发中,应该根据具体情况权衡是否使用Throwable
作为catch
块的类型。
在可能的情况下,最好还是捕获具体的异常类型,这样可以提供更精确的异常处理逻辑,并避免隐藏潜在的错误。只有在确实需要捕获所有可能的异常和错误时,才考虑使用Throwable
。
问题29:方法的返回值可以为null,不强制返回空集合,或者空对象等,必须添加注释充分说明什么情况下会返回null值
答案:
在Java编程中,方法返回null
是完全合法的,但需要谨慎处理,以避免引入潜在的NullPointerException
。为了避免调用者在不恰当地处理null
值时出现问题,应该在方法的Javadoc中明确地说明何时会返回null
。
例如,考虑一个可能返回null
的方法:
/*** 返回一个对象的列表。* 如果没有找到任何对象,则返回null。* @return 对象列表或null*/
public List<Object> getObjects() {// ... 方法实现 ...return objects; // 可能为null
}
在这个例子中,Javadoc清楚地说明了getObjects
方法在没有找到任何对象时会返回null
。这要求调用者在处理返回值之前检查是否为null
:
List<Object> objects = getObjects();
if (objects != null) {for (Object obj : objects) {// 处理每个对象}
}
通过在文档中明确指出返回null
的条件,可以提高代码的可读性和健壮性,同时减少因null
值处理不当而导致的错误。
问题30:防止NPE,是程序员的基本修养,注意NPE产生的场景
答案:
NullPointerException
(NPE)是Java中最常见的运行时异常之一,通常发生在尝试使用null
值的对象执行操作时。为了防止NPE,程序员需要养成良好的编程习惯,并在编码时注意以下几个常见的NPE场景:
-
返回类型为对象时的自动拆箱:如果一个方法返回的是包装器类型的值,并且该值为
null
,在调用时尝试自动拆箱将会导致NPE。public Integer getValue() {return null; }public void process() {int value = getValue(); // NPE发生在这里 }
-
数据库的查询结果:数据库查询可能返回
null
,需要在使用前进行检查。String name = resultSet.getString("name"); // 可能为null if (name != null) {// 使用name }
-
集合中的元素:即使集合本身不为空,其中的元素也可能为
null
。List<String> list = /* ... */; if (!list.isEmpty() && list.get(0) != null) {// 使用list的第一个元素 }
-
远程调用返回的对象:远程调用可能因各种原因失败而返回
null
。Object result = remoteCall(); if (result != null) {// 处理result }
-
Session中获取的数据:从Web应用的Session中获取的数据可能为
null
。Object data = session.getAttribute("data"); if (data != null) {// 使用data }
-
级联调用:一连串的对象调用可能导致NPE,如果链中的任何一个对象为
null
。obj.getA().getB().getC(); // 如果obj或中间对象为null,将导致NPE
为了防止这些NPE,可以在代码中添加适当的null
检查,或者使用Java 8引入的Optional
类来优雅地处理可能为null
的情况:
public Optional<Integer> getValue() {return Optional.ofNullable(null);
}public void process() {getValue().ifPresent(value -> {// 使用value});
}
Object result = remoteCall();
if (result != null) {// 处理result
}
-
Session中获取的数据:从Web应用的Session中获取的数据可能为
null
。Object data = session.getAttribute("data"); if (data != null) {// 使用data }
-
级联调用:一连串的对象调用可能导致NPE,如果链中的任何一个对象为
null
。obj.getA().getB().getC(); // 如果obj或中间对象为null,将导致NPE
为了防止这些NPE,可以在代码中添加适当的null
检查,或者使用Java 8引入的Optional
类来优雅地处理可能为null
的情况:
public Optional<Integer> getValue() {return Optional.ofNullable(null);
}public void process() {getValue().ifPresent(value -> {// 使用value});
}
通过这些方法,可以显著减少NPE的发生,提高代码的健壮性。
相关文章:

【建议收藏】30个Java异常的知识点,你能撑到第几个?
文章目录 问题1:Error 和 Exception 区别是什么?问题2:运行时异常和一般异常(受检异常)区别是什么?问题3:JVM 是如何处理异常的?问题4:throw 和 throws 的区别是什么?问题5ÿ…...

【Linux系统编程】环境基础开发工具使用
目录 1、Linux软件包管理器yum 1.1 什么是软件包 1.2 安装软件 1.3 查看软件包 1.4 卸载软件 2、Linux编辑器-vim 2.1 vim的概念 2.2 vim的基本操作 2.3 vim的配置 3、Linux编译器-gcc/g 3.1 gcc编译的过程编辑编辑编辑 3.2 详解链接 动态链接 静态链接 4…...

滚雪球学Redis[6.2讲]:Redis脚本与Lua:深入掌握Redis中的高效编程技巧
全文目录: 📝前言🚦正文🌟6.2.1 Lua脚本的优势🖋️6.2.2 EVAL命令与Lua脚本编写🐵编写Lua脚本的基本步骤🐶示例:简单的GET和SET操作🐱示例:Lua实现自增和过期…...

上市不到一月,极氪7X交付破万台!论纯电,极氪真“遥遥领先”
承认吧,在纯电这条赛道上,极氪真就“遥遥领先”~ 推出的第一款原生纯电猎装极氪001,就常年霸榜25万级豪华纯电销冠 主打豪华大车的极氪009,成为40万以上高端“保姆车”的不二之选 第一次面向主流纯电市场推出的豪华大五座——极…...

【Linux】理解文件系统与软硬链接,观察inode号理解<“软链接是包含路径的新文件“,“硬链接是关于文件名的机制“>,最终明白<什么是真正删除一个文件>
前言 大家好吖,欢迎来到 YY 滴Linux系列 ,热烈欢迎! 本章主要内容面向接触过C的老铁 主要内容含: 欢迎订阅 YY滴C专栏!更多干货持续更新!以下是传送门! YY的《C》专栏YY的《C11》专栏YY的《Lin…...

Java高并发控制之按业务对象加同步锁
一、需求 最常见的一个场景,账户余额更新! 业务场景稍复杂点,一个客户有多个虚拟余额账户,产生交易时,需要同时更新客户的多个余额账户,现在需要为余额更新做并发控制。 二、解决方案 1、依赖数据的乐观锁&…...

Python魔法函数__iter__的用法
下面是找到的一个比较好的科学解释: Python中可迭代对象(Iterable)并不是指某种具体的数据类型,它是指存储了元素的一个容器对象,且容器中的元素可以通过__iter__( )方法或__getitem__( )方法访问。 1.__iter__方法的作用是让对象可以用for…...

Redis-缓存一致性
缓存双写一致性 更新策略探讨 面试题 缓存设计要求 缓存分类: 只读缓存:(脚本批量写入,canal 等)读写缓存 同步直写:vip数据等即时数据异步缓写:允许延时(仓库,物流&a…...

SAP学习笔记 - 豆知识13 - Msg 番号 NR751 - Object RF_BELEG R100、番号範囲間隔 49 不存在 FBN1
其实这种就是自动採番的番号没弄。 比如跨年了,那该新年度的番号范围没弄啊,就会出这种错误。 把番号范围给加一下就可以了。 1,现象 比如点 VL02N 出荷传票变更 画面,点 出库确认 就会出如下错误: Object RF_BEL…...

美摄科技云服务解决方案,方案成熟,接入简单
美摄科技作为视频处理领域的先锋,凭借其强大的技术实力和深厚的行业经验,推出了成熟的云服务解决方案,为轻量化视频制作开辟了全新的道路。 一、成熟方案,接入无忧 美摄科技云服务解决方案的最大亮点在于其成熟度和易用性。我们…...

【bug】paddleocr draw_ocr_box_txt ValueError: incorrect coordinate type
【bug】paddleocr draw_ocr_box_txt ValueError: incorrect coordinate type 环境 python 3.10.15pillow 10.4.0 paddleocr 2.8.1错误详情 错误文本 Traceback (most recent call last):....draw_left.polygon(box, fillcolor)ValueError: inco…...

python的多线程和多进程
首先需要明确的是,多进程和其他语言的一样,能够利用多核cpu,但是python由于GIL的存在,多线程在执行的时候,实际上,每一时刻只有一个线程在执行。相当于是单线程。然而多线程在某些情况下,还是能…...

基于SpringBoot+Vue+uniapp的时间管理小程序的详细设计和实现(源码+lw+部署文档+讲解等)
详细视频演示 请联系我获取更详细的演示视频 项目运行截图 技术框架 后端采用SpringBoot框架 Spring Boot 是一个用于快速开发基于 Spring 框架的应用程序的开源框架。它采用约定大于配置的理念,提供了一套默认的配置,让开发者可以更专注于业务逻辑而不…...

HMAC-MD5参数签名算法
更多中电联在线工具 HMAC-MD5 是一种基于 MD5 哈希函数的消息认证码(MAC)算法。它用于确保消息的完整性和认证,通常用于数据传输和 API 请求。其基本步骤如下: 密钥准备:选择一个密钥(K)&#…...

【word】文章里的表格边框是双杠
日常小伙伴们遇到word里插入的表格,边框是双杠的,直接在边框和底纹里修改边框的样式就可以,但我今天遇到的这个有点特殊,先看看表格在word里的样式是怎么样,然后我们聊聊如何解决。 这个双杠不是边框和底纹的设置原因…...

我常用的两个单例模式写法 (继承Mono和不继承Mono的)
不继承Mono 不继承Mono代表不用挂载到场景物体上面,因此直接饿汉式 加 合并空运算符判空创建实例 >(lambda表达式)的意思是get,就是将instance赋给Instance属性 //单例private static JsonDataManager instance new JsonDataManager();public stati…...

Android 自定义Toast显示View
1、创建一个tosat显示的布局文件:toast_custom.xml <?xml version"1.0" encoding"utf-8"?> <com.hjq.shape.layout.ShapeLinearLayout xmlns:android"http://schemas.android.com/apk/res/android"android:layout_width&…...

SCRM呼叫中心高保真Axure原型 源文件分享
在数字化时代,客户关系管理(CRM)对于企业的成功至关重要。SCRM呼叫中心后台作为一款专为CRM设计的软件原型,致力于为企业提供高效、智能的客户沟通解决方案。本文将详细介绍该产品的核心功能及其对企业提升客户满意度和销售业绩的…...

Ubuntu(Linux)tcpdump使用方法详解
tcpdump命令 1.从所有网卡获取数据包 tcpdump -i any2.从指定网卡获取数据包 tcpdump -i eth03.指定网卡,IP,写文件 tcpdump -i eth0 host 192.168.16.101 -w ./tcp.dat //host 后面是发送方的地址4.指定网卡,源IP且目的IP,写入…...

Centos安装Nginx 非Docker
客户的机器属于 Centos7 系列,由于其较为陈旧,2024开始众多镜像和软件源都已失效。此篇文章将详细记录在 Centos7 操作系统上从零开始安装 Nginx 的整个流程。 本文Nginx是安装在/usr/local/nginx下 详细步骤如下: 准备Nginx安装包&#x…...

免费版的音频剪辑软件:这四款有没有你的菜?
随着音频编辑需求的日益增长,免费的音频剪辑软件逐渐成为许多创作者、学生和普通用户的心头好。今天,就让我为大家介绍几款热门的免费音频剪辑软件,并分享一下我的使用感受吧! 一、福昕音频剪辑 直通车(复制粘贴到网站…...

Facebook的隐私之战:数据保护的挑战与未来
在数字化时代,隐私保护成为了公众关注的焦点,尤其是在社交媒体巨头Facebook身上。随着用户数据泄露事件的频发,Facebook面临着日益严峻的隐私挑战。这些挑战不仅涉及法律法规的遵循,还影响着用户信任、公司声誉以及未来的发展方向…...

自定义注解和组件扫描在Spring Boot中动态注册Bean(二)
在Spring Boot中,自定义注解和组件扫描是实现动态注册Bean的两种重要手段。通过它们,开发者可以灵活地管理Spring容器中的Bean,提高开发效率和代码的可维护性。本文将详细讲解自定义注解和组件扫描在Spring Boot中如何动态注册Bean。 自定义…...

常见网络协议的介绍、使用场景及 Java 代码样例
以下是几种常见网络协议的介绍、使用场景及 Java 代码样例: 一、HTTP(HyperText Transfer Protocol,超文本传输协议) 介绍: HTTP 是用于在 Web 浏览器和 Web 服务器之间传输超文本的应用层协议。基于请求 - 响应模式…...

音视频好文总结
RTSP play同步 ffplay播放器研究分析 ffplay播放器 暂停、逐帧、音量、快进快退seek功能分析 RTSP RTP RTCP SDP基础知识 flv格式分析与解复用 TS格式详解 m3u8格式详解 FLV格式详解 MP4格式详解 HLS协议详解 RTMP协议详解 HTTP-FLV协议详解 H.264 SPS、PPS详解 H…...

云服务器磁盘满了,清理docker无用缓存、容器等清理
docker system prune 命令用于清理 Docker 系统中的各种未使用资源。根据你提供的警告信息,这条命令将会移除以下内容: 所有已停止的容器(all stopped containers) 所有未被至少一个容器使用的网络(all networks no…...

Flutter flutter_native_splash 使用指南
Flutter flutter_native_splash 使用指南 视频 https://youtu.be/dGq6LbipvXA https://www.bilibili.com/video/BV1d52tYFEzz/ 前言 原文 使用 flutter_native_splash 优化 Flutter 启动画面体验 本文详细介绍了如何在 Flutter 中使用 flutter_native_splash 插件自定义启动…...

谷歌审核放宽,恶意软件不再封号?是反垄断案影响还是开发者们的错觉
最近,谷歌因其“垄断”案而成为科技行业的焦点,这个案件可能导致谷歌业务的重大调整。同时,在Google Play上,一些开发者发现谷歌审核好像放宽了不少,这是不是与反垄断有关,谷歌应用上架或将迎来春天&#x…...

C++实现一个线程池
原文链接:C实现一个线程池 介绍 线程池是提高CPU利用率的一个非常高效的方法,线程池就是通过预先创建多个线程,当有任务时就执行,无任务时就阻塞. 相比一般的多线程方法,线程池更加简单,模块化,并且效率更高,因为不会重复创建删除线程. 预备知识 异步线程(包括f…...

为什么inet_ntoa会返回错误的IP地址?
目录 1、调用inet_addr和inet_ntoa实现整型IP与点式字符串之间的转换 1.1、调用inet_addr将点式字符串IP转换成整型IP 1.2、调用inet_ntoa将整型IP转换成点式字符串IP 2、调用inet_ntoa返回错误点式字符串IP的原因分析 3、解决多线程调用inet_ntoa返回错误点式字符串IP的办…...