Java 异常

在Java核心知识的面试中,你总能碰到关于处理Exception和Error的面试题。Exception处理是Java应用开发中一个非常重要的方面,也是编写强健而稳定的Java程序的关键,这自然使他成为了面试中的常客。关于Java中的Error和Exception的面试题目多事关于Exception和Error的概念,如何处理Exception,以及处理Exception时需要遵守的最佳实践等等。尽管关于多线程、垃圾回收、JVM概念和面向对象程序设计等方面的问题依然主宰着这类面试,你仍然需要回答"如何有效的处理错误"做准备。一些面试官也会测试程序员的调试技巧,因为快速的处理异常是另一个重要的Java编程技巧。如果一个程序员对于不常见且难于处理的ClassNotFoundException或OutOfMemoryError非常熟悉,则很有可能拥有着良好的实战经验。

什么是异常?

程序中出现的错误被称为异常。

异常可以分为两大类:编译时异常和运行时异常。

编译时异常一般是指语法错误,可以通过编译器的提示加以修正,这里我们不予讨论。

运行时异常包括:

——运行错误:如数组下标越界、除数为0等

——逻辑错误:如年龄超过200岁等


异常类体系结构图:


异常类体系结构说明

Throwable有两个子类,它们是:

——Error类:Error类的异常通常为内部错误,因此在正常情况下并不期望用户程序捕获它们。

——Exception类:绝大部分用户程序应当捕获的异常类的根类。

一些常用的异常类都直接或间接派生自Exception类,因此我们可以认为绝大部分的异常都属于Exception。


自定义异常类

当系统内置的异常类不能满足我们的要求时,我们可以自定义异常类,继承Exception即可。

/**
 * 自定义异常类
 * 继承自Exception
 * 至于要搞几个构造函数,您随便
 * @author Liao
 *
 */
public class AgeException extends Exception {

	private static final long serialVersionUID = 1L;

	public AgeException() {
		super("年龄不正确");
	}

}
测试自定义异常类:

public class TestException {

	public static void main(String[] args) {
		
		Scanner input = new Scanner(System.in);
		
		//提示用户输入年龄
		System.out.print("请输入年龄:");
		int age = input.nextInt();
		
		if (age < 0 || age >200){
			try {
				//如果年龄不在合法范围内就抛出自定义的年龄异常
				throw new AgeException();
			} catch (AgeException e) {
				e.printStackTrace();
			}
		} else {
			System.out.println("您的年龄为:" + age);
		}
	}
}


下面通过一些面试题来深入体会Java 异常。


1.Java中什么是Exception?

   这个问题经常在第一次问有关异常的时候或者是面试菜鸟的时候问。我从来没见过面高级或者资深工程师的时候有人问这玩意,但是对于菜鸟,是很愿意问这个的。 简单来说,异常是Java传达给你的系统和程序错误的方式。在java中,异常功能是通过实现比如 Throwable,Exception,RuntimeException之类的类,然还有一些处理异常时候的关键字,比如 throw,throws,try,catch,finally之类的。 所有的异常都是通过Throwable衍生出来的。Throwable把错误进 一步划分为 java.lang.Exception 和 java.lang.Error.  java.lang.Error 用来处理系统错误,例如java.lang.StackOverFlowError 或者 Java.lang.OutOfMemoryError 之类的。然后 Exception用来处理程序错误,请求的资源不可用等等。

2.Java中的检查型异常和非检查型异常有什么区别?

这又是一个非常流行的Java异常面试题,也被称作受控异常和非受控异常,会出现在各种层次的Java面试中。检查型异常和非检查型异常的主要区别在于其处理方式。检查型异常需要使用 try, catch和finally关键字在编译期进行处理,否则会出现编译器会报错。对于非检查型异常则不需要这样做。Java中所有继承自 java.lang.Exception类的异常都是检查型异常,所有继承自RuntimeException或Error的异常都被称为非检查型异常,当然RuntimeException也是Exception的子类。

3.Java中的NullPointerException和ArrayIndexOutOfBoundException之间有什么相同之处?

在Java异常面试中这并不是一个很流行的问题,但会出现在不同层次的初学者面试中,用来测试应聘者对检查型异常和非检查型异常的概念是否熟悉。顺便说一 下,该题的答案是,这两个异常都是非检查型异常,都继承自RuntimeException。该问题可能会引出另一个问题,即Java和C的数组有什么不 同之处,因为C里面的数组是没有大小限制的,绝对不会抛出ArrayIndexOutOfBoundException。

4.在Java异常处理的过程中,你遵循的那些最好的实践是什么?

