1 异常
- 概述:
客观角度:不符合现实生活的各种情况,都可以理解为是异常
Java语言角度:在代码的运行过程中,出现的各种错误导致程序停止运行,那么这些错 误就是异常。 - 注意:
在程序中,异常并不是一个一个表示信息的字符串。异常是一个对象来表示,是JVM 虚拟机创建的异常对象,如果程序中出现了异常对象,就会抛出这个异常对象的各种信 息。 - 异常类型:
Throwable:抛出。 每一个异常类型的顶层父类
子类:
Error:错误 错误一般表示比较严重的问题,一旦出现错误无法通过代码解决。
Exception:异常 异常一般表示比较轻微的问题,如果程序中出现了异常,可以通过代码解决这个异常。
异常中有两种分支:
编译时异常:除了RuntimeException类型之外的其他类型
运行时异常:RuntimeException 和他的子类类型
1.1 编译时异常和运行时异常
- 编译时异常:在代码编译阶段,系统会检查代码的语法格式等情况,如果在检查的过程 中出现了问题,就提示一个错误,这些问题就属于编译时异常。
- 运行时异常:在代码编译阶段不对代码进行检查,但是在代码运行阶段,如果出现了一 些逻辑等会导致程序意外终止的问题,这些问题就属于运行时异常。
- 注意:
不管是编译时异常,还是运行时异常,都是在运行阶段才会出错。
1.2 JAVA虚拟机默认处理异常的方式
- 如果在代码中的某个方法内出现了错误情况,系统会将这个错误发生的原因,发生异常 类型,发生的路径封装到异常对象中。
- 如果当前方法中没有处理这个异常对象,就将异常往上抛出,抛给调用该方法的方法。
- 如果调用的方法也没有处理异常,那么就一层一层往上抛出,直到抛给main方法,main 方法再抛给虚拟机
- 虚拟机将当前异常对象通过标准错误流,打印到控制台,并结束自己。
代码
package demos2_err;
public class Demo01 {
public static void main(String[] args) {
//接收到test01抛出的异常对象之后,main也无法处理异常
//往上抛出该异常,抛给虚拟机
//虚拟机接收到异常对象之后,就通过错误输出流将异常对象中的信息打印到控制台,并结束自己
test01();
}
private static void test01() {
//接收到test02方法抛出的异常对象之后,本身也无法处理
//只能将该异常对象抛出,抛给main方法
test02();
}
private static void test02() {
//出现一个错误
//将这个错误发生的各种信息封装到一个异常对象中
//该方法就将这个异常对象抛给test01();
int i = 10 / 0;
System.out.println(i);
}
}
2 手动处理异常的方式
- 异常声明
- 异常捕获
2.1 异常声明
- 异常的声明:如果在某个方法中出现了编译时异常,可以在当前方法上声明这个异常的类型,声明之后编译时异常就会消失。
总结:异常的声明只能处理编译时异常
异常的声明,声明之后,编译时异常就会消失 - 声明异常的格式:
修饰符 返回值类型 方法名称 (参数列表)throws 异常类型1,异常类型2…{
方法体语句;
}
- 注意事项:
(1)异常的声明,不能从本质上解决问题,只能在编译阶段不检查这段代码
如果后续传入一些错误的数据,在运行阶段也可能会发生错误。
(2)如果方法1中进行了异常的声明,方法2调用了方法1,那么方法2需要对该异 常进行捕获或者处理。
(3)在声明异常的时候,尽量声明小的异常类型
代码
package demos2_err;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
public class Demo02 {
//在方法上声明方法中出现的编译时异常
//声明之后,该编译时异常就会在编译阶段不做检查
//在运行阶段,还有可能出现问题
public static void main(String[] args)throws ParseException {
SimpleDateFormat sim = new SimpleDateFormat("yy年");
String str = "20年";
Date d = sim.parse(str);
System.out.println(d);
System.out.println("你好你好");
}
}
3 异常的捕获
- 异常的捕获:如果代码的某个位置会出现了错误情况,可以使用特定的格式,捕获这个 错误,捕获之后可以按照自己定义的方式去处理异常。
- 格式:
try … catch
try … catch … finally
try … finally
3.1 try … catch
3.1.1 格式:
try{
可能会出现错误的代码
}catch(异常类型 异常对象名称){
处理异常的方式
}
3.1.2 执行流程:
- 先执行try中的代码,检测是否出现异常
- 如果try中的代码没有出现问题,trycatch直接结束,代码正常执行trycatch后面的 代码。
- 如果try中出现了异常,程序立即跳转到catch中查看出现异常所属的类型和catch中声明的类型是否一样,如果一样,就捕获该异常按照指定的方式去处理异常,处理之后,trycatch结束,程序继续运行。
- 如果try中出现了catch中没有声明的异常类型,就不能捕获该异常,这时虚拟机来处理这个异常(默认处理方式)。
3.1.3注意事项:
(1)如果在某行代码中,出现了异常,立即去catch块中去匹配异常类型,出现错误的代码后面的代码就不能执行了。
代码
package demos2_err;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Scanner;
public class Demo04 {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
//执行try中代码
//如果出现异常,立即执行catch块
//如果没有出现异常,程序正常往下执行,不会执行catch块
try{
int i = 10 / sc.nextInt();//如果录入的数据为0:出现算术异常
System.out.println(i);
int[] arr = {1,2,3,4};
System.out.println(arr[sc.nextInt()]);//如果录入的值不是0-3出现异常
//如果try中出现的异常对象所属类型,和定义的类型相同,执行定义的处理方式
//如果try中出现的异常对象和定义的类型不匹配,就由虚拟机默认处理
}catch(ArithmeticException ae){
System.out.println("算术异常发生了");
}
System.out.println("hello");
}
}
3.2 try…catch格式的多种异常情况
3.2.1 格式:
try{
可能出现错误的代码
}catch(异常类型1 对象名1){
异常1的处理方式
}catch(异常类型2 对象名2){
异常2的处理方式
}...
3.2.2 流程:
- 先执行try中的代码,检测是否出现异常
- 如果出现了异常, 就先和异常类型1匹配,如果能匹配上就执行异常1的处理方 式,处理之后,直接结束整个try…catch语句,执行之外的代码。
- 如果不能和异常类型1匹配,就继续和异常类型2匹配,如果能匹配上,就执行异 常类型2的处理方式,之后结束整个try…catch语句,执行之外的代码。
- 如果异常类型2不能匹配,依次类推,往后匹配。
- 如果出现的异常,catch中的类型都不能匹配,虚拟机默认处理
3.2.3 注意事项:
(1)如果定义的多个catch块中的类型,有子父类的关系,不能将父类的类型定义在 子类的类型前面。
(2)如果ctach块中定义的多个异常类型,要使用同一种处理方式,可以使用|来定义异常类型。
代码
package demos2_err;
import java.util.Scanner;
public class Demo05 {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
try{
int i = 10 / sc.nextInt();//如果录入的数据为0:出现算术异常
System.out.println(i);
int[] arr = {1,2,3,4};
System.out.println(arr[sc.nextInt()]);//如果录入的值不是0-3出现异常
arr = null;
System.out.println(arr[0]);
}catch(ArithmeticException ae){
System.out.println("算术异常发生了!");
}catch (ArrayIndexOutOfBoundsException | NullPointerException ae){
System.out.println("数组异常发生了!");
}catch(Exception e) {
System.out.println("异常发生了");
}
System.out.println("hello");
}
}
3.3 try … catch … finally
- 格式:
try{
可能会发生错误的代码
}catch(异常类型1 异常对象1){
异常1的处理方式
}catch(异常类型2 异常对象2){
异常2的处理方式
…
}finally{
一定需要执行的代码
}
- finally:
使用原因:
(1)如果有某些代码一定要执行,将代码放在try中或者catch中或者trycatch外 都有可能执行不到
(2)将这段代码放在finally块中,不管遇到什么情况,系统都会去执行finally中的 内容。 - 注意事项:
(1)finally关键字不能单独使用,一般是用来定义一个代码块
(2)finally代码块也不能单独使用,一定是和try代码块一起使用
代码
package demos2_exception;
import java.util.Scanner;
public class Demo06 {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
try{
int i = 10 / sc.nextInt();//如果录入的数据为0:出现算术异常
System.out.println(i);
int[] arr = {1,2,3,4};
System.out.println(arr[sc.nextInt()]);//如果录入的值不是0-3出现异常
}catch(ArithmeticException a){
System.out.println("算术异常发生了");
//一定会执行的代码(不管遇到任何情况都会执行)
}finally{
System.out.println("一定要执行的代码");
}
}
}
3.4 try…finally格式
- 格式:
try{
第一段代码
}finally{
第二段代码
}
- 作用:
如果在程序中,有多段代码,多段代码都需要有执行的机会,那么可以将这多段代码,分别放在try中和finally中,这样,多段代码之间就会互不影响,都有执行的机会。
用来分隔代码,让代码之间互不影响,都有执行的机会。
代码
package demos2_exception;
import java.util.Scanner;
public class Demo07 {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
//如果两段代码都需要执行,可以使用tryfinally分隔两段代码
//其中一段定义在try中先执行
try{
try{
int i = 10 / sc.nextInt();//如果录入的数据为0:出现算术异常
System.out.println(i);
}catch(ArithmeticException ae){
System.out.println("算术异常发生了!");
}
//另外一段定义在finally中,一定会被执行
}finally{
try{
int[] arr = {1,2,3,4};
System.out.println(arr[sc.nextInt()]);//如果录入的值不是0-3出现异常
}catch(ArrayIndexOutOfBoundsException aioe){
System.out.println("数组索引越界异常发生了");
}
}
}
}
4 异常类型中的方法
- 概述:在异常的体系中,顶层父类
Throwable
中定义了一下操作异常对象方法 - 常用的方法:
方法名 | 解释 |
---|---|
getMessage() | 返回发生异常的原因 |
getCause() | 返回引起调用者异常对象发生的另一个异常对象 |
toString() | 返回该异常对象发生的原因及所属的类型 |
printStackTrace() | 返回该异常发生的路径,原因及类型 |
代码
package demos2_exception;
import java.util.Scanner;
public class Demo08 {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
try{
int i = 10 / sc.nextInt();
System.out.println(i);
}catch(ArithmeticException ae){
System.out.println("算术异常发生了");
//当前异常对象是否是被其他异常对象引起的
//如果是由其他异常对象引起,就会返回引起的异常对象
//如果不是由其他异常对象引起,返回null
System.out.println(ae.getCause());
//返回该异常对象发生的原因
System.out.println(ae.getMessage());
//返回该异常对象所属的类型 + 原因
System.out.println(ae.toString());
//调用错误流,输出异常对象发生的路径,原因和所属类型
ae.printStackTrace();
}
}
}
5 异常中的构造方法:
Throwable()
:创建一个没有任何属性值的异常对象Throwable(String message)
:创建一个有原因的异常对象
5.1 throw关键字
- throw:抛出异常
- 使用场景:如果在某个方法中出现了和正常生活不符合的情况,开发人员可以在该方法 中创建一个异常对象,但是创建的异常对象自己不会自动抛出,需要使用throw关键字 抛出异常。
- 注意事项:
(1)在创建一个异常对象之后,一定要主动抛出,如果不抛出,该异常对象就不起作用。
(2)如果抛出的是一个运行时异常,在编译阶段不做检查,在运行阶段如果传入的数据是错误的,就会执行该异常。
(3)如果抛出的是一个编译时异常,在抛出之后,编译期间就会出现这个异常,可以声明也可以捕获处理。
代码
public void setAge(int age) throws Exception {
if(age <= 0){
//创建一个运行时异常对象
RuntimeException r = new RuntimeException("年龄非法");
//抛出一个异常对象
throw r;
}
this.age = age;
}
public class Demo09 {
public static void main(String[] args)throws Exception {
Person p = new Person();
Scanner sc = new Scanner(System.in);
p.setName("张三");
try{
System.out.println("请录入您的年龄:");
p.setAge(sc.nextInt());
}catch(RuntimeException re){
System.out.println("您的年龄录入错误,请重新录入");
p.setAge(sc.nextInt());
}
System.out.println(p.getName() + "..." +p.getAge());
}
}
5.2 throw和throws关键字的区别
- throw关键字是用来抛出一个创建的异常对象
throws关键字使用来声明一个异常类型 - throw关键字在方法中使用
throws关键字在方法的声明上(参数列表的后面)使用 - throw一次只能抛出一个异常对象
throws可以一次抛出多个异常类型(抛出的多个类型之间使用逗号分隔)
6 自定义异常类型
- 使用原因:
在异常的体系中官方定义了很多异常类型,但是大多类型都没有自己特殊的方法和属 性,目的就是为了使用不同的类型来区分各种问题,当看到对应的类型时,就知道代码中出现了什么问题,方便去开发者分析代码并解决问题。 - 步骤:
(1)创建一个类型以Exception结尾
(2)将这个异常类型继承Exception或者RunTimeException
如果继承了Exception类型,当前定义的类型就是一个编译时异常
如果继承了RuntimeException类型,当前定义的类型就是一个运行时异常类型
(3)在该类中调用父类的构造方法,用于让子类创建对象
代码
package demos2_exception;
public class IllegeAgeException extends RuntimeException{
public IllegeAgeException(){
}
public IllegeAgeException(String message) {
super(message);
}
}
7 可变参数
- 概述:
可变参数又称参数个数可变,一般用来定义方法的形参 - 使用场景:
如果某个方法的形式参数个数不确定,可以将该形参定义为可变参数
定义可变参数之后,该方法可以接收任何个数参数 - 好处:
由于参数数目不定,使用可变参数函数能缩短编码,灵活性和易用性较高 - 注意事项:
(1)可变参数底层是使用数组实现的
使用这个可变参数的方式,和使用数组的方式一模一样
(2)如果方法中存在多个参数,包括可变参数,可变参数要放在最后
(3)一个方法的参数列表中,只能存在一个可变参数
(4)在传递实际参数时,数据类型要和可变参数的数据类型对应
代码
package demos3_xingcan;
public class Demo01 {
public static void main(String[] args) {
System.out.println(getSum(10.23,10,20,30));
}
//定义一个方法,可以获取若干整数和一个小数的和
public static double getSum(double d,int... x){//
//在方法中,可以将可变参数当做一个数组来使用
int sum = 0;
for (int i = 0; i < x.length; i++) {
sum += x[i];
}
return sum + d;
}
}
7.1 可变参数练习
定义一个方法,方法可以接收一个字符串(表示姓名),可以接收若干门学生成绩(int)
方法功能为打印传入的姓名,和若干成绩的总和(总成绩)
代码
public class Demo02 {
public static void main(String[] args) {
printMess("张三",90,80,60,40,50,10);
}
// 1、定义一个方法,方法可以接收一个字符串(表示姓名),可以接收若干门学生成绩(int)
// 方法功能为打印传入的姓名,和若干成绩的总和(总成绩)
public static void printMess(String name,int...course){
int sum = 0;
for (int i = 0; i < course.length; i++) {
sum += course[i];
}
System.out.println("该学生的姓名为:" + name + ",总成绩为:" + sum);
}
}