Java面试题(三)异常系列

1.java异常有哪几种,有哪些特点

java中异常主要分为 Error 和 Exception。
Exception 分为被检查的异常(checked exception)和运行时(非检查)的异常(runtime exception,即不受检查的异常);
Error 表示系统错误,通常不能预期和恢复(譬如 JVM 崩溃、内存不足等);

  • 被检查的异常(Checked exception)在程序中能预期且要尝试修复(如我们必须捕获 FileNotFoundException 异常并为用户提供有用信息和合适日志来进行调试,Exception 是所有被检查的异常的父类);
  • 运行时异常(Runtime Exception)又称为不受检查异常,譬如我们检索数组元素之前必须确认数组的长度,否则就可能会抛出 ArrayIndexOutOfBoundException 运行时异常,RuntimeException 是所有运行时异常的父类。

2.throw 与 throws的区别

throw:

  • throw 语句用在方法体内,表示抛出异常,由方法体内的语句处理。
  • throw是具体向外抛出异常的动作,所以它抛出的是一个异常实例,执行throw一定是抛出了某种异常。

throws:

  • throws语句是用在方法声明后面,表示如果抛出异常,由该方法的调用者来进行异常的处理。
  • throws主要是声明这个方法会抛出某种类型的异常,让它的使用者要知道需要捕获的异常的类型。
  • throws表示出现异常的一种可能性,并不一定会发生这种异常。

3.final, finally, finalize的区别

  • final 用于声明属性,方法和类,分别表示属性不可变,方法不可覆盖,类不可继承。
    内部类要访问局部变量,局部变量必须定义成final类型,例如,一段代码……

  • finally是异常处理语句结构的一部分,表示总是执行。

  • finalize是Object类的一个方法,在垃圾收集器执行的时候会调用被回收对象的此方法,可以覆盖此方法提供垃圾收集时的其他资源回收,例如关闭文件等。JVM不保证此方法总被调用

4.列出5个常见运行时异常

  • NullPointerException 空指针异常
  • ClassCastException 类型转换异常
  • ClassNotFoundException 找不到指定类异常
  • NumberFormatException 字符串转换数字异常
  • NoSuchMethodException 方法不存在异常

5.try-catch-finally执行顺序

  • 不管是否有异常产生,finally块中代码都会执行,除非遇到System.exit(0)
  • 当try和catch中有return语句时,finally块仍然会执行
  • finally是在return后面的表达式运算执行的,所以函数返回值在finally执行前确定的,无论finally中的代码怎么样,返回的值都不会改变,仍然是之前return语句中保存的值
  • finally中最好不要包含return,否则程序会提前退出,返回值不是try或catch中保存的返回值
  • finally 块里 return 语句会把 try 块或者 catch 块里的 return 语句效果给覆盖掉且吞掉了异常

6.ClassNotFoundException和NoClassDefFoundError的区别

首先NoClassDefFoundError是一个错误(Error),而ClassNOtFoundException是一个异常,

ClassNotFoundException的产生原因:
Java支持使用Class.forName方法来动态地加载类,任意一个类的类名如果被作为参数传递给这个方法都将导致该类被加载到JVM内存中,如果这个类在类路径中没有被找到,那么此时就会在运行时抛出ClassNotFoundException异常。
另外还有一个导致ClassNotFoundException的原因就是:当一个类已经某个类加载器加载到内存中了,此时另一个类加载器又尝试着动态地从同一个包中加载这个类。

NoClassDefFoundError产生的原因:
如果JVM或者ClassLoader实例尝试加载(可以通过正常的方法调用,也可能是使用new来创建新的对象)类的时候却找不到类的定义。要查找的类在编译的时候是存在的,运行的时候却找不到了。这个错误往往是你使用new操作符来创建一个新的对象但却找不到该对象对应的类。这个时候就会导致NoClassDefFoundError。

7.下面代码的输出结果

1. public int getNum() {
2.     try {
3.         int a = 1 / 0;
4.         return 1;
5.     } catch (Exception e) {
6.         return 2;
7.     } finally {
8.         return 3;
9.     }
10.}

代码走到第3行的时候遇到了一个MathException,这时第4行的代码就不会执行了,代码直接跳转到catch语句中,走到第 6 行的时候,异常机制有一个原则:如果在catch中遇到了return或者异常等能使该函数终止的话那么有finally就必须先执行完finally代码块里面的代码然后再返回值。因此代码又跳到第8行,可惜第8行是一个return语句,那么这个时候方法就结束了,因此第6行的返回结果就无法被真正返回。如果finally仅仅是处理了一个释放资源的操作,那么该道题最终返回的结果就是2。因此上面返回值是3。

8.下面代码的输出结果

public class smallT {
    public static void main(String[] args) {
        smallT t = new smallT();
        int b = t.get();
        System.out.println(b);
    }

    public int get() {
        try {
            return 1;
        }
        finally {
            return 2;
        }
    }
}

返回结果是2,因为finally 中的return会覆盖 try 中的return

9.下面3个方法分别返回什么

    public static int test1(){
        int ret = 0;
        try{
            return ret;
        }finally{
            ret = 2;
        }
    }
    public static int test2(){
        int ret = 0;
        try{
            int a = 5/0;
            return ret;
        }finally{
            return 2;
        }
    }
    public static void test3(){
        try{
            int a = 5/0;
        }finally{
            throw new RuntimeException("hello");
        }
    }
  • test1 方法运行返回 0,因为执行到 try 的 return ret; 语句前会先将返回值 ret 保存在一个临时变量中,然后才执行 finally 语句,最后 try 再返回那个临时变量,finally 中对 ret 的修改不会被返回。

  • test2 方法运行返回 2,因为 5/0 会触发 ArithmeticException 异常,但是 finally 中有 return 语句,finally 中 return 不仅会覆盖 try 和 catch 内的返回值且还会掩盖 try 和 catch 内的异常,就像异常没有发生一样(特别注意,当 finally 中没有 return 时该方法运行会抛出 ArithmeticException 异常),所以这个方法就会返回 2,而且不再向上传递异常了。

  • test3 方法运行抛出 hello 异常,因为如果 finally 中抛出了异常,则原异常就会被掩盖。

