Throwable异常
异常处理
概述
异常: 就是指程序出现了不正常的情况
用来封装错误信息的对象。
组成结构:类型,提示,行号。
异常的继承结构
查看Java的API文档可知, Java中所有错误和异常的顶级父类是Throwable类
Throwable类下有两个子类, 分别是Error和Exception, 两者的区别是:
-
Error: 是指不需要捕获的严重问题, 通常是java程序以外的问题, 比如硬件问题或者内存不足导致的问题等
因此, 如果java程序中出现了Error, 我们无需处理。 -
Exception: 称为异常类, 它表示程序本身可以处理的问题
Exception下有很多异常子类, 其中有一个异常子类是RuntimeException类, 这里还可以将异常分为两大类:
-
编译时异常:
其他异常类以及不是RuntimeException子类的异常类都是检查异常(也叫编译时异常)
在编写完程序后, Java编译器会对其进行检查, 如果检查出此类异常, 就必须要显式处理, 否则程序将无法进行编译。
例如:ClassNotFoundException、
FileNotFoundException、
SQLException等都是编译时异常。 -
运行时异常:
RuntimeException以及子类被称为未经检查的异常(也叫运行时异常)
这类异常通常在编写完程序后没有问题, 但是运行程序才出现异常, 需要我们回来修改代码进行解决的异常
这类异常无需显式处理, 当然也可以像编译时异常一样处理
例如:IndexOutOfBoundsException、
ArithmeticException、
NullPointerException、
ClassCastException 等都是运行时异常。
判断一个异常是不是运行时异常, 可以通过检查这个异常类是不是RuntimeException的子类, 或者检查这个异常是否只有在程序运行时才会出现!
虚拟机的默认处理方式
如果程序在运行时出现了问题,而我们又没有处理该问题,最终虚拟机会做默认的处理,而这种默认处理方式为:
-
将异常的名称(类型)、异常的原因以及异常出现的位置等信息输出在了控制台(Console窗口)
-
将程序停止运行(这意味着,出现异常的代码后面的代码将不会再执行)
异常示例-1:
1 | package exception; |
运行结果:
1 | Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: 6 |
如果程序出现了异常, 需要我们自己处理, 有两种方案:
- 使用try…catch…进行处理(捕获异常)
- 使用throws进行处理(抛出异常)
异常处理之try…catch…处理
try…catch处理异常的格式为:
1 | try{ |
执行流程为:
-
程序执行到try{}中的代码时, 如果出现了异常, 将会自动产生一个异常对象, 该异常对象将会被提交给java运行时系统;
-
当Java运行时系统接收到异常对象时, 回到catch()中寻找匹配的异常类型, 找到后就进入catch{}中进行异常的处理;
-
执行完毕后, 程序还可以继续执行try…catch之后的代码
代码案例1
1 | package exception; |
finally块
作用就是确保一定要执行某些代码
代码案例
1 | package exception; |
代码案例2
1 | package exception; |
代码案例3
1 | package exception; |
throw关键字
当程序发生错误而无法处理的时候,会抛出对应的异常对象,除此之外,在某些时刻,您可能会想要自行抛出异常,例如字异常处理结束后,再将异常抛出,让下一层异常处理块来捕捉,若想要自行抛出异常,您可以使用"throw"关键字,并生成执行的异常对象后抛出.
例如:throw new ArithmeticException();
代码案例
Person
1 | package exception; |
异常处理之throws处理
程序中会声明许多的方法,这些方法中可能会因某些错误而引发异常,但是不希望直接在这个方法中处理这些异常,而希望调用这个它的方法来统一处理,这时候可以使用throws关键字来声明这个方法将会抛出的异常
throws 处理异常的格式为:
1 | ...方法名() throws 异常类名 { |
代码案例
Person
1 | package exception; |
ThrowDemo
1 | package exception; |
throws的重写规则
代码案例
1 | package exception; |
异常常用方法
代码案例
1 | package exception; |
自定义异常
代码案例
IllegalAgeException
1 | package exception; |
Person
1 | package exception; |
ThrowDemo
1 | package exception; |
总结: 什么时候需要try…catch异常, 什么时候需要throws异常?
-
如果这个异常是方法内部的代码造成的异常, 而不是因为调用者的传参导致的异常(也就是说这个异常和调用者没有关系), 通常需要我们try…catch异常
-
如果这个异常是调用者的传参导致的异常, 则将异常throws抛出(就是将异常抛给调用者)
-
不要在main方法上throws抛出异常, 因为这样会将异常抛给虚拟机, 而虚拟机是不会帮我们处理异常的!(虚拟机会按照默认方式处理:输出异常信息以及终止程序执行!)
扩充内容–JAVA常见异常
Java.io.NullPointerException
- null 空的,不存在的
- NullPointer 空指针
空指针异常,该异常出现在我们操作某个对象的属性或方法时,如果该对象是null时引发。
1 | String str = null; |
上述代码中引用类型变量str的值为null,此时不能通过它调用字符串的方法或引用属性,否则就会引发空指针异常。
解决办法:
找到为什么赋值为null,确保该对象的值不能为null再操作属性或方法即可。
java.lang.NumberFormatException: For input string: “xxxxx”
- Number 数字
- Format 格式
数字格式异常,该异常通常出现在我们使用包装类将一个字符串解析为对应的基本类型时引发。
1 | String line = "123.123";//小数不能转换为整数! |
上述代码中由于line的字符串内容是"123.123".而这个数字是不能通过包装类Integer解析为一个整数因此出现该异常。注:非数字的字符出在解析时也会出现该异常。
解决办法:
确保解析的字符串正确表达了基本类型可以保存的值
1 | String line = "123"; |
java.lang.StringIndexOutOfBoundsException
- index 索引,下标
- Bounds 边界
- OutOfBounds 超出了边界
字符串下标越界异常。该异常通常出现在String对应的方法中,当我们指定的下标小于0或者大于等于字符串的长度时会抛出该异常。
1 | String str = "thinking in java"; |
解决办法:
指定下标时的范围应当在>=0并且<=字符串的长度。
java.io.InvalidClassException
- Invalid 无效的
- Class 类
无效的类异常,该异常出现在使用java.io.ObjectInputStream在进行对象反序列化时在readObject()方法中抛出。这通常是因为反序列化的对象版本号与该对象所属类现有的版本号不一致导致的。
可以通过在类上使用常量:
1 | static final long serialVersionUID = 1L; |
来固定版本号,这样序列化的对象就可以进行反序列化了。
JAVA建议我们实现Serializable接口的类主动定义序列化版本号,若不定义编译器会在编译时 根据当前类结构生成版本号,但弊端是只要这个类内容发生了改变,那么再次编译时版本号就会改变,直接的后果就是之前序列化的对象都无法再进行反序列化.
如果自行定义版本号,那么可以在改变类内容的同时不改变版本号,这样一来,反序列化以前的 对象时对象输入流会采取兼容模式,即:当前类的属性在反序列化的对象中还存在的则直接还原,不存在的就是用该属性的默认值
出现该异常的解决办法:
- 首先使用上述常量固定版本号
- 重新序列化对象(将对象通过ObjectOutputStream重新序列化并写出)
- 再进行反序列化即可
需要注意,之前没有定义序列化版本号时序列化后的对象都无法再反序列化回来,所以若写入了文件,可将之前的那些文件都删除,避免读取即可。
java.io.NotSerializableException
- NotSerializable 不能序列化
不能序列化异常,该异常通常出现在我们使用java.io.ObjectOutputStream进行对象序列化(调用writeObject)时。原因时序列化的对象所属的类没有实现java.io.Serializable接口导致
出现该异常的解决办法:
将序列化的类实现该接口即可
java.io.UnsupportedEncodingException
- Unsupported 不支持的
- Encoding字符集
不支持的字符集异常,该异常通常出现在使用字符串形式指定字符集名字时,犹豫字符集名字拼写错误导致。例如
1 | 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 没有找到
文件没有找到异常,该异常通常出现在我们使用文件输入流读取指定路径对应的文件时出现
1 | FileInputStream fis = new FileInputStream("f1os.dat"); |
上述代码如果指定的文件f1os.dat文件不在当前目录下,就会引发该异常:
java.io.FileNotFoundException: f1os.dat (系统找不到指定的文件。)
注:
抽象路径"f1os.dat"等同于"./f1os.dat"。因此该路径表示当前目录下应当有一个名为f1os.dat的文件。
还经常出现在文件输出流写出文件时,指定的路径无法将该文件创建出来时出现
1 | FileOutputStream fos = new FileOutputStream("./a/fos.dat"); |
上述代码中,如果当前目录下没有a目录,那么就无法在该目录下自动创建文件fos.dat,此时也会引发这个异常。
其他API上出现该异常通常也是上述类似的原因导致的。
解决办法:
在读取文件时,确保指定的路径正确,且文件名拼写正确。
在写出文件时,确保指定的文件所在的目录存在。
java.net.ConnectException: Connection refused: connect
-
connection 连接
-
refused 拒绝
连接异常,连接被拒绝了.这通常是客户端在使用Socket与远端计算机建立连接时由于指定的地址或端口无效导致无法连接服务端引起的.
1 | System.out.println("正在连接服务端..."); |
解决办法:
-
检查客户端实例化Socket时指定的地址和端口是否正常
-
客户端连接前,服务端是否已经启动了
java.net.BindException: Address already in use
- bind 绑定
- address 地址
- already 已经
- Address already in use 地址已经被使用了
绑定异常,该异常通常是在创建ServerSocket时指定的服务端口已经被系统其他程序占用导致的.
1 | 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()方法被调用.那么阻塞方法会立即抛出中断异常并停止线程的阻塞使其继续运行.
例如:
1 | try { |
如果线程t1调用Thread.sleep(1000)处于阻塞的过程中,其他线程调用了t1线程的inerrupt()方法,那么t1调用的sleep()方法就会立即抛出中断异常InterruptedException并停止阻塞.
java.util.NoSuchElementException
-
such 这个
-
Element 元素
没有这个元素的异常.该异常通常发生在使用迭代器Iterator遍历集合元素时由于没有先通过hasNext()方法判断存在下一个元素而贸然通过next()获取下一个元素时产生(当集合所有元素都经过迭代器遍历一遍后还使用next获取).
1 | while(it.hasNext()){ |
上述代码中循环遍历时,每次调用hasNext()确定存在下一个元素时,循环里面连续调用过两次next()方法,这意味着第二次调用next()方法时并没有判断是否还存在.所以在最后会出现异常.
解决办法:
保证每次调用next()方法前都确定hasNext()为true才进行即可.
java.util.ConcurrentModificationException
-
Concurrent 并发
-
Modification 修改
并发修改异常.这个异常也经常出现在使用迭代器遍历集合时产生.
当我们使用一个迭代器遍历集合的过程中,通过集合的方法增删元素时,迭代器会抛出该异常.
1 | while(it.hasNext()){ |
解决办法:
使用迭代器提供的remove()方法可以删除通过next()获取的元素.
1 | while(it.hasNext()){ |
java.lang.UnsupportedOperationException
support 支持
unsupported 不支持的
operation 操作
不支持的操作异常.该异常出现在很多的API中.
例如:常出现在我们对数组转换的集合进行增删元素操作时抛出.
1 | String[] array = {"one","two","three","four","five"}; |