异常

Java的异常分为两种,一种是运行时异常(RuntimeException),一种是非运行异常也叫检查式异常(CheckedException)。

1、运行时异常不需要程序员去处理,当异常出现时,JVM会帮助处理。常见的运行时异常有:

ClassCastException(类转换异常)

ClassNotFoundException

IndexOutOfBoundsException(数组越界异常)

NullPointerException(空指针异常)

ArrayStoreException(数组存储异常,即数组存储类型不一致)

还有IO操作的BufferOverflowException异常

2、非运行异常需要程序员手动去捕获或者抛出异常进行显示的处理,因为Java认为Checked异常都是可以被修复的异常。常见的异常有:

IOException

SqlException

 

 

三、异常的捕获和处理 

Java异常处理的五个关键字:try、catch、finally、throw、throws

不能直接在main方法里把Exception抛出去交给JAVA运行时系统出力就完事了,这是一种不负责任的表现。如果想把程序写得特别健壮,使用try……catch去捕获异常并处理掉捕获后的异常是必不可少的做法。

三、try...cath...finally语句

3.1. try语句

3.2. catch语句 

 

3.3. finally语句 

四、声明并抛出异常 

五.新特性

例如try-with-resources (编译时自动生成处理逻辑去关闭那些拓展了AutoCloseable和Closeable的对象)和 multiple catch等方便了我们 对异常的处理

import java.io.*;

public class Main2 {
    public static void main(String[] args) {
        File file = new File("d:\\test.txt");
        try (BufferedReader br = new BufferedReader(new FileReader(file))) {// Try-with-resources
            String s =br.readLine();
            if(s==null) {
                throw new BlankTextException("空白!");
            }

        } catch(IOException | BlankTextException e ){// Multiple catch
              System.out.print(e.getMessage());
            }
        }
    }

六、使用自定义异常

package com.cxy.test;
/**
 * 自定义的一个异常类MyException,继承Exception类
 */

public class MyException extends Exception {
	private int id;
	
	public MyException(String message,int id) {
		super(message);//调用父类Exception的构造方法
		this.id = id;
	}
		//获取异常代码
		public int getId() {
			return id;
		}
	}

package com.cxy.test;

import java.text.MessageFormat;

public class TestMyException {
	//throws MyException,抛出我们自定义的MyException类的异常。
	public void regist(int num) throws MyException{
		if(num<0) {
			//使用throw手动抛出一个MyException类的异常对象。
			throw new MyException("人数为负,不合理",1);
		}
		  /**
		   * 注意:假如我们抛出了异常之后,
	       * System.out.println(MessageFormat.format("登记人数:{0}",num));是不会被执行的。
	       * 抛出异常之后整个方法的调用就结束了。
	       */
		System.out.println(MessageFormat.format("登记人数:{0}",num));
	}
	public void manage() {
		try {
			regist(-100);
		} catch (MyException e) {
			// TODO Auto-generated catch block
			 System.out.println("登记失败,错误码:"+e.getId());

			e.printStackTrace();
			
		}
		System.out.println("操作结束");
	}

	public static void main(String[] args) {
		TestMyException t = new TestMyException();
		t.manage();
	}

}

 

六、异常处理总结 

养成良好的编程习惯,不要把错误给吞噬掉(即捕获到异常以后又不做出相应处理的做法,这种做法相当于是把错误隐藏起来了,可实际上错误依然还是存在的), 也不要轻易地往外抛错误,能处理的一定要处理,不能处理的一定要往外抛。往外抛的方法有两种,一种是在知道异常的类型以后,方法声明时使用throws把 异常往外抛,另一种是手动往外抛,使用“throw+异常对象”你相当于是把这个异常对象抛出去了,然后在方法的声明写上要抛的那种异常。

NoClassDefoundError 和 ClassNotFoundException

NoClassDeFoundError


ClassNotFoundException


一个继承自Exception,另一个继承自Error。
 

NoClassDeFoundError


根据上面的继承结构图我们可以发现NoClassDeFoundError继承自LinkageError,根据名字我们就可以猜到这是一个类加载过程中连接时错误。

在Linking的解析阶段:解析阶段是JVM将常量池内的符号引用替换为直接引用的过程
 

符号引用:就是用一组符号来描述所引用的目标,符号引用可以是任何形式的字面量,只要使用时能无歧义的定位到目标即可。

直接引用: 就是直接指向目标的指针

 

public static void main(String[] args) {
      new ArrayList();
  }

比如里面的new ArrayList(),在类生命周期的解析阶段之前,还只是符号这就是符号引用。 在解析过程中,JVM会去找ArrayList是否被加载,如果未加载会先加载ArrayList,然后返回ArrayList的引用。这就是直接引用

所以,如果在连接阶段的解析时,找不到相应的类,就会报NoClassDeFoundError
 

package errorTest;

public class Main {
    public static void main(String[] args) {
        ABC abc=new ABC();
    }
}
class ABC{

}
class abc extends ABC{

}

由于Windows中,文件名不区分大小写,所以下面这个例子中,在编译的时候,先编译父类ABC.class,再编译子类abc,class,并直接覆盖ABC.class的内容

 解析 阶段的时候,就找不到ABC这个类啦~。所以就报 NoClassDeFoundError 

