今日内容
- 选择排序
- 二分查找
- 异常处理
教学目标
- 能够理解选择排序的执行原理
- 能够理解二分查找的执行原理
- 能够辨别程序中异常和错误的区别
- 说出异常的分类
- 列举出常见的三个运行期异常
- 能够使用try…catch关键字处理异常
- 能够使用throws关键字处理异常
- 能够自定义并使用异常类
第一章 选择排序
1.1 选择排序概述
- 另外一种排序的方式,选中数组的某个元素,其后面的元素依次和选中的元素进行两两比较,将较大的数据放在后面,依次从前到后选中每个元素,直至所有数据按要求完成排序
- 如果有n个数据进行排序,总共需要比较n-1次
- 每一次比较完毕,下一次的比较就会少一个数据参与
1.2 选择排序图解
需求:将数组中的数据76543做升序排序。
针对当前的数组:
第一次:
先选择第一个空间,使用第一个空间和身后所有的空间进行比较,在比较的过程中,有哪个空间中的值比第一个空间中的小,就进行交换。直到把最后一个空间比较完之后,那么第一个空间就一定是最小值。
第二次:
选择第二个空间,从身后第三个空间开始依次比较,然后找到一个当前最小的最终保存在第二个空间中。
1.3 选择排序代码实现
选择排序代码实现得思路和步骤:
1)定义一个数组。
2)定义一个函数将选择排序的功能封装到函数中。
3)如果两个空间中需要交换数据,这时需要定义一个临时变量temp来保存选中空间的数据。
4)使用for循环对选中空间的下标进行遍历,这个for循环作为外层循环。
5)使用for循环对选中空间后面的空间的下标进行遍历,这个for循环作为内层循环。
6)使用判断结构对选中空间中的数据和后面的空间中的数据进行比较,如果选中空间中数据大于后面空间中的数据,则交换空间中的数据。如果选中空间中数据小于后面空间中的数据,则不交换空间中的数据。
7)在定义一个函数用来打印数组中的数据。
/*
选择排序:
另外一种排序的方式,选中数组的某个元素,其后面的元素依次和选中的元素进行两两比较,将较大的数据放在后面,依次从前到后选中每个元素,直至所有数据按要求完成排序
*/
public class ArrayDemo {
public static void main(String[] args) {
//定义一个数组
int[] arr = {7, 6, 5, 4, 3};
System.out.println("排序前:" + Arrays.toString(arr));
// 这里减1,是控制比较的轮数
//外循环提供的是当前需要选择的那个空间的下标
for (int i = 0; i < arr.length - 1; i++) {
// 从i+1开始,直到最后一个元素
//内循环提供的是 外循环提供的某个空间身后剩余的所有空间下标
for (int j = i+1; j < arr.length; j++) {
//对选中的空间和后面的某个空间进行比较
if (arr[i] > arr[j]) {
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
}
}
System.out.println("排序后:" + Arrays.toString(arr));
}
}
第二章 二分查找
2.1 普通查找和二分查找
普通查找
原理:遍历数组,获取每一个元素,然后判断当前遍历的元素是否和要查找的元素相同,如果相同就返回该元素的索引。如果没有找到,就返回一个负数作为标识(一般是-1)
二分查找
注意:二分查找对数组是有要求的,数组必须已经排好序
使用二分查找法需要记录数组中三个不同的角标:
1)起始角标:0角标 :start
2)结束角标: 数组.length-1尾角标 :end
3)中间角标: (起始角标+结束角标)/2 : min
对于升序的数组使用折半查找法方法:使用查找的值(key)和中间角标对应的值进行比较,存在三种情况:
1)如果key等于中间角标对应的值,说明已经找到key值,将找到的key值对应的下标返回给调用者
2)如果key大于中间角标对应的值,说明key值在中间角标对应的值后面,所以起始角标向后移动,结束角标不动,即start=min+1,重新计算中间角标,中间角标min=(start+end)/2。
3)如果key小于中间角标对应的值,说明key值在中间角标对应的值前面,所以结束角标向前移动,起始角标不动,即end=min-1,重新计算中间角标,中间角标min=(start+end)/2。
注意,以上三种情况是一个重复的过程,需要使用循环控制循环次数,循环条件是start<=end
2.2 二分查找(折半查找法)图解
折半查找分析图:
需求:数据如下,使用二分查找法查找指定数据在数组中的索引。
2 4 7 9 11 44 60 88 90
核心代码:
//定义三个变量并给初始化值
int start = 0;//头角标
int end =arr.length-1;//尾角标
int min=(start+end)/2;//中间角标
//假设要找的值x=11 88 4 55
//是一个循环过程,条件是start<=end
while(start<=end){
1.if(x == arr[min])
return min;
2.if(x > arr[min])
{
//要找的值在后面,所以start向后移动
start = min +1;
}
3.if(x < arr[min])
{
//要找的值在前面,所以end向前移动
end = min - 1;
}
4.//无论是执行第2步还是第3步,都需要更新中间索引
min = (start+end)/2;
}
2.3 二分查找代码实现
/*
折半查找
*/
class Demo6
{
public static void main(String[] args)
{
//定义数组
int[] arr={2,4,7,9,11,44,60,88,90};
//调用自定义的折半查找的函数
int index=binarySearch(arr,55);
System.out.println("index="+index);
}
/*
折半查找的方法
1.有没有返回值?
有,想要查找数组中某个数据的下标,所以返回要查找数据的下标
如果数组中没有该数据,则返回-1
2.有没有参数?
有,数组和想要查找的数据
*/
//自定义折半查找的函数
public static int binarySearch(int[] arr,int key)
{
//定义三个变量并给初始化值
int start = 0;//头角标
int end =arr.length-1;//尾角标
int min=(start+end)/2;//中间角标
//使用循环往复控制折半
while (start<=end)
{
//对中间的值和指定的值进行比较
if (key==arr[min])
{
return min;
}
//指定的值大于中间的值
if (key>arr[min])
{
//头角标向后移动
start=min+1;
}
//指定的值小于中间的值
if (key<arr[min])
{
//尾角标向前移动
end=min-1;
}
//如果头角标和尾角标发生变化,要时刻更新中间角标
min=(start+end)/2;
}
//循环结束,说明没有找到指定的数据
return -1;
}
}
第三章 异常
3.1 异常概念
异常:不正常。
生活中的异常:
例:在上课时,突然间停电,造成上课终止。 处理:等待来电、使用备用发电机。
程序中的异常:
程序在运行的过程,出现了一些突发状况,造成程序无法继续运行。我们把上述的突发状况,无法正常运行的这些状态称为Java中的异常。
Java中的异常:就是程序中出现的错误(bug),或者不正常现象。而我们在开发程序的时候,就需要对这些问题进行预先的判断和处理。
学习Java中的异常,我们需要研究:
1、什么是异常;
2、异常问题怎么去解决和处理;
3、我们自己怎么把问题报告给程序的使用者;
我们编写程序的时候,在程序中肯定会有问题(bug)出现,而sun公司把开发中最最常见的一些问题,进行总结和抽取,形成了一个体系,这个体系就是我们要学习的异常体系。
异常指的并不是语法错误,语法错了,编译不通过,不会产生字节码文件,根本不能运行.
3.2 异常的产生过程解析(掌握)
需求:根据用户指定的下标,输出该下标对应的数据。
1)定义一个类ExceptionDemo;
2)定义一个主方法,在主方法中定义一个int类型的数组,在这个数组中存放int类型的数据;
3)在这个类中自定义一个函数getValue,在这个函数中根据调用者传递过来的下标和数组返回给调用者
对应的数据;
4)在主方法中调用自定义函数getValue,接收自定义函数返回回来的数据,并将数据打印到屏幕上;
说明:上述代码发生异常的过程:
1)jvm先去调用main函数,在main函数中调用了getValue函数,然后jvm将getValue函数加载到内存中;
2)Jvm在执行getValue函数的时候,由于数组下标index的值3超过了数组的最大下标的范围,所以在这里发生了异常问题,ArrayIndexOutOfBoundsException,这样导致程序就不会向下执行,jvm会在发生异常的地方停止,jvm会将发生异常信息(发生异常的位置、异常内容、异常类型等)封装到一个类中(Java的异常类),然后把这个封装了异常信息类的对象(new 异常类)丢给了当前调用函数的地方。
3)如果发生异常,jvm会自动创建封装了异常信息的异常类的对象,然后将对象使用throw关键字抛给调用getValue函数的地方。
4)由于getValue函数把问题抛给了main函数,所以导致了main函数中也有了异常,而main函数中的异常是被迫接收的,此时main函数中并没有解决此异常的解决方案,但是main函数是jvm调用的,所以main函数又将异常抛给了jvm虚拟机,jvm已经是底层了,不能再将异常抛出,jvm需要对这个问题进行处理,即将这个问题显示到屏幕上,让程序的使用者看到。
3.3 异常简单应用举例
需求:代码和上述代码相同,我们要解决上述问题的发生。
1)由于在自定义函数中两个参数都是接收外界传递过来的,我们为了保证程序的健壮(合法)性,
所以我们要对传递过来的数据进行合法性的判断;
2)分别对下标和数组进行判断,如果不合法,将发生问题的异常抛出去给程序员看;
/*
针对发生的异常进行简单的处理
*/
class ExceptionDemo1
{
public static void main(String[] args)
{
//定义数组
int[] arr={1,2,5};
//int value=getValue(arr,1);
int value=getValue(arr,1);
System.out.println(value);
}
//定义函数根据指定的下标返回对应的值
public static int getValue(int[] arr,int index)
{
/*
以后在开发中,定义函数的时候,对外界传递过来的参数一定要
合法性的判断
这里需要对错误数据进行判断,然后将错误信息报告给调用者
在实际开发中我们一般会给固定的文件写错误信息(错误文档)
*/
//System.out.println("haha");
/*
* 发生空指针异常的地方,一定是使用了某个引用变量,而这个引用变量又不指向任何的对象
* 也就是说引用变量中保存的是null。这是使用空的引用调用属性或行为,而由于根本引用不指向
* 任何的对象,那么就没有属性和行为而言,更无法去调用了。肯定就发生空指针异常。
*/
if(arr==null)
{
throw new NullPointerException("对不起,数组引用变量的值不能为null");
}
if(index<0 || index>=arr.length)
{
throw new ArrayIndexOutOfBoundsException("下标越界了。。。");
}
return arr[index];
}
}
3.4 异常体系
我们书写程序,肯定会有问题的发生,这些问题统称为异常。而sun公司把最常见的一些异常进行类的描述和封装。然后我们如果在程序中遇到了这些问题,就可以直接通过这些描述异常的类进行错误信息的封装。然后把这些信息丢给程序的调用者。
异常的根类是java.lang.Throwable
,Throwable这个类描述的是Java中所有异常和错误的共性内容。其下有两个子类:java.lang.Error
与java.util.Exception
,平常所说的异常指java.util.Exception
。
Throwable体系:
-
Error:严重错误Error,无法通过处理的错误,只能事先避免,好比绝症。
在程序运行时,会产生一些错误信息。java把这些错误信息使用Error或其子类进行描述。
错误属于系统级别的,是由于JVM在操作内存时(JVM需要借助操作系统来实现内存的操作),出现了一些不正常的操作,造成内存错误,出现错误后操作系统就会把这个错误返回给JVM。
在程序中,遇到错误时,java没有针对性的解决方案,只能通过修改源代码的方式来解决程序中的错误问题。
-
Exception:表示异常,异常产生后程序员可以通过代码的方式纠正,使程序继续运行,是必须要处理的。好比感冒。
在程序运行时,也会出现一些异常状况。表示Java程序中存在的异常问题,而不是错误问题。这些异常问题,在程序中通过判断等形式是可以检测并且预防的。针对这些异常问题,程序员在写代码的时候一旦发生,必须给出有效的解决方案。
java对于异常状况是有针对性的解决方案(异常处理),例:角标越界、空指针异常等。
异常状况的发生,通常是JVM在操作一些数据时,出现的问题,java对于异常的发生,是可以通过一些手段(捕获)避免程序终止运行,保证让程序继续向下正常执行。
3.5 异常分类
我们平常说的异常就是指Exception,因为这类异常一旦出现,我们就要对代码进行更正,修复程序。
异常(Exception)的分类:根据在编译时期还是运行时期去检查异常?
- 编译时期异常:checked异常。在编译时期,就会检查,如果没有处理异常,则编译失败。(如日期格式化异常)
- 运行时期异常:runtime异常。在运行时期,检查异常.在编译时期,运行异常不会让编译器检测(不报错)。
小结:
RuntimeException和Exception有什么区别:
Exception属于编译时异常,编译器在编译时会检测该异常是否异常的处理方案 ,如果没有处理方案,编译不能通过。
RuntimeException属于运行时异常,编译器不会检测该异常是否有异常的处理方案,不需要声明。
说明:在Exception的所有子类异常中,只有RuntimeException不是编译异常,是运行时异常,其他子类都是编译异常。
第四章 异常的处理
Java异常处理的五个关键字:try、catch、finally、throw、throws
4.1 抛出异常throw
在编写程序时,我们必须要考虑程序出现问题的情况。比如,在定义方法时,方法需要接受参数。那么,当调用方法使用接受到的参数时,首先需要先对参数数据进行合法的判断,数据若不合法,就应该告诉调用者,传递合法的数据进来。这时需要使用抛出异常的方式来告诉调用者。
在java中,提供了一个throw关键字,它用来抛出一个指定的异常对象。那么,抛出一个异常具体如何操作呢?
-
创建一个异常对象。封装一些提示信息(信息可以自己编写)。
-
需要将这个异常对象告知给调用者。怎么告知呢?怎么将这个异常对象传递到调用者处呢?通过关键字throw就可以完成。throw 异常对象。
throw用在方法内**,用来**抛出一个异常对象,将这个异常对象传递到调用者处,并结束当前方法的执行。
使用格式:
throw new 异常类名(参数);
例如:
throw new NullPointerException("要访问的arr数组不存在");
throw new ArrayIndexOutOfBoundsException("该索引在数组中不存在,已超出范围");
学习完抛出异常的格式后,我们通过下面程序演示下throw的使用。
public class ThrowDemo {
public static void main(String[] args) {
//创建一个数组
int[] arr = {2,4,52,2};
//根据索引找对应的元素
int index = 4;
int element = getElement(arr, index);
System.out.println(element);
System.out.println("over");
}
/*
* 根据 索引找到数组中对应的元素
*/
public static int getElement(int[] arr,int index){
//判断 索引是否越界
if(index<0 || index>arr.length-1){
/*
判断条件如果满足,当执行完throw抛出异常对象后,方法已经无法继续运算。
这时就会结束当前方法的执行,并将异常告知给调用者。这时就需要通过异常来解决。
*/
throw new ArrayIndexOutOfBoundsException("哥们,角标越界了~~~");
}
int element = arr[index];
return element;
}
}
注意:如果产生了问题,我们就会throw将问题描述类即异常进行抛出,也就是将问题返回给该方法的调用者。
那么对于调用者来说,该怎么处理呢?一种是进行捕获处理,另一种就是继续将问题声明出去,使用throws声明处理。
4.2 声明异常throws(掌握)
声明异常:将问题标识出来,报告给调用者。如果方法内通过throw抛出了编译时异常,而没有捕获处理(稍后讲解该方式),那么必须通过throws进行声明,让调用者去处理。
关键字throws运用于方法声明之上,用于表示当前方法不处理异常,而是提醒该方法的调用者来处理异常(抛出异常).
声明异常格式:
修饰符 返回值类型 方法名(参数) throws 异常类名1,异常类名2…
{
方法体
}
注意:
1)throws后面可以跟多个异常类,使用逗号分隔;
2)在函数中如果发生了异常,jvm会拿发生的异常和声明的异常类进行匹配,如果匹配才会把发生的异常交给别人处理;
需求:声明的简单使用
1)定义一个ThrowDemo类;
2)在定义一个Demo类,在这个类中定义一个函数show,在show函数中对传入的参数x进行判断,如果x等于0,则使用throw关键字抛出Exception异常;
3)由于抛出的是编译时异常所以需要在show函数上使用throws关键字声明这个异常,告诉调用者,有异常;
4)在show函数中打印x的值;
5)在ThrowDemo类中创建Demo类的对象,使用对象调用函数;
声明异常的代码演示:
public class ThrowsDemo {
public static void main(String[] args) throws Exception {
show(2);
}
// 如果定义功能时有问题发生需要报告给调用者。可以通过在方法上使用throws关键字进行声明
public static void show(int x) throws Exception {
if (x == 0) {//如果x等于0
// 我假设 如果不是 a.txt 认为 该文件不存在 是一个错误 也就是异常 throw
throw new Exception("x不能等于0");
}
System.out.println(x);
}
}
注意:在开发中,main函数中不会出现声明,在main函数通常是使用捕获。
4.3 捕获异常try…catch(掌握)
4.3.1格式
就是遇到异常时,不再把异常交给他人处理,自己处理。
在程序中有异常,但这个异常我们不能继续使用throws声明,这时不处理,程序无法编译通过,那么在程序中只能使用捕获方式解决问题。
格式如下:
try{
编写可能会出现异常的代码
}catch(异常类型 对象名){
处理异常的代码
//记录日志/打印异常信息/继续抛出异常
}
**try:**该代码块中编写可能产生异常的代码。
**catch:**用来进行某种异常的捕获,实现对捕获到的异常进行处理。
注意:try和catch都不能单独使用,必须连用。
演示如下:
public class ThrowDemo {
/*
* 一般在开发中我们不会在主函数上面抛异常,主函数一般是最后处理者,
* 我们需要在主函数中对异常进行处理------》捕获
*/
public static void main(String[] args){
try
{
show(0);//异常代码
}catch(Exception e)//Exception e=new Exception("x不能等于0")
{
//处理异常的代码
//System.out.println("hahhahaha");
//System.out.println(e.getMessage());//x不能等于0
// System.out.println(e);
e.printStackTrace();
}
}
public static void show(int x) throws Exception {
if (x == 0) {//如果x等于0
throw new Exception("x不能等于0");
}
System.out.println(x);
}
}
总结:不管自己是函数的定义者,还是函数的调用者,只要是在自己的函数中有异常发生, 那么自己都可以使用上述的两种方案对异常进行处理。
4.3.2如何获取异常信息:
Throwable类中定义了一些查看方法:
异常中的常用方法
方法 | 说明 |
---|---|
String getMessage() | 获取报错原因. |
String toString() | 获取报错的类型和原因 |
printStackTrace() | 直接打印报错的类型、原因和位置.包含了异常的类型,异常的原因,还包括异常出现的位置. |
代码演示:
public class Test04 {
public static void main(String[] args) {
//解析异常(编译时期异常)
String s = "2000-11-12";
SimpleDateFormat sdf = new SimpleDateFormat("yyyy/MM/dd");
try {
//解析
Date date = sdf.parse(s);
System.out.println(date);
}catch (ParseException e){
//e是一个异常对象
//1.getMessage()获取报错原因
// String ss = e.getMessage();
// System.out.println(ss); //Unparseable date: "2000-11-12"
//2.toString()获取报错的类型和原因
// String ss = e.toString();
// System.out.println(ss); //java.text.ParseException: Unparseable date: "2000-11-12"
//3.printStackTrace()直接打印报错的类型、原因和位置
/*
java.text.ParseException: Unparseable date: "2000-11-12"
at java.base/java.text.DateFormat.parse(DateFormat.java:388)
at com.itheima_03.Demo05_tryCatch.main(Demo05_tryCatch.java:17)
*/
e.printStackTrace();
}
//当trycatch执行完程序会继续往后执行
}
}
4.3.3捕获多个异常
多个异常使用捕获又该如何处理呢?
多个异常一次捕获,多次处理。
一般我们是使用一次捕获多次处理方式,格式如下:
try{
编写可能会出现异常的代码
}catch(异常类型A e){ 当try中出现A类型异常,就用该catch来捕获.
处理异常的代码
//记录日志/打印异常信息/继续抛出异常
}catch(异常类型B e){ 当try中出现B类型异常,就用该catch来捕获.
处理异常的代码
//记录日志/打印异常信息/继续抛出异常
}
4.4 finally 代码块(掌握)
finally:有一些特定的代码无论异常是否发生,都需要执行。另外,因为异常会引发程序跳转,导致有些语句执行不到。而finally就是解决这个问题的,在finally代码块中存放的代码都是一定会被执行的。
什么时候的代码必须最终执行?
当我们在try语句块中打开了一些物理资源(磁盘文件/网络连接/数据库连接等),我们都得在使用完之后,最终关闭打开的资源。
finally的语法:
try…catch…finally:自身需要处理异常,最终还得关闭资源。
try{
可能发生异常的代码
}catch(异常类名 变量名){
处理异常的代码。
}finally{
程序中永远都能执行到的代码
}
在程序中随时都有可能发生异常,一旦程序中发生了异常,这样就会导致在发生异常的地方之后的所有代码都不会运行。可是在程序中,有时有些代码不管程序中有没有问题都必须执行。这时这部分代码必须写在finally代码块中。
注意:finally不能单独使用。
比如在我们之后学习的IO流中,当打开了一个关联文件的资源,最后程序不管结果如何,都需要把这个资源关闭掉。
finally代码参考如下:
需求:
1)创建一个Test类;
2)定义一个Demo1类,在这个类中定义一个show函数,根据传递进来的参数x进行判断,如果x等于0,则使用throw关键字抛异常Exception;
3)对这个异常我们不使用声明处理,我们使用捕获进行处理,即使用try-catch-finally进行处理;
4)并分别在try-catch-finally的代码中使用return关键字返回给调用者1,2,3;
5)在Test类中创建Demo1对象,并使用对象调用类中的show函数,并打印返回来的值;
package cn.itcast.sh.b_excep_other;
class Demo1
{
public int show(int x)
{
try
{
if(x==0){
throw new Exception("x是零");
}
System.out.println("try.....");
return 1;
}catch(Exception e)
{
System.out.println("捕获异常");
return 2;
}finally
{
System.out.println("必须执行");
return 3;
}
}
}
public class Test {
public static void main(String[] args) {
Demo1 d = new Demo1();
int value=d.show(0);
System.out.println(value);
}
}
说明:在上述代码中,因为传入show函数中的值是0,所以发生异常,那么在jvm执行catch中的return 2代码的时候,jvm发现此时下面有finally关键字,那么jvm先不会执行return 2语句,jvm会先去执行finally中的代码,因为此时finally中有return 3语句,在函数中,遇见return关键字就会结束整个函数,此时jvm不会回来执行return2语句,如果在finally代码块中没有return语句,那么jvm在执行finally代码块里面的代码之后就又会回到catch中继续执行return 2语句。
finally代码块是永远都会被执行的代码,不管程序发生什么问题,最后JVM一定会把finally中的代码执行一遍。
使用场景
之后会学习IO流,IO流就相当于水龙头。
打开水龙头之后,中间不管是做了什么操作,最后都一定要关闭水龙头。(节约资源)
关闭水龙头(节约资源)的代码就可以写在finally
4.5 异常注意事项
-
运行时异常被抛出可以不处理。即不捕获也不声明抛出。
-
父类方法没有抛出异常,子类覆盖父类该方法时也不可抛出异常。此时子类产生该异常,只能捕获处理,不能声明抛出
需求:
1)创建类Test2;
2)定义一个Fu类和Zi类,在Fu类中定义一个method函数;
3)在Zi类中复写method函数,在这个函数中抛Exception异常;
4)在类Test2中创建Zi类对象,通过子类对象调用子类中的method函数;
5)在Zi类中的method中捕获异常;
package cn.itcast.sh.b_excep_other;
class Fu
{
public void method()
{
System.out.println("父类中的method");
}
}
class Zi extends Fu
{
public void method() //throws Exception
{
System.out.println("子类重写方法method");
try {
throw new Exception("异常");
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
public class Test2 {
public static void main(String[] args) {
Zi z=new Zi();
z.method();
}
}
-
在try/catch后可以追加finally代码块,其中的代码一定会被执行,通常用于资源回收。
-
如果finally有return语句,永远返回finally中的结果,避免该情况.
需求:
1)创建一个Test类;
2)定义一个Demo1类,在这个类中定义一个show函数,根据传递进来的参数x进行判断,如果x等于0,则使用throw关键字抛异常Exception;
3)对这个异常我们不使用声明处理,我们使用捕获进行处理,即使用try-catch-finally进行处理;
4)并分别在try-catch-finally的代码中使用return关键字返回给调用者1,2,3;
5)在Test类中创建Demo1对象,并使用对象调用类中的show函数,并打印返回来的值;
package cn.itcast.sh.b_excep_other; class Demo1 { public int show(int x) { try { if(x==0){ throw new Exception("x是零"); } System.out.println("try....."); return 1; }catch(Exception e) { System.out.println("捕获异常"); return 2; }finally { System.out.println("必须执行"); return 3; } } } public class Test { public static void main(String[] args) { Demo1 d = new Demo1(); int value=d.show(0); System.out.println(value); } }
第五章 自定义异常
5.1 概述
为什么需要自定义异常类:
我们说了Java中不同的异常类,分别表示着某一种具体的异常情况,那么在开发中总是有些异常情况是SUN没有定义好的,此时我们根据自己业务的异常情况来定义异常类。,例如年龄负数问题,考试成绩负数问题。
在上述代码中,发现这些异常都是JDK内部定义好的,但是实际开发中也会出现很多异常,这些异常很可能在JDK中没有定义过,例如年龄负数问题,考试成绩负数问题.那么能不能自己定义异常呢?
什么是自定义异常类:
在开发中根据自己业务的异常情况来定义异常类.
自定义一个业务逻辑异常: LoginException。一个登陆异常类。
异常类如何定义:
-
自定义一个编译期异常: 自定义类 并继承于
java.lang.Exception
。 -
自定义一个运行时期的异常类:自定义类 并继承于
java.lang.RuntimeException
。异常也是一个类,那么就和我们学习面向对象中定义类没有区别:
格式:
public class 异常类的名字 extends Exception / RuntimeException { //不需要任何的属性和行为,仅仅只需要提供构造函数即可。 public 异常类的名字(){} public 异常类的名字( String message ){ super(message); } }
注意:不需要任何的属性和行为,仅仅只需要提供构造函数即可。
查看异常源码
public class NullPointerException extends RuntimeException {
//版本号 没什么用
private static final long serialVersionUID = 5162710183389028792L;
//构造方法
public NullPointerException() {
super();
}
//构造方法
public NullPointerException(String s) {
super(s);
}
}
-
通过查看源码我们发现,异常类中什么都没有定义。
-
不同的异常只是类名不同而已,内部并没有特殊的方法。类名不同方便程序员识别不同的错误。
-
自定义异常的作用
- 自定义异常的作用就是在出现异常时,让异常的名字更加直观。
5.2 自定义异常的练习
练习:
需求:定义一个类,描述矩形,提供计算面积的功能。要求对长和宽进行判断, 如果非法直接抛出长或宽非法异常。
分析和步骤:
1)定义一个类Rectangle描述矩形,在这个类中分别定义长length和宽width两个私有属性,并对外提供get和set方法;
2)在Rectangle类中定义一个构造函数分别给长length和宽width初始化值;
3)在这个类的构造函数中分别对length和width两个属性进行判断,如果长和宽不合法,分别对长和宽进行抛异常,同时构造函数要声明异常;
4)定义一个类IllegalWidthException对非法的宽进行异常处理,在这个类中分别定义无参构造函数和有一个参数的构造函数,同时这个类需要继承Exception类,这样在编译的时候就可以检测异常;
5)定义一个类IllegalLengthException对非法的长进行异常处理,在这个类中分别定义无参构造函数和有一个参数的构造函数,同时这个类需要继承Exception类,这样在编译的时候就可以检测异常;
6)在Rectangle类中定义一个计算矩形面积的函数getArea,将最终的面积值返回给调用者;
7)定义一个异常测试类ThrowTest,在这个测试类中创建矩形类Rectangle的对象,并通过对象调用计算圆的面积的函数getArea并打印,同时并对异常进行捕获处理;
/*
* 需求:定义一个类,描述矩形,提供计算面积的功能。
* 要求对长和宽进行判断, 如果非法直接抛出长或宽非法异常。
*/
//自己定义长和宽非法的异常
class IllegalWidthException extends Exception{
public IllegalWidthException() {
super();
}
public IllegalWidthException(String message) {
super(message);
}
}
//非法的长度异常
class IllegalLengthException extends Exception{
public IllegalLengthException() {
super();
}
public IllegalLengthException(String message) {
super(message);
}
}
//描述矩形
class Rectangle{
//长和宽
private double length;
private double width;
public Rectangle(double length, double width) throws IllegalLengthException, IllegalWidthException {
//对长和宽进行合法的验证
if( length <= 0 ){
throw new IllegalLengthException("非法的矩形长度");
}
if( width <= 0 ){
throw new IllegalWidthException("非法的矩形宽度");
}
this.length = length;
this.width = width;
}
public double getLength() {
return length;
}
public void setLength(double length) {
this.length = length;
}
public double getWidth() {
return width;
}
public void setWidth(double width) {
this.width = width;
}
//计算面积
public double getArea(){
return this.length * this.width;
}
}
public class ThrowTest {
public static void main(String[] args) {
//创建矩形对象
try {
Rectangle r = new Rectangle(-3,4);
System.out.println(r.getArea());
} catch ( IllegalWidthException e) {
e.printStackTrace();
} catch ( IllegalLengthException e) {
e.printStackTrace();
}
}
}
5.3 关于捕获多个异常注意事项
一般我们是使用一次捕获多次处理方式,格式如下:
try{
编写可能会出现异常的代码
}catch(异常类型A e){ 当try中出现A类型异常,就用该catch来捕获.
处理异常的代码
//记录日志/打印异常信息/继续抛出异常
}catch(异常类型B e){ 当try中出现B类型异常,就用该catch来捕获.
处理异常的代码
//记录日志/打印异常信息/继续抛出异常
}
注意:这种异常处理方式,要求多个catch中的异常不能相同,并且若catch中的多个异常之间有子父类异常的关系,那么子类异常要求在上面的catch处理,父类异常在下面的catch处理。
关于捕获多个异常注意事项代码演示如下:
需求:
1)定义一个Demo类,在这个类中分别定义主函数和一个show函数;
2)在分别定义两个异常类A和B;
3)A类继承Exception类,并创建两个构造函数;
4)B类继承A类,并创建两个构造函数;
5)在Demo类中的主函数中调用show函数,并传参0;
6)在Demo类中的show函数中分别写三个判断语句,使用throw关键字分别抛出B、A和Exception类三个异常类的对象,同时在show函数声明三个异常;
7)这样在main函数中就得对异常语句(调用show函数语句)进行捕获异常处理;
package cn.itcast.sh.b_excep_other;
//自定义异常类
class A extends Exception
{
public A() {
super();
}
public A(String message) {
super(message);
}
}
class B extends A
{
public B() {
super();
}
public B(String message) {
super(message);
}
}
public class Demo {
public static void main(String[] args) {
// TODO Auto-generated method stub
try
{
show(0);//new Exception() new A() new B()
//这里可能发生A B Exception异常中的某一个异常
/*
* Exception e =new Exception()
* Exception e=new A()
* Exception e=new B()
*/
}catch(B b)
{
}catch(A a)
{
}catch(Exception e)//
{
}
}
public static void show(int x) throws B,A,Exception
{
if(x==0)
{
throw new B();
}
if(x==1)
{
throw new A();
}
if(x==2)
{
throw new Exception();
}
}
}