跳至主要內容

[JAVA]-异常

holic-x...大约 17 分钟JAVA异常

[JAVA]-异常

[TOC]

1.基础概念

<1>异常处理机制

​ Java的异常机制能让程序具有良好的容错性,让程序更加健壮,当程序出现意外的情况,系统会自动生成一个Exception对象通知程序进程处理,从而实现业务功能和实现代码的分离。

🔖异常的继承体现

🔖使用try-catch捕获异常

Try试图捕获异常。{}内是捕获异常的范围

Catch是捕获到异常以后如何处理 其中catch代码块可以出现多次。

try {
  // 业务逻辑处理
	// 可能出现异常的代码;
} catch(异常类名 变量名) {
	// 异常的处理代码;
}

参考案例:

public class ExceptionDemo {
	public static void main(String[] args) {
		/**
		 * 使用try...catch语句捕获异常
		 */
		try
		{
//			System.out.println(9/0);
//			Class.forName("java.util.ArrayListhahah");
			FileInputStream fis = new FileInputStream("c:/test.txt");
		}catch (ArithmeticException e) {
			System.out.println("捕获到了数学运算异常的错误...");
		} catch (FileNotFoundException e) {
			System.out.println("捕获到了文件不存在的异常...");
		} catch (Exception e) {
			System.out.println("捕获到了未知的异常...");
		}
	}
}

🔖Java7提供了多异常的捕获

​ 在jdk1.7之前,每个catch只能捕获一种类型的异常,但是从java7以后一个catch可以捕获多个异常。使用一个catch捕获多个异常需要注意以下情况:

  • 捕获多个异常,多种异常之间用|分割

  • 捕获多种类型的异常时,异常变量有隐式的final修饰,所以程序不能对异常变量重新赋值

public class MultiExceptionDemo {
	public static void main(String[] args) {
		try {
			int a = Integer.parseInt(args[0]);
			int b = Integer.parseInt(args[1]);
			int c = a / b;
			System.out.println("当前执行的结果c是:" + c);
			/**
			 * 一次性捕获多个异常需用 |隔开
			 */
		} catch (IndexOutOfBoundsException|NumberFormatException|ArithmeticException e) {
			System.out.println("捕获到了数组越界,数字格式异常,算数异常 等之一的错误...");
			/**
			 * 多异常捕获的变量是默认被final修饰的  所以下方的代码报错...
			 * e=new ArithmeticException("aa");
			 * 不能够对final修饰的变量进行二次修改赋值
			 */
		} catch (Exception e) { //最大的异常始终是放在最后一个捕获
			System.out.println("捕获到了未知的异常...");
			e=new RuntimeException("aaa");
		}
	}
}

🔖访问异常信息

public class AccessExceptionDemo {
	/**
	 * 通过相关的方法了解具体捕获到的异常信息
	 */
	public static void main(String[] args) {
		try
		{
			FileInputStream fis = new FileInputStream("c://a.txt");
		}catch(IOException e)
		{
			System.out.println(e.getClass());
			System.out.println(e.getMessage());
			System.out.println(e.getStackTrace());
			//了解具体的异常信息
			e.printStackTrace();
		}
	}
	/**
	 * 结果显示:
	 * class java.io.FileNotFoundException
	 * c:\a.txt (系统找不到指定的文件。)
	 * [Ljava.lang.StackTraceElement;@6d06d69c
	 * java.io.FileNotFoundException: c:\a.txt (系统找不到指定的文件。)
	 * at java.io.FileInputStream.open0(Native Method)
	 * at java.io.FileInputStream.open(Unknown Source)
	 * at java.io.FileInputStream.<init>(Unknown Source)
	 * at java.io.FileInputStream.<init>(Unknown Source)
	 * at com.exception.base.AccessExceptionDemo.main(AccessExceptionDemo.java:13)
	 */
}

🔖使用finally回收资源

​ 有些时候 程序中在try代码块里 打开了一些物理资源(数据库的连接,网络连接,磁盘文件)等 最终要把这些资源进行回收,提高系统的运行能力。

