目录
Java异常处理机制
java中所有错误与异常的超类为:Throwable。其下有两个子类:Error和Exception
Error是非程序异常,即程序不能捕获的异常,一般是编译或者系统性的错误,比如虚拟机内存溢出等
Exception是程序异常类,由程序内部产生.Exception分为运行时异常(RuntimeException)非运行时异常类。
运行时异常(RuntimeException):特点是java编译器不会检查它,当程序中可能出现这类异常时,会编译通过,但是在运行时会出现错误,如空指针异常(NullPotionException),数组下标越界异常(ArrayIndexoutOfBoundsException)等。
非运行时异常:程序必须进行处理的异常,编译不通过,必须捕获或抛出,如:IOException,ClassNotFoundException等
异常处理机制中的try-catch
/**
* 异常处理机制中的try-catch
* 语法:
* try{
* 代码片段...
* }catch(XXXException e){
* 出现错误后的补救措施(B计划)
* }
*/
public class TryCatchDemo {
public static void main(String[] args) {
System.out.println("程序开始了...");
/*
try{}语句块不能单独写,后面要么跟catch语句块要么跟finally语句块
异常处理机制关注的是:明知道程序可能出现某种错误,但是该错误无法通过修改逻辑
完全规避掉时,我们会使用异常处理机制,在出现该错误是提供一种补救办法。
凡是能通过逻辑避免的错误都属于bug!就应当通过逻辑去避免!
*/
try {
// String str = null;
// String str = "";
String str = "a";
/*
若str=null的情况
当JVM执行到下面代码时:str.length()会出现空指针,此时虚拟机就会根据该情况
实例化一个对应的异常实例出来,即:空指针异常实例 NullPointerException实例
然后将程序从一开始执行到报错这句话的过程设置到该异常实例中,此时该异常通过
类型名字可以表达出现了什么错误,并将来可以通过输出错误信息来得知错误出现在那里
虚拟机会将该异常抛出
当某句代码抛出了一个异常时,JVM会做如下操作:
1:检查报错这句话是否有被异常处理机制控制(有没有try-catch)
如果有,则执行对应的catch操作,如果没有catch可以捕获该异常则视为没有
异常处理动作
2:如果没有异常处理,则异常会被抛出当当前代码所在的方法之外由调用当前方法的
代码片段处理该异常
*/
System.out.println(str.length());//抛出空指针异常
System.out.println(str.charAt(0));
System.out.println(Integer.parseInt(str));
/*
当try中某句代码报错后,就会跳出try执行下面对应的catch块,执行后就会
退出catch继续向后执行。因此try语句块中报错代码以下的内容都不会被执行
*/
System.out.println("!!!!!!!!!!!!!!");
// }catch(NullPointerException e){
// //这里实际开发中是写补救措施的,通常也会将异常信息输出便于debug
// System.out.println("出现了空指针,并解决了!");
// }catch(StringIndexOutOfBoundsException e){
// System.out.println("处理字符串下标越界问题!");
// }
/*
当try语句块中可能出现的几种不同异常对应的处理办法相同时,可以采取合并
catch的做法,用同一个catch来捕获这几种可能出现的异常,而执行措施使用
同一个。
*/
}catch(NullPointerException|StringIndexOutOfBoundsException e){
System.out.println("处理空指针或下标越界!");
/*
当catch捕获某个超类型异常时,那么try语句块中出现它类型异常时都可以被这个
catch块捕获并处理。
如果多个catch捕获的异常之间存在继承关系时,一定是子类异常在上超类异常在下
*/
}catch(Exception e){
System.out.println("出错了!");
}
System.out.println("程序结束了...");
}
}
异常机制中的finally
finally块定义在异常处理机制中的最后一块。它可以直接跟在try之后,或者最后一个catch后
finally可以保证只要程序执行到了try语句块中,无论try语句块中的代码是否出现异常,最终finally都必定执行
/**
* 通常finally块用于做释放资源这类操作,比如IO操作后的关闭流动作就非常适合在finally中进行
*/
public class FinallyDemo {
public static void main(String[] args) {
System.out.println("程序开始了...");
try{
String str = "abc";
System.out.println(str.length());
return;
}catch(Exception e){
System.out.println("出错了,并处理了");
}finally{
System.out.println("finally中的代码执行了!");
}
System.out.println("程序结束了");
}
}
import java.io.FileOutputStream;
import java.io.IOException;
/**
* 异常处理机制在IO中的实际应用
*/
public class FinallyDemo2 {
public static void main(String[] args) {
FileOutputStream fos = null;
try {
fos = new FileOutputStream("fos.dat");
fos.write(1);
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if(fos!=null) {
fos.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
自动关闭特性
JDK7之后,java提供了一个新的特性:自动关闭。旨在IO操作中可以更简洁的使用异常处理机制完成最后的close操作。
语法:
try(
定义需要在finally中调用close()方法关闭的对象.
){
IO操作
}catch(XXXException e){
...
}语法中可在try的"()"中定义的并初始化的对象必须实现了java.io.AutoCloseable接口,否则编译不通过
import java.io.FileOutputStream;
import java.io.IOException;
public class AutoCloseableDemo {
public static void main(String[] args) {
/*
该特性是编译器认可的,并非虚拟机。
*/
try(
/*只有实现了AutoCloseable接口的类才可以在这里定义!编译器最终会补充代码在
finally中调用其close关闭*/
FileOutputStream fos = new FileOutputStream("fos.dat");
){
fos.write(1);
} catch (IOException e) {
e.printStackTrace();
}
}
}
throw关键字
throw用来对外主动抛出一个异常,通常下面两种情况我们主动对外抛出异常:
1:当程序遇到一个满足语法,但是不满足业务要求时,可以抛出一个异常告知调用者
2:程序执行遇到一个异常,但是该异常不应当在当前代码片段被解决时可以抛出给调用者
/**
* 测试异常的抛出
*/
public class Person {
private int age;
public int getAge() {
return age;
}
public void setAge(int age) throws Exception {
if(age<0||age>100){
//使用throw对外抛出一个异常
throw new RuntimeException("年龄不合法!");
}
this.age = age;
}
}
public class ThrowDemo {
public static void main(String[] args) {
Person p = new Person();
p.setAge(10000);//符合语法,但是不符合业务逻辑要求。
System.out.println("此人年龄:"+p.getAge());
}
}
throws关键字
当一个方法中使用throw抛出一个非RuntimeException的异常时,就要在该方法上使用throws声明这个异常的抛出,此时调用该方法的代码就必须处理这个异常,否则编译不通过。
/**
* 测试异常的抛出
*/
public class Person {
private int age;
public int getAge() {
return age;
}
/**
* 当一个方法使用throws声明异常抛出时,调用此方法的代码片段就必须处理这个异常
*/
public void setAge(int age) throws Exception {
if(age<0||age>100){
//使用throw对外抛出一个异常
//throw new RuntimeException("年龄不合法!");
//除了RuntimeException之外,抛出什么异常就要在方法上声明throws什么异常
throw new Exception("年龄不合法!");
}
this.age = age;
}
}
当我们调用一个含有throws声明异常抛出的方法时,编译器要求我们必须处理这个异常,否则编译不通过。处理手段有两种:
使用try-catch捕获并处理这个异常
在当前方法上继续使用throws声明该异常的抛出给调用者解决。具体选取哪种取决于异常处理的责任问题。
/**
* throw关键字,用于主动对外抛出一个异常
*/
public class ThrowDemo {
public static void main(String[] args){
System.out.println("程序开始了...");
try {
Person p = new Person();
/*
当我们调用一个含有throws声明异常抛出的方法时,编译器要求
我们必须添加处理异常的手段,否则编译不通过.而处理手段有两种
1:使用try-catch捕获并处理异常
2:在当前方法上继续使用throws声明该异常的抛出
具体用哪种取决于异常处理的责任问题
*/
p.setAge(100000);//典型的符合语法,但是不符合业务逻辑要求
System.out.println("此人年龄:"+p.getAge()+"岁");
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("程序结束了...");
}
}
注意: 永远不应当在main方法上使用throws
含有throws的方法被子类重写时的规则
import java.awt.*;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.sql.SQLException;
/**
* 子类重写超类含有throws声明异常抛出的方法时对throws的几种特殊的重写规则
*/
public class ThrowsDemo {
public void dosome()throws IOException, AWTException {}
}
class SubClass extends ThrowsDemo{
// public void dosome()throws IOException, AWTException {}
//可以不再抛出任何异常
// public void dosome(){}
//可以仅抛出部分异常
// public void dosome()throws IOException {}
//可以抛出超类方法抛出异常的子类型异常
// public void dosome()throws FileNotFoundException {}
//不允许抛出额外异常(超类方法中没有的,并且没有继承关系的异常)
// public void dosome()throws SQLException {}
//不可以抛出超类方法抛出异常的超类型异常
// public void dosome()throws Exception {}
}
Java异常可以分为可检测异常,非检测异常
可检测异常:可检测异常经编译器验证,对于声明抛出异常的任何方法,编译器将强制执行处理或声明规则;不捕捉这个异常,编译器就通不过,不允许编译
非检测异常:非检测异常不遵循处理或者声明规则。在产生此类异常时,不一定非要采取任何适当操作,编译器不会检查是否已经解决了这有一个异常
RuntimeException类属于非检测异常,因为普通JVM操作引起的运行时异常随时可能发生,此类异常一般是由特定操作引发。但这些操作在java应用中会频繁出现,因此它们不受编译器检查与处理或声明规则的限制。
常见的RuntimeException子类
IIIegalArgunmentException:抛出的异常表明向方法传递了一个不合法或不正确的参数
NullPointerException:当应用程序试图在需要对象的地方使用null时,抛出该异常
ArrayIndexOutOfBoundsException:当使用的数组下标超出数组允许范围时,抛出该异常
ClassCastException:当试图将对象强制转换为不是实例的子类时,抛出该异常
NumberFormatException:当应用程序试图将字符串转换成一种数值类型,但该字符串不能转换为适当格式时,抛出该异常
异常中常用的方法
public class ExceptionApiDemo {
public static void main(String[] args) {
System.out.println("程序开始了");
try {
String str = "abc";
System.out.println(Integer.parseInt(str));
} catch (NumberFormatException e) {
//异常最常用的方法,用于将当前错误信息输出到控制台
e.printStackTrace();
//获取错误消息.记录日志的时候或提示给用户可以使用它
String message = e.getMessage();
System.out.println(message);
}
System.out.println("程序结束了");
}
}
自定义异常
自定义异常通常用来定义那些业务上的异常问题。
定义自定义异常需要注意以下问题:
异常的类名要做到见名知义
需要是Exception的子类
提供超类异常提供的所有种类构造器
/**
* 非法的年龄异常
* 自定义异常通常用来说明业务上的错误.
*/
public class IllegalAgeException extends Exception{
public IllegalAgeException() {
}
public IllegalAgeException(String message) {
super(message);
}
public IllegalAgeException(String message, Throwable cause) {
super(message, cause);
}
public IllegalAgeException(Throwable cause) {
super(cause);
}
public IllegalAgeException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
super(message, cause, enableSuppression, writableStackTrace);
}
}
/**
* 测试异常的抛出
*/
public class Person {
private int age;
public int getAge() {
return age;
}
/**
* 当一个方法使用throws声明异常抛出时,调用此方法的代码片段就必须处理这个异常
*/
public void setAge(int age) throws IllegalAgeException {
if(age<0||age>100){
//抛出自定义异常
throw new IllegalAgeException("年龄超范围:"+age);
}
this.age = age;
}
}
Java异常总结
异常处理机制是用来处理那些可能存在的异常,但是无法通过修改逻辑完全规避的场景。
而如果通过修改逻辑可以规避的异常是bug,不应当用异常处理机制在运行期间解决!应当在编码时及时修正。
try语句块用来包含可能出错的代码片段
catch用来捕获并处理对应的异常,可以定义多个,也可以合并多个异常在一个catch中
finally是异常的最后一块,只要程序执行到try中则必走,一般用于释放资源这类操作。
throw用于主动对外抛出异常。要么是满足语法不满足业务主动抛出异常,要么就是实际发生了异常但是不应当在当前代码片段被解决时抛出。具体情况要结合实际业务分析。
throws用于在方法声明时声明该异常的抛出,使得调用者必须处理该异常。
Java常见异常
java.io.NullPointerException
null 空的,不存在的
NullPointer 空指针
空指针异常,该异常出现在我们操作某个对象的属性或方法时,如果该对象是null时引发
String str = null;
str.length(); //空指针异常
上述代码中引用类型变量str的值为null,此时不能通过它调用字符串的方法或引用属性,否则就会发生空指针异常。
解决办法: 找到为什么赋值为null,确保该对象的值不能为null在操作属性或方法即可
java.lang.NumberFormatException
Number 数字
Format 格式
数字格式异常,该异常通常出现在我们使用包装类将一个字符串解析为对应的基本类型时引发。
String line = "12.12"; //小数不能转换为整数!
int i = Integer.parseInt(line); //抛出异常NumberFormatException
System.out.println(i);
上述代码中由于line的字符串内容是"12.12",而这个数字是不能通过包装类Integer解析为一个整数因此出现该异常。
注:非数字的字符在解析时也会出现该异常
解决办法:确保解析的字符串正确表达了基本类型可以保存的值
String line = "12";
int i = Integer.parseInt(line);
System.out.println(i); //12
java.lang.StringIndexOutOfBoundsException
index 索引,下标
Bounds 边界
OutOfBounds 超出了边界
字符串下标越界异常。该异常通常出现在String对应的方法中,当我们指定的下标小于0或者大于等于字符串的长度时会抛出该异常。
String str = "thinking in java";
char c = str.charAt(20);//出现异常
System.out.println(c);
解决办法:指定下标时的范围应当在>=0并且<=字符串的长度。
java.io.InvalidClassException
Invalid 无效的
Class 类
无效的类异常,该异常出现在使用java.io.ObjectInputStream在进行对象反序列化时在readObject()方法中抛出。这通常是因为反序列化的对象版本号与该对象所属类现有的版本号不一致导致的。
可以通过在类上使用常量:来固定版本号,这样序列化的对象就可以进行反序列化了。
static final long serialVersionUID = 1L;
JAVA建议我们实现Serializable接口的类主动定义序列化版本号,若不定义编译器会在编译时 根据当前类结构生成版本号,但弊端是只要这个类内容发生了改变,那么再次编译时版本号就会改变,直接的后果就是之前序列化的对象都无法再进行反序列化.
如果自行定义版本号,那么可以在改变类内容的同时不改变版本号,这样一来,反序列化以前的 对象时对象输入流会采取兼容模式,即:当前类的属性在反序列化的对象中还存在的则直接还原,不存在的就是用该属性的默认值
出现该异常的解决办法:
-
首先使用上述常量固定版本号
-
重新序列化对象(将对象通过ObjectOutputStream重新序列化并写出)
-
再进行反序列化即可
注意:之前没有定义序列化版本号时序列化后的对象都无法再反序列化回来,所以若写入了文件,可将之前的那些文件都删除,避免读取即可。
java.io.NotSerializableException
NotSerializable 不能序列化
不能序列化异常,该异常通常出现在我们使用java.io.ObjectOutputStream进行对象序列化(调用writeObject)时。原因是序列化的对象所属的类没有实现java.io.Serializable接口导致。
解决办法:将序列化的类实现该接口即可
java.io.UnsupportedEncodingException
Unsupported 不支持的
Encoding字符集
不支持的字符集异常,该异常通常出现在使用字符串形式指定字符集名字时,由于字符集名字拼写错误导致。例如
PrintWriter pw = new PrintWriter("pw.txt", "UFT-8");
上述代码中,字符集拼写成"UFT-8"就是拼写错误。
常见的字符集名字:
GBK:我国的国标编码,其中英文1个字节,中文2字节
UTF-8:unicode的传输编码,也称为万国码。其中英文1字节,中文3字节。
ISO8859-1:欧中的字符集,不支持中文。
java.io.FileNotFoundException
File 文件
NotFound 没有找到
文件没有找到异常,该异常通常出现在我们使用文件输入流读取指定路径对应的文件时出现
FileInputStream fis = new FileInputStream("f1os.dat");
上述代码如果指定的文件f1os.dat文件不在当前目录下,就会引发该异常:java.io.FileNotFoundException: f1os.dat (系统找不到指定的文件。)
注:抽象路径"f1os.dat"等同于"./f1os.dat"。因此该路径表示当前目录下应当有一个名为f1os.dat的文件。
还经常出现在文件输出流写出文件时,指定的路径无法将该文件创建出来时出现
FileOutputStream fos = new FileOutputStream("./a/fos.dat");
上述代码中,如果当前目录下没有a目录,那么就无法在该目录下自动创建文件fos.dat,此时也会引发这个异常。
其他API上出现该异常通常也是上述类似的原因导致的。
解决办法:
在读取文件时,确保指定的路径正确,且文件名拼写正确。
在写出文件时,确保指定的文件所在的目录存在。
java.net.ConnectException: Connection refused: connect
connection 连接
refused 拒绝
连接异常,连接被拒绝了.这通常是客户端在使用Socket与远端计算机建立连接时由于指定的地址或端口无效导致无法连接服务端引起的.
System.out.println("正在连接服务端...");
Socket socket = new Socket("localhost",8088);//这里可能引发异常
System.out.println("与服务端建立连接!");
解决办法:
检查客户端实例化Socket时指定的地址和端口是否正常
客户端连接前,服务端是否已经启动了
java.net.BindException: Address already in use
bind 绑定
address 地址
already 已经
绑定异常,地址已经被使用了,该异常通常是在创建ServerSocket时指定的服务端口已经被系统其他程序占用导致的.
System.out.println("正在启动服务端...");
ServerSocket serverSocket = new ServerSocket(8088);//这里可能引发异常
System.out.println("服务端启动完毕");
解决办法:
有可能是重复启动了服务端导致的,先将之前启动的服务端关闭
找到该端口被占用的程序,将其进程结束
重新指定一个新的服务端口在重新启动服务端
java.net.SocketException: Connection reset
socket 套接字
net 网络
reset 重置
套接字异常,链接重置。这个异常通常出现在Socket进行的TCP链接时,由于远端计算机异常断开(在没有调用socket.close()的之前直接结束了程序)导致的。
解决办法:
无论是客户端还是服务端当希望与另一端断开连接时,应当调用socket.close()方法,此时会进行TCP的挥手断开动作。
这个异常是无法完全避免的,因为无法保证程序在没有调用socket.close()前不被强制杀死。
java.lang.InterruptedException
interrupt 中断
中断异常.这个异常通常在一个线程调用了会产生阻塞的方法处于阻塞的过程中,此时该线程的interrupt()方法被调用.那么阻塞方法会立即抛出中断异常并停止线程的阻塞使其继续运行.
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
}
如果线程t1调用Thread.sleep(1000)处于阻塞的过程中,其他线程调用了t1线程的inerrupt()方法,那么t1调用的sleep()方法就会立即抛出中断异常InterruptedException并停止阻塞.
java.util.NoSuchElementException
such 这个
Element 元素
没有这个元素异常.该异常通常发生在使用迭代器Iterator遍历集合元素时由于没有先通过hasNext()方法判断存在下一个元素而冒然通过next()获取下一个元素时产生(当集合所有元素都经过迭代器遍历一遍后还使用next获取).
while(it.hasNext()){
String str = (String)it.next();
//这里就可能产生NoSuchException异常
System.out.println(it.next());
}
上述代码中循环遍历时,每次调用hasNext()确定存在下一个元素时,循环里面连续调用过两次next()方法,这意味着第二次调用next()方法时并没有判断是否还存在.所以在最后会出现异常.
解决办法:保证每次调用next()方法前都确定hasNext()为true才进行即可.
java.util.ConcurrentModificationException
Concurrent 并发
Modification 修改
并发修改异常.这个异常也经常出现在使用迭代器遍历集合时产生.
当我们使用一个迭代器遍历集合的过程中,通过集合的方法增删元素时,迭代器会抛出该异常.
while(it.hasNext()){
//出现ConcurrentModificationException
String str = (String)it.next();
if("#".equals(str)){
c.remove(str);//遍历过程中不要通过集合方法增或删元素
}
System.out.println(str);
}
解决办法:使用迭代器提供的remove()方法可以删除通过next()获取的元素.
while(it.hasNext()){
String str = (String)it.next();
if("#".equals(str)){
// c.remove(str);
it.remove();
}
System.out.println(str);
}
java.lang.UnsupportedOperationException
support 支持
unsupported 不支持的
operation 操作
不支持的操作异常.该异常出现在很多的API中.
例如:常出现在我们对数组转换的集合进行增删元素操作时抛出.
String[] array = {"one","two","three","four","five"};
System.out.println("array:"+ Arrays.toString(array));
List<String> list = Arrays.asList(array);//将数组转换为一个List集合
System.out.println("list:"+list);list.set(0,"six");
System.out.println("list:"+list);
//对该集合的操作就是对原数组的操作
System.out.println("array:"+ Arrays.toString(array));//由于数组是定长的,因此任何会改变数组长度的操作都是不支持的!
list.add("seven");//UnsupportedOperationException
java.lang.IllegalArgumentException: wrong number of arguments
Illegal 非法的
Argument 参数
wrong number of arguments 参数的数量有误
非法的参数异常.该异常出现在反射API中
例如:当我们用Method的invode方法反射调用一个有参方法,而指定的实参个数不符合时出现
Person p = new Person();
p.say("Hello!",5);//实例化
Class cls = Class.forName("reflect.Person");
Object o = cls.newInstance();
//调用方法
//获取say(String,int)方法
Method m = cls.getMethod("say",String.class,int.class);
//m.invoke(o,"嘿嘿");//反射调用时值传递了一个String参数,此时会抛出异常
//正确写法
m.invoke(o,"嘿嘿",1);//需要传递第二个参数,该参数为一个int值。
java.lang.ClassNotFoundException: xxxx(包名).XXXXX(类名)
ClassNotFound 类没有找到
类没有找到异常.该异常出现在反射API中
例如:当我们使用一个类的完全限定名使用Class.forName()加载这个类的类对象时,如果指定的完全限定名拼写有误会出现该异常
//加载reflect包中的类Person
//Class cls = Class.forName("reflect.Person1");//完全限定名拼写错误会时出现异常
//正确写法
Class cls = Class.forName("reflect.Person");
java.lang.NoSuchMethodException
NoSuchMethod 没有这个方法
没有这个方法异常,该异常通常出现在反射API中
例如:当我们通过类对象Class获取其表示的类中某个指定的方法时,如果指定的方法名错误或参数列表错误时会导致该异常的抛出:
Person类定义:
package reflect;
public class Person {
public void say(String info){
System.out.println(name+":"+info);
}
public void watchTV(){
System.out.println(name+"看电视");
}
}
反射操作代码片段:
//实例化
Class cls = Class.forName("reflect.Person");
//调用方法
//Method m = cls.getMethod("says",String.class);//没有says(String)这个方法,导致报错
//Method m = cls.getMethod("say",String.class,int.class);//参数列表错误,导致报错
Method m = cls.getMethod("say",String.class);//正确