10.下面代码分别打印什么内容

try
{
    System.out.println("try");
    return;
}
catch(Exception ex)
{
    System.out.println("异常发生了");
}
finally
{
    System.out.println("finally");
}
System.out.println("异常处理后续的代码");

打印结果是
try
finally
try块中存在return语句,那么首先也需要将finally块中的代码执行完毕,再执行return语句,而且之后的其他代码也不会再执行了

try
{
    System.out.println("try");
    System.exit(0);
}
catch(Exception ex)
{
    System.out.println("异常发生了");
}
finally
{
    System.out.println("finally");
}
System.out.println("异常处理后续的代码");

打印结果是
try
先执行try块中的System.exit(0)语句,已经退出了程序,所以不会执行finally块的代码

11.写一个堆内存溢出异常

    public static void main(String[] args) {
        List<HeapOOM> list = new LinkedList<HeapOOM>();
        while(true){
            list.add(new HeapOOM());
        }
    }

死循环new对象,堆内存溢出异常
在这里插入图片描述

12.写一个栈内存溢出异常

public class StackOverFlow {
    private int i;

    public void plus() {
        i++;
        plus();
    }

    public static void main(String[] args) {
        StackOverFlow stackOverFlow = new StackOverFlow();
        try {
            stackOverFlow.plus();
        } catch (Error e) {
            System.out.println("Error:stack length:" + stackOverFlow.i);
            e.printStackTrace();
        }
    }
}

13.写一个运行时常量池溢出异常

public static void main(String[] args) {
    // 使用List保持着常量池引用,避免Full GC回收常量池行为
    List<String> list = new ArrayList<String>();
    
    int i = 0;
    while (true) {
        list.add(String.valueOf(i++).intern());
    }
}

14.写一个方法区内存溢出异常

public class JavaMethodAreaOOM {
    public static void main(String[] args) {
        while (true) {
            Enhancer enhancer = new Enhancer();
            enhancer.setSuperclass(OOMObject.class);
            enhancer.setUseCache(false);
            enhancer.setCallback(new MethodInterceptor() {
                public Object intercept(Object obj, Method method,
                        Object[] args, MethodProxy proxy) throws Throwable {
                    return proxy.invokeSuper(obj, args);
                }
            });
            enhancer.create();
        }
    }
    static class OOMObject {
    }
}

15.下面的代码有什么问题

class ExceptionTest{

    private static String str = "11,22,33,fg,44,55,66";

    public static void test1() throws NumberFormatException {
        String[] split = str.split(",");
        for (String s : split) {
            System.out.println(Long.parseLong(s));
        }
    }

    public static void test2() throws SQLException {
        String[] split = str.split(",");
        for (String s : split) {
            System.out.println(Long.parseLong(s));
        }
    }

    public static void main(String[] args) {
        ExceptionTest.test1();
        ExceptionTest.test2();
    }
}

NumberFormatException继承的是RuntimeException,属于非检查异常,运行时异常
SQLException继承的是Exception,属于检查型异常,所有必须处理
所以上面的代码编译就会出错,因为test2方法没有处理异常,而test1在运行时会抛出数字类型转换错误

16.下面代码有什么问题

public static void main(String[] args){
       int a = 3;
       int b = 0;
       int c = a / b;          
       System.out.println(c);
}

编译通过,执行时结果:

Exception in thread “main” java.lang.ArithmeticException: / by zero

at com.learnjava.exception.ExceptionTest.main(ExceptionTest.java:9)

因为除数为0,所以引发了算数异常。

17.关于异常,有什么经验体会

  • 方法返回值尽量不要使用 null(特殊场景除外),这样可以避免很多 NullPointerException 异常。

  • catch 住了如果真的没必要处理则至少加行打印,这样可在将来方便排查问题。

  • 接口方法抛出的异常尽量保证是运行时异常类型,除非迫不得已才抛出检查类型异常。

  • 避免在 finally 中使用 return 语句或者抛出异常,如果调用的其他代码可能抛出异常则应该捕获异常并进行处理,因为 finally 中 return 不仅会覆盖 try 和 catch 内的返回值且还会掩盖 try 和 catch 内的异常,就像异常没有发生一样(特别注意,当 try-finally 中没有 return 时该方法运行会继续抛出异常)。

  • 尽量不要在 catch 块中压制异常(即什么也不处理直接 return),因为这样以后无论抛出什么异常都会被忽略,以至没有留下任何问题线索,如果在这一层不知道如何处理异常最好将异常重新抛出由上层决定如何处理异常。

  • 方法定义中 throws 后面尽量定义具体的异常列表,不要直接 throws Exception。

  • 捕获异常时尽量捕获具体的异常类型而不要直接捕获其父类,这样容易造成混乱。

  • 避免在 finally 块中抛出异常,不然第一个异常的调用栈会丢失。

  • 不要使用异常控制程序的流程,譬如本应该使用 if 语句进行条件判断的情况下却使用异常处理是非常不好的习惯,会严重影响性能。

  • 不要直接捕获 Throwable 类,因为 Error 是 Throwable 类的子类,当应用抛出 Errors 的时候一般都是不可恢复的情况。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

珍妮玛•黛金

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值