Java 异常处理

首先对于错误或者异常通常发生的阶段要做下了解,也就是运行时和编译时。说实话,我目前很模糊。

1 编译时和运行时

那么下面请转战一篇帖子: java编译时与运行时概念与实例详解

编译时的基础概念:编译期就是编译器帮助把源代码翻译成机器可识别的字节码的时期。那么主要做了啥呢,就是做了一些简单的翻译工作,比如检查一下有没有少个逗号,词法分析,语法分析之类的。如果发现了有什么错误,编译器就会通知。

运行时的基础概念:运行时就是指代码跑起来了,被装载到内存里面去了。(你的代码保存在磁盘上没有装入内存之前是个死家伙,只有跑到内存中才能变成活的),运行时类型检查与前面的编译时类型检查(也叫静态类型检查)是不一样的,不只是简单的扫描代码,而是在内存中做一些操作,做判断。(这样很多编译时无法发现的错误,在运行时就可以发现报错了,最好还是写的时候就避免这个逻辑错误就好了。例如空指针异常。)

案例:

public class YunXingShiYIchang {
	public static void main(String[] args) {
		int[] a = new int[3];
		int b = a[4];
		System.out.println(b);
	}
}

以上代码在编写期间没有任何报错的。

但是执行出来却出现了:

Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: 4
	at YunXingShiYIchang.main(YunXingShiYIchang.java:5)

可见这样明显错误的代码,编译器并没有报错,等到真正跑起来的时候出现了数组索引越界这个异常。

文章里简单介绍了关于编译时运行时涉及到的其他知识点,包括重载与重写,泛型,异常,多态。这个有时间需要仔细研究。

好,关于编译时和运行时的概念大概就讲这么些,目前知道这么些就可以理解下面的东西了。

Java中的异常

Java 异常处理

异常值得是程序中的一些错误,但并不是所有的错误都是异常,并且有的错误是可以避免的,例如你少写了一个分好,那么运行出来的结果是提示java.lang.Error,如果写了一个上面的示例错误代码,运行时会给一个数组越界异常。这些异常有的是因为用户错误引起的,有的是程序错误引起的,还有的是一些物理错误引起的。

异常大致分为三类:

检查性异常:最具代表性的检查性异常是用户错误或者问题引起的异常,因为程序员是无法预见的,例如要打开一个不存在的文件,一个异常就发生了,这些异常在编译时不能被简单的忽略。

运行时异常:是在编译时被忽略的异常,在运行期间进行检查找到的异常,通常可以被程序员避免。

错误:错误不是异常的范畴,而是脱离测好难过许愿控制的问题。错误在代码中通常被忽略。比如,当栈溢出时,一个错误就发生了。

异常的相关类结构

所有的异常类都是Exception类的子类,并且Exception类是Throwable的子类,其中Exception同时还有个兄弟类Error同样继承自Throwable类。

而对于Error来说,用来指示运行时环境发生的错误。java程序通常是不捕获Error的,并且Error一般发生在严重故障的时候。他们在Java程序处理范畴之外。比如说JVM内存溢出,一般的程序不会从错误中恢复。

java中内置的运行时异常类以及作用:

ArithmeticException:出现异常的计算条件时,就抛出此异常。例如 9/0.

ArrayIndexOutOfBoundException:用非法的索引访问数组的时候抛出的异常。比如找 -1 位置,或者是明明最大索引是8, 找的时候却来了个10.

ArrayStoreException:试图将一个错误的对象存储到一个对象数组时抛出。

ClassCastException:试图将对象强转为不是实例的子类时抛出该异常

IllegalArgumentException:非法参数异常。表明向方法传递了一个不合法或者不正确的参数。

IllegalMonitorStateException:跑出的异常表明某一线程已经试图等待对象的监视器,或者试图通知其他正在等待的监视器而本身却没有指定监视器的线程。

