JAVASE学习笔记 (十)
异常体系结构
Java中把异常当成对象来处理,并且定义了一个基类
java.long.Throwable
作为所有异常的超类
在Java API中已经定义了许多异常类.这些异常分为两大类:
- 错误:
Error
主要是jvm(Virtual MachineError
)错误和awt错误(GUI编程) - 异常:
Exception
IO异常,Runtime异常等等
Error
- Error类对象由Java虚拟机生成并抛出,大多数错误与代码编写者所执行的操作无关。
- Java虚拟机运行错误(
Virtual MachineError
),当JVM不再有继续执行操作所需的内存资源时,将出现OutOfMemoryError
。这些异常发生时,Java虚拟机(JVM) 一般会选择线程终止; - 还有发生在虚拟机试图执行应用时,如类定义错误(
NoClassDefFoundError
) 、链接错误(LinkageError
)。这些错误是不可查的,因为它们在应用程序的控制和处理能力之外,而且绝大多数是程序运行时不允许出现的状况。
error包括堆溢出,栈溢出等,下文有演示
Exception
- 在Exception分支中有一个重要的子类
RuntimeException
(运行时异常):ArraylndexOutOfBoundsException
(数组下标越界)NullPointerException
(空指针异常)ArithmeticException
(算术异常)MissingResourceException
(丢失资源)ClassNotFoundException
(找不到类)等异常,这些异常是不检查异常,程序中可以选择捕获处理,也可以不处理。
- ◆这些异常- -般是由程序逻辑错误引起的,程序应该从逻辑角度尽可能避免这类异常的发生;
Error和Exception的区别:
Error通常是 灾难性的致命的错误,是程序无法控制和处理的,当出现这些异常时,Java虚拟机(JVM) - -般会选择终止线程; Exception通常情况下是可以被程序处理的,并且在程序中应该尽可能的去处理这些异常。
异常的演示
-
Error的演示,我们的代码是正确,但是由于硬件或者其他不可控的原因,莫名其妙出现的错误
-
编译的时候没有出错,运行的时候也没有出错,在运行一段时间后,因为程序员不可控的情况,程序产生了错误
-
堆内存(Heap)溢出
import java.util.*; public class Heap{ public static void main(String[] args){ int count =1; List<Heap> list = new ArrayList<>(2000000000); while(true){ System.out.println(count++); list.add(new Heap()); } } }
java.lang.OutOfMemoryError: Java heap space 堆内存不够用,我们无法在程序中进行控制,只能通过跳转Java中分配的内存进行控制
-
栈内存(Stack)溢出:运行的是局部变量和调用的犯法
import java.util.*; public class Stack{ static int count =1; public static void main(String[] args){ new Stack().test01(); } public void test01(){ System.out.println(count++); //递归调用 test01(); } }
Exception in thread “main” java.lang.StackOverflowError 栈内存溢出,因为递归调用
-
-
受检查的异常:编译时异常:checked:在编译期间就检查代码是否有可能出现错误,需要你进行处理,如果不处理编译无法进行,
强制进行处理
import java.util.*;
import java.io.*;
public class CheckedException{
public static void main(String[] args){
//错误: 未报告的异常错误FileNotFoundException; 必须对其进行捕获或声明以便抛出
//下面的代码,可能会出现错误,你必须要对这种错误进行处理,如果不处理编译无法通过
InputStream is = new FileInputStream("D:/a.txt");
}
}
- 不受检查的异常:运行时异常:unchecked:在编译期间,你的代码虽然在逻辑可能出错,但是语法是正确,编译可以通过,但是运行的时候就报错。
其根类为 RuntimeException
import java.util.*;
import java.io.*;
public class UnCheckedException{
public static void main(String[] args){
//运行时报错:java.lang.ArithmeticException: / by zero
int i = 10/0;
}
}
异常处理机制(无法处理Error)
- 抛出异常
- 捕获异常
异常处理5个关键字:
try(监控区域,无异常执行),catch(捕获异常,有异常执行),finally(善后工作,比如关闭IO流,关闭资源等等。无论有无异常都执行),throw,throws
catch(要捕获的类型):Throwable
最高
throws
throw概述
throws:声明在方法的后面,方法中可能出现的错误向调用者声明,自己不具有处理该错误的能力,那么让调用者处理更好,调用者需要为可能发送的错误负责。//谁调用谁处理
一级一级往上抛出,若没有执行者能处理,就一直抛到JVM。
throw一般在方法体中使用,throws一般在方法上使用
import java.io.*;
public class Demo01{
//最终给了Java的虚拟机自行处理,如何处理跟我没有关系
public static void main(String...args)throws FileNotFoundException{//main调用者
m1();
}
public static void m1()throws FileNotFoundException{
m2();
}
public static void m2()throws FileNotFoundException{
m3();
}
public static void m3()throws FileNotFoundException{//调用者
//调用m4方法;
m4();//对可能发送的错误进行复杂
}
public static void m4()throws FileNotFoundException{//m4本身没有处理能力,告知调用者可能出现的错误
InputStream is = new FileInputStream("D:\123.txe");//编译时异常
}
}
throws关键字的使用规则
-
在方法的后面可以同时抛出多个异常信息
public class Demo02{ public static void m4()throws FileNotFoundException,ClassNotFoundException{ InputStream is = new FileInputStream("D:\123.txe");//编译时异常 Class.forName("com.os.model.Student");//编译时异常 } }
-
调用者抛出的异常可以大于等于被调用者抛出的异常
import java.io.*; public class Demo02{ public static void m2()throws Exception/*大于*/{ m3(); } public static void m3()throws IOException/*大于*/,ClassNotFoundException/*等于*/{ m4(); } public static void m4()throws FileNotFoundException,ClassNotFoundException{ InputStream is = new FileInputStream("D:\123.txe");//编译时异常 Class.forName("com.os.model.Student");//编译时异常 } }
-
继承关系中,子类重写方法抛出的异常需要小于等于父类抛出的异常
package ex01; import java.io.*; class Father{ public void say()throws IOException { System.out.println("父类中的方法"); } } class Child extends Father { public void say()throws FileNotFoundException{ System.out.println("子类重写的方法"); } }
什么是方法重写?继承关系
两同:方法名称相同,形参的列表相同(数据类型和个数)
两小:
- 返回值类型(引用数据类型):子类返回值类型可以小于等于父类的返回值类型
- 异常:子类抛出的异常需要小于等于父类抛出的异常
一大:子类的访问修饰符需要大于等于父类的访问修饰符
throws方式缺点:容易造成代码之间耦合度比较紧密(调用者和被调用者比较紧密)
try…catch
概述
自己承担,自己可以处理出现错误的后果,可以使调用者不要在错误进行处理。
语法格式:
- 1.一个try可以对应多个catch: try…catch(){}…catch(){}…catch(){}
- 2.一个try可以对应1个finally: try…finally{}
- 3.try…catch(){}…catch(){}…catch(){}…finally{}
部分规则
- 一个try可以对应多个catch
- finally 块中的 return 返回后方法结束执行,不会再执行 try 块中的 return 语句。总之,会在try的return之前执行finally的内容,如果finally有return,相当于截断了正常的try中的return,提前结束
package ex03;
import java.io.*;
public class Demo05{
public static void main(String...args){//main调用者
try{
Class.forName("Demo02");//加载类
InputStream is = new FileInputStream("D:/123.txe");//编译时异常
System.out.println("正确");
}catch(FileNotFoundException e){
System.out.println("e对象是系统自动创建:"+e);
System.out.println("文件没有找到");
}catch(ClassNotFoundException e){
System.out.println("e对象是系统自动创建:"+e);
System.out.println("类没有找到");
}
}
}
- 一个try可以对应多个catch,前面的异常捕获的异常类型范围小于后面捕获类型
package ex03;
import java.io.*;
public class Demo05{
public static void main(String...args){//main调用者
try{
int num = 10/0;
Class.forName("Demo02");//加载类
InputStream is = new FileInputStream("D:/123.txe");//编译时异常
System.out.println("正确");
}/*catch(IOException e){//IOException是FileNotFoundException的父类,范围大
错误语法
}*/catch(FileNotFoundException e){
System.out.println("e对象是系统自动创建:"+e);
System.out.println("文件没有找到");
}catch(ClassNotFoundException e){
System.out.println("e对象是系统自动创建:"+e);
System.out.println("类没有找到");
}catch(Exception e){//IOException是FileNotFoundException的父类,范围大
System.out.println("e对象是系统自动创建:"+e);
}
}
}
偷懒:同时处理多个异常
- 使用两者共同的父异常
package ex04;
import java.io.*;
public class Demo06{
public static void main(String...args){//main调用者
try{
Class.forName("Demo02");//加载类
InputStream is = new FileInputStream("D:/123.txe");//编译时异常
System.out.println("正确");
}catch(Exception e){//Exception范围大,但是它不仅仅处理ClassNotFound和FileNotFound
System.out.println("e对象是系统自动创建:"+e);
}
}
}
- JDK7提供的方法“
(FileNotFoundException | ClassNotFoundException e)
”:同时处理这两个异常,都执行
package ex05;
import java.io.*;
public class Demo07{
public static void main(String...args){//main调用者
try{
Class.forName("Demo02");//加载类
InputStream is = new FileInputStream("D:/123.txe");//编译时异常
System.out.println("正确");
}catch(FileNotFoundException | ClassNotFoundException e){
System.out.println("e对象是系统自动创建:"+e);
}
}
}
finally:无论对错都会执行
finally:修饰类不能被继承,修饰方法不能被重写,修饰变量不能改变
注意一:不要在finally中设置return
package ex06;
import java.io.*;
public class Demo08{
public static void main(String...args){//main调用者
int result = m1(999);
System.out.println(result);
}
public static int m1(int num){
try{
System.out.println("try...."+num);
return num+100;
}finally{
System.out.println("finally...."+num);
return 200;
}
}
}
结果
finally的语句会先于try或者catch的返回语句之前执行,如果finally中有return语句,那么try或catch中的return语句会被finally中的return覆盖,不建议在finally中放return
面试题
告知代码运行结果
public class F10 {
public static void main(String[] args) {
int i =m1(999);
System.out.println(i);
}
public static int m1(int num){
try{
System.out.println("try==="+num);
return num;
}finally {
num = num +1 ;
System.out.println("finally...."+ num);
}
}
}
//输出结果:999
输出“999”原因:
在try中,底层是通过定义一个临时变量来返回值。所以返回temp=999的临时变量,而不是finally中num=1000并没有改变temp的值。
int temp = num;//临时变量
System.out.println("try...."+temp);
return temp;
手动抛出异常
就算程序正确也可手动抛出异常
package ex09;
import java.util.*;
public class Demo09{
public static void main(String...args){//main调用者
//System.out.println(ex);
int num = new Random().nextInt(10);
if(num<4){
//第一种方式:
/*
System.out.println(num+"程序错误");//后面的代码不应该在执行了
return;//终止方法
*/
//throw 异常对象;
//1.手动创建一个异常对象,之前我们遇到都是系统自动创建
//NullPointerException ex = new NullPointerException("你的程序错误了!");
//throw ex;
throw new NullPointerException("你的程序错误了!"+num);
}
System.out.println(num+"程序正确了");
}
}
手动抛出异常+捕获=流程控制
判断奇数偶数
开发中的方式1(返回boolean 或者 int)
package ex10;
import java.util.*;
public class Demo10{
public static void main(String...args){//main调用者
Scanner scan = new Scanner(System.in);
System.out.print("请输入一个整数:");
int num = scan.nextInt();
boolean flag = m1(num);
//显示不同的效果
if(flag){
System.out.println("偶数");
}else{
System.out.println("奇数");
}
System.out.println("===========");
if(m2(num)==0){
System.out.println("===偶数");
}else{
System.out.println("====奇数");
}
}
public static boolean m1(int number){
if(number%2==0){
return true;
}else{
return false;
}
}
public static int m2(int number){
return number%2;
}
}
方式2
package ex11;
import java.util.*;
public class Demo11{
public static void main(String...args){//main调用者
Scanner scan = new Scanner(System.in);
System.out.print("请输入一个整数:");
int num = scan.nextInt();
try{
m1(num);
System.out.println("奇数");
}catch(RuntimeException e){//系统创建了对象,堆地址
System.out.println("偶数");
}
}
public static void m1(int number){
if(number%2==0){
throw new RuntimeException();
}
}
}
有人说后者效率比前者低,原因:
后者因为用了
catch
,系统自动创建了对象,产生堆地址 前者全在栈中,
但只要不大量使用,后者并没有什么影响
自定义异常
使用Java内置的异常类可以描述在编程时出现的大部分异常情况。除此之外,用户还可以自定义异常。用户自定义异常类,只需继承Exception类(绝大情况下,都继承RuntimeException)即可。
- 在程序中使用自定义异常类,大体可分为以下几个步骤:
- 创建自定义异常类。
- 在方法中通过throw关键字抛出异常对象。
- 如果在当前抛出异常的方法中处理异常,可以使用try-catch语句捕获并处理;否则在方法的声明处通过throws关键字指明要抛出给方法调用者的异常,继续进行下一步操作。
- 在出现异常方法的调用者中捕获并处理异常。
package ex12;
import java.util.*;
public class Demo12{
public static void main(String...args){//main调用者
Scanner scan = new Scanner(System.in);
System.out.print("请输入一个整数:");
int num = scan.nextInt();
try{
m1(num);
System.out.println("奇数");
}catch(MyException e){//系统创建了对象,堆地址
System.out.println(e);
System.out.println("偶数");
}
}
public static void m1(int number){
if(number%2==0){
throw new MyException("自定义异常");
}
}
}
//绝大部分情况下,我们都是继承的是运行时异常
class MyException extends RuntimeException{
public MyException(){}
public MyException(String message){
super(message);//父类中构造方法
}
}
将编译时异常转换为运行时异常(特别推荐的技巧)
这么做的的原因:
import java.util.Properties;
public class F09 {
public static void main(String[] args) {
m1();
}
public static void m1(){
try {
Class.forName("demo12351");
} catch (Exception e) {
System.out.println(e.toString());
}
System.out.println("如果程序错误,这段内容不应该输出");
}
}
运行结果:
解决方案一:修改catch:
try {
Class.forName("demo12351");
} catch (Exception e) {
System.out.println(e.toString());
return;
}
解决方案二:转换为运行时异常(推荐:解耦)
import java.util.Properties;
public class F09 {
public static void main(String[] args) {
m1();
}
public static void m1(){
try {
Class.forName("demo12351");
} catch (Exception e) {
/*程序员记录使用*/
e.printStackTrace();//控制台输出错误信息,返回错误位置
System.out.println(e.toString());//获取异常类和异常类信息
throw new RuntimeException(e.getMessage());//返回的异常信息
}
System.out.println("如果程序错误,这段内容不应该输出");
}
}
结果:
总结
1.处理运行时异常时,采用逻辑去合理规避同时辅助try-catch处理
2. 在多重catch块后面,可以加一个catch (Exception) 来处理可能会被遗漏的异常
3. 对于不确定的代码,也可以加上try-catch ,处理潜在的异常
4. 尽量去处理异常,切忌只是简单地调用printStackTrace()
去打印输出
5. 具体如何处理异常,要根据不同的业务需求和异常类型去决定
6. 尽量添加finally语句块去释放占用的资源