零基础学习之Java异常
概述
正如“人无完人”一样,程序也不是完美的,它总会存在这样那样的问题,而有些问题并不是能够通过程序员开发更好的代码来解决的,如果我们忽视它,可能就会造成程序的终止,甚至是系统的崩溃。因此,我们需要想办法来合理的解决它,这就是Java中异常的由来。
异常是指程序在执行过程中,出现的非正常情况。比如:空的引用、数组下标越界、内存溢出错误、读取文件不存在、网络断开等
一般来说有两种方法来处理异常:
- 遇到错误就终止程序运行;
- 开发人员在编写程序的时候考虑到可能出现的非正常情况,提前做好处理(这个是最好的,不过也是不容易的);
那究竟异常有哪些?常见的处理方法是什么?我想通过下面的文章,你一定可以得到想要的答案!
基本介绍
在Java这样一个面向对象的语言中,异常被当成了一个对象来处理。其根类是java.lang.Throwable类,Throwable类有分成了两个类,即Error和Exception。这是异常的两个大类,他们分别代表了两种情况:
- Error:不能处理的错误,这是系统内部的错误,运行时报错,属于系统问题。一般发生这种异常,JVM会选择终止程序,开发人员需要提前避免。
- Exception:可以处理的异常,这是比较常见的异常,开发人员可以根据Java提供的类和问题对这类异常进行处理。
Error和Execption包括很多种情况,这才是我们需要考虑的,下面对它们进行分别介绍。
Error
对于严重错误Error,没有办法处理,只能做到提前避免。常见的Error有:StackOverflowError(内栈溢出错误)和OutOfMemoryError (内存溢出错误)。
代码示例
OutOfMemoryError
Java中堆是用来存储对象实例的,,因此如果我们不断地创建对象, 并且对象没有被垃圾回收, 那么当创建的对象过多时, 会导致 heap 内存不足, 进而引发 OutOfMemoryError 异常.
/**
* 内存溢出
*/
public class Heap{
public static void main(String[] args){
//创建数组list,用来保存对象
List<Integer> list = new ArryList<>();
int i=0;
while(true){
//不断创建对象
list.add(i++);
}
}
}
StackOverflowError
JVM 的运行时数据区中有一个叫做栈的内存区域, 此区域的作用是: 每个方法在执行时都会创建一个栈帧, 用于存储局部变量表、操作数栈、方法出口等信息。因此当创建一个无限递归的递归调用, 当递归深度过大时, 就会耗尽栈空间, 进而导致了 StackOverflowError 异常.
/**
* 栈溢出
*/
public class Stack{
public static void main(String[] args){
//调用方法
new Stack().test();
}
//创建测试方法
public void test() {
//自己调用自己(递归调用)
test();
}
}
Exception
一般来说,我们所说的异常就是指Exception,因为这类异常一旦出现,我们就要对代码进行更正,修复程序进行处理。根据在编译时期还是运行时期去检查异常,可以将Exception分为:
- 编译时期异常:checked异常。在编译时期,就会检查,如果没有处理异常,则编译失败(开发工具会提示),如文件找不到异常等。
- 运行时期异常:runtime异常。在运行时期,检查异常.在编译时期,运行异常不会被编译器检测到(不开发工具不会提示),如空指针异常,类型转换异常,数字操作异常,类型不匹配异常等。
代码示例
编译时异常这里就不举例了,在开发工具里会标红提示。下面是常见的运行时异常代码示例。
package com.atguigu.demo;
import org.junit.Test;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.util.Scanner;
public class Demo1 {
/*
编译时异常:必须进行处理,否则编译不通过
*/
//文件找不到
@Test
public void test07() {
//这里FileInputStream报红,编译异常,需要处理
FileInputStream fis = new FileInputStream("Java异常学习笔记.txt");
}
/*
运行时异常
*/
public static void main(String[] args) {
//类型不匹配异常
Scanner input = new Scanner(System.in);
System.out.print("请输入一个整数:");
//这个时获取控制台的输入,如果输入的不是整数,则报类型不匹配异常
int num = input.nextInt();
}
//空指针异常
@Test
public void testNullPointerException() {
String[] names = new String[5];
System.out.println(names[0].length());//字符串的长度
}
//类型转换异常
@Test
public void testClassCastException() {
Object obj = new Object();
String str = (String) obj;
}
//数学操作异常异常
@Test
public void testArithmeticException(){
int i = 1/0;
}
}
异常的处理
既然Java程序异常是非常常见的,那我们该如何处理它呢。由于Java将异常作为对象来处理,因此我们可以将Java异常的处理分成三个部分:
- Java程序出现异常时会生成一个异常类对象,该异常对象将被提交给Java运行时系统,这个过程称为抛出(throw)异常。
- Java程序出现异常时将问题标识出来,报告给调用者,让调用者去处理。这个过程通过throws进行声明。
- Java程序出现异常时,在方法中使用try-catch的语句块来处理异常。
throw
Java程序出现异常时会生成一个异常类对象,该异常对象将被提交给Java运行时系统,这个过程称为抛出(throw)异常。异常对象的生成有两种方式:
- 自动生成:程序运行过程中,虚拟机检测到程序发生了问题,如果在当前代码中没有找到相应的处理程序,就会在后台自动创建一个对应异常类的实例对象并抛出——自动抛出
- 手动创建:Exception exception = new ClassCastException();——创建好的异常对象不抛出对程序没有任何影响,和创建一个普通对象一样,但是一旦throw抛出,就会对程序运行产生影响了。
使用格式:
throw new 异常类名(参数);
代码示例
package com.atguigu.demo;
public class Demo2 {
public static void main(String[] args) {
//定义数组和访问的角标
int[] arr = {1,2,3,4};
int index = 4; //测试数组越界异常
// int[] arr = {1,2,3,4}; //为null,测试空指针异常
//调用方法测试
int element = getElement(arr, index);
System.out.println(element);
}
public static int getElement(int[] arr, int index) {
//判断数组为空异常
if (arr == null){
throw new NullPointerException("数组为空!");
}
//判断数组越界异常
// 数组最大下标 arr.length-1
if (index < 0 || index > arr.length -1){
throw new ArrayIndexOutOfBoundsException("角标越界!");
}
int element = arr[index];
return element;
}
}
throws
Java程序出现异常时将问题标识出来,报告给调用者,让调用者去处理。这个过程通过throws进行声明。关键字throws运用于方法声明之上,用于表示当前方法不处理异常,而是提醒该方法的调用者来处理异常(如果调用者不处理,则接着抛出异常).(不主动处理的方式,甩锅给别人)
声明异常格式:
修饰符 返回值类型 方法名(参数) throws 异常类名1,异常类名2…{ } //可以声明多个异常(也可直接一个Exception)
代码示例
package com.atguigu.demo;
/*
声明异常
*/
public class Demo4 {
//声明一个Exception
public static void main(String[] args) throws Exception {
method();//不能直接调用,必须接着声明异常或者处理异常,否则编译不通过
}
//声明多个异常
public static void method() throws
NullPointerException,ArrayIndexOutOfBoundsException{
System.out.println("声明异常!");
}
}
try…catch
Java程序出现异常时,在方法中使用try-catch的语句块来处理异常。try-catch的方式就是捕获异常:Java中对异常有针对性的语句进行捕获,可以对出现的异常进行指定方式的处理。(主动处理的方式,不甩锅给别人)
捕获异常语法如下:
try{
//可能会出现异常的代码放在这里
}catch(异常类型 e){
//处理异常的代码
//记录日志/打印异常信息/继续抛出异常
}catch(异常类型 e){
//处理异常的代码
//记录日志/打印异常信息/继续抛出异常
}
...
注意: try和catch都不能单独使用,必须连用(除非有finally,后面说)。
catch可以有好几个,即可以捕获几个异常
代码示例
package com.atguigu.demo;
/*
try catch 捕获异常
*/
public class Demo5 {
public static void main(String[] args) {
//获取方法异常信息
try {
//正常执行代码
add();
} catch (Exception e) {
//捕获异常
System.out.println("异常.......");
// String message = e.getMessage();
// System.out.println(message);
e.printStackTrace();
}
//多个异常捕获
// catch(ArrayIndexOutOfBoundsException a) {
//
// }
}
//声明异常
public static void add() throws Exception {
//System.out.println("add......");
int i = 1 / 0;
}
}
finally块
在上述的try中,如果出现了异常,则异常后面的语句就不执行,但有些特定的语句是需要必须执行的。因此就需要finally块就是解决这个问题的,这是因为在Java中finally代码块中存放的代码都是一定会被执行的。
当在try语句块中打开了一些物理资源(磁盘文件/网络连接/数据库连接等),我们都得在使用完之后关闭打开的资源。
finally的语法:
try{
}catch(...){
}finally{
//无论try中是否发生异常,也无论catch是否捕获异常,也不管try和catch中是否有return语句,都一定会执行
}
或
try{
}finally{
//无论try中是否发生异常,也不管try中是否有return语句,都一定会执行
}
代码示例
package com.atguigu.demo;
public class Demo6 {
public static void main(String[] args) {
try{
System.out.println("程序开始执行了");
int i = 1/0;
}catch (Exception e){
e.printStackTrace();
}finally {
System.out.println("这段代码总会执行");//这里没有写前面说的资源情况,主要为了方便理解
}
}
}
finally与return(面试)
形式一:从try回来
public class TestReturn {
public static void main(String[] args) {
int result = test("12");
System.out.println(result);
}
public static int test(String str){
try{
Integer.parseInt(str);
return 1;
}catch(NumberFormatException e){
return -1;
}finally{
System.out.println("test结束");
}
}
}
形式二:从catch回来
public class TestReturn {
public static void main(String[] args) {
int result = test("a");
System.out.println(result);
}
public static int test(String str){
try{
Integer.parseInt(str);
return 1;
}catch(NumberFormatException e){
return -1;
}finally{
System.out.println("test结束");
}
}
}
形式三:从finally回来
public class TestReturn {
public static void main(String[] args) {
int result = test("a");
System.out.println(result);
}
public static int test(String str){
try{
Integer.parseInt(str);
return 1;
}catch(NumberFormatException e){
return -1;
}finally{
System.out.println("test结束");
return 0;
}
}
}
自定义异常
基本介绍
异常的情况有非常多,因此Java内部并不能把这些都包含进去,这就需要开发人员根据自己的实际业务情况进行自定义异常。比如年龄负数问题,考试成绩负数问题等等。
异常类如何定义:
- 自定义一个编译期异常::自定义类 并继承于 java.lang.Exception。
- 自定义一个运行时期的异常类:自定义类 并继承于 java.lang.RuntimeException。
代码示例
package com.atguigu.demo;
public class Demo7 {
public static void main(String[] args){
try {
new Input().method();
}
catch(WrongInputException wie) {
System.out.println(wie.getMessage());
}
}
}
// 自定义的类继承Exception
class WrongInputException extends Exception {
WrongInputException(String s) {
super(s);
}
}
class Input {
void method() throws WrongInputException {
// 抛出自定义的类
throw new WrongInputException("Wrong input");
}
}
异常注意事项
多个异常使用捕获又该如何处理呢?
- 多个异常分别处理。
- 多个异常一次捕获,多次处理。(推荐)
- 多个异常一次捕获一次处理。
注意:多个异常一次捕获,多次处理的方式,要求多个catch中的异常不能相同,并且若catch中的多个异常之间有子父类异常的关系,那么子类异常要求在上面的catch处理,父类异常在下面的catch处理。
-
运行时异常被抛出可以不处理。即不捕获也不声明抛出。
-
如果finally有return语句,永远返回finally中的结果,避免该情况.
-
如果父类抛出了多个异常,子类重写父类方法时,抛出和父类相同的异常或者是父类异常的子类或者不抛出异常。
-
父类方法没有抛出异常,子类重写父类该方法时也不可抛出异常。此时子类产生该异常,只能捕获处理,不能声明抛出