IllegalStateException:在非法或者不适当的时间调用方法时所产生的信号。换句话说,即java环境或者java应用程序没有处于请求操作所要求的适当状态下。例如sdk没有初始化,你就调用了里面的核心方法。时常会要抛出这样的异常。

IllegalThreadStateException:线程没有处于请求操作所要求的的状态下抛出的异常。比如 调用了start()方法之后,线程执行完了,又调用了一次start()就会抛出这样的异常。

IndexOutOfBoundsException: 指示某排序索引超出范围时抛出。

NegativeArraySizeException:如果应用程序视图创建大小为负数的驻足,就会抛出该异常。

NullPointerException:这个是相当常见了,空指针异常。当程序在需要对象的地方,这个对象却为null,就会抛出该异常。

NumberFormatException:数据类型转换错误。

SecurutyException:由安全管理器抛出的异常。

StringIndexOutOfBoundsException:由String的方法抛出,指示索引或者为负数,或者超出字符串的大小。

UnsupportedOperationException:当不支持请求操作的时候,抛出该异常。


java中内置的编译时异常类的作用:

ClassNotFoundException:应用程序视图加载类的时候找不到相应的类,抛出该异常。

CloneNotSupportedException: 当调用Object类中的clone方法克隆对象的时候,但是该对象的类无法实现Cloneable接口时,抛出该异常。

IllegalAccessException:拒绝访问一个类的时候,抛出该异常。

InstantiationException:当驶入使用class类中的newInstance方法创建一个实例的时候,而制定的对象是一个接口或者是一个抽象类无法被实例化的时候,就会抛出该异常。

InterruptedException:一个线程被另外一个限额航中断抛出该异常

NoSuchFieldException:请求的变量不存在

NoSuchMethodException:请求的方法不存在。

而对于这些运行时异常也好,编译时异常也好,这些类的具体实现实则是相当简单的。下面上一段某具体异常的实现:


/*
 *  Licensed to the Apache Software Foundation (ASF) under one or more
 *  contributor license agreements.  See the NOTICE file distributed with
 *  this work for additional information regarding copyright ownership.
 *  The ASF licenses this file to You under the Apache License, Version 2.0
 *  (the "License"); you may not use this file except in compliance with
 *  the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */

package java.lang;

/**
 * Thrown when a class loader is unable to find a class.
 */
public class ClassNotFoundException extends ReflectiveOperationException {

    private static final long serialVersionUID = 9176873029745254542L;

    private Throwable ex;

    /**
     * Constructs a new {@code ClassNotFoundException} that includes the current
     * stack trace.
     */
    public ClassNotFoundException() {
        super((Throwable) null);
    }

    /**
     * Constructs a new {@code ClassNotFoundException} with the current stack
     * trace and the specified detail message.
     *
     * @param detailMessage
     *            the detail message for this exception.
     */
    public ClassNotFoundException(String detailMessage) {
        super(detailMessage, null);
    }

    /**
     * Constructs a new {@code ClassNotFoundException} with the current stack
     * trace, the specified detail message and the exception that occurred when
     * loading the class.
     *
     * @param detailMessage
     *            the detail message for this exception.
     * @param exception
     *            the exception which occurred while loading the class.
     */
    public ClassNotFoundException(String detailMessage, Throwable exception) {
        super(detailMessage);
        ex = exception;
    }

    /**
     * Returns the exception which occurred when loading the class.
     *
     * @return Throwable the exception which occurred while loading the class.
     */
    public Throwable getException() {
        return ex;
    }

    /**
     * Returns the cause of this Throwable, or {@code null} if there is no
     * cause.
     *
     * @return Throwable the receiver's cause.
     */
    @Override
    public Throwable getCause() {
        return ex;
    }
}

其实对于异常类,主要的逻辑再Throwable里面实现了,其他的子类啥的实现逻辑其实都是很简单的。所以个人认为分这么多子类的主要目的也就是做分类。

异常的使用

Android——Exception异常的正确打开方式

1)throw: 

throw关键字,通常在代码片段中直接抛出异常。

