throw,意为“投掷、抛、扔”。Throw、Throws和Throwable三者都用于异常处理。
Throwable在Java中是异常处理这个分支的顶级父类,其它所有异常处理的实现都依赖于Throwable
打开Java官方文档(Java8版本),找到Throwable,它的直接子类为Error和Exception。
Error和Exception两者的特点在于Error异常程序无法处理,只能交由人工介入修改代码,比如栈溢出、堆溢出等等;而Exception异常可以提前发觉,并作出有效处理。
在Error中,常见的有栈溢出和堆溢出等等。
举个例子,StackOverflowError
public class ErrorTest {
public static void main(String[] args) {
main(args);
}
}
无限递归,执行这个程序就会报栈溢出异常。
再比如堆异常,OutOfMemoryError
public class ErrorTest {
public static void main(String[] args) {
Integer[] testArray = new Integer[1024*1024*1024];
}
}
在Exception中就有非常多我们所熟知的异常情况了,比如NullPointerException(空指针异常)、ArrayIndexOutOfBoundsException(数组下标越界)、NumberFormatException(数字格式化异常)等等。
public class ExceptionTest {
public static void main(String[] args) {
int[] testArray = null;
System.out.println(testArray[0]); //空指针异常
}
}
public class ExceptionTest {
public static void main(String[] args) {
int[] testArray = new int[3];
System.out.println(testArray[3]); //数组下标越界
}
}
public class ExceptionTest {
public static void main(String[] args) {
String num = "abc";
System.out.println(Integer.parseInt(num)); //数字格式化异常
}
}
throws应用在方法声明处,指明此方法在执行时可能会出现的异常类型。一旦该方法执行时出现异常,就会在异常代码处生成一个异常类的对象,此对象满足Throws后的异常类型时,就会被抛出。(这里有两个过程,代码有异常时,1.生成一个异常对象;2. throws捕获到这个异常,将异常对象抛出)
throws和try-catch-finally一起称为异常处理的两种方式。
try-catch-finally是在出现异常时主动处理掉异常,使得程序可以继续执行下去;而throws捕获到异常之后向上抛出异常对象,不去真正地处理这个异常。
所谓向上抛出异常对象,是将异常对象交给调用者去处理,比如方法A调用方法B,B通过throws抛出异常,而A可以选择使用try-catch-finally处理掉异常,也可以通过throws继续向上抛出异常对象,直到异常被真正处理掉。如果一直没有方法去处理异常,异常对象最终会被抛给JVM,从而导致程序停止运行。
@Test
public void throwsTest(){ //调用者解决抛出的异常
try{
formatChange("abc");
}
catch (NumberFormatException e){
System.out.println("转换格式错误!");
}
catch (Exception e){
System.out.println("出现错误");
}
}
private int formatChange(String str) throws NumberFormatException{ //出现异常向上抛出
return Integer.parseInt(str);
}
当一个方法中存在异常需要处理,在大多数情况下,既可以选择try-catch-finally直接处理掉这个异常,也可以选择throws向上抛出异常,交给调用者去处理(异常抛到最后,总要有一方真正地去处理这个异常,怎么处理?还是用try-catch-finally呗),在选择上比较自由,但是,出现以下两种情况时,需要遵循一定的规则(如有补充,敬请指出)。
针对第一条,这是一个规定,子类中重写的方法使用throws抛出的异常必须不大于父类中被重写的方法抛出异常的范围。举个例子,父类中的方法B抛出NullPointerException异常,则子类中重写B方法就不能抛出如Exception这种比NullPointerException范围更大的异常;如果父类中被重写的方法没有抛出任何异常,则子类更不能抛出异常。
为什么?展示一段代码。
//假设父类中的方法B抛出NullPointerException异常,子类中的方法B可以抛出Exception
private void test(ParentClassTest parent){
try{
parent.B();
}
catch(NullPointerException e){
System.out.println("出现了空指针异常");
}
}
在本示例中,假设父类中的方法B抛出NullPointerException异常,子类中重写的方法B可以抛出Exception。那么传进给test方法的参数如果是父类的实例化对象,那么调用test方法没有任何问题。如果传进的参数是子类的实例化对象,再去调用子类重写的方法B,那么就有可能抛出Exception异常,try-catch结构就压不住这个异常了,这显然是一个不合理的操作。
针对第二条,假设方法A中调用了方法C、D、E,这三个方法都有可能产生异常,且存在递进关系,也就是D、E执行需要C执行完成、E执行依赖C、D执行完成。那么就推荐在C、D、E中向上抛出异常,在方法A中集中处理。为什么?如果C、D、E都是向上抛出异常,而A使用try-catch-finally去处理这个异常,如果某个方法真的出现异常,则不再继续执行。而如果C、D、E都使用try-catch-finally直接解决掉异常,那么即使产生了异常,方法A也不会接收到异常的产生,那么还会接着往下执行,但是C出现了异常,再执行D、E没有任何意义。
如果在程序编写时有手动抛出异常的需求,则可以使用throw
throw使用在方法体内。与try-catch-finally和throws都不同,异常处理的两个阶段:1.遇到异常,生成异常对象;2.捕获到异常,进行抛出或处理。try-catch-finally和throws都处在第二个阶段,都是捕获到异常后的相关处理,一般使用系统根据异常类型自动生成的异常对象进行处理。而throw应用在第一阶段,手动地产生一个异常对象。
举一个例子,判断一个数值是否为非负数,如果为负数,则抛出异常。
class ThrowTest{
private int Number;
public void judge(int num){
if(num>=0){
this.Number = num;
}
else{
throw new RuntimeException("传入参数为负数");
}
}
}
@Test
public void test2(){
ThrowTest throwTest = new ThrowTest();
throwTest.judge(-100);
}
成功抛出异常。
使用try-catch捕获一下异常。
@Test
public void test2(){
ThrowTest throwTest = new ThrowTest();
try{
throwTest.judge(-100);
}
catch (RuntimeException e){
System.out.println(e.getMessage());
}
}
如果把throw抛出的异常改为Exception,则直接报错,也就是不能编译。Exception包含两种异常:编译时异常和运行时异常,前者在编译前就要检查是否有可能产生编译时异常;后者是在编译后运行时才会判断的异常。而throw new Exception包含了编译时异常,需要显式处理掉这个异常,怎么处理?try-catch-finally或者throws
class ThrowTest{
private int Number;
public void judge(int num) throws Exception{
if(num>=0){
this.Number = num;
}
else{
throw new Exception("传入参数为负数");
}
}
}
调用方也要随着进行更改。
@Test
public void test2(){
ThrowTest throwTest = new ThrowTest();
try{
throwTest.judge(-100);
}
catch (RuntimeException e){
System.out.println(e.getMessage());
}
catch (Exception e){
System.out.println(e.getMessage());
}
}
throw还可以抛出自定义异常类。
自定义异常类的声明需要继承于现有的异常体系。
class MyException extends RuntimeException{
static final long serialVersionUID = -703489719076939L; //可以认为是一种标识
public MyException(){}
public MyException(String message){
super(message);
}
}
此时我们可以抛出自定义的异常
class ThrowTest{
private int Number;
public void judge(int num) throws MyException{
if(num>=0){
this.Number = num;
}
else{
throw new MyException("不能输入负数");
}
}
}
调用者修改
@Test
public void test2(){
ThrowTest throwTest = new ThrowTest();
try{
throwTest.judge(-100);
}
catch (MyException e){
System.out.println(e.getMessage());
}
}
三者共同点在于都属于是异常处理的范畴内。
不同点:
Throwable是异常处理这个分支的顶层父类,其它异常类的实现都需要继承于Throwable
throw应用在方法体内,是生成异常对象的一种方式
throws应用在方法声明处,声明出可能要抛出的各种异常类,是处理异常的方式