ClassNotFoundException

这个这个错误其实是经常遇到的,Class.forName()操作,程序运行的时候通过类的全限定名去加载时找不到这个类,就会报异常了,所以这类操作需要手动抛出异常或者try/catch(虽然编辑器一般会进行提示)

public class Main2 {
    public static void main(String[] args) {
        try {
            Class.forName("com.cxy.Test");
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}

 

  • NoClassDeFoundError 继承自Error属于用户程序无法捕获处理的异常
  • ClassNotFoundException 继承自Exception属于用户程序能捕获处理的异常
  • NoClassDeFoundError 发生在类生命周期中解析阶段找不到相应的类
  • ClassNotFoundException 发生在类生命周期的加载阶段,找不到相应的类。
     

第一,尽量不要捕获类似 Exception 这样的通用异常,而是应该捕获特定异常,或者是把我们自定义的 RuntimeException 被扩散出来,而不是被捕获。

进一步讲,除非深思熟虑了,否则不要捕获 Throwable 或者 Error,这样很难保证我们能够正确程序处理 OutOfMemoryError。

第二,不要生吞(swallow)异常。这是异常处理中要特别注意的事情,因为很可能会导致非常难以诊断的诡异情况。

生吞异常,往往是基于假设这段代码可能不会发生,或者感觉忽略异常是无所谓的,但是千万不要在产品代码做这种假设!

如果我们不把异常抛出来,或者也没有输出到日志(Logger)之类,程序可能在后续代码以不可控的方式结束。没人能够轻易判断究竟是哪里抛出了异常,以及是什么原因产生了异常。

第三,生产环境拒绝e.printStackTrace();

官方文档的开头就是“Prints this throwable and its backtrace to the standard error stream”。

问题就在这里,在稍微复杂一点的生产系统中,标准出错(STERR)不是个合适的输出选项,因为你很难判断出到底输出到哪里去了。

尤其是对于分布式系统,如果发生异常,但是无法找到堆栈轨迹(stacktrace),这纯属是为诊断设置障碍。所以,最好使用产品日志,详细地输出到日志系统里。

第四:Throw early, catch late

public void readPreferences(String fileName){
	 //...perform operations... 
	InputStream in = new FileInputStream(fileName);
	 //...read the preferences file...
}

 比如这个方法,可能第一步filaName 就是 null,那么程序就会抛出 NullPointerException,但是由于没有第一时间暴露出问题,堆栈信息可能非常令人费解,往往需要相对复杂的定位。在发现问题的时候,第一时间抛出,能够更加清晰地反映问题。

public void readPreferences(String filename) {
	Objects. requireNonNull(filename);
	//...perform other operations... 
	InputStream in = new FileInputStream(filename);
	 //...read the preferences file...
}

像上面这样,或者仿照写一个Asser方法,将异常情况throw early

至于“catch late”,捕获异常后,需要怎么处理呢?最差的处理方式,就是“生吞异常”,本质上其实是掩盖问题。如果实在不知道如何处理,可以选择保留原有异常的 cause 信息,直接再抛出或者构建新的异常抛出去。在更高层面,因为有了清晰的(业务)逻辑,往往会更清楚合适的处理方式是什么。

有的时候,我们会根据需要自定义异常,这个时候除了保证提供足够的信息,还有两点需要考虑:

  • 是否需要定义成 Checked Exception,因为这种类型设计的初衷更是为了从异常情况恢复,作为异常设计者,我们往往有充足信息进行分类。
  • 在保证诊断信息足够的同时,也要考虑避免包含敏感信息,因为那样可能导致潜在的安全问题。比如登陆的校验,避免输出用户数据等信息,避免坏人的攻击。参考 java.net.ConnectException,出错信息是类似“ Connection refused (Connection refused)”,而不包含具体的机器名、IP、端口等,一个重要考量就是信息安全。

业界有一种争论(甚至可以算是某种程度的共识),Java 语言的 Checked Exception 也许是个设计错误:

  • Checked Exception 的假设是我们捕获了异常,然后恢复程序。但是,其实我们大多数情况下,根本就不可能恢复。Checked Exception 的使用,已经大大偏离了最初的设计目的。

  • Checked Exception 不兼容 functional 编程,如果你写过 Lambda/Stream 代码,相信深有体会。

性能角度

 Java 的异常处理机制,这里有两个可能会相对昂贵的地方:

  • try-catch 代码段会产生额外的性能开销,或者换个角度说,它往往会影响 JVM 对代码进行优化,所以建议仅捕获有必要的代码段,尽量不要一个大的 try 包住整段的代码;与此同时,利用异常控制代码流程,也不是一个好主意,远比我们通常意义上的条件语句(if/else、switch)要低效。

  • Java 每实例化一个 Exception,都会对当时的栈进行快照,这是一个相对比较重的操作。如果发生的非常频繁,这个开销可就不能被忽略了。

当我们的服务出现反应变慢、吞吐量下降的时候,检查发生最频繁的 Exception 也是一种思路。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值