异常
概述:异常是指在程序的运行过程中所发生的不正常的事件,它会中断正在运行的程序,简而言之就是程序出现了不正常的情况。异常的本质就是java当中对可能出现的问题进行描述的一种对象体现。
常见的异常有:
1.ArithmeticException除数不能为0异常
2.NullPointException空指针异常
3.ArrayIndexOutOfBoundsException数组越界异常
4.ClassCastException类型转换异常
异常分类:
Error:错误,由java虚拟机生成并抛出,程序不对其做处理。在程序执行的整个阶段,都可发生错误,错误是无法解决的,可以避免 例如: StackOverflow
Exception:所有异常类的父类。其子类对应了各种各样可能出现的异常事件,一般需要用户显示的声明或者捕获。
RuntimeException:运行时期异常。又称非受检异常,RuntimeException及其所有子类都是运行时期异常。
编译时期异常:不是继承自RuntimeException的Exception的子类都成为编译时期异常。
处理异常:
目的:不是为了修复程序的错误,而是程序出现错误时,能够让程序继续执行下去
JVM默认处理异常的过程:
1、打印错误信息
a、异常名称,例如:java.lang.ArithmeticException
b、异常消息 / by zero
c、异常所发生的方法位置 at 包名.类名.方法名
d、异常所在java文件中 类名.java
e、异常发生行号 例如:20
2、终止程序
System.exit(0);
其实JVM处理异常的方式并不能满足我们实际开发的需求,实际需要我们自己处理异常
处理异常的步骤:
1、try…catch…finally
格式:
try{
//放置程序可能出现问题的代码
}catch(异常类 异常名){
//这里放置异常处理的代码
} finally{
//释放资源
}
2、throws
这里需要注意的是:
1、try块的代码越少越好
2、try块中一旦发生异常,那么try块中发生异常,后面所有的代码都不会执行
3、多个catch块只会执行一个
4、Exception接收异常必须放在异常的最后面,满足先子类后父类
异常执行流程:
1、程序执行到错误行,系统会创建一个异常对象,并且抛给我们
ArithmeticException exp = new ArithmeticException("/by zero");
throw exp;
2、程序进入catch块进行逐个匹配,匹配成功,程序执行catch块代码
ArithmeticException ae = exp;
Exception e = exp;
匹配失败,交给jvm处理
处理异常的标准方式:
1.能够显示处理的尽量显示处理,提高程序的可读性
2.但是一定要在异常处理的最后加上 父类Exception处理
举例代码如下
public class ExceptionDemo02 {
public static void main(String[] args) {
System.out.println("Start Application");
int a = 10;
int b = 0;
int[] arr = new int[3];
Object obj = null;
Object obj2 = new Integer(100);
try {
// System.out.println(a / b);
// System.out.println(arr[4]);
// obj.equals("ss");
String s = (String) obj2;
} catch (ArithmeticException ae) {
System.out.println("算数异常,出问题1");
} catch (ArrayIndexOutOfBoundsException aioobe) {
System.out.println("数组越界,出问题2");
} catch (NullPointerException ne) {
System.out.println("空指针异常,出问题3");
} catch (Exception e) {
System.out.println("出问题了");
}
System.out.println("End");
}
}
Throwable类:
错误的相关信息都保存在这个类里面,它是所有异常的父类
观察Throwable的源码:
Throwable t = new NullPointerException("空指针异常");
class NullPointerException extends RuntimeException {
public NullPointerException(String s) {
super(s); // "空指针异常"
}
}
class RuntimException extends Exception {
public RuntimeException(String message) {
super(message); // "空指针异常"
}
}
class Exception extends Throwable {
public RuntimeException(String message) {
super(message); // "空指针异常"
}
}
class Throwable {
private String detailMessage; "空指针异常"
public String toString() {
String s = getClass().getName(); // java.lang.NullPointerException
String message = getLocalizedMessage(); "空指针异常"
return (message != null) ? (s + ": " + message) : s; java.lang.NullPointerException:"空指针异常"
}
public String getLocalizedMessage() {
return getMessage(); // "空指针异常"
}
public String getMessage() {
return detailMessage; // "空指针异常"
}
// "空指针异常"
public Throwable(String message) {
fillInStackTrace(); // 将错误信息填充到堆栈中
detailMessage = message; "空指针异常"
}
public synchronized Throwable fillInStackTrace() {
if (stackTrace != null ||
backtrace != null Out of protocol state ) {
fillInStackTrace(0);
stackTrace = UNASSIGNED_STACK;
}
return this;
}
private native Throwable fillInStackTrace(int dummy);
}
如何来阅读异常?
- 从下往上看,先看你自己写的
- 再逐层往上查阅,
- 如果不是你自己写的方法出问题,再看源码的方法是否出问题
- 源码里面也有可能是本地方法出问题
举例代码如下
public class ExceptionDemo03 {
public static void main(String[] args) {
// 利用多态来创建异常对象
// Throwable t = new NullPointerException("空指针异常");
// System.out.println(t); // java.lang.NullPointerException: 空指针异常
// System.out.println(t.toString());
try {
method();
} catch (Exception e) {// Exception e = new NullPointerException("空指针异常");
// System.out.println("出问题了");
// StackTraceElement[] stackTrace = e.getStackTrace(); // 从堆栈中获取错误信息
// /*
// * String getClassName()
// 返回类的完全限定名,该类包含由该堆栈跟踪元素所表示的执行点。
// String getFileName()
// 返回源文件名,该文件包含由该堆栈跟踪元素所表示的执行点。
// int getLineNumber()
// 返回源行的行号,该行包含由该堆栈该跟踪元素所表示的执行点。
// String getMethodName()
// boolean isNativeMethod()
// 如果包含由该堆栈跟踪元素所表示的执行点的方法是一个本机方法,则返回 true。
// ExceptionDemo03.java|com.sxt.exceptiondemo.ExceptionDemo03|113|show|false
// ExceptionDemo03.java|com.sxt.exceptiondemo.ExceptionDemo03|107|method|false
// ExceptionDemo03.java|com.sxt.exceptiondemo.ExceptionDemo03|74|main|false
// */
// for (StackTraceElement element : stackTrace) {
// String fileName = element.getFileName();
// String className = element.getClassName();
// int lineNumber = element.getLineNumber();
// String methodName = element.getMethodName();
// boolean nativeMethod = element.isNativeMethod();
// System.out.println(fileName + "|" + className
// + "|" + lineNumber + "|" + methodName + "|" + nativeMethod) ;
ExceptionDemo03.java|com.sxt.exceptiondemo.ExceptionDemo03|64|main|false
// }
e.printStackTrace();
}
System.out.println("go on");
}
public static void method() {
System.out.println("start Method");
show();
System.out.println("end Method");
}
public static void show() {
System.out.println("start show");
NullPointerException t = new NullPointerException("空指针异常");
throw t;
}
}
处理编译时异常和运行时异常
编译时异常的处理: Ctrl + 1
运行时异常的处理: try…catch throws
举例代码如下
public class ExceptionDemo05 {
public static void main(String[] args) {
method1();
try {
method2();
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("go on");
}
private static void method2() {
Object obj = null;
// try {
//
// } catch (Exception e) {
// e.printStackTrace();
// }
System.out.println(obj.equals("dada"));
}
private static void method1() {
Student s = new Student();
try {
s.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
}
}
class Student implements Cloneable {
@Override
protected Object clone() throws CloneNotSupportedException {
// TODO Auto-generated method stub
return super.clone();
}
}
有了try…catch处理异常,为什么还需要throws处理方式?
在某些情况下,我们不会处理异常,或者我们没有权限异常,干脆我就不想处理异常
在这种情况下我们可以抛出异常,在方法上抛出
谁调用我有问题的方法就谁处理
千万不要交给虚拟机处理
throws处理异常的格式
[修饰符] 返回值类型 方法名(参数列表) [throws 异常类1,异常类2…]{
}
注意:
1.异常的声明只是一种可能性,可能方法中没有异常,但是也可以声明
2.子类重写方法抛出的异常不能够被扩大
举例代码如下
public class ExceptionDemo06 {
public static void main(String[] args) {
System.out.println("start");
try {
method();
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("end");
}
private static void method() throws ArithmeticException{
method2();
}
private static void method2() throws ArithmeticException, NullPointerException {
int a =10;
int b = 0;
System.out.println(a / b);
}
}
class Fu {
public void show() throws Exception {
}
}
class Zi extends Fu {
@Override
public void show() throws NullPointerException {
}
}
有了throws为什么还需要throw?
格式:
throw 异常对象;
谁调用就将对象抛给谁
当抛出的是编译时异常的时候,必须要在方法体上声明异常
当是运行时异常的时候,可以不用声明,但是建议声明
throw和throws的区别(重点)
1.throws出现在方法的声明上,throw关键出现在方法内
2.throws表示一种异常的可能性,throw表示程序的必然性
3.throws可以声明多个异常类,throw只能够抛出一个异常
4.throws声明的是异常类,throw抛出的是异常对象
举例代码如下
public class ExceptionDemo07 {
public static void main(String[] args) {
String s = new String(new byte[] {11,22,33,44}, -1, 3);
System.out.println(s);
try {
calc();
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("go on");
}
private static void calc() throws CloneNotSupportedException {
int a = 10;
int b = 0;
// 方式一处理异常:
// if (b != 0) {
// System.out.println(a / b);
// }
// 方式二处理异常:
if (b == 0) {
throw new CloneNotSupportedException("除数不能为0");
}
System.out.println(a / b);
}
}
finally关键字
try {
} catch() {
} finally {
}
finally关键字
1.finally修饰代码块一定会被执行,除非碰到程序退出
System.exit(0);
Runtime.getRuntime().exit(0);
2.一般用来释放内存资源,比如关闭IO流的输入输出对象,数据库连接对象等等…
举例代码如下
public class ExceptionDemo08 {
public static void main(String[] args) throws FileNotFoundException {
System.out.println("start");
// try {
// System.out.println(10/0);
// } finally {
// System.out.println("finally代码块执行了");
// }
// Scanner input = new Scanner(System.in);
// InputStream is = new FileInputStream("a.txt");
//
// try {
// System.out.println(10 / 0);
// } catch (Exception e) {
// e.printStackTrace();
// // return;
// // System.exit(0);
// } finally {
// System.out.println("finally代码块执行了");
// if (input != null) {
// input.close();
// }
//
// if (is != null) {
// try {
// is.close();
// } catch (IOException e) {
// // TODO Auto-generated catch block
// e.printStackTrace();
// }
// }
// }
try (Scanner in = new Scanner(System.in);
InputStream inputStream = new FileInputStream("a.txt");) {
System.out.println(10/0);
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("end");
}
}
finally碰到return
finally一定会执行
执行顺序:
在try语句中,在执行return语句时,要返回的结果已经准备好了,就在此时,程序转到finally执行了。
在转去之前,try中先把要返回的结果存放到不同于x的局部变量中去,执行完finally之后,再从中取出返回结果,
因此,即使finally中对变量x进行了改变,但是不会影响返回结果。它应该使用栈保存返回值。
finally final finallize的区别:
final:
在java中,final可以修饰类,成员方法,成员变量
final修饰类时,不能被子类继承
final修饰成员方法时,不能被子类重写
final修饰的成员变量变为常量,只能赋值一次
finallize:
finallize()方法在java.lang.Object类定义,即所有类里面都有这个方法,这个方法在gc启动,该对象被回收的时候被调用。其实gc可以回收大部分的对象(凡是new出来的对象,gc都能搞定,一般情况下我们又不会用new以外的方式去创建对象),所以一般是不需要程序员去实现finalize的。
特殊情况下,需要程序员实现finalize,当对象被回收的时候释放一些资源,比如:一个socket链接,在对象初始化时创建,整个生命周期内有效,那么就需要实现finalize,关闭这个链接。 使用finalize还需要注意一个事,调用super.finalize();
一个对象的finalize()方法只会被调用一次,而且finalize()被调用不意味着gc会立即回收该对象,所以有可能调用finalize()后,该对象又不需要被回收了,然后到了真正要被回收的时候,因为前面调用过一次,所以不会调用finalize(),产生问题。 所以,推荐不要使用finalize()方法,它跟析构函数不一样。
finally:
在try语句中,在执行return语句时,要返回的结果已经准备好了,就在此时,程序转到finally执行了。
在转去之前,try中先把要返回的结果存放到不同于x的局部变量中去,执行完finally之后,再从中取出返回结果,
因此,即使finally中对变量x进行了改变,但是不会影响返回结果。它应该使用栈保存返回值。
举例代码如下
public class ExceptionDemo09 {
public static void main(String[] args) {
System.out.println(test()); // 4 3
}
public static int test() {
int x = 1;
try {
System.out.println(x++ / 0); // 2
} catch (Exception e) {
x++; //
return x; // return 3;
} finally {
++x; // 4
System.out.println(x); // 4
}
return x;
}
}
自定义异常
异常的本质就是Java中对错误问题的描述的一种对象体现
java提供的异常不够我们自己使用,我们需要自定义异常,自己应用在我们的开发中
例如:
自定义分数异常: 0~100之间
自定义异常的步骤
1.创建一个异常类继承 Exception或者RuntimeException
2.在自定义异常类中书写构造方法即可
a.无参构造
b.带错误信息的构造方法
代码实现
public class ExceptionDemo10 {
public static void main(String[] args) {
Scanner input = new Scanner(System.in);
System.out.print("请输入分数:");
double score = input.nextDouble();
Teacher t = new Teacher();
try {
System.out.println(t.isBetween0to100(score) ? "分数合法" : "分数不合法");
} catch (ScoreException e) {
e.printStackTrace();
} finally {
if (input != null) {
input.close();
}
}
System.out.println();
System.out.println("分数修改完毕!!!");
}
}
class Teacher {
// 判断分数是否在0~100分之间
public boolean isBetween0to100(double score) throws ScoreException {
if (score < 0 || score > 100) {
throw new ScoreException("分数必须在0~100分之间:" + score);
}
return true;
}
}
class ScoreException extends Exception {
private static final long serialVersionUID = -3114508965246281767L;
public ScoreException() {}
public ScoreException(String message) {
super(message);
}
}
需求:自定义运行时异常: 身份证异常
代码实现
public class ExceptionDemo11 {
public static void main(String[] args) {
boolean checkIDCard = false;
try (Scanner input = new Scanner(System.in);){
String id = input.next();
checkIDCard = CheckUtils.checkIDCard(id);
} catch (Exception e) {
e.printStackTrace();
}
if (checkIDCard) {
System.out.println("合法");
} else {
System.out.println("非法身份证");
}
}
}
class CheckUtils {
private CheckUtils() {}
public static boolean checkIDCard(String idCard) throws IDCardException {
if (!idCard.matches("(\\d{14}[0-9a-zA-Z])|(\\d{17}[0-9a-zA-Z])")) {
throw new IDCardException("身份证不满足要求!!!!" + idCard);
}
return true;
}
}
class IDCardException extends RuntimeException {
private static final long serialVersionUID = 392084157998377850L;
public IDCardException() {}
public IDCardException(String msg) {
super(msg);
}
}
断言assert
一、概述
在C和C++语言中都有assert关键,表示断言。
在Java中,同样也有assert关键字,表示断言,用法和含义都差不多。
二、语法
在Java中,assert关键字是从JAVA SE 1.4 引入的,为了避免和老版本的Java代码中使用了assert关键字导致错误,Java在执行的时候默认是不启动断言检查的(这个时候,所有的断言语句都将忽略!),如果要开启断言检查,则需要用开关-enableassertions或-ea来开启。
assert关键字语法很简单,有两种用法:
1、assert <boolean表达式>
如果<boolean表达式>为true,则程序继续执行。
如果为false,则程序抛出AssertionError,并终止执行。
2、assert <boolean表达式> : <错误信息表达式>
如果<boolean表达式>为true,则程序继续执行。
如果为false,则程序抛出java.lang.AssertionError,并输入<错误信息表达式>。
举例
public class AssertFoo {
public static void main(String args[]) {
//断言1结果为true,则继续往下执行
assert true;
System.out.println("断言1没有问题,Go!");
System.out.println("\n-----------------\n");
//断言2结果为false,程序终止
assert false : "断言失败,此表达式的信息将会在抛出异常的时候输出!";
System.out.println("断言2没有问题,Go!");
}
}
/*保存代码到C:\AssertFoo.java,然后按照下面的方式执行,查看控制台输出结果:
1、编译程序:
C:\>javac AssertFoo.java
2、默认执行程序,没有开启-ea开关:
C:\>java AssertFoo
断言1没有问题,Go!
-----------------
断言2没有问题,Go!
3、开启-ea开关,执行程序:
C:\>java -ea AssertFoo
断言1没有问题,Go!
-----------------
Exception in thread "main" java.lang.AssertionError: 断言失败,此表达式的信息将
会在抛出异常的时候输出!
at AssertFoo.main(AssertFoo.java:10)
*/
注意:
assert关键字用法简单,但是使用assert往往会让你陷入越来越深的陷阱中。应避免使用。笔者经过研究,总结了以下原因:
1、assert关键字需要在运行时候显式开启才能生效,否则你的断言就没有任何意义。而现在主流的Java IDE工具默认都没有开启-ea断言检查功能。这就意味着你如果使用IDE工具编码,调试运行时候会有一定的麻烦。并且,对于Java Web应用,程序代码都是部署在容器里面,你没法直接去控制程序的运行,如果一定要开启-ea的开关,则需要更改Web容器的运行配置参数。这对程序的移 植和部署都带来很大的不便。
2、用assert代替if是陷阱之二。assert的判断和if语句差不多,但两者的作用有着本质的区别:assert关键字本意上是为测试 调试程序时使用的,但如果不小心用assert来控制了程序的业务流程,那在测试调试结束后去掉assert关键字就意味着修改了程序的正常的逻辑。
3、assert断言失败将面临程序的退出。这在一个生产环境下的应用是绝不能容忍的。一般都是通过异常处理来解决程序中潜在的错误。但是使用断言就很危险,一旦失败系统就挂了。
assert的思考
assert既然是为了调试测试程序用,不在正式生产环境下用,那应该考虑更好的测试JUint来代替其做用,JUint相对assert关键的所提供的功能是有过之而无不及。当然完全可以通过IDE debug来进行调试测试。在此看来,assert的前途一片昏暗。
因此,应当避免在Java中使用assert关键字,除非哪一天Java默认支持开启-ea的开关,这时候可以考虑。对比一下,assert能给你带来多少好处,多少麻烦,这是我们选择是否使用的的原则。