​ 垃圾回收机制只负责回收堆内存中的对象,不会回收任何物理资源(比如数据库连接,网络,IO等)

public class FilallyDemo {
	public static void main(String[] args) {
		FileInputStream fis = null;
		try
		{
			fis = new FileInputStream("c:\\test.txt");
		}catch(IOException e)
		{
			System.out.println(e.getMessage());
		}finally{//finally语句块是无论如何都会执行的语句块
			if(fis!=null)
				try {
					fis.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			System.out.println("finally代码块执行...");
		}
	}
}
public class FinallyCommonView {
	public static void main(String[] args) {
		int a=test();
		System.out.println(a);
	}

	public static int test() {
		int i=0;
		try {
			System.out.println(9 / 0);
			return i=i+100;
		} catch (Exception e) {
			System.out.println("捕获到了异常..");
			//这里的return是执行的 但是并没有结束整个方法 代码继续往下执行
			return i=i+3;
		}finally {
			System.out.println("无论如何都会执行的代码...");
			return i=i+6;
		}
	}
	/**
	 * 结果分析:
	 * finally语句块是无论如何都会执行的语句块,因此在上述语句中,分为
	 * try、catch、finally三个部分,try中的return语句不会执行,
	 * catch中虽然执行了相应的return语句,但此时并没有结束整个方法,
	 * 而是继续往下执行,直到执行完filally语句块的内容,再执行return
	 * 语句结束整个方法因此最终结果输出为3+6=9
	 */
}

finally语句块不会执行的情况分析

​ try语句没有被执行到,如在try语句之前return就返回了,这样finally语句就不会执行。这也说明了finally语句被执行的必要而非充分条件是:相应的try语句一定被执行到。

​ 在try块|catch块中有System.exit(0);这样的语句。System.exit(0)是终止Java虚拟机JVM的,连JVM都停止了,所有都结束了,当然finally语句也不会被执行到。

​ 在try-catch-finally中, 当return遇到finally,return对finally无效,即:

​ 在try catch块里return的时候,finally也会被执行。

​ finally里的return语句会把try catch块里的return语句效果给覆盖掉。

[!attention]

return语句并不一定都是函数的出口,执行return时,只是把return后面的值复制了一份到返回值变量里去了。如果try..catch…finally…三个语句之内没有return语句,则其会调用这三个语句块之外的return语句执行!

<2>Check异常和Runtime异常体系

​ Java的异常被分为两大类,Check异常(检查期异常)和Runtime异常(编译时异常),所有的RuntimeException以及子类都被称为运行时异常,除了运行时异常的都是检查期异常。

​ 其中运行时异常可以不用捕获,代码仍然能编译通过,检查期必须人工捕获,否则编译不通过

public class ExceptionDemo {
	/**
	 * Java的异常分为两大类:Check异常和Runtime异常
	 * Check检查期异常必须要进行捕获,否则编译无法通过
	 * Runtime运行时异常可以不需要进行捕获,编译也可正常通过
	 */
	public static void main(String[] args) {
		System.out.println(9/0);//运行时异常:数学运算错误
		try {
			//检查期异常:需要人为进行捕获,或者是抛出指定异常,否则编译出错
			FileInputStream fis = new FileInputStream("c://test.txt");
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		}
	}
}

[!attention]

常见的运行时异常(案例分析如下):

NullPointerException、ClassNotFoundException、ArithmeticException、ArrayIndexOutOfBoundsException、lllegalAccessException、lllegalAccessException、ClassCastException

public class RuntimeExceptionTest {
	public static void main(String[] args) {
		/**
		 * 1.java.lang.NullPointerException
		 *   空指针异常:调用了未经初始化的对象或者是不存在的对象
		 * 	  数组的初始化是指对数组分配需要的空间,而初始化后的数组
		 *   对应的数组元素并没有被初始化(实例化),其依然还是空的,
		 *   所以调用的时候出现空指针异常
		 */
		int[] arr = null;
		System.out.println(arr[0]);
		
		/**
		 * 2.java.lang.ClassNotFoundException
		 *   指定的类不存在
		 */
		
		/**
		 * 3. java.lang.ArithmeticException: 
		 *    / by zero -->数学运算异常(算术异常)
		 */
		System.out.println(1/0);
		
		/**
		 * 4.java.lang.ArrayIndexOutOfBoundsException
		 *   数组下标越界异常(数组下标的取值范围为0->length-1)
		 */
		String[] s = new String[3];
		System.out.println(s[3]);
		
		/**
		 * 5.java.lang.lllegalArgumentException
		 *   方法参数错误异常
		 *   调用类库的方法时出现参数传递错误
		 */
		
		/**
		 * 6.java.lang.lllegalAccessException
		 * 	   没有访问权限
		 *   当应用程序要调用一个类,但当前的方法没有对该类的访问权限,则会出现这个异常	
		 */
    
     /**
		 * 6.ClassCastException
		 * 	 类型转化异常一般在继承和多态场景中比较常见
		 *	 例如继承同一个父类的不同子类之间的强制转化		
		 */
    // Dog、Cat分别继承Animal
    Animal a1 = new Dog();
    Animal a2 = new Cat();
    Dog d1 = (Dog)a1;
    Dog d2 = (Dog)a2;// 异常
	}
}

🔖使用throws声明抛出异常

public class ThrowsExceptionTest {
	public static void main(String[] args) {
		/**
		 * 如果是要调用一个抛出异常的方法,可以有两种处理方式
		 * 1.直接在本方法中处理异常
		 * 2.继续向上一级抛出指定异常(throws 异常)
		 */
		try {
			test();
		} catch (IOException e) {
			System.out.println("捕获到文件不存在异常...");
		} catch (SQLException e) {
			e.printStackTrace();
		}
	}
	/**
	 * 在方法声明处抛出异常使用“throws”关键字
	 * 如果是抛出多个异常则可用 “,”进行分隔
	 */
	public static void test() throws IOException,SQLException
	{
		FileInputStream fis = new FileInputStream("c://test.txt");
	}
}

子类继承父类

​ 子类重写父类的方法 那么抛出的异常必须比父类更小,更少的异常

​ 两小原则针对的是检查期异常,而运行期异常无关

/**
 * 子类在重写父类的方法时要遵循“两小原则”
 * 子类抛出的异常要比父类小
 * 子类抛出的异常要比父类少
 * “两小原则”是针对检查期异常而言的,与运行时异常无关
 */
class Father
{
	public static void test()throws IOException,SQLException
	{
		FileInputStream fis = new FileInputStream("c://test.txt");
	}
}

public class OverrideExceptionDemo extends Father{
	public static void test()throws IOException,SQLException
	{
		FileInputStream fis = new FileInputStream("c://test.txt");
	}
	public static void main(String[] args) {
		try {
			test();
		} catch (IOException e) {
			e.printStackTrace();
		} catch (SQLException e) {
			e.printStackTrace();
		}
	}
}

🔖使用throw抛出异常

​ 如果需要在程序中自行抛出异常,则使用throw进行抛出异常

public class ThrowExceptionTest {
	public static void main(String[] args) {
//		testThrow(-1);
		try {
			testThrow2(-2);
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
	public static void testThrow(int i)
	{
		/**
		 * 如果参数i满足某些条件则手动利用“throw”关键字抛出异常
		 * 但在方法声明处需要再次进行声明
		 */
		if(i<0)
		{
			//自行抛出RuntimeException或者其子类
			throw new RuntimeException("i值小于0,不满足条件...");
		}
	}
	public static void testThrow2(int i) throws Exception
	{
		if(i<0)
		{
			//自行抛出Exception,需要在方法声明处使用“throws”关键字再次抛出异常
			throw new Exception("i值小于0,不满足条件...");
		}
	}
}

🔖关键字throw与throws的区别

如果调用一个抛出异常的方法 那么调用者有两种处理方式

  • 1.在本方法内处理

  • 2.继续向上一级抛出 throws IOException

如果需要在程序中自行抛出异常,则使用throw进行抛出异常

throws是方法可能抛出异常的声明。(用在声明方法时,表示该方法可能要抛出异常)

区别:

  • throws出现在方法函数头;而throw出现在函数体。

  • throws表示出现异常的一种可能性,并不一定会发生这些异常;throw则是抛出了异常,执行throw则一定抛出了某种异常对象。

  • 两者从某种程度上来说都是消极处理异常的方式,只是抛出或者可能抛出异常,但是不会由函数去处理异常,真正的处理异常由函数的上层调用处理。

<3>自定义异常

​ 在一些特殊的业务场景下,异常的类名通常也包含了该异常的有用信息,所以在抛出异常的时候应该选择合适的异常类,从而明确该异常情况,因此可借助自定义异常构建基于不同业务场景的异常处理机制

自定义异常的步骤如下:

# 基础的自定义异常
1.继承RuntimeException 或者Exception
2.写一个无参的构造函数
3.写一个String类型的构造函数
public class SelfException extends RuntimeException{
	/**
	 * 自定义异常类
	 * 1、继承RuntimeException或者Exception
	 * 2、写一个无参的构造函数
	 * 3、写一个String类型的构造函数
	 */
	public SelfException()
	{
		
	}
	public SelfException(String msg)
	{
		super(msg);
	}
}
public class SelfExceptionDemo {
	//定义方法f1
	public static double f1(int a,int b)
	{
		//求解两个数相除,要求b不能为负数,否则抛出异常
		if(b<0)
			throw new SelfException("被除数不能为负数!!");
		else if(b==0)
			throw new RuntimeException("被除数不能为0");
		return a/b;
	}
	public static void main(String[] args) {
		//测试
		System.out.println(f1(2,5));
	}
}

🚀案例分析:模拟借书系统

需求描述:模拟借书系统

a.定义字符串数组保存图书信息

b.提示用户输入,分别按“书名”、“图书序号”查找图书

c.根据输入信息进行适当的异常处理

  • 如果输入类型错误,抛出“错误命令异常”,并提示重新输入
  • 如果书名不存在,抛出“图书不存在异常”,并提示重新输入
  • 如果图书序号超出字符串数组范围,抛出“图书不存在异常”并提示重新输入
public class BorrowBookException extends Exception {
	/**
	 * 自定义异常类
	 * 1、继承RuntimeException或者Exception
	 * 2、写一个无参的构造函数
	 * 3、写一个String类型的构造函数
	 */
	public BorrowBookException()
	{
		
	}
	public BorrowBookException(String str)
	{
		super(str);
	}
}
public class BorrowBookSystem {
	public static LinkedHashMap save(String[] name,String[] id)
	{
		//1.根据定义字符串数组保存图书信息(书名、图书序号)
		LinkedHashMap lhm = new LinkedHashMap();
		for(int i=0;i<name.length;i++)
			lhm.put(id[i], name[i]);
		return lhm;
	}
	
	public static void main(String[] args) {
	
		String[] name = {"java","语文","数学","英语","物理","化学","生物","政治","高数","Oracle"};
		String[] id = {"1","2","3","4","5","6","7","8","9","10"};
		Scanner sc = new Scanner(System.in);
		LinkedHashMap lhm = save(name,id);
		while(true)
		{
			System.out.println("请选择要进行查找的方式:1.书名  2.图书序号");
			int choose = sc.nextInt();
			if(!(choose==1||choose==2))
				try {
					throw new BorrowBookException("输入类型错误,请重新输入");
				} catch (BorrowBookException e) {
					System.out.println(e.toString());//输出异常提示信息
				}
			else if(choose==1)
			{
				System.out.println("请输入要查询的信息(输入exit退出)");
				String str = sc.next();
				if(!str.equals("exit"))
				{
					if(!lhm.containsValue(str))
						try {
							throw new BorrowBookException("图书不存在,请重新输入...");
						} catch (BorrowBookException e) {
							e.printStackTrace();//输出具体的异常信息
						}
					else
						System.out.println("图书查找成功");
				}
				else
				{
					System.exit(0);
				}
			}
			else if(choose==2)
			{
				System.out.println("请输入要查询的信息(输入exit退出)");
				String str = sc.next();
				if(!str.equals("exit"))
				{
					if(Integer.valueOf(str)>lhm.keySet().size())
						try {
							throw new BorrowBookException("图书不存在异常,请重新输入...");
						} catch (BorrowBookException e) {
							e.printStackTrace();
						}
					else
						System.out.println("图书查找成功");
				}
				else
				{
					System.exit(0);
				}
			}
		}
	}
}

🚀扩展案例:java判断输入数据是否为纯数字?

​ 异常除却用作业务逻辑异常处理,还可在一些特殊的场景中应用,例如在java判断输入数据是否为纯数字,可能一开始想到的是循规蹈矩遍历输入的数据信息,随后一个个字符进行校验或者借助工具类、正则表达式等方式实现。

​ 此处换一种思路考虑也可借助异常处理机制去实现,也就是直接将这个数据的字符串信息进行数字转化,如果它可以被转化说明是纯数字,如果出现异常则相应通过try...catch...进行捕获处理

方式1:使用Character.isDigit(char)判断

		char num[] = str.toCharArray();//把字符串转换为字符数组
		StringBuffer title = new StringBuffer();//用StringBuffer将非数字放入title中
		StringBuffer hire = new StringBuffer();//把数字放到hire中
		for (int i = 0; i < num.length; i++) 
		{
			// 判断输入的数字是否为数字还是字符
			if (Character.isDigit(num[i]))
			{
				/**
				 * 把字符串转换为字符,再调用Character.isDigit(char)
				 * 方法判断是否是数字,是返回True,否则False
				 */
				hire.append(num[i]);// 如果输入的是数字,把它赋给hire
			} 
			else 
			{
				title.append(num[i]);// 如果输入的是字符,把它赋给title
			}
		}

方式2:使用类型转换判断

	try {
				String str="123abc";
			}
			int num=Integer.valueOf(str);//把字符串强制转换为数字
			return true;//如果是数字,返回True
		} catch (Exception e) {
		return false;//如果抛出异常,返回False
	}

方式3:使用正则表达式判断

