1 、异常概述与异常体系结构
问题的引入:在使用计算机语言进行项目开发的过程中,即使程序员把代码写得尽善尽美, 在系
统的运行过程中仍然会遇到一些问题,因为很多问题不是靠代码能够避免的,比如:客户输入数据
的格式,读取文件是否存在,网络是否始终保持通畅等等。
- 异常:在Java语言中,将程序执行中发生的不正常情况称为“异常”。(开发过程中的语法错误
和逻辑错误不是异常)
-
Java程序在执行过程中所发生的异常事件可分为两类:
-
Error:Java虚拟机无法解决的严重问题。如:JVM系统内部错误、资源耗尽等严重情况。比如:StackOverflowError和OOM。一般不编写针对性 的代码进行处理。 (一般问题都比较严重)public class ErrorTest {
public static void main(String[] args) {
//栈溢出:递归调用就可能会导致
//StackOverflowError
main(args);//来回反复调用main ,内存会很快溢出OOM错误://new实在堆空间中的,如果空间过大,堆空间就可能不够用//2.堆溢出,java.lang.OutOfMemoryEorry
Integer[] arr = new Integer[1024*1024*1024*1024*1024];
}
} -
Exception: 其它因编程错误或偶然的外在因素导致的一般性问题,可以使 用针对性的代码
- 对于这些错误,一般有两种解决方法:一是遇到错误就终止程序的运行。另一种方法是由程
序员在编写程序时,就考虑到错误的检测、错误消息的提示,以及错误的处理。
-
捕获错误最理想的是在编译期间,但有的错误只有在运行时才会发生。比如:除数为0,数组下标越界等
-
分类:编译时异常和运行时异常
-
- 运行时异常:
-
是指编译器不要求强制处置的异常。一般是指编程时的逻辑错误,是程序员应该积极避免其出现的异常。java.lang.RuntimeException类及它的子类都是运行时异常。
-
对于这类异常,可以不作处理,因为这类异常很普遍,若全处理可能会对程序的可读性和运行效率产生影响。
- 运行时异常:执行java.exe命名时,可能出现的异常
2. 编译时异常
-
是指编译器要求必须处置的异常。即程序在运行时由于外界因素造成的一般性异常。编译器要求Java程序必须捕获或声明所有编译时异常。
-
对于这类异常,如果程序不处理,可能会带来意想不到的结果。
- 编译时异常:执行javac.exe命名时,可能出现的异常。
2.常见异常 :
-
java.lang.RuntimeException(运行时异常)
ClassCastException 两个类型间转换不兼容引发的运行异常- ArrayIndexOutOfBoundsException 数组越界
NullPointerException 空指针异常 ArithmeticException 算术异常- NumberFormatException 数字格式异常
- InputMismatchException 输入不匹配异常
2. 编译时异常
.IOExeption 输入或输出异常 (读写异常) FileNotFoundException 文件找不到异常EOFException java.lang.ClassNotFoundException- java.lang.InterruptedException
- java.io.FileNotFoundException
- java.sql.SQLException
面试题:常见的异常有哪些?
常见异常举例:
1.空指针异常:
//空指针异常
@Test
public void test2(){
int[] arr = null;
System.out.println(arr[1]);
String str = "abc";
str = null;
System.out.println(str.charAt(0));
}
2.数组角标越界
@Test
public void test1(){
// int[] arr = new int[3];
// System.out.println(arr[-1]);
String str = "abc";
System.out.println(str.charAt(3));
}
3. ClassCastException 数据类型转换异常
// ClassCastException 数据类型转换异常
@Test
public void test3(){
Object obj = new Date();
String str = (String)obj;
}
4. NumberFormatException 数字格式异常
// NumberFormatException 数字格式异常
@Test
public void test4(){
String str = "123";
int i = Integer.parseInt(str);//此时可以转换
str = "abc";
int i1 = Integer.parseInt(str);
}
5.InputMismatchException 输入不匹配异常
// InputMismatchException 输入不匹配异常
@Test
public void test5(){
Scanner scanner = new Scanner(System.in);
int score = scanner.nextInt();//若输入的不是整型,就会报错
System.out.println(score);
}
6.ArithmeticException 算术异常
// ArithmeticException 算术异常
@Test
public void test6(){
int a = 10;
int b = 2;
// System.out.println(a / b);//没问题
b = 0;
System.out.println(a / b);
}
编译时异常:
public void test7(){
File file = new File("Hello.txt");
FileInputStream fis = new FileInputStream(file);//Unhandled exception type //FileNotFoundException
int data = fis.read();
while(data != -1){
System.out.println((char)data);
data = fis.read();
}
fis.close();
}
3.异常处理机制一
异常处理机制引入:
Java异常处理
Java异常处理的方式:
方式一:try-catch-finally
方式二:thorws + 异常类型
Java提供的是异常处理的抓抛模型。
过程一:“抛”:程序正在执行的过程中,一旦出现异常,就会在异常代码处生成一个对应异常类的对象并将此对象抛出。
一旦抛出对象以后,其后面代码将不再执行。
关于异常对象的产生:①系统自动生成的异常对象
②手动生成一个异常对象,并抛出(throw)
②手动抛出一个异常对象 :
package com.hlq.shou_dong_yi_chang;
public class StudentTest {
public static void main(String[] args) {
try {
Student s1 = new Student();
s1.regist(-1001);
System.out.println(s1);
} catch (Exception e) {
//这个Message就是下面throw new Exception(Message)
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("您输入的数字非法!!!");
//通常new Exception 或者new RunTimeException
// throw new RuntimeException("您输入非法!!!");
//生成了一个异常对象
throw new Exception("nin shu ru fei fa !!!");
}
}
}
throw与throws的区别:
throw这个是在抛的过程中,关于异常对象产生的一种方式,叫手动生成一个异常对象,而throws是处理异常的一种方法
过程二: “ 抓 ” :可以理解为异常处理的方式:①try-catch-finally ②thorws + 异常类型
①try-catch-finally的使用:
try{
//可能出现异常的代码
}catch(异常类型1 变量名1){
//处理异常的方式1
}catch(异常类型2 变量名2){
//处理异常的方式2
}
......
finally{
//一定会执行的代码
}
说明:
1.finally是可选的,可以不用写
2.使用try将可能出现异常代码包装起来,在执行过程中,一旦出现异常,就会生成一个对应异常类的对象,根据此对象的类型,去匹配catch
3.一旦try中的异常,匹配到某一个catch时,就进入catch中进行异常处理,一旦处理完成,就跳出当前的try-catch结构(在没有写finally的情况下)。继续执行其后面的代码。
4.catch中异常类型若没有子父类关系,谁在上谁在下无所谓,若满足子父类关系,要求子类必须声明在父类上面,否则报错
5.常用的异常对象处理方式:①String getMessage() ②printStackTrace()
6.在try结构中声明的变量,在出了try结构以后,就不能再调用
7.try-catch-finally结构可以相互嵌套
体会:使用try-catch-finally处理编译时异常,使得程序在编译时不会出错,但在运行时仍然有可能出错,相当于我们使用try-catch-finally将一个编译时可能出现的异常,延迟到运行时出现。
try-catch练习:
package com.hlq.yi_chang;
import org.junit.Test;
/*
* 异常的处理
*/
public class ExceptionTest1 {
@Test
public void test1(){
String str = "123";
str = "abc";
//可能会出现异常的代码
try{
// 代码执行到这里 一旦出现了异常就会创建一个异常类型的对象
// 然后抛出去,然后就到catch进行一个捕获,正好与你抛出的匹配
// 所以就输出了“出现数值转换异常了...”,处理完以后程序就可以
// 正常运行了 所以输出了“hello------------2”
int num = Integer.parseInt(str);
//hello------1并未输出
System.out.println("hello----------1");
}catch(NumberFormatException e){//若将NumberFormatException换成
//NullPointerException依然出错
// System.out.println("出现数值转换异常了...");
System.out.println(e.getMessage());
e.printStackTrace();
}catch(NullPointerException e){
System.out.println("hello------------3");
}catch(Exception e){
System.out.println("hello-------4");
}
System.out.println("hello------------2");
}
}
finally的用法:
1.finally是可选的
2.finally中声明的是一定会被执行的代码。即使catch中又出现异常了、try中有return语句了、catch中有return语句等情况
3.像数据库连接、输入输出流、网络Socket等资源,JVM是不能自动回收的,我们需要自己手动的进行资源释放。此时资源释放,就需要声明在finally中。
体会:开发中由于运行时异常比较常见,所以我们通常就不针对运行时异常编写try-catch-finally;然而对于编译时异常,我们一定要考虑异常的处理
finally练习:
package com.hlq.yi_chang;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import org.junit.Test;
public class FinallyTest {
@Test
public void test1(){
try{
int a = 10;
int b = 0;
System.out.println(a / b);
}catch(ArithmeticException e){
// e.printStackTrace();
//catch里面又有异常
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 {
File file = new File("hello.txt");
fis = new FileInputStream(file);
int data = fis.read();
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){
fis.close();
}
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
4.异常处理的方式二 :throws + 异常类型
1.“throws + 异常类型”写在方法的声明处,指明方法在执行时,可能会抛出异常类型。一旦方法体执行时,出现异常,仍会在异常代码处生成一个异常类的对象,此对象满足throws后面这个异常类型时,就会被抛出。异常代码后面的代码就不会被执行
2.体会(两者的对比):try-catch-finally:真正的将异常给处理掉了
throws的方式只是将异常抛给了方法的调用者。并没有真正将异常处理掉
详细请看下面代码:
package com.hlq.yi_chang;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import org.junit.Test;
public class ExceptionTest2 {
public static void main(String[] args) {
// method2();处理不了把异常抛给了main()方法;此时main方法无需再抛,try一下就可以
// 若method2();抛出的是个处理方法不一样的异常,则catch写出对应的处理方式
//若抛出的异常在method3();里已经处理好了,在main方法中调用metho3();method3()
//就没有异常了。
try{
method2();
}catch (IOException e){
e.printStackTrace();
}
}
public static void method3(){
try{
method2();
}catch(IOException e){
e.printStackTrace();
}
}
// 若method1();抛出去两个,而且处理异常方式都不一样,则method2();也抛出去两个
public static void method2()throws IOException{
method1();
}
@Test
//throws 把异常给抛出去了 抛给了method2();因为FileNotFoundException是
//IOException的子类 所以把父类给抛出去;
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();
}
}
5.方法重写的规则之一:
1.子类重写的方法抛出的异常类型不大于父类的被重写的方法抛出的异常类型
package com.hlq.override_yichang;
import java.io.FileNotFoundException;
import java.io.IOException;
public class OverrideTest {
public static void main(String[] args) {
OverrideTest o1 = new OverrideTest();
o1.display(new SubClass());
//此时调用的s.method()实际是执行的是子类重写过的方法,
//若子类抛出去的异常比父类还要大,这时到display()下的catch里面,
//会包不住这个异常的,所以子类不大于父类抛出去的异常
}
public void display(SuperClass s){
try {
s.method();
} catch (IOException e) {
e.printStackTrace();
}
}
}
class SuperClass{
public void method() throws IOException{
}
}
class SubClass extends SuperClass{
public void method() throws FileNotFoundException{
}
}
开发中如何选择使用try-catch-finally还是使用“throws”?
1.如果父类中被重写的方法没有用throws的方式抛出去异常,则子类重写的方法也不能使用throws,意味着如果子类重写的方法有异常,必须使用try-catch-finally方式处理
2.执行的方法aaa中,先后又调用了另外的几个方法,这几个方法又是递进关系执行的。我们建议这几个方法使用throws的方式进行处理,而执行的方法aaa可以考虑使用try-catch-finally的方式进行处理(方法没人调,自己处理,有人调,抛出,谁调谁处理(一般用try-catch-finally))。
说明:递进关系:a方法得出的结果需要供b方法使用,b方法得出的结果需被c方法使用..
6.用户自定义异常类
1.如何自定义异常类
1.继承现有的异常结构:RunTimeException、Exception
2.提供全局常量 serialVersionUID
3.提供重载的构造器
//自定义异常类
package com.hlq.shou_dong_yi_chang;
/*
* 如何自定义异常类:
1.继承现有的异常结构:RunTimeException、Exception
2.提供全局常量 serialVersionUID
3.提供重载的构造器
*/
public class MyException extends Exception{
//唯一标识MyException
static final long serialVersionUID = -614897190745766939L;
public MyException(){
}
public MyException(String msg){
super(msg);
}
}
//***********************************************************************************
//使用自定义异常类
package com.hlq.shou_dong_yi_chang;
public class StudentTest {
public static void main(String[] args) {
try {
Student s1 = new Student();
s1.regist(-1001);
System.out.println(s1);
} catch (Exception e) {
//这个Message就是下面throw new Exception(Message)
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("您输入的数字非法!!!");
//通常new Exception 或者new RunTimeException
// throw new RuntimeException("您输入非法!!!");
//生成了一个异常对象
// throw new Exception("nin shu ru fei fa !!!");
throw new MyException("不能输入负数");
}
}
}