day16-异常处理

  1. 概述

  2. 异常体系结构

  3. 常见异常

  4. 异常的处理方式

  5. try-catch方式处理异常

  6. finally关键字

  7. 编译时异常和运行时异常的不同处理方式

  8. throws方式处理异常

  9. 重写方法异常抛出的原则

  10. 开发中如何选择处理异常的方式

  11. 手动抛出异常对象

  12. 自定义异常

  13. 练习

 

1,概述

    平时所说的异常处理是指对Exception的处理

day16-异常处理

 

2,异常体系结构

    Java中异常类的继承关系:     java.lang.Throwable         |----java.lang.Error:一般不写针对性的代码进行处理,处理不了         |----java.lang.Exception:可以进行处理的异常             |----编译时异常                 |----IOException                     |----FileNotFoundException                 |----ClassNotFoundException             |----运行时异常                 |----NullPointerException                 |----ArrayIndexOutOfBoundsException                 |----ClassCastException                 |----NumberFormatException                 |----InputMismatchException                 |----ArithmeticException  

3,常见异常

package com.atguigu.java;

import java.util.Date;
import java.util.Scanner;

import org.junit.Test;

public class ExceptionTest {
	// 编译时异常在Eclipse中会直接标出错误,javac命令不会通过并生成字节码文件
	// 以下都是运行时异常
	// NullPointerException,空指针异常。当一个引用为null时还通过它去调用一些结构
	@Test
	public void test1() {
		int[] arr = null;
		System.out.println(arr[2]);
	}
	
	// IndexOutOfBoundException,索引越界
	@Test
	public void test2() {
		// ArrayIndexOutOfBoundException
		int[] arr = new int[10];
		System.out.println(arr[10]);
		// StringIndexOutOfBoundException
		String str = "abc";
		System.out.println(str.charAt(3));
	}
	
	// ClassCastException,类型转换异常
	@Test
	public void test3() {
		Object obj = new Date();
		String str = (String)obj;
	}
	
	// NumberFormatException
	@Test
	public void test4() {
		String str = "abc";
		int num = Integer.parseInt(str);
	}
	
	// InputMismatchException
	@Test
	public void test5() {
		Scanner scanner = new Scanner(System.in);
		int score = scanner.nextInt(); // 当输入一个字符串时报错
		System.out.println(score);
		scanner.close();
	}
	
	// ArithmeticException
	@Test
	public void test6() {
		int a = 10;
		int b = 0;
		System.out.println(a / b);
	}
}

 

4,异常的处理方式

    异常的处理过程:抓抛模型         抛:程序在执行过程中一旦出现异常,就会在异常代码处生成一个对应异常类的对象,并将此对象抛出。接着其后的代码就不再继续执行,程序中止         抓:处理异常的方式。1,try-catch-finally;2,throws

 

5,try-catch方式处理异常

package com.atguigu.java;

/*
 * try-catch-finally格式:
 *     try {
 *     // 可能会出现异常的代码
 *     }catch(异常类型1 变量名1) {
 *         // 处理异常的方式1
 *     }catch(异常类型2 变量名2) {
 *         // 处理异常的方式2
 *     }
 *     ...
 *     finally { // 可选的
 *         // 一定会执行的代码
 *     }
 * 执行过程:将可能出现异常的代码放在try{}中,当出现异常并抛出异常对象后,该对象与后面catch
 *     中的异常类型进行匹配,匹配上后进入catch进行异常处理,处理完后在没有finally的前提下,
 *     结束try-catch结构并执行后面的代码
 * catch中的异常类型如果不是子父类的关系,则先后顺序不重要;如果有子父类关系,则子类写在前,
 *     否则报错
 * 在catch中常用的异常处理方式,都能看到异常信息,e.getMessage()返回字符串,e.printStackTrace()
 *     打印详细信息
 * 在try中定义的变量,出了try{}后就不能再被调用
 */

import org.junit.Test;

public class ExceptionTest1 {
	@Test
	public void test1() {
		String str = "abc";
		int num = 0;
		try {
			num = Integer.parseInt(str); // 这里抛出异常的对象后,在try{}中之后的代码就不再执行
			System.out.println("hello---1");
		}catch(NumberFormatException e) { // 因为异常类型相符,捕获处理了上面抛出的异常并执行catch{}中的语句
			// System.out.println("数值转换异常");
			// System.out.println(e.getMessage());
			e.printStackTrace();
		}catch(NullPointerException e) {
			System.out.println("空指针异常");
		}catch(Exception e) { // 父类异常
			System.out.println("出现异常");
		}
		
		System.out.println(num); // 上面声明num必须要给初始化值,因为在try{}中有可能不会赋值
		System.out.println("hello---2"); // 代码正常执行
	}
}

 

6,finally关键字

package com.atguigu.java;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;

import org.junit.Test;

/*
 * finally中的代码一定会被执行,即使catch中也出现了异常导致程序中止,或者是try中return
 *     语句,catch中有return语句结束方法调用
 * 像数据库连接、输入输出流、网络编程socket等资源,JVM是不能自动回收的,需要我们手动进行
 *     资源释放,此时释放资源的操作就可以写在finally中
 * try-catch语句可以嵌套
 */