这个问题在面试技术经理是非常常见的一个问题。因为异常处理在项目设计中是非常关键的,所以精通异常处理是十分必要的。异常处理有很多最佳实践,下面列举集中,它们提高你代码的健壮性和灵活性:

1)调用方法的时候返回布尔值来代替返回null,这样可以 NullPointerException。由于空指针是java异常里最恶心的异常,你可以参考一下下面的技术文章 coding best practices to minimize NullPointerException。去看看里面具体的例子。
2) catch块里别不写代码。空catch块是异常处理里的错误事件,因为它只是捕获了异常,却没有任何处理或者提示。通常你起码要打印出异常信息,当然你最好根据需求对异常信息进行处理。
3)能抛受控异常(checked Exception)就尽量不抛受非控异常(checked Exception)。通过去掉重复的异常处理代码,可以提高代码的可读性。
4) 绝对不要让你的数据库相关异常显示到客户端。由于绝大多数数据库和SQLException异常都是受控异常,在Java中,你应该在DAO层把异常信息处理,然后返回处理过的能让用户看懂并根据异常提示信息改正操作的异常信息。
5) 在Java中,一定要在数据库连接,数据库查询,流处理后,在finally块中调用close()方法。


5.既然我们可以用RuntimeException来处理错误,那么你认为为什么Java中还存在检查型异常?

这是一个有争议的问题,在回答该问题时你应当小心。虽然他们肯定愿意听到你的观点,但其实他们最感兴趣的还是有说服力的理由。我认为其中一个理由是,存在 检查型异常是一个设计上的决定,受到了诸如C++等比Java更早的编程语言设计经验的影响。绝大多数检查型异常位于java.io包内,这是合乎情理,因为在你请求了不存在的系统资源的时候,一段强壮的程序必须能够优雅的处理这种情况。通过把IOException声明为检查型异常,Java 确保了你能够优雅的对异常进行处理。另一个可能的理由是,可以使用catch或finally来确保数量受限的系统资源(比如文件描述符)在你使用后尽早 得到释放。

6.throw和throws这两个关键字在Java中有什么区别?

一个java初学者应该掌握的面试问题。 throw 和 throws乍看起来是很相似的尤其是在你还是一个java初学者的时候。尽管他们看起来相似,都是在处理异常时候使用到的。但在代码里的使用方法和用到的地方是不同的。throws 总是出现在一个函数头中,用来标明该成员函数可能抛出的各种异常, 你也可以申明未检查的异常,但这不是编译器强制的。如果方法抛出了异常那么调用这个方法的时候就需要将这个异常处理。另一个关键字  throw 是用来抛出任意异常的,按照语法你可以抛出任意 Throwable (i.e. Throwable 或任何Throwable的衍生类) , throw可以中断程序运行,因此可以用来代替return . 最常见的例子是用 throw 在一个空方法中需要return的地方抛出 UnSupportedOperationException 代码如下 :

private static void show(){
		
		throw new UnsupportedOperationException("Not yet implemented");
	}

7.什么是异常链?

“异常链”是Java中非常流行的异常处理概念,是指在进行一个异常处理时抛出了另外一个异常,由此产生了一个异常链条。该技术大多用于将“ 受检查异常” ( checked exception)封装成为“非受检查异常”(unchecked exception)或者RuntimeException。顺便说一下,如果因为因为异常你决定抛出一个新的异常,你一定要包含原有的异常,这样,处理 程序才可以通过getCause()和initCause()方法来访问异常最终的根源。
异常链的例子:

public class ChainedException {