		String str = "";
		boolean isNum = str.matches("[0-9]+");
		/**
		 * +表示1个或多个(如"3"或"225")
		 * *表示0个或多个([0-9]*)(如""或"1"或"22")
		 * ?表示0个或1个([0-9]?)(如""或"7")
		 * ps:这个方法只能用于判断是否是正整数
		 */

<4>Java的异常跟踪栈及异常的使用规则

🔖Java的异常跟踪栈

​ 读取异常栈信息 ,根据异常栈信息 寻找出错的原因和错误所在代码,然后可以阅读自己的业务逻辑,也可以通过断点追踪进行调试。

public class PrintStackTest {
	public static void main(String[] args) {
		System.out.println("滴滴滴..");
		f1();
	}
	public static void f1() {
		// ....业务逻辑代码....
		System.out.println("hello");
		f2();
		// ....业务逻辑代码....
		System.out.println("world");
	}
	public static void f2() {
		// ....业务逻辑代码....
		f3();
		// ....业务逻辑代码....
	}
	public static void f3() {
		// ....业务逻辑代码....
		f4();
		// ....业务逻辑代码....
	}
	public static void f4() {
		// ....业务逻辑代码....
		System.out.println("业务测试...");
		throw new SelfException("自定义异常触发....");
		// ....业务逻辑代码....
	}
}
输出结果显示:
滴滴滴..
hello
业务测试...
Exception in thread "main" com.myexception.SelfException: 自定义异常触发....
	at com.exception.base.PrintStackTest.f4(PrintStackTest.java:34)
	at com.exception.base.PrintStackTest.f3(PrintStackTest.java:27)
	at com.exception.base.PrintStackTest.f2(PrintStackTest.java:21)
	at com.exception.base.PrintStackTest.f1(PrintStackTest.java:14)
	at com.exception.base.PrintStackTest.main(PrintStackTest.java:8)

🔖异常的使用规则

不可否认的是Java的异常机制很方便,但是滥用异常机制也会造成负面影响。

主要包含以下两个方面:

