我是好吃懒做的prgers, 在iOS摸鱼了好几年, 突然心血来潮, 想去Java摸鱼玩
1 开发中的错误
在开发Java程序的过程中, 会遇到各种各样的错误, 比如:
- 语法错误
- 导致编译失败, 程序无法正常运行
- 逻辑错误
- 比如需要执行加法操作时, 不小心写成了减法操作
- 运行时错误
- 在程序运行过程中产生的意外, 会导致程序终止运行, 在Java中也叫做异常
程序产生了异常, 一般称之为: 抛出异常, 如果没有主动去处理它, 会导致程序终止运行
思考: 下面代码的打印结果是什么?
public static void main(String[] args) {
System.out.println(1);
Integer i1 = new Integer("123");
System.out.println(2);
Integer i2 = new Integer("abc");
System.out.println(3);
}
由于
abc无法转换成整数,new Integer("abc")会抛出一个异常
1. 异常类型:java.lang.NumberFormatException
2. 由于没有主动去处理这个异常,所以导致程序终止运行
打印结果: 1, 2
2 异常
Java中有各种各样的异常, 所有的异常最终都继承自java.lang.Throwable
2.1 异常的种类
异常分为两种:
检查型异常和非检查型异常
检查型异常
这类异常一般难以避免,编译器会进行检查,如果开发没有处理这类异常,编译器会报错
哪些异常是检查型异常?
除Error、RuntimeException以外的异常
非检查型异常
这类异常一般可以避免,编译器不会进行检查,如果开发者没有处理这类异常,编译器将不会报错
哪些异常是非检查型异常?
Error、RuntimeException
2.2 常见的检查型异常
//java.io.FileNotFoundException 文件不存在
FileOutputStream fos = new FileOutputStream("F:/prgers/1.text");
FileOutputStream fmt = new SimpleDateFormat("yyyy-MM-dd");
//java.text.ParseException 字符串的格式不对
Date date = fmt.parse("2066/06/06");
//java.lang.InterruptedException
Thread.sleep(1000);
//java.lang.ClassNotFoundException 不存在这个类
Class cls = Class.forName("Dog");
//java.lang.InstantiationException 没有无参构造方法
//java.lang.IllegalAccessException 没有权限访问构造方法
Dog dog = (Dog)cls.newInstance();
2.3 常见的非检查型异常 - Error
for (int i = 0; i < 200; i++) {
//java.lang.OutOfMemoryError 内存不够用
long[] a = new long[1000000000];
}
public static void test() {
test();
}
public static void main(String[] args) {
//java.lang.StackOverflowError 栈内存溢出
test();
}
2.4 常见的非检查型异常 - RuntimeException
//java.lang.NullPointerException 使用可空指针
StringBUilder s = null;
s.append("abc");
//java.lang.NumberFormatException 数字的格式不对
Integer i = new Integer("abc");
Int[] array = { 11, 22, 33 };
//java.lang.ArrayIndexOutOfBoundsException 数组越界
array[4] = 44;
Object obj = "123.4";
//java.lang.ClassCastException 类型不匹配
Double d = (Double) obj;
在了解了常用的异常以后,如何防止程序因为抛出异常导致终止运行呢?
3 异常处理方式
3.1 try-catch
try {
代码1
代码2(可能会抛出异常)
代码3
} catch(异常A e) {
//当抛出【异常A】类型的异常时,会进入这个代码块
} catch(异常B e) {
//当没有抛出【异常A】类型
//但抛出【异常B】类型的异常时,会进入这个代码块
} catch(异常C e) {
//当没有抛出【异常A】类型,【异常B】类型
//但抛出【异常C】类型的异常时,会进入这个代码块
}
代码4
伪代码解释
如果【代码2】没有抛出异常
1【代码1、3】都会被执行
2 所有的catch都不会被执行
3 【代码 4】会被执行
如果【代码2】抛出了异常
1【代码1】会被执行,【代码3】不会被执行
2 会选择匹配的catch来执行代码
3 【代码 4】会被执行
注意
父类型的异常必须写在子类型的后面
1【异常A】不可以是【异常B、C】的父类型
2【异常B】不可以是【异常C】的父类型
举个例子
public static void main(String[] args) {
System.out.println(1);
try {
System.out.println(2);
Integer integer = new Integer("abc");
System.out.println(3);
} catch (NumberFormatException e) {
System.out.println(4);
}
System.out.println(5);
}
//打印结果: 1、2、4、5
一个catch捕获多种类型的异常
try {
} catch (异常A | 异常B | 异常C) {
}
从Java 7开始,单个catch可以捕获多种类型的异常,如果并列的几个异常类型之间存在父子关系,保留父类型即可
异常对象的常用方法
try {
Integer integer = new Integer("abc");
} catch (NumberFormatException e) {
//异常的描述
System.out.println(e.getMessage());
//异常名称 + 异常描述
System.out.println(e);
//打印堆栈信息
e.printStackTrace();
}
3.2 finally
try或catch正常执行完毕后,一定会执行finally中的代码
try {
}catch (异常 e) {
} finally {
}
finally可以和try-catch搭配使用,也可以只和try搭配使用
try {
}finally {
}
经常会在
finally中编写一些关闭、释放资源的代码(比如关闭文件)
PrintWriter out = null;
try {
out = new PrintWriter("F:/test.txt");
out.print("My name is prgers");
}catch (异常 e) {
e.printStackTrace();
} finally {
if (out != null) {
out.close();
}
}
finally细节
如果在执行
try或catch时,JVM退出或者当前线程被中断、杀死,finally可能不会被执行。
如果try或catch中使用了return、break、continue等提前结束语句,finally会在return、break、continue之前执行。
举个例子
public static void main(String[] args) {
System.out.println(get());
}
static int get() {
try {
new Integer("abc");
System.out.println(1);
return 2;
} catch (Exception e) {
System.out.println(3);
return 4;
} finally {
System.out.println(5);
}
}
//打印结果 3、5、4
3.3 throws
作用:将异常抛给上层方法
static void test() throws FileNotFoundException, ClassNotFoundException {
PrintWriter out = new PrintWriter("/User/prgers/1.txt");
Class cls = Class.forName("Person");
}
如果
throws后面的异常类型存在父子关系,保留父类型即可
static void test() throws Exception {
PrintWriter out = new PrintWriter("/User/prgers/1.txt");
Class cls = Class.forName("Person");
}
static void test() throws Throwable {
PrintWriter out = new PrintWriter("/User/prgers/1.txt");
Class cls = Class.forName("Person");
}
可以一部分异常使用
try-catch处理,另一部分异常使用throws处理
static void test() throws FileNotFoundException {
PrintWriter out = new PrintWriter("/User/prgers/1.txt");
try {
Class cls = Class.forName("Person");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
throws的细节
当父类的方法没有
throws异常,子类的重写方法也不能throws异常
当父类的方法有throws异常,子类的重写方法可以
<1> 不throws异常
<2>throws跟父类一样的异常
<3>throws父类异常的子类型
public class Person {
public void test1() {}
public void test2() throws IOException{}
public void test3() throws IOException{}
public void test4() throws IOException{}
}
public class Student extends Person {
@Override
public void test1() {}
@Override
public void test2(){}
@Override
public void test3() throws IOException {}
@Override
public void test4() throws IOException {}
}
3.4 throw
使用
throw可以抛出一个新建的异常
public class Person {
public Person(String name) {
if(name == null || name.length() == 0) {
throw new IllegalArgumentException("name must not be empty");
}
}
}
4 自定义异常
在开发中,如果想要自定义异常类型, 基本都是以下两种做法
继承Exception
- 使用起来代码会稍微复杂
- 希望开发者重视这个异常,认真处理这个异常
继承RuntimeException
- 使用起来代码会更加简洁
- 不严格要求开发者去处理这个异常
public class EmptyNameException extends RuntimeException {
public EmptyNameException() {
super("name must not be empty");
}
}
举个例子
public class Person {
private String name;
public Person(String name) {
if(name == null || name.length() == 0) {
throw new EmptyNameException();
this.name = name;
}
}
}
5 使用异常的好处
- 将错误处理代码与普通代码区分开
- 能将错误信息传播到调用堆栈中
- 能对错误类型进行区分和分组