public void testThrow() {
		throw new RuntimeException("运行时异常");
	}

如以上示例,打印结果如下:

Exception in thread "main" java.lang.RuntimeException: 运行时异常
	at YunXingShiYIchang.testThrow(YunXingShiYIchang.java:13)
	at YunXingShiYIchang.main(YunXingShiYIchang.java:8)

注意,打印的结果里面附带了自己添加的信息。并且当我们抛出异常的时候,会发现接下来的代码就不会走了直接打断 game over。

2)throws:

Throws在方法签名上使用,并且是可以抛出多种异常的,用逗号隔开

public void testOutIndext()throws IndexOutOfBoundsException{
		ArrayList list = new ArrayList();
		list.add("wo");
		System.out.println(list.get(2));
	}
Exception in thread "main" java.lang.IndexOutOfBoundsException: Index: 2, Size: 1
	at java.util.ArrayList.rangeCheck(Unknown Source)
	at java.util.ArrayList.get(Unknown Source)
	at YunXingShiYIchang.testOutIndext(YunXingShiYIchang.java:19)
	at YunXingShiYIchang.main(YunXingShiYIchang.java:9)

但是讲到这里,也就是大概清楚了两个关键字的语法,下面出现了两个疑问, 1 两者有什么区别呢? 2 为什么要抛异常。就像第二段代码,我即使不专门写抛出去的异常,照样也会抛啊。

3)Throw 和 throws有什么区别:

对于异常的处理,主要有两种方式,要么在本方法的逻辑里面加上try catch 代码块,要么就是抛到方法外部让调用这个方法的外部程序处理。 如果异常得不到程序员的手动处理,那么JVM就会自己尝试捕获,不过一旦轮到JVM捕获的话,就会直接中断程序,并且将异常打印出来。

throw是方法内部逻辑使用的一个关键字,只能抛出一个异常。并且这个异常意在方法内部逻辑处理。也就是一般会有try catch 代码块进行单独的处理。。如果你不做处理的话,那好吧JVM亲自处理,当然你的程序八成得挂掉了。

下面是关于Throw关键字的真正用法有抛出就要有捕获:

public int testThrow(int b) {
		try{
			if (b == 0) {
				throw new ArithmeticException();
			} else if (b < 0) {
				throw new IllegalArgumentException();
			} else {
				return 10 / b;
			}

		} catch(ArithmeticException a) {
			//捕获到相应异常的时候会走到catch里面,不至于程序员捕获不到异常而使程序直接挂掉。
			//我们要做的就是 做一些异常处理逻辑。
			//当然该方法是需要返回一个int值的,这里不返回编译过不去。
			//说白了就是强制使整个逻辑有结果。偏合理。
			
			//首先打印了一下这个异常
			a.printStackTrace();
			return 0;
			
		} catch (IllegalArgumentException e) {
			e.printStackTrace();
			return 0;
		}
	}

看看调用它的方法:

public static void main(String[] args) {
		YunXingShiYIchang yichang = new YunXingShiYIchang();
		System.out.println(yichang.testThrow(0));
		System.out.println(yichang.testThrow(-2));
		System.out.println(yichang.testThrow(2));
	}

我们看,调用的时候,前两个的参数都是一些不合理数据,但是由于我们做了捕获处理,所以不至于程序挂掉,运行结果如图所示:


会发现由于catch代码里面我们自己尝试打印出来了日志,所以会出现日志,但是代码没有出现中断,而是继续往下走了。给的值就是catch里面的值 前两个是 0, 0. 是捕获到异常了自行处理的。

由此看出,异常是配合 try catch 一块来用的。 并且异常的作用就是抛出抛出。

当然针对throw关键字所抛出的异常,如果你不在方法内部进行处理的话,人家也是会往外抛异常的。你也可以尝试在调用这个方法的地方给捕获住,这样也可以的。如以下代码:

public int testThrow1(int b) {
			if (b == 0) {
				throw new ArithmeticException();
			} else if (b < 0) {
				throw new IllegalArgumentException();
			} 
			return 10 / b;
			
	}
public static void main(String[] args) {
		YunXingShiYIchang yichang = new YunXingShiYIchang();
		try {
			yichang.testThrow1(-1);
		}catch (Exception e) {
			e.printStackTrace();
		}

		System.out.println(yichang.testThrow(0));
		System.out.println(yichang.testThrow(-2));
		System.out.println(yichang.testThrow(2));
		
		
	}

从打印结果上看,照样不耽误下面代码的执行。但是要注意的是,此时的testThrow1()方法,是没有返回值的。因为出了异常。直接抛出去了,怎么会走return的逻辑呢?个人感觉这样在外部处理 throw关键字抛出的代码是不太好的。


Throws关键字的用法:

throws 是针对方法来说的,它修饰的是一个方法,并且它声明的是可能会抛出的所有异常信息,可以一个,可以多个。throws将异常进行声明,但是不处理,一旦抛出是往方法外部抛,谁调用就交给谁处理。而且必须处理,是必须要上try catch 代码块的。除非你打算继续往上抛。

例如下面这段代码,用的是Throws。

public void testOutIndext()throws Exception, IndexOutOfBoundsException{
			ArrayList list = new ArrayList();
			list.add("wo");
			System.out.println(list.get(2));
	}

在看看调用他的地方:


不写try catch, 就报错,提示你要写try catch, 或者继续向上抛异常

总之呢throws着重体现了外部处理这个特点。

4) try-catch-finally:

综上所述,我们的异常其实是要配合try catch 来使用的, try catch 就是 捕获异常的意思。 以上代码中就有try catch 的使用代码。 其语法再重复一下:

try
{
   // 程序代码, 在此时也叫被保护代码
}catch(ExceptionName e1)
{
   //Catch 块
}

catch语句包含了要捕获异常类型的声明。当保护代码中发生发生一个异常的室友,try后面的catch块就会被检查。如果发生的异常包含在catch块中,异常就会传递到该catch块中,和传递一个参数到方法是一样的。 

关于finally关键字:

上方没有提及finnaly,下面有try catch 通常使用的语法场景,

try{
  // 程序代码
}catch(异常类型1 异常的变量名1){
  // 程序代码
}catch(异常类型2 异常的变量名2){
  // 程序代码
}finally{
  // 程序代码
}

finally在 try catch 有可以,没有也可以。在try catch 的最后写代码块。 但是应当注意的是,无论是否发生异常,finally代码块中的代码是始终被执行的,并且始终在return的前面执行。。只在return那一句逻辑的前面执行。因为return是最后一句执行代码本就天经地义,但是加了一个finally,,也是无论什么情况下最后执行的意思,所以排到老二,,目前我是这么理解的。

在 finally 代码块里面,可以运行清理类型等收尾性质的语句。

Java中捕获多个异常的顺序

针对一段try catch 代码块,如果有多个catch 的话,那么catch里面的异常类形参,是要有顺序的。 子类一定要在前面的catch里面,父类一定要在后面的catch里面:

try { …… }

    catch(ClassCastException ex){ …… }

    catch(NumberFormatException ex){ …… }

    catch(Exception ex){ …… }  // 此句必须放在最后!

正如上面的代码。原因是,catch捕获异常的时候,会根据catch里面的类型逐个执行,是逐个执行!当发现try中产生的异常和catch的异常匹配的时候就会停止,直接走对应catch里面的逻辑。否则就会继续找对应的catch。  但是Exception类是所有异常类的父类。一旦写在最上面的话,,那么指定它上去就会被匹配直接走到catch里面去,其他异常的压根连比对都不用比对了!没可能执行。

好像如果你把Exception 的 catch 提到第一位,压根编译就不通过。

java异常进阶

深入理解java异常处理机制



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

娅娅梨

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

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

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

打赏作者

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

抵扣说明:

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

余额充值