3.9 递归-时间API-异常

递归

递归:方法自己调用自己(将一个大问题拆分为多个相同的小问题,再将小问题拆分为更多个相同的小小问题)

  1. 直接递归
  2. 间接递归

注意事项:递归如果没有设计好,就会出现 StackOverflowError(栈内存溢出)

递归原理

递归是基于函数调用栈的原理实现的,当一个方法被调用时,会在调用栈中创建一个对应的栈内存,包含方法的参数、局部变量和返回地址等信息,在递归中,方法会在自身的定义中调用自身,这会导致多个相同方法的栈内存依次入栈,当满足终止条件时,递归开始回溯,栈内存依次出栈,方法得以执行完毕

递归的关键是定义好递归的终止条件和递归调用的条件,如果没有适当的终止条件或递归调用的条件不满足,递归可能会陷入无限循环,导致栈内存溢出的错误

递归的应用场景

递归在很多问题中都有应用,特别是那些可以被分解成更小规模的子问题的情况,以下是一些常见的递归应用场景:

  1. 数学问题:如计算阶乘、斐波那契数列等
  2. 数据结构操作:如遍历树的节点、链表反转等
  3. 搜索和回溯算法:如深度优先搜索、回溯法等
  4. 分治法:如归并排序、快速排序等

递归在解决这些问题时,能够简化代码逻辑,提高代码的可读性和可维护性

递归的优缺点

递归作为一种强大的编程技术,具有一些优点和缺点:

优点:

  • 简化问题:递归能够将复杂问题分解成更小规模的子问题,简化了问题的解决过程
  • 提高代码可读性:递归能够直观地表达问题的解决思路,提高了代码的可读性
  • 实现高效算法:递归在某些算法中能够实现高效的解决方法,如分治法等

缺点:

  • 栈溢出风险:递归可能导致方法调用栈过深,造成栈溢出错误
  • 性能损耗:递归调用需要创建多个栈帧,对系统资源有一定的消耗
  • 可能造成代码难以理解:递归的使用需要谨慎,过度使用可能使代码难以理解和调试

因此,在使用递归时需要权衡其优缺点,并根据具体问题选择合适的解决方案

代码演示

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熊技术站

小练习

题目:计算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熊技术站

JDK8版本之前和之后的对比

图片[3]-3.9 递归-时间API-异常-IT熊技术站

JDK8版本之前

Date类 :代表时间和日期

  1. 构造方法:

public Date() :将此刻的时间封装为对象

public Date(long time) :根据传入的毫秒值,封装时间对象

  1. 成员方法:

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熊技术站
  1. 构造方法:

public SimpleDateFormat() :根据默认的格式,来格式化当前时间对象

public SimpleDateFormat(String s) :根据指定的模式,来格式化当前时间对象

  1. 成员方法:

public String format(Date d) :对传入的日期对象进行格式化操作,返回格式化好的字符串

public Date parse(String source) :对传入的日期字符串进行解析,解析为日期对象

Calendar类 :jdk1.1版本开始,取代Date类的

  1. 创建对象:

public static Calendar getInstance() :获取Calendar的对象

  1. 成员方法:

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熊技术站

Java中常见的异常

NullPointerException

空指针异常,操作一个null对象的方法或者属性时触发。

OutOfMemoryError

内存异常异常,这不是程序能控制的,是指要分配的对象的内存超出了当前最大的内存堆,需要调整堆内存大小(-Xmx)以及优化才程序

IOException

IO,即:input,output,我们在读写磁盘文件,网络内容的时候会发生的一种异常,这种异常是受检查的异常,需要手工捕获

FileNotFoundException

文件找不到异常,如果文件不存在就会抛出这种异常

ClassNotFoundException

类找不到异常,在类路径下不能加载指定的类

ClassCastException

类型转换异常,如将一个数字强制转换成字符串

throw和throws的区别?

throw: 是真实抛出一个异常

throws: 是声明程序可能会抛出一个异常

运行时和编译时异常

编译时异常

编译期间,代码格式,语法全部没有错误,但就是出现了红色波浪线

运行时异常

编译通过了,运行异常了

Java程序如果出现了运行时异常的默认方案:

  1. 内部自动创建一个异常对象,不断的抛给上一级,直到抛给JVM虚拟机
  2. JVM虚拟机接收到了异常对象,将异常的错误信息打印在控制台,强制终止JVM程序

弊端:后续代码无法继续执行

异常的处理方案:

  1. 抛出异常
  2. try … catch捕获异常

异常处理方案一:抛出异常

在异常代码的方法上, 加入throws

格式: throws 异常的类名

细节:

  1. 如果你调用的方法抛出了异常, 也需要对其进行异常处理
  2. 子类在重写父类方法的时候, 如果父类方法没有抛异常, 子类绝对不能抛,只能try…catch
  3. 子类不能抛出父类没有的异常, 或者比父类更大的异常,父类坏了, 儿子不能比父类还坏

异常处理方案二: try…catch捕获异常

好处: 可以将异常对象捕获, 将问题抓住, 后续的代码可以继续执行

格式:
    try {
        可能出现问题的代码
    } catch (要捕获的异常类型 对象名) {
        异常的处理方案
    }

执行流程:

  1. 执行try { } 中的代码,看是否有异常产生
  2. 有 : 被catch进行捕获
  3. 没有 : 就不执行catch里面的代码

细节补充:

  1. catch是可以编写多个的, 如果编写了多个catch, 最大的异常一定要放在最后
  2. 快捷键: ctrl + alt + T
  3. 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);
}
© 版权声明
THE END
喜欢就支持一下吧
点赞0赞赏 分享
评论 共1条

请登录后发表评论