  • 把异常和普通的代码混淆在一起,不编写任何错误处理代码

  • 不要把异常替代流程控制

注意事项:

  • 不要过度使用异常

  • 不要使用过于庞大的try块

  • 避免使用catch all 语句,要业务逻辑详细的描述异常

  • 不要忽略掉捕获到的异常

[!attention]

在实际的业务场景中,往往借助框架提供的入口构建自身业务的异常处理机制,从而实现对代码的规范,便于跟踪代码逻辑和异常情况分析,但不管是处于何种场景,更多的是希望异常机制给我们提供更多的程序运行相关的信息,从而辅助开发和运维

2.Optional

Optional概述

​ 可能包含或不包含非null值的容器对象

常用方法

方法名说明
static <T> Optional<T> of(T value)获取一个Optional对象,封装的是非null值的对象
static <T> Optional<T> ofNullable(T value)获取一个Optional对象,Optional封装的值对象可以是null也可以不是null

参考案例:使用Optional封装对象

class Student {
    String name;
    Integer age;

    public Student(String name, Integer age) {
        this.name = name;
        this.age = age;
    }
}

public class OptionalDemo1 {
    public static void main(String[] args) {
        Student stu1 = null;
        //ofNullable方法:获取一个Optional对象,Optional封装的值对象可以是null也可以不是null
        Optional<Student> os1 = Optional.ofNullable(stu1);
        System.out.println(os1); // Optional.empty

        // of方法:获取一个Optional对象,封装的是非null值的对象
        Student stu2 = new Student("h",18);
        Optional<Student> os2 = Optional.of(stu2);
        System.out.println(os2);// Optional[com.noob.demo.optional.Student@50cbc42f]

        // of方法:如果封装一个null值对象,则抛出空指针异常
        Optional<Student> os3 = Optional.of(stu1); // stu1为null,抛出空指针异常

    }
}

参考案例:常用方法

public class OptionalDemo2 {
    public static void main(String[] args) {
//        Student s = new Student("h",18);
        Student s = null;
        // 且如果封装的是一个null,那么通过get方法再次获取会抛出NoSuchElementException
        Optional<Student> optional = Optional.ofNullable(s);
        // 判断Optional所封装的对象是否不为空,如果不为空返回true,否则返回false
        if(optional.isPresent()){
            // get() 如果存在值,返回值,否则抛出NoSuchElementException
            Student student = optional.get();
            System.out.println(student);
        }else{
            System.out.println("Optional封装的对象为空");
        }
    }
}

参考案例:处理空指针

public class OptionalDemo3 {
    public static void main(String[] args) {
//        method1();
//        method2();
//        method3();
    }

    private static void method3() {
        //Student s = new Student("h",18);
        Student s = null;
        Optional<Student> optional = Optional.ofNullable(s);
        //ifPresent (Consumer<? super T> action):如果不为空,则使用该值执行给定的操作,否则不执行任何操作
        optional.ifPresent(student -> System.out.println(student));
    }

    private static void method2() {
        Student s = new Student("h",18);
        //Student s = null;
        Optional<Student> optional = Optional.ofNullable(s);
        //orElseGet(Supplier<? extends T> supplier):如果不为空,则返回具体的值,否则返回由括号中函数产生的结果
        Student student = optional.orElseGet(()-> new Student("xx" , 24));
        System.out.println(student);
    }

    private static void method1() {
        //Student s = new Student("h",18);
        Student s = null;
        Optional<Student> optional = Optional.ofNullable(s);
        //orElse(T other):如果不为空,则返回具体的值,否则返回参数中的值
        Student student = optional.orElse(new Student("xx", 24));
        System.out.println(student);
    }
}
评论
  • 按正序
  • 按倒序
  • 按热度
Powered by Waline v3.1.3