目录
Java 异常处理
异常概述
在程序中错误可能产生于程序员没有预料到的各种情况,或者超出了程序员可控范围的环境因素。
在Java中在程序运行时可能出现的错误叫做异常。
异常是一个在程序执行期间发生的事,它中断了正在执行的程序的正常指令流。
异常机制已经成为判断一门编程语言是否成熟的标准。
错误: 错误不是异常,而是脱离程序员控制的问题。错误在代码中通常被忽略。例如,当栈溢出时,一个错误就发生了,它们在编译也检查不到的。
package yichang;
/** 异常是程序中的一些错误,但并不是所有的错误都是异常,并且错误有时候是可以避免的。
比如说,你的代码少了一个分号,那么运行出来结果是提示是错误 java.lang.Error;
如果你用System.out.println(11/0),那么你是因为你用0做了除数,会抛java.lang.ArithmeticException 的异常。
*/
public class D1 {
public static void main(String[] args) {
System.out.println(10);//会执行
double a = 10/0;
System.out.println(a);//不会执行
System.out.println(100);//不会执行
}
}
/**执行结果
10
Exception in thread "main" java.lang.ArithmeticException: / by zero
at yichang.D1.main(D1.java:6)
*/
程序员真理:程序会出错!!!
- 用户输入了非法数据。
- 要打开的文件不存在。
- 网络通信时连接中断,或者JVM内存溢出。
这些异常有的是因为用户错误引起,有的是程序错误引起的,还有其它一些是因为物理错误引起的。
- 检查性异常(Checked):最具代表的检查性异常是用户错误或问题引起的异常,这是程序员无法预见的
例如要打开一个不存在文件时,一个异常就发生了,这些异常在编译时不能被简单地忽略。
Java认为Checked异常都是可以在编译阶段被处理的异常,所以强制程序处理所有的Checked异常
Checked异常体现了java设计哲学:没有完善处理的代码根本不会被执行,体现了java的严谨性
Checked异常的处理方式:
①:当方法明确知道如何处理异常,程序应该使用try...catch块来捕获该异常,然后在对应的catch块中修补该异常。
②:当方法不知道如何处理异常,应该在定义该方法时声明抛出该异常。
- 运行时异常: 运行时异常是可能被程序员避免的异常。与检查性异常相反,运行时异常可以在编译时被忽略。
Runtime异常无须显式声明抛出,如果程序需要捕捉Runtime异常,也可以使用try...catch块来捕获Runtime异常。
问题是:大部分的方法总是不能明确知道如何处理异常,这就只能声明抛出异常了。
异常处理机制
Exception 类的层次
- 所有的异常类是从 java.lang.Exception 类继承的子类。
- Exception 类是 Throwable 类的子类。除了Exception类外,Throwable还有一个子类Error 。
- Java 程序通常不捕获错误。它们在Java程序处理的范畴之外。
-
Error 用来指示运行时环境发生的错误。例如,JVM 内存溢出。一般地,程序不会从错误中恢复。
-
异常类有两个主要的子类:IOException 类和 RuntimeException 类。
Java常见异常
package yichang;
public class D2 {
public static void main(String[] args) {
D2.pp();//无限循环调用了
}
public static void pp() {
System.out.println("this is pp....");
pp();
}
}
/**
运行结果
。。。
。。。
this is pp....
this is pp....
this is pp....
Exception in thread "main" java.lang.StackOverflowError
。。。
。。。
*/
package yichang;
public class D1 {
public static void main(String[] args) {
// double a = 10 / 0; //修改bug注释掉
//这里明显是一个编程的bug,因为除法计算要求分母非0。修正bug,这里进行分母为0的处理
int k = 0;
if (k != 0)
System.out.println(10 / k);
else
System.out.println("除数为0,不能执行除法计算");
System.out.println("end.....");
}
}
/**运行结果:
除数为0,不能执行除法计算
end.....
*/
package yichang;
public class D3 {
public static void main(String[] args) {
D3 a = new D3();
a=null;
System.out.println(a); // println 方法 String s = String.valueOf(x); 输出String值
System.out.println(a.toString()); //D3继承了object的toString()方法,输出对象的地址
}
}
/**
输出结果
null
Exception in thread "main" java.lang.NullPointerException
at yichang.D3.main(D3.java:8)
这里出错的原因是调用对象的方法之前没有进行非空判断,所以NullPointerException
bug处理方法
if (a != null)
System.out.println(a.hashCode());
else
System.out.println("obj没有具体对象");
*/
package yichang;
public class D4 {
public static void main(String[] args) {
int[] arr=new int[3]; //异常的原因是访问数组的下标超出允许的范围[0,arr.length]
System.out.println(arr[3]); //ArrayIndexOutOfBoundsException
//异常的原因是通过下标访问字符串中字符时,超出了下标允许的范围[0,str.length()]
String str="abcd";
System.out.println(str.charAt(4));//StringIndexOutOfBoundsException
}
}
/**运行結果
Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: 3
at yichang.D4.main(D4.java:6)
解决方法:通过下标访问时应该判定下标的取值是否合法
int[] arr = new int[] { 1, 2, 3, 4 };
int index = 0;
if (index >= 0 && index < arr.length)
System.out.println(arr[index]);// 避免ArrayIndexOutOfBoundsException
String str = "asdfasdf";
if (index >= 0 && index < str.length())
System.out.println(str.charAt(index));// 避免StringIndexOutOfBoundsException
*/
④常见的 第四种运行时异常:ClassCastException试图把一个对象转换成非法类型
package yichang;
public class D5 extends DA {
public static void main(String[] args) {
Object a = new D5();
D5 c = new D5();
DA cc = new DA();
DA d = (DA) c; // 强转a或c都可以运行
// DB b = (DB)a; //a用c不能通过编译 ,DB类和D5类没关系,不能强转 用a编译可以通过视为向下转型,运行不行实际上是D5类没关系
D5 e = (D5) cc;// 可以通过编译,但是发生向下转型异常
}
}
class DA {
public DA() {
System.out.println("调用DA方法!");
}
}
class DB {
}
/**
运行结果:
调用DA方法!
调用DA方法!
调用DA方法!
Exception in thread "main" java.lang.ClassCastException: yichang.DA cannot be cast to yichang.D5
at yichang.D5.main(D5.java:10)
解决方案:进行类型强制转换之前,应该对变量类型进行判断,如果合法才进行类型的转换
D5 e = (D5) cc;替换为:
if(cc!=null && cc instanceof D5){
D5 e = (D5)cc;
}
else
System.out.println("cc不是D5类型");
*/
⑤常见的第五种运行时异常:NumberFormatException数据格式异常,一般出现在数据类型转换中
package yichang;
public class D6 {// NumberFormatException数据格式异常,一般出现在数据类型转换中
public static void main(String[] args) {
String a = "1.23";
Double c = Double.parseDouble(a);//可以运行
System.out.println(c);
Integer b = Integer.parseInt(a);// 可以通过编译,但不能运行
System.out.println(b);
}
}
/**
运行结果:
1.23
Exception in thread "main" java.lang.NumberFormatException: For input string: "1.23"
at java.lang.NumberFormatException.forInputString(NumberFormatException.java:65)
at java.lang.Integer.parseInt(Integer.java:580)
at java.lang.Integer.parseInt(Integer.java:615)
at yichang.D6.main(D6.java:8)
*/
这里进行数据的合法性验证后再进行数据类型转换比较繁琐,所以最终的解决方案是使用try/catch语法结果,针对出现问题后进行处理 :
package yichang;
public class D6 {// NumberFormatException数据格式异常,一般出现在数据类型转换中
public static void main(String[] args) {
String a = "1.23";
Double c = Double.parseDouble(a);
System.out.println(c);
Integer kk = 0;
try {
kk = Integer.parseInt(a); // 如果没有问题则进行数据类型转换
} catch (NumberFormatException e) {
kk = 100;// 如果数据不合法,则使用这个默认值
}
System.out.println(kk);
}
}
/**
输出结果:
1.23
100
*/
Java 内置异常类
Java 根据各个类库也定义了一些其他的异常,下面的表中列出了 Java 的非检查性异常。
下面的表中列出了 Java 定义在 java.lang 包中的检查性异常类。
异常方法
下面的列表是 Throwable 类的主要方法:
异常的捕获和处理
注意:
- try块中的局部变量和catch块中的局部变量(包括异常变量),以及finally中的局部变量,他们之间不可共享使用。
- Java采用的是终结式异常处理机制,java中异常处理的任务就是将执行控制流从异常发生的地方转移到能够处 理这种异常的地方去。也就是说:当一个函数的某条语句发生异常时,这条语句的后面的语句不会再执行。它失去了焦点,执行流跳转到最近的匹配的异常处理catch代码块去执行,异常被处理完后,执行流会接着在“处理了这个异常的catch代码块”后面接着执行。
- try{}后面必须跟一个finally或者catch,其中有个特殊写法可以省略后面的finally和catch
package yichang;
public class D7 {
public static void main(String[] args) {
Integer kk = null;
try {
String str = "123.456";
kk = Integer.parseInt(str); // 由于str参数数据不合法,不是整数,所以这里异常NumberFormatException
System.out.println("数据转换结束");// 上句出现异常,这里就不会执行
} catch (Exception e) {// 出现异常后进行类型判断,如果是这种异常则执行这里的代码段,可以有多个捕捉异 常
System.out.println("出现了错误:" + e.getMessage()); // e.getMessage():String用于获取异常的 提示信息
e.printStackTrace(); // 在控制台(标准错误流System.err)上输出异常调用栈。底层问题输出顺序未必就在System.out.println("转换结果为:" + kk);前面,随机的。
}
System.out.println("转换结果为:" + kk);
}
}
输出结果:
出现了错误:For input string: "123.456"
java.lang.NumberFormatException: For input string: "123.456"
转换结果为:null
at java.lang.NumberFormatException.forInputString(NumberFormatException.java:65)
at java.lang.Integer.parseInt(Integer.java:580)
at java.lang.Integer.parseInt(Integer.java:615)
at yichang.D7.main(D7.java:8)
- catch 不能独立于 try 存在。
- 在 try/catch 后面添加 finally 块并非强制性要求的。
- try 代码后不能既没 catch 块也没 finally 块。
- try, catch, finally 块之间不能添加任何代码。
- 如果throw语句抛出的异常是Checked异常,则该throw语句要么处于try块里,显式捕获该异常,要么放在一个带throws声明抛出的方法中,即把该异常交给该方法的调用者处理。
- 如果throw语句抛出的异常是Runtime异常,则该语句无须放在try块中,也无须放在带throws声明抛出的方法中,程序既可以显示使用try...catch来捕获并处理该异常,也可以完全不理会该异常,把该异常交给该方法调用者处理。
package yichang;
import java.util.Scanner;
public class D8 {
public static void main(String[] args) throws Exception {
Scanner sc = new Scanner(System.in);
String a = sc.nextLine();
if ("yuan".equals(a)) {
throw new RuntimeException("dddd");// 编码产生异常,其中构造器参数就是异常信息
}
System.out.println("键盘录入为" + a);
System.out.println("程序终止。");
}
}
Integer kk=null;
try{
String str="123.456";
kk=Integer.parseInt(str);
System.out.println("数据转换结束");
} catch(NumberFormatException e){
System.out.println("数据格式错误!");
//继续抛出异常,不再是直接消费处理掉了,导致的结果是try之后的代码并没有得到执行
throw new Exception(e.getMessage());
//throw e;在用在方法上throws声明抛出,因为 NumberFormatException是运行时异常
//throw new Exception(""); 需要在方法上声明抛出,因为Exception是受检型异常
} catch(Exception e){
System.out.println("出现了错误:"+e.getMessage());
}
System.out.println("转换结果为"+kk);
- 在catch中声明的异常对象封装了异常发生的信息,在catch语句块中可以使用这个对象的一些方法获取这些信息getMessage():String
- 用于获取有关异常事件的信息,一般是异常信息的描述,例如【For input string: "123.456"】
- toString():String,输出格式为【java.lang.NumberFormatException: For input string: "123.456"】
- printStackTrace():void用来跟踪异常事件发生时执行堆栈的内容,注意:这里的输出默认采用错误流
- System.err进行输出,如果还使用了System.out进行输出,则不能保证显示顺序
-
注意:使用 printStackTrace ()输出调用栈使用的是 System.err 输出报错信息,不是编程使用的 System.out ,所以输出顺序有可能和预计不一致
-
System.out.println(e); //java.lang.ArithmeticException: / by zeroSystem.out.println(e.getMessage()); // / by zeroe.printStackTrace(); 调用栈
JDK1.7引入多异常捕获
try{
int k=0; system.out.println(10/k);
}catch(ArithmeticException e){
System.out.println(e);
}catch(NullPointerException e){
System.out.println(e);
}
//两次捕捉异常的处理是一样的,就可以将两个异常卸载一个catch中,其中多个异常类型之间使用|分割
try{
int k=0;
system.out.println(10/k);
}catch(ArithmeticException | NullPointerException e){//含义和上面的一致
System.out.println(e);
}
package yichang;
public class D09 {
public static void main(String[] args) {
try{
int k = 0;
System.out.println(10 / k);
}catch(ArithmeticException e){
System.out.println(e);
e=new ArithmeticException("YYYYY"); //注意按照Java变量的规则,这里的类型不能变,所以可以new当前类型,也可以子类型 ;
throw e;
}catch(NullPointerException e){
System.out.println(e);
}
}
}
package yichang;
public class D10 {
public static void main(String[] args) {
try {
int k = 0;
System.out.println(10 / k);
} catch (ArithmeticException | NullPointerException e) { // 要求异常类型之间不能有继承关系,如果有继承 关系应该只写大异常类型即可
System.out.println(e);
// e=new ArithmeticException("yyyy"); //错的。所以不能修改对象引用
throw e;
}
}
}
编码练习: 要求输入一个合法的字符串类型的整数,进行数据类型转换,如果数据不合法则中断程序执行,并进行提示。
package yichang;
import java.util.Scanner;
public class D11 {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
System.out.println("请输入一个整数");
String ss = sc.nextLine();
try {
int kk = Integer.parseInt(ss.trim());
System.out.println("输入数据正确,具体值为:" + kk);
} catch (NumberFormatException e) {
System.out.println("输入的数据格式错误!");
} catch (Exception e) {
System.out.println("其它错误:" + e.getMessage());
}finally{ //不管陷阱代码中是否出现异常,是否执行了catch语句,finally语句块一定要执行
System.out.println("finally....");
}
System.out.println("end pp....");//这个语句有可能不执行,但是finally是无论如何都会被执行。
//典型案例去掉所有的catch语句,当出现异常时finally还是执行了的,但是这个语句没有执行
}
}
- status是非零参数,那么表示是非正常退出。
- System.exit(0)是正常退出程序,而System.exit(1)或者说非0表示非正常退出程序。
package yichang;
public class D12 {
public static void main(String[] args) {
try {
int a = 10;
System.out.println(10/a);
System.exit(0);//無條件立即终止执行
}finally {//按道理无论try是否抛出异常都要执行finally,此时finally却没被执行
System.out.println("finally 语句块!");
}
}
}
/*
输出结果:
1
*/
- 程序所在的线程死亡或者cpu关闭
- 如果在finally代码块中的操作又产生异常,则该finally代码块不能完全执行结束
package yichang;
import java.util.Scanner;
public class D13 {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
System.out.println("请输入一个整数");
String ss = sc.nextLine();
try {
int kk = Integer.parseInt(ss.trim());//strim()方法去掉输入前后的空格,如果输入错误,是NumberFormatException错误
System.out.println("输入数据正确,具体值为:" + kk);
System.exit(0);//终止了finally语句块的执行
} finally {
int kk = 0;
System.out.println(10 / kk); // 这里出现异常,提供提示为ArithmeticException, 而没有上面出现的NumberFormatException 了
System.out.println("finally代码块执行结束。");//不执行,finally代码块不能完全执行结束
}
}
}
package yichang;
import java.util.Scanner;
public class D14 {
public int yuan() {
try {
Scanner sc = new Scanner(System.in);
System.out.println(10 / sc.nextInt());// 可能出现ArithmeticException异常和NumberFormatException异常
System.out.println("try...");//出现上述两种输入异常 此语句不会执行
return 0;// 由于有finally语句,所以这里的return不会立即执行 ,要等finally语句执行后才能返回
} catch (Exception e) {
System.out.println("catch...");
return 1;
} finally {
System.out.println("finally...");
return 2;//这里的return会替换前面的return结果,不管是否异常都是 2。
}
}
public static void main(String[] args) {
D14 a = new D14();
System.out.println(a.yuan());
}
}
一、final :
1、修饰符(关键字) 如果一个类被声明为final,意味着它不能再派生新的子类,不能作为父类被继承。因此一个类不能及被声明为abstract,又被声明为final的。
2、将变量或方法声明为final,可以保证他们使用中不被改变。被声明为final的变量必须在声明时给定初值,而以后的引用中只能读取,不可修改,被声明为final的方法也同样只能使用,不能重载。
二、finally:
在异常处理时提供finally块来执行清楚操作。
三、finalize:
是方法名。java技术允许使用finalize()方法在垃圾收集器将对象从内存中清除之前做必要的清理工作。这个方法是在垃圾收集器在确定了,被清理对象没有被引用的情况下调用的。
finalize是在Object类中定义的,因此,所有的类都继承了它。子类可以覆盖finalize()方法,来整理系统资源或者执行其他清理工作。
如果catch代码块里面为空就会隐藏异常。catch(Exception e){//异常被隐藏了}
自定义异常
- 所有异常都必须是 Throwable 的子类。
- 如果希望写一个检查性异常类,则需要继承 Exception 类。只继承Exception 类来创建的异常类是检查性异常类。
- 如果你想写一个运行时异常类,那么需要继承 RuntimeException 类。
- 一个异常类和其它任何类一样,包含有变量和方法。
package yichang;
public class BankAccount { //银行账户类
//balance为余额,number为卡号
private double balance;
private long number;
public BankAccount(long number) { //构造方法
this.number = number;
}
public void deposit(int amounts) {//deposit(存款)这是个存钱的动作,方法
this.balance += amounts;
}
public void withdraw(int amounts)throws InsufficientFundsException {//withdraw取钱方法,throws该方法可能抛出的异常
if(amounts < this.balance)
this.balance -= amounts;
else {
double needs = amounts - this.balance;//是个正数,说明了还欠多少钱
throw new InsufficientFundsException(needs);//抛出具体的对象
}
}
public double getbalance() {//获取当前余额值
return this.balance;
}
public long getnumber() {//获取卡号
return this.number;
}
}
package yichang;
//资金不足异常类 InsufficientFundsException
public class InsufficientFundsException extends Exception {
private double amounts; // 此处的amounts(金额)用来储存当出现异常(取出钱多于余额时)所缺乏的钱.
public InsufficientFundsException(double amounts) {// 构造函数给金额amounts赋值
this.amounts = amounts;
}
public double getamounts() {//获取当前金额,就是知道现在有多少钱
return amounts;
}
}
package yichang;
public class BankDemo {//银行的一个账户小样类
public static void main(String[] args) {
BankAccount a = new BankAccount(1112138);
System.out.println("存款五万元!");
a.deposit(50000);//调用存款方法存款五万元
try {
System.out.println("\n取款三万元");
a.withdraw(30000);//调用方法取款三万元
System.out.println("\n再取三万块");
a.withdraw(30000);
}catch(InsufficientFundsException e) {
System.out.println("余额不足还差"+e.getamounts());
}
}
}
输出结果:
存款五万元!
取款三万元。
再取三万块。
余额不足还差10000.0
通用异常
在Java中定义了两种类型的异常和错误。
- JVM(Java虚拟机) 异常:由 JVM 抛出的异常或错误。例如:NullPointerException 类,ArrayIndexOutOfBoundsException 类,ClassCastException 类。
- 程序级异常:由程序或者API程序抛出的异常。例如 IllegalArgumentException 类,IllegalStateException 类。
世界上的意外不可穷举,程序可能发生的异常总是大于程序员所能考虑的意外!