异常
什么是异常?
- 概念:程序在运行过程中出现的特殊情况。
- 异常处理的必要性:任何程序都可能存在大量的未知问题、错误;如果不对这些问题进行正确处理,则可能导致程序的中断,造成不必要的损失。
异常的分类
-
Throwable:可抛出的,一切错误或异常的父类,位于java.lang包中。
Error:JVM、硬件、执行逻辑错误,不能手动处理。
public class TestError {
public static void main(String[] args) {
m1();//无穷递归
}
//Exception in thread "main" java.lang.StackOverflowError
//程序当中--->error---->致命的。不能通过手动另外一段代码解决错误
public static void m1() {
int a = 20;
int b = 30;
m1();
}
}
Exception:程序在运行和配置中产生的问题,可处理。
RuntimeException:运行时异常,可处理,可不处理(不管可由java虚拟机处理,打印堆栈跟踪信息)。
import java.util.Scanner;
public class TestException {
public static void main(String[] args) {
//不处理--由java虚拟机处理,打印堆栈跟踪信息
//m1();
//m2();
//m3();
//m4();
//m5();
//m6();
}
//java.lang.ClassCastException类型转换异常
public static void m1() {
Object o = new Integer(2);
Scanner sc = (Scanner)o;
}
//java.lang.ArithmeticException算术异常
public static void m2() {
System.out.println(10/0);
}
//java.lang.ArrayIndexOutOfBoundsException数组下标越界
public static void m3() {
int[] nums = new int[1] ;
System.out.println(nums[1]);
}
//java.lang.StringIndexOutOfBoundsException
public static void m4() {
String str = "ABC" ;
System.out.println(str.charAt(5));
}
//java.util.InputMismatchException输入匹配异常
public static void m5() {
Scanner sc = new Scanner(System.in);
System.out.println("请输入一个数字:");//当输入的不是数值是字符
int num = sc.nextInt();
}
//java.lang.NullPointerException空指针异常
public static void m6() {
Object o = null;
o.hashCode();
}
}
CheckedException:受查异常,必须处理。
public class TestException2 {
public static void main(String[] args)throws Exception {//采用默认的处理方案,JVM处理打印堆栈跟踪信息
Class.forName("xx.xx");//权限命名
}
//运行报错 java.lang.ClassNotFoundException
}
异常的产生
- 自动抛出异常:当程序在运行时遇到不符合规范的代码或结果时,会产生异常。
- 手动抛出异常:语法:throw new 异常类型(“实际参数”);
public class TestThrowException {
public static void main(String[] args) {
Student stu = new Student();
stu.setAge(180);//在调用者角度来说,不清楚违规的问题
System.out.println(stu.getAge());
}
}
class Student{
private int age;
public int getAge() {
return this.age;
}
public void setAge(int age) {
if(age >0 && age<125) {
this.age=age;
}else {
RuntimeException re = new RuntimeException();//1.创建异常对象,单独存在仅仅是个对象
throw re;//2.结合throw关键字,抛出异常
//throw new RuntimeException();
//this.age = 18;//;
}
}
}
- 产生异常结果:相当于遇到return语句,导致程序因异常而终止。
public class TestMethosBack {
public static void main(String[] args) {
//printSign(10);
System.out.println("因为异常的产生所以此句不执行");
}
public static void printSign(int rows) {
if(rows % 2 == 0) {
throw new RuntimeException();//相当于遇到了return语句,无条件结束当前方法
}
System.out.println("小白正在加载菱形");
}
}
异常的传递
- 异常的传递:按照方法的调用链反向传递,如始终没有异常处理,最后会由JVM进行默认异常处理(打印堆栈跟踪信息)
public class TestTransferException {
public static void main(String[] args) {
System.out.println("main--start");
m1(10);
System.out.println("main--end");
}
public static void m1(int n) {
System.out.println("m1--start");
m2(n);
System.out.println("m1--end");
}
public static void m2(int n) {
System.out.println("m2--start");
m3(n);
System.out.println("m2--end");
}
public static void m3(int n) {
System.out.println("m3--start");
if(n%2 == 0) {
throw new RuntimeException();
}
System.out.println("m3--end");
}
}
main--start m1--start m2--start m3--start
- 受查异常:throws声明异常,修饰在方法参数列表后端。throws称为消极的处理方式
public class TestTransferException {
public static void main(String[] args)throws Exception {//JVM打印跟踪信息,抛到了JVM
System.out.println("main--start");
m1(10);
System.out.println("main--end");
//method(2);
}
public static void m1(int n) throws Exception{
System.out.println("m1--start");
m2(n);
System.out.println("m1--end");
}
public static void m2(int n)throws Exception{
System.out.println("m2--start");
m3(n);
System.out.println("m2--end");
}
public static void m3(int n) throws Exception{//向上声明:声明该方法可能存在受查异常
System.out.println("m3--start");
if(n%2 == 0) {
throw new Exception();
// throw new RuntimeException();
}
System.out.println("m3--end");
}
public static void method(int n)throws RuntimeException{//运行时异常可以不声明
if(n%2==0) {
throw new RuntimeException();
}
}
}
- 运行时异常:因可处理,可不处理,无需声明异常。
异常的处理
- try{
可能出现异常的代码
}catch(Exception e){
异常处理的相关代码,如:getMessage()、printStackTrance()
}finally{
无论是否出现异常,都需要执行的代码结构,常用于释放资源。
}
import java.util.Scanner;
public class TestTryCatch {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
System.out.println("请输入一个被除数");
int num1 = sc.nextInt();
System.out.println("请输入一个除数");//当除数为0时,出现异常
int num2 = sc.nextInt();
try {
int result = num1/num2;
System.out.println("运行结果为"+result);
}catch(Exception e){//e=new ArithmeticException
//System.out.println("除数不能为0");//处理方案,自定义处理
//e.printStackTrace();
System.out.println(e.getMessage());
}
System.out.println("程序结束");
}
}
import java.util.Scanner;
public class TestMoreTryCatch {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int num1=0;
int num2 =0;
try {
System.out.println("请输入一个被除数");
num1 = sc.nextInt();
System.out.println("请输入一个除数");//当除数为0时,出现异常
num2 = sc.nextInt();//输入的不能为字母
}(Exception e){
System.out.println("请输入正确的整数");
}
try {
int result = num1/num2;
System.out.println("运行结果为"+result);
}catch(Exception e){//e=new ArithmeticException
System.out.println("除数不能为0");
}
}
}
请输入一个被除数
10
请输入一个除数
o
请输入正确的整数
除数不能为0
没有办法对异常进行精准掌控则使用给一个try-catch即可
常见异常处理结构
- try{} catch{}
- try{} catch{}catch{}
- try{} catch{}finally{}
- try{} catch{}catch{}finally{}
- try{} finally{}
注:多重catch,遵循从子(小)到父(大)的顺序,父类异常在最后
import java.util.InputMismatchException;
import java.util.Scanner;
public class TestMoreTryCatch {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int num1=0;
int num2 =0;
try {
System.out.println("请输入一个被除数");
num1 = sc.nextInt();
System.out.println("请输入一个除数");//当除数为0时,出现异常
num2 = sc.nextInt();//输入的不能为字母
int result = num1/num2;
System.out.println("运行结果为"+result);
//上述程序报的异常一个是ArithmeticException InputMismatchException
}/*catch(Exception e) {
System.out.println("出现未知问题"); //放到最后
}*/catch(ArithmeticException e){
System.out.println("除数不能为0");
}catch(InputMismatchException e) {
System.out.println("请输入正确的整数");
}
}
}
finally优先于return前
public class TestFinally {
public static void main(String[] args) {
System.out.println(method(2));
}
public static int method(int n) {
try{
if(n%2 == 0) {
throw new RuntimeException();
}
return 20;
}catch(Exception e) {
System.out.println("解决异常");
return 30;
}finally {
System.out.println("程序结束");
}
}
}
自定义异常
- 需继承自Exception或Exception的子类,常用于RuntimeException(运行时异常)。
- 必须提供的构造方法:
无参数构造方法
String message参数的构造方法。(调用父类有参构造方法,Throwable)
public class TestDefinedException {
public static void main(String[] args) {
Student stu = new Student();
try{
stu.setAge(250);//是可能出现异常的代码
}catch(Exception e) {
//System.out.println(e.getMessage());//只获得报错的原因即可
System.err.println(e.getMessage());//红色提醒信息
}
try{
stu.setSex("未知");//用以测试异常
}catch(SexMismatchException se) {
//System.out.println(se.getMessage());
System.err.println(se.getMessage());
}catch(Exception e) {
e.printStackTrace();
}
//Class.forName("xxx.xxx");//参数(包名.类名)可能写错! 很具体
}
}
//受查异常
class SexMismatchException extends Exception{
public SexMismatchException() {}
public SexMismatchException(String message) {
super(message);
}
}
//运行时异常
class AgeInputException extends RuntimeException{
public AgeInputException() {}//支持创建无异常原因信息的异常对象
public AgeInputException(String message) {//提供有参构造方法,支持编写异常原因信息
super(message);//调用父类的有参构造方法,为message属性赋值
}
}
//在应用场景下,可以根据自身的需要,自定义异常
class Student{
private int age;
private String sex;
public void setSex(String sex)throws Exception{//告知调用者使用该方法会存在异常必须处理
if(sex.equals("男")||sex.equals("女")) {
this.sex=sex;
}else {
//在用户输入一个性别后;就做好提醒;性别的输入可能不准确;受查异常。
throw new SexMismatchException("性别输入男或者女");
}
}
public int getAge() {
return this.age;
}
public void setAge(int age) {
if(age > 0 && age< 123) {
this.age=age;
}else {
throw new AgeInputException("年龄的赋值应该在0岁——123岁之间");//抛出时异常的父类,不合理。现存的定义好的异常,没有符合现在程序的场景
}
}
}
方法覆盖
- 带有异常声明的方法覆盖:
方法名、参数列表、返回值类型必须和父类相同。
子类的访问修饰符和父类相同或是比父类更宽。
子类中的方法,不能抛出比父类更宽的异常,但可以比父类抛出更多的异常(前提是抛出的异常小于或等于父类异常级别)。
public class TestOverrideException {
public static void main(String[] args) {
Super sup = new Sub();//父类引用指向子类对象 多态
try {
sup.method(); //编译看左边,运行看右边,编译期间调用的是父类的method,运行期间用的子类重写的method
}catch(Exception e) {
e.printStackTrace();
}
}
}
//带有异常的方法覆盖
//1.父类中方法声明了异常,子类重写后可声明也可不声明
//2.父类中方法没有声明异常,则子类也不可以声明异常。
//3.父类中方法声明异常,子类可以声明异常的异常与其相等或是其子类
//4.子类可以声明比父类更多的异常,必须小于其父类声明的异常
class Super{
public void method() throws Exception{
System.out.print("method in Super");
}
}
class Sub extends Super {
public void method()throws Exception /*3.ClassNotFoundException*//*4.ClassNotFoundException,RuntimeException,IOException*/{
System.out.print("method in Sub");
}
}
interface Printable{
void print()throws Exception;
}
class MyClass implements Printable{
@Override
public void print() throws ClassNotFoundException,RuntimeException{
System.out.print("print in MyClass");
}
}
finally的补充扩展知识
扩充方法执行中字节码操作指令
cmd>javap -verbose TestResultValue > TestResultValue.bytecode
public class TestResultValue {
public static void main(String[] args) {
int result = m1();
System.out.println(result);
//当前案例,不能用应用层来理解。//值输出为30
}
public static int m1() {
int a =10;
try {
a=20;
throw new RuntimeException();
}catch(Exception e) {
a=30;
return a;
}finally {
a=40;
}
}
}
-
try可以简单理解为尝试运行代码,任何一行代码出现异常,则不会继续执行try里面后面的代码,而是跳到catch中
-
finally表示必定会执行的代码,无论代码时候出现异常,会在return前执行
-
如果在finally里面写return,将使得前面的代码没有意义,(不要如此)
-
在return之前会将a的值放到内存中的某个位置缓存起来,等会在return的时候将会使用此缓存的值进行返回
基本数据类型,缓存的是变量的值 引用数据类型,缓存的是当前对象的值 当引用数据类型时,finally里面不是改变原引用的地址,而是去改变对象的属性值