递归
递归:方法自己调用自己(将一个大问题拆分为多个相同的小问题,再将小问题拆分为更多个相同的小小问题)
- 直接递归
- 间接递归
注意事项:递归如果没有设计好,就会出现 StackOverflowError(栈内存溢出)
递归原理
递归是基于函数调用栈的原理实现的,当一个方法被调用时,会在调用栈中创建一个对应的栈内存,包含方法的参数、局部变量和返回地址等信息,在递归中,方法会在自身的定义中调用自身,这会导致多个相同方法的栈内存依次入栈,当满足终止条件时,递归开始回溯,栈内存依次出栈,方法得以执行完毕
递归的关键是定义好递归的终止条件和递归调用的条件,如果没有适当的终止条件或递归调用的条件不满足,递归可能会陷入无限循环,导致栈内存溢出的错误
递归的应用场景
递归在很多问题中都有应用,特别是那些可以被分解成更小规模的子问题的情况,以下是一些常见的递归应用场景:
- 数学问题:如计算阶乘、斐波那契数列等
- 数据结构操作:如遍历树的节点、链表反转等
- 搜索和回溯算法:如深度优先搜索、回溯法等
- 分治法:如归并排序、快速排序等
递归在解决这些问题时,能够简化代码逻辑,提高代码的可读性和可维护性
递归的优缺点
递归作为一种强大的编程技术,具有一些优点和缺点:
优点:
- 简化问题:递归能够将复杂问题分解成更小规模的子问题,简化了问题的解决过程
- 提高代码可读性:递归能够直观地表达问题的解决思路,提高了代码的可读性
- 实现高效算法:递归在某些算法中能够实现高效的解决方法,如分治法等
缺点:
- 栈溢出风险:递归可能导致方法调用栈过深,造成栈溢出错误
- 性能损耗:递归调用需要创建多个栈帧,对系统资源有一定的消耗
- 可能造成代码难以理解:递归的使用需要谨慎,过度使用可能使代码难以理解和调试
因此,在使用递归时需要权衡其优缺点,并根据具体问题选择合适的解决方案
代码演示
public class MethodDemo {
/*
需求:求5的阶乘
-----------------------------------
分析:
!5 = 5 * !4
!4 = 4 * !3
!3 = 3 * !2
!2 = 2 * !1
!1 = 1
*/
public static void main(String[] args) {
int result = jc(5);
System.out.println("5的阶乘是:" + result);
}
public static int jc(int num) {
if (num == 1) {
return 1;
} else {
// num:5
// 思路:调用一个方法来计算4的阶乘
return num * jc(num - 1);
}
}
}
画内存图分析
![图片[1]-3.9 递归-时间API-异常-IT熊技术站](https://www.cutrui.cn/wp-content/uploads/2023/08/yuque_diagram.png)
小练习
题目:计算1-n的和的结果,使用递归思想
分析:
假如求1-10的和
1~10的和:10 + (1~9的和)
1~9的和:9 + (1~8的和)
…….
1~2的和:2 + (1~1的和)
1~1的和:1
public static void main(String[] args) {
int result = getSum(10);
System.out.println(result);
}
public static int getSum(int num) {
if (num == 1) {
return 1;
} else {
return num + getSum(num - 1);
}
}
案例:不死神兔(斐波那契数列)
有一对兔子,从出生后第3个月起每个月都生一对兔子,小兔子长到第三食月后每个月又生一对兔子,假如兔子都不死,问第二十个月的兔子对数为多少?
public static void main(String[] args) {
System.out.println(get(20));
}
public static int get(int month) {
if (month == 1 || month == 2) {
// 如果要求第一个月, 或者是第二个月的兔子对儿数, 直接返回1
return 1;
} else {
// month : 4
// 第三个月的兔子对儿数 = 第一个月 + 第二个月
// 第四个月的兔子对儿数 = 第二个月 + 第三个月
return get(month - 2) + get(month - 1);
}
}
时间API
![图片[2]-3.9 递归-时间API-异常-IT熊技术站](https://www.cutrui.cn/wp-content/uploads/2023/08/image-40.png)
JDK8版本之前和之后的对比
![图片[3]-3.9 递归-时间API-异常-IT熊技术站](https://www.cutrui.cn/wp-content/uploads/2023/08/image-41.png)
JDK8版本之前
Date类 :代表时间和日期
- 构造方法:
public Date() :将此刻的时间封装为对象
public Date(long time) :根据传入的毫秒值,封装时间对象
- 成员方法:
public void setTime(long time) :根据传入的毫秒值,封装时间对象
public long getTime() :获取时间原点,到时间对象所经历过的毫秒值
代码演示:
import java.util.Date;
public class DateDemo1 {
/*
Date类 : 表示日期或时间
构造方法 :
1. 构造方法 :
public Date() : 将此刻的时间封装为对象
public Date(long date) : 会从时间原点, 根据毫秒值设置时间对象
2. 成员方法 :
public void setTime(long time) : 根据毫秒值设置时间对象
public long getTime() : 获取从时间原点, 到这个对象所经历过的毫秒值
*/
public static void main(String[] args) {
// 从时间原点, 到 [此刻] 时间所经历过的毫秒值
System.out.println(System.currentTimeMillis());
Date d = new Date(1000L);
// 获取从时间原点, 到这个 [对象] 所经历过的毫秒值
System.out.println(d.getTime());
}
}
SimpleDateFormat 类 :日期格式化类
![图片[4]-3.9 递归-时间API-异常-IT熊技术站](https://www.cutrui.cn/wp-content/uploads/2023/08/image-42.png)
- 构造方法:
public SimpleDateFormat() :根据默认的格式,来格式化当前时间对象
public SimpleDateFormat(String s) :根据指定的模式,来格式化当前时间对象
- 成员方法:
public String format(Date d) :对传入的日期对象进行格式化操作,返回格式化好的字符串
public Date parse(String source) :对传入的日期字符串进行解析,解析为日期对象
Calendar类 :jdk1.1版本开始,取代Date类的
- 创建对象:
public static Calendar getInstance() :获取Calendar的对象
- 成员方法:
public int get(int field) :根据传入的字段(静态常量),获取指定的日历值
Calendar.YEAR :年
Calendar.MONTH :月(注意,Calendar类的设计者,将月份设计的是0~11,月份取出来应该+1)
Calendar.DAY_OF_MONTH :日
Calendar.DAY_OF_WEEK :星期(注意:星期取出之后需要查表)
Calendar.DAY_OF_YEAR :获取一年中的第几天
public void set(int year, int month, int date) :设置日期对象的年,月,日
public void set(int field, int value) :设置某一个字段
public abstract void add(int field, int amount) :修改某一个字段的偏移量
public static void main(string[]args) {
//获取Calendar的对象
Calendar c Calendar.getInstance();
int year c.get(Calendar.YEAR);
int month c.get(Calendar.MONTH);
int day c.get(Calendar.DAY_OF_MONTH)
int weekIndex c.get(Calendar.DAY_OF_WEEK);
char[]weeks={' ', '日', '一', '二', '三', '四', '五', '六'};
System.out.println(weeks[weekIndex]);
}
异常
- 指的是程序执行的过程中,出现的非正常的情况,最终会导致JVM的非正常停止
异常产生的原因
- 在Java中异常产生,主要是有三种原因:
(1)编写程序代码中的错误产生的异常,比如数组越界、空指针异常等,这种异常叫做未检查的异常,一般需要在类中处理这些异常
(2)Java内部错误发生的异常,Java虚拟机产生异常
(3)通过throw(抛出异常)语句手动生成的异常,这种异常叫做检查的异常,一般是用来给方法调用者一些必要的信息
异常分类
![图片[5]-3.9 递归-时间API-异常-IT熊技术站](https://www.cutrui.cn/wp-content/uploads/2023/08/yuque_diagram-2.png)
Java中常见的异常
NullPointerException
空指针异常,操作一个null对象的方法或者属性时触发。
OutOfMemoryError
内存异常异常,这不是程序能控制的,是指要分配的对象的内存超出了当前最大的内存堆,需要调整堆内存大小(-Xmx)以及优化才程序
IOException
IO,即:input,output,我们在读写磁盘文件,网络内容的时候会发生的一种异常,这种异常是受检查的异常,需要手工捕获
FileNotFoundException
文件找不到异常,如果文件不存在就会抛出这种异常
ClassNotFoundException
类找不到异常,在类路径下不能加载指定的类
ClassCastException
类型转换异常,如将一个数字强制转换成字符串
throw和throws的区别?
throw: 是真实抛出一个异常
throws: 是声明程序可能会抛出一个异常
运行时和编译时异常
编译时异常
编译期间,代码格式,语法全部没有错误,但就是出现了红色波浪线
运行时异常
编译通过了,运行异常了
Java程序如果出现了运行时异常的默认方案:
- 内部自动创建一个异常对象,不断的抛给上一级,直到抛给JVM虚拟机
- JVM虚拟机接收到了异常对象,将异常的错误信息打印在控制台,强制终止JVM程序
弊端:后续代码无法继续执行
异常的处理方案:
- 抛出异常
- try … catch捕获异常
异常处理方案一:抛出异常
在异常代码的方法上, 加入throws
格式: throws 异常的类名
细节:
- 如果你调用的方法抛出了异常, 也需要对其进行异常处理
- 子类在重写父类方法的时候, 如果父类方法没有抛异常, 子类绝对不能抛,只能try…catch
- 子类不能抛出父类没有的异常, 或者比父类更大的异常,父类坏了, 儿子不能比父类还坏
异常处理方案二: try…catch捕获异常
好处: 可以将异常对象捕获, 将问题抓住, 后续的代码可以继续执行
格式:
try {
可能出现问题的代码
} catch (要捕获的异常类型 对象名) {
异常的处理方案
}
执行流程:
- 执行try { } 中的代码,看是否有异常产生
- 有 : 被catch进行捕获
- 没有 : 就不执行catch里面的代码
细节补充:
- catch是可以编写多个的, 如果编写了多个catch, 最大的异常一定要放在最后
- 快捷键: ctrl + alt + T
- JDK7版本可以, catch的 ( ) 中可以编写多个异常类, 中间使用 | 符号隔开
格式:catch (ArithmeticException | NullPointerException e)
throw和throws的区别 :
throw是负责抛出异常对象的 (干力气活)
throws是声明此方法存在异常 (声明)
————————————————————————————-
throw抛出异常对象的时候, 如果是运行时异常, 方法上面就不需要做throws的声明了,如果是编译时异常,方法上必须加throws声明
案例演示区别代码:
public void setAge(int age) throws StudentAgeException {
if (age >= 0 && age <= 120) {
this.age = age;
} else {
// 思路: 这个问题不应该我们处理的时候, 就抛给调用者
throw new StudentAgeException();
}
}
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
Student stu = new Student();
System.out.println("请输入学生姓名: ");
String name = sc.nextLine();
stu.setName(name);
System.out.println("请输入学生年龄: ");
while (true) {
try {
int age = Integer.parseInt(sc.nextLine());
stu.setAge(age);
break;
} catch (NumberFormatException e) {
System.out.println("您的年龄格式输入有误, 请重新输入一个整数年龄: ");
} catch (StudentAgeException e) {
System.out.println("您的年龄范围输入有误, 请重新输入一个0~120之间的整数年龄: ");
}
}
System.out.println(stu);
}
自定义异常
- Java无法为这个世界上全部的问题提供异常类
- 如果企业想通过异常的方式来管理自己的某个业务问题,就需要自定义异常类了
1. 自定义编译时异常
- 定义一个异常类继承Exception
- 重写构造器。
- 在出现异常的地方用throw new自定义对象抛出,
- 作用:编译时异常是编译阶段就报错,提醒更加强烈,一定需要处理!
2. 自定义运行时异常
- 定义一个异常类继承RuntimeException.
- 重写构造器。
- 在出现异常的地方用throw new自定义对象抛出!
- 作用:提醒不强烈,编译阶段不报错!!运行时才可能出现!!
Throwable常见成员方法:
// 给用户展示可以使用getMessage
public String getMessage():返回异常的错误原因
public String tostring():返回异常类名 + 错误原因
// 包含了异常的类型异常s原因还包括异常出现的位置在开发和调试阶段都得使用printstackTrace
public void printstackTrace():返回异常类名 + 错误原因 + 异常的错误行号
优化上方案例代码
public void setAge(int age) {
if (age >= 0 && age <= 120) {
this.age = age;
} else {
// 思路: 这个问题不应该我们处理的时候, 就抛给调用者
throw new StudentAgeException("您的年龄范围输入有误, 请重新输入一个0~120之间的整数年龄: ");
}
}
public class StudentAgeException extends RuntimeException {
public StudentAgeException() {
}
public StudentAgeException(String message) {
super(message);
}
}
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
Student stu = new Student();
System.out.println("请输入学生姓名: ");
String name = sc.nextLine();
stu.setName(name);
System.out.println("请输入学生年龄: ");
while (true) {
try {
int age = Integer.parseInt(sc.nextLine());
stu.setAge(age);
break;
} catch (NumberFormatException e) {
System.out.println("您的年龄格式输入有误, 请重新输入一个整数年龄: ");
} catch (StudentAgeException e) {
System.out.println(e.getMessage());
}
}
System.out.println(stu);
}
- 最新
- 最热
只看作者