异常
异常 : Exception
JVM处理异常的方式
JVM处理异常的手段 :
1. 从异常代码位置立刻中断程序
2. 在控制台打印报错信息
a. 异常类型
b. 异常产生的原因
c. 异常产生的位置
d. 红色字体打印
e. 告知异常产生的线程
f. 开启新的线程打印异常信息
3. JVM自动生成一个此异常类型的对象在内存中
程序员自己处理异常的方式
抛出去 : 现阶段最推荐的方式
含义 : 交付给调用者处理,谁调用谁处理 (没人处理最终JVM处理) -> throws
包起来 : 后阶段最常用的方式
含义 : 程序员自己立刻处理,根据自己写的代码完成异常的处理操作 -> try..catch
抛出去的处理方式
格式 :
方法的声明(形式参数列表) throws 异常的类名1,异常的类名2...{
方法体;
}
1. 运行时期异常 : 没有任何意义
2. 编译时期异常 : 有意义,程序不报编译错误,至少可以运行了
a . 但是如果程序本来就写错了,抛出编译时异常程序依旧中断,JVM介入处理编译时期异常
b . 如果在一个方法上抛出了编译时异常,此方法被调用的位置也需要再次处理这个编译时期异常
注意事项:
1. 一个方法可以抛出多个异常
2. 根据多态性 : 抛出了父类型异常子类型异常也一并抛出
包起来的处理方式
格式 :
try{
//可能产生异常的代码
}catch(异常类型 对象名){
//自己处理该异常的代码
}
1. 运行时期异常 : 有意义,不会影响其他的代码执行
2. 编译时期异常 : 有意义
a . 程序不再包编译错误
b . 不会影响其他的代码执行
try...catch
格式 :
try{
//可能产生异常的代码
}catch(异常类型 对象名){
//自己处理该异常的代码
}
try..catch的执行流程 :
1. try中无异常 :
a. 从try的第一行代码依次向下执行
b. 跳过catch
c. 执行try..catch后方代码
2. try中有异常,catch捕获到了
a. 立刻终止try中代码执行 (try中异常代码下方的代码不执行了)
b. 进入到catch中执行catch中的代码
c. 执行try..catch后方代码
3. try中有异常,catch没有捕获到
a. 在try中异常代码位置立刻中断整个程序
b. jvm介入处理异常
4. try中有异常,catch捕获到了,catch中有异常
a. 立刻终止try中代码执行 (try中异常代码下方的代码不执行了)
b. 进入到catch中执行catch中的代码
c. 当执行到catch中的异常语句,因为没有处理,所以JVM介入
b. 按照JVM处理异常的方式处理 -> 终止程序.....
try..catch的注意事项 :
1. 一个catch只能捕获一个异常类型
2. 一个try结构中可以有多个catch [只能有一个catch执行 -> 逻辑]
3. catch捕获父类型异常,子类型异常也可以被捕获到 -> 多态性
4. 可以先捕获子类型异常[专项处理],再捕获父类型异常[通用处理] --> 不要反过来
finally代码块
finally : 代码块 -> 配合try..catch使用
格式 :
try{
//可能产生异常的代码
catch(异常类型 对象名){
//自己处理该异常的代码
}finally{
//无论如何都会执行的代码
}
注意:
如果前方把jvm手动退出(System.exit(状态码)),finally代码块才不会执行; //其他任何情况都会执行
异常对象的方法
String getMessage() : 获取异常的错误信息
String toString() : 获取异常的类型 + 异常的错误信息
void printStackTrace() : 获取异常的类型 + 异常的错误信息 + 异常产生的位置 + 红色字体 + 开启新线程打印异常信息
自定义异常类
定义自定义异常类的步骤:
1. 定义一个类,名字叫 XxxxxException
2. 如果定义的是一个运行时期异常 extends RuntimeException
如果定义的是一个编译时期异常 extends Exception
3. 在自定义异常类中 自动生成2个构造方法 : 无参构造,带有 String message 参数的有参构造
使用自定义异常类的步骤:
1. 编写方法
2. 在方法的某个判断条件下, throw new 异常类型("异常的错误信息"); --> 暴露异常
a. 暴露的是运行时期异常 : 不用处理
b. 暴露的是编译时期异常 : 一定抛出此异常
递归算法
递归 : 在方法的内部调用方法自己
递归在生活中的体现 :
从前有座山,山里有座庙,庙里有个老和尚,老和尚给小和尚讲故事
从前有座山,山里有座庙,庙里有个老和尚,老和尚给小和尚讲故事
从前有座山,山里有座庙,庙里有个老和尚,老和尚给小和尚讲故事
从前有座山,山里有座庙,庙里有个老和尚,老和尚给小和尚讲故事
.....
递归和循环的区别 :
循环 : 把一段重复的代码进行封装,代码可以重复执行
递归 : 让一段方法的逻辑重复执行 --> 方法可以传递参数,递归多次调用方法时,参数不一样完成的功能效果不一样
递归的思考技巧:
1. 找规律
2. 定义方法,方法一定要有形参
3. 递归一定要有出口
4. 勇敢的在方法内部调用方法自己
//再次调用方法的时候要有实参的变化,往出口的方向前进
案例:求1-n之间的和()
/*
递归 :
递进 : 往出口方向前进的过程
回归 : 当得到一个环节的结果后不断返回的过程
求1-n的和
1-n之间的和() = 1-(n-1)之间的和() + n;
1-5之间的和() = 1-4之间的和() + 5;
1-4之间的和() = 1-3之间的和() + 4;
1-3之间的和() = 1-2之间的和() + 3;
1-2之间的和() = 1-1之间的和() + 2;
1-1之间的和() = 1; --> 出口
*/
public class Demo {
public static void main(String[] args) {
int sum = diGuiSum(5);
System.out.println("sum = " + sum);
}
public static int diGuiSum(int n){
//先写出口
if (n == 1){
return 1;
}
//再写规律
return diGuiSum(n - 1) + n;//往出口方向前进
}
}
内存图:
案例:斐波那契数列(不死神兔)
数组版:
public class ArrayDemo {
public static void main(String[] args) {
//不死神兔数组版
int[] arr = new int[20];
//确定是
arr[0] = 1;
arr[1] = 1;
//遍历数组
for (int i = 2; i < arr.length; i++) {
arr[i] = arr[i - 1] + arr[i - 2];
}
System.out.println("第20个月的兔子对数是 : " + arr[19]);
}
}
递归版:
public class Demo {
public static void main(String[] args) {
int rabbit = getRabbit(20);
System.out.println("rabbit = " + rabbit);
}
//递归必须有方法
public static int getRabbit(int month){
//先写出口
if (month == 1 || month == 2){
return 1;
}
//再写规律 这个月 = 上个月 + 上上个月;
return getRabbit(month - 1) + getRabbit(month - 2);
}
}