	public static void main(String[] args) {
		
		try {
			method1();
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
	
	public static void method1() throws Exception{
		
		try {
			method2();
		} catch (Exception e) {
			throw new Exception("方法1的异常信息",e);
		}
	}
	
	public static void method2() throws Exception{
		
		throw new Exception("方法2的异常信息");
	}
}
测试结果:


说明:所谓的链式异常就是指在某些时候需要同原始异常一起抛出一个新的异常(带有附加信息)。main()方法调用method1(),然后在method1()中又调用了method2(),method2()抛出异常,被method1()中的catch捕获,在method1()的catch块中又抛出一个新异常,被main()方法的catch块捕获,所以异常信息是如上图显示的。

8.你曾今自定义实现过异常吗?怎么写的?

很显然,我们绝大多数都写过自定义或者业务异常,像AccountNotFoundException。在面试过程中询问这个Java异常问题的主要原因 是去发现你如何使用这个特性的。这可以更准确和精致的去处理异常,当然这也跟你选择checked 还是unchecked exception息息相关。通过为每一个特定的情况创建一个特定的异常,你就为调用者更好的处理异常提供了更好的选择。相比通用异常(general exception),我更倾向更为精确的异常。大量的创建自定义异常会增加项目class的个数,因此,在自定义异常和通用异常之间维持一个平衡是成功 的关键。


9.JDK7中对异常处理做了什么改变?

这是最近新出的Java异常处理的面试题。JDK7中对错误(Error)和异常(Exception)处理主要新增加了2个特性,一是在一个catch 块中可以出来多个异常,就像原来用多个catch块一样。另一个是自动化资源管理(ARM), 也称为try-with-resource块。这2个特性都可以在处理异常时减少代码量,同时提高代码的可读性。对于这些特性了解,不仅帮助开发者写出更 好的异常处理的代码,也让你在面试中显的更突出。


10.你遇到过OutOfMemoryError错误吗?你是怎么搞定的?

这个面试题会在面试高级程序员的时候用,面试官想知道你是怎么处理这个危险的OutOfMemoryError错误 的。必须承认的是,不管你做什么项目,你都会碰到这个问题。所以你要是说没遇到过,面试官肯定不会买账。要是你对这个问题不熟悉,甚至就是没碰到过,而你 又有3、4年的Java经验了,那么准备好处理这个问题吧。在回答这个问题的同时,你也可以借机向面试秀一下你处理内存泄露、调优和调试方面的牛逼技能。 我发现掌握这些技术的人都能给面试官留下深刻的印象。


11.如果执行finally代码块之前方法返回了结果,或者JVM退出了,finally块中的代码还会执行吗?

这个问题也可以换个方式问:“如果在try或者finally的代码块中调用了System.exit(0),结果会是怎样”。了解finally块是怎么 执行的,即使是try里面已经使用了return返回结果的情况,对了解Java的异常处理都非常有价值。只有在try里面是有 System.exit(0)来退出JVM的情况下finally块中的代码才不会执行。


12.Java中final、finally、finalize关键字的区别?

这是一个经典的Java面试题了。我的一个朋友为Morgan Stanley招电信方面的核心Java开发人员的时候就问过这个问题。final和finally是Java的关键字,而finalize则是方法。 final关键字在创建不可变的类的时候非常有用,只是声明这个类是final的。而finalize()方法则是垃圾回收器在回收一个对象前调用,但也 Java规范里面没有保证这个方法一定会被调用。finally关键字是唯一一个和这篇文章讨论到的异常处理相关的关键字。在你的产品代码中,在关闭连接 和资源文件的是时候都必须要用到finally块。


13.下面代码都有哪些错误?

public class ExceptionsTest {

	public static void start() throws IOException, RuntimeException {
		throw new RuntimeException("Not able to Start");
	}

	public static void main(String args[]) {
		try {
			start();
		} catch (Exception ex) {
			ex.printStackTrace();
		} catch (RuntimeException re) {
			re.printStackTrace();
		}
	}
}

这段代码会在捕捉异常代码块的RuntimeException类型变量“re”里抛出编译异常错误。因为Exception是 RuntimeException的超类,在start方法中所有的RuntimeException会被第一个捕捉异常块捕捉,这样就无法到达第二个捕 捉块,这就是抛出“exception java.lang.RuntimeException has already been caught”的编译错误原因。


14.下面代码有哪些错误?

public class SuperClass {
	public void start() throws IOException {
		throw new IOException("Not able to open file");
	}
}

public class SubClass extends SuperClass {
	public void start() throws Exception {
		throw new Exception("Not able to start");
	}
}

这段代码编译器将对子类覆盖start方法产生不满。因为每个Java中方法的覆盖是有规则的,一个覆盖的方法不能抛出的异常比原方法继承关系高。因为这 里的start方法在超类中抛出了IOException,所有在子类中的start方法只能抛出要么是IOExcepition或是其子类,但不能是其 超类,如Exception。


15.下面的Java异常代码有什么错误?

public class SuperClass {
	public static void start() {
		
		System.out.println("Java Exception interivew question Answers for Programmers");
	}

	public static void main(String args[]) {
		try {
			start();
		} catch (IOException ioe) {
			ioe.printStackTrace();
		}
	}
}

上面的Java异常例子代码中,编译器将在处理IOException时报错,因为IOException是受检查异常,而start方法并没有抛出 IOException,所以编译器将抛出“异常, java.io.IOException 不会在try语句体中抛出”,但是如果你将IOException改为Exception,编译器报错将消失,因为Exception可以用来捕捉所有运 行时异常,这样就不需要声明抛出语句。


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值