public class FinallyTest {
	@Test
	public void testMethod() {
		int num = method();
		System.out.println(num);
	}
	
	public int method() { // 要保证在try-catch中有一个返回值
		try {
			int[] arr = new int[10];
			System.out.println(arr[10]); // 在这里抛出异常
			return 1;
		}catch(ArrayIndexOutOfBoundsException e) {
			System.out.println(e.getMessage());
			return 2; // 有可能try{}中有异常导致return没有执行,所以这里要有return
		}finally {
			System.out.println("一定会执行"); // try{}中抛出异常被catch捕获,当
						// return 2执行完,代表这个方法调用完成,但在return之前会先
						// 执行finally中的操作
			// return 3; // 由上可知,finally在return 2之前执行,所以最终返回的是3
		}
	}
	
	@Test
	public void test1() {
		try {
			int a = 10;
			int b = 0;
			System.out.println(a / b);
		}catch(ArithmeticException e) {
			// e.printStackTrace();
			int[] arr = new int[10];
			System.out.println(arr[10]); // 在这里抛出异常,会导致程序中止
		}catch(Exception e) {
			e.printStackTrace();
		}finally {
			System.out.println("一定会执行"); // 会被执行
		}
	}
	
	@Test
	public void test2() {
		FileInputStream fis = null;
		try { // 这是一段在编译时就可能抛出异常的代码,如果不加try-catch就过不了编译
			File file = new File("hello.txt"); // 如果当前路径下没有这个文件,这里就会抛出未找到文件的异常
			fis = new FileInputStream(file);
			
			int data = fis.read(); // 可能会抛出IO异常
			while(data != -1) {
				System.out.print((char)data);
				data = fis.read();
			}
		}catch(FileNotFoundException e) {
			e.printStackTrace();
		}catch(IOException e) {
			e.printStackTrace();
		}finally {
			try {
				if(fis != null) // 如果new File失败,那fis还是null,此时直接执行关闭操作会报空指针异常
					fis.close(); // 如果try{}中生成了流对象,最后就要关闭它。这个操作本身也可能抛出异常,所以要放在try-catch中
			}catch(IOException e) {
				e.printStackTrace();
			}
		}
	}
}

 

7,编译时异常和运行时异常的不同处理方式

    对于如空指针异常的运行时异常,不管做不做异常处理,如果执行了printStackTrace(),效果都差不多,还是要改写代码。且运行时异常随处可见,到处都有可能报错     开发中一般不对运行时异常做处理,但一定要对编译时异常做处理,因为编译都过不去就不谈程序运行了     使用try-catch处理编译时异常,使得程序在编译时不报错,但运行时仍有可能报错,这样做只是将编译时可能出现的异常延迟到运行时出现

 

8,throws方式处理异常

package com.atguigu.java;

/*
 * 格式:throws + 异常类型 写在方法声明处,指明执行该方法可能会抛出的异常。当该方法执行
 *     并出现异常时,就会在异常出生成一个异常类的对象,该对象如果是throws后的异常类型,
 *     就会被抛出,对该方法来讲异常处理结束。异常代码之后的代码就不再执行
 * try-catch-finally是真正将异常处理掉了,而throws只是将异常抛给了调用者,并未将异常
 *     处理掉
 */

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;

public class ExceptionTest2 {
	
	public static void main(String[] args) {
		try { // 在main()中就必须要处理了,否则程序中止
			method2();
		}catch(IOException e) { // 如果method2()抛出的是两种异常,那么针对这两种,要写两个catch
			e.printStackTrace();
		}
		
		method3(); // 异常已被处理过
	}
	
	public static void method3() {
		try {
			method2();
		}catch(IOException e) {
			e.printStackTrace();
		}
	}
	
	public static void method2() throws IOException { // 调用method1()可能会有异常,继续向上抛出,IOException是父类,就将两种异常写成一个
		method1();
	}
	
	public static void method1() throws FileNotFoundException, IOException { // 将可能出现的异常抛给调用该方法的地方
		File file = new File("hello.txt");
		FileInputStream fis = new FileInputStream(file);
		
		int data = fis.read();
		while(data != -1) {
			System.out.print((char)data);
			data = fis.read();
		}
		fis.close();
	}
}

 

9,重写方法抛出异常的原则

    在方法重写时提到,重写方法抛出的异常可以是被重写方法抛出的异常,或者比后者小的异常类型

package com.atguigu.java;

import java.io.FileNotFoundException;
import java.io.IOException;

/*
 * 方法重写规则之一:
 *     子类重写的方法抛出的异常类型不大于父类被重写方法抛出的异常类型
 * 通过下面的例子可知,父类被重写方法中如果没有抛异常,子类重写方法中绝对不能抛异常
 */

public class OverrideTest {
	
	public static void main(String[] args) {
		OverrideTest test = new OverrideTest();
		test.display(new SubClass());
	}
	
	public void display(SuperClass s) {
		try {
			s.method(); // 在main()中传入的是一个子类对象,这里调用的是子类重写的方法
		}catch(IOException e) { // 方法形参是SuperClass类型,它的method()会抛出IOException类型异常
							// 所以被重写方法抛出的异常要不大于重写方法抛出的异常,才能在多态情况下正常使用
			e.printStackTrace();
		}
	}
}

class SuperClass {
	public void method() throws IOException {

	}
}

class SubClass extends SuperClass {
//	public void method() { // 没有抛出异常也是小于被重写方法抛出的异常类型
//		
//	}
	public void method() throws FileNotFoundException {
		
	}
}

 

10,开发中如何选择处理异常的方式

    如果父类中被重写方法没有throws抛出异常,则子类重写方法也不能使用throws抛出异常,意味着如果子类重写方法中有异常,只能使用try-catch-finally方式处理     在这种情况下:A方法的执行结果被B方法使用,B方法的执行结果被C方法使用,这三个方法都有可能出现异常,不要在每个方法中都用try-catch处理,而是将异常用throws抛出交给最后调用者一起处理  

11,手动抛出异常对象

    在抓抛模型中,“抛”是异常对象的产生,既可以是由系统自动生成的异常对象,也可以是手动生成的异常对象并抛出(throw)     throw与throws,对于手动生成的异常对象,需要使用throw将其抛出,写在方法体内,这是抛的过程;throws是用来处理异常的,将生成的异常对象向调用方抛出,写在方法的声明处,属于抓的过程

package com.atguigu.java;

import javax.management.RuntimeErrorException;

public class StudentTest {
	
	public static void main(String[] args) {
		try {
			Student s = new Student();
			s.regist(1001);
			System.out.println(s);
		} catch (Exception e) {
			// TODO Auto-generated catch block
			System.out.println(e.getMessage());
		}
	}
}

class Student {
	private int id;
	
	public void regist(int id) throws Exception {
		if(id > 0) {
			this.id = id;
		}else {
			// System.out.println("输入有误"); // 在没有手动抛出异常时,这个方法执行完,上面的main()中就该打印属性id为0的默认值了
			// 所以需要在这里手动抛出异常来让程序中止		
			// throw new RuntimeException("输入有误"); // 运行时异常,可以不用去处理编译也能通过,传入的字符串就是getMessage()返回的
			throw new Exception("输入有误"); // 包含编译时异常,必须要处理了
		}
	}

	@Override
	public String toString() {
		return "Student [id=" + id + "]";
	}
	
}

 

12,自定义异常

package com.atguigu.java;

/*
 * 自定义异常类的步骤:
 *     1,继承现有的异常类:RuntimeException、Exception
 *     2,提供全局常量:serialVersionUID
 *     3,提供重载的构造器
 */

public class MyException extends Exception {
	static final long serialVersionUID = -703489787677766939L; // 用来标识唯一的类
	
	public MyException() {
		
	}
	
	public MyException(String msg) {
		super(msg);
	}
}
    使用自定义异常类,可以把上面Student类中regist方法中抛出的异常改为MyException,new的时候调用的就是重写的第二个构造器

 

13,练习

package com.atguigu.java;

public class ReturnExceptionDemo {
	static void methodA() {
		try {
			System.out.println("进入方法A"); // 1
			throw new RuntimeException("制造异常"); // 3
		}finally {
			System.out.println("用A方法的finally"); // 2
		}
	}

	static void methodB() {
		try {
			System.out.println("进入方法B"); // 4
			return;
		}finally {
			System.out.println("调用B方法的finally"); // 5
		}
	}

	public static void main(String[] args) {
		try {
			methodA();
		}catch(Exception e) {
			System.out.println(e.getMessage());
		}
		
		methodB();
	}
}
    自定义异常

package com.atguigu.java;

public class EcDef extends Exception { // 自定义异常类
	static final long serialVersionUID = -3387516993624229948L;
	
	public EcDef() {
		
	}
	
	public EcDef(String msg) {
		super(msg);
	}
}
    测试

package com.atguigu.java;

public class EcmDef {

	public static void main(String[] args) {
		try {
			int i = Integer.parseInt(args[0]); // 这里也可能出现运行时异常,可以不用去管它
			int j = Integer.parseInt(args[1]);
			
			int result = ecm(i, j); // 可能会抛出EcDef类型的异常
			System.out.println(result);
		}catch(NumberFormatException e) {
			System.out.println("数据类型不一致");
		}catch(ArrayIndexOutOfBoundsException e) {
			System.out.println("缺少命令行参数");
		}catch(ArithmeticException e) {
			System.out.println("除数为0");
		}catch(EcDef e) {
			System.out.println(e.getMessage());
		}
	}
	
	public static int ecm(int i, int j) throws EcDef { // 因为是Exception类型的非运行时异常,既然没有try-catch,那就一定要抛出
										// 如果throw的是一个RuntimeException,则不用throws
		if(i < 0 || j < 0) {
			throw new EcDef("分子或分母为负数");
		}
		return i / j;  // 可能出现除数为0的运行时异常
	}
}

上一篇:敏捷开发流程总结


下一篇:英语阅读——Love and logic:The story of a fallacy