JAVA学习 第三部分

前言

java 语言的 安装、介绍;语法、流程控制、数组、javaBean 等知识,可以查询上上一个内容

Java学习 第一部分

java 语言的 String、StringBuilder、集合、对象和类 进阶、窗体 等知识,可以查阅上一个内容

Java学习 第二部分

JAVA API

API (Application Programming Interface) 应用程序编程接口;提供了已经预装的一些类和方法

Object 类

所有的类都继承自 Object 类,都继承了来自 Object 提供的类方法;

%title插图%num

toString();

默认情况下,toString() 方法会返回对象的类名,后跟符号 @ 和对象的哈希码的无符号十六进制表示;(不严谨的说是哈希处理过后的内存地址)

Java
public class testing {
    public static void main(String[] args) {
        C c = new C();
        // 使用打印语句打印对象名时,源码层面是默认调用该对象的 .toString() 方法
        System.out.println(c); 
        System.out.println(c.toString()); // 默认返回 包名+类名(全类名)@哈希码
}

class C {}

效果展示

%title插图%num

这个字符串并不一定对于每个对象的实际内容都是有意义的,因为它没有提供关于对象状态的详细信息,所以通常情况下,可以根据自己的需要重写 toString() 方法

Java
public class testing {
    public static void main(String[] args) {
        C c = new C();
        System.out.println(c); 
        System.out.println(c.toString());
}
class C {
    @Override
    public String toString() {
        return "重写后的 toString()";
    }
}

效果展示

%title插图%num

但在 Java 类中覆盖 toString() 方法的主要目的是为对象提供一个清晰、有意义的字符串表示形式,以便于调试、日志记录和代码可读性。
IDEAtoString(); 已经考虑过,可以直接通过 IDEA 编辑器生成对应的 toString() 代码

%title插图%num

效果展示

%title插图%num

例如 ArrayList 类,就已经重写了 toString() 方法,实现打印呈现数组效果。如果一个类打印的不是初始地址信息,那么它大概率就是重写了该方法

Java
import java.util.ArrayList;

public class testing {
    public static void main(String[] args) {
        ArrayList<String> list = new ArrayList<>();
        list.add("a");
        list.add("b");
        list.add("c");
        System.out.println(list);
    }
}

效果展示

%title插图%num

equals(Object obj);

指示其他对象是否与此对象 “相等”。Object类 中的 equals 方法,默认比较的是对象的内存地址

Java
public class equalsDemo {
    public static void main(String[] args) {
        T t = new T("张三");
        T d = new T("张三");
        System.out.println(t == d); // false
        // equals 底层默认原理很简单 为 return (this == obj) 效果基本同上
        System.out.println(t.equals(d)); // false
    }
}

class T {
    String name;

    public T(String name) {
        this.name = name;
    }
}

效果展示

%title插图%num

查看 equals 源码不难发现,Objectequals 的功能只是this == obj,似乎是 java 定了一个规则,方便子类去重写

因此,父类 equals 方法基本就是为了给子类重写,通过自身业务制作 equals 方法,以便子类定制比较规则

重写 equals 方法
class Student {
    String name;
    int age;

    public Student(String name, int age) {
        this.name = name;
        this.age = age;
    }

    @Override
    // 根据自身业务重写 equals 方法
    public boolean equals(Object obj) {
        // 判断是否为 Student 类 (同时实例化 Student 类)
        if(obj instanceof Student t){
            // 进行逐个比较
            return this.age == t.age && this.name.equals(t.name);
        }
        return false;
    }
}
实现 equals 效果
public class equalsDemo {
    public static void main(String[] args) {
        Student t = new Student("张三", 22);
        Student d = new Student("张三", 22);
        Student c = new Student("张三", 26);
        System.out.println(t == d); // false ←比较地址
        System.out.println(t.equals(d)); // true
        System.out.println(t.equals(c)); // false
    }
}

效果展示

%title插图%num

当然,IDEA 也是提供了自动生成 equals 方法(右键 → 生成..),智能生成对应 equals 的比较操作

Java
class Student {
    String name;
    int age;

    public Student(String name, int age) {
        this.name = name;
        this.age = age;
    }

    @Override // IDEA 生成的 equals 代码
    public boolean equals(Object o) {
        // 判断地址是否相同
        if (this == o) return true;
        // getClass() 的对比是两个对象的字节码文件对比
        // 例如返回值: class cn.smmcat.object.Student
        // 如果字节码不同,意味着类型不相同 直接返回 false
        if (o == null || getClass() != o.getClass()) return false;
        // 代码能走到这说明类型相同 可做强转
        Student student = (Student) o;
        // 逐个成员值进行比较
        return age == student.age && Objects.equals(name, student.name);
    }
}

其他内容

ObjectsObject 的子类(JDK 1.7开始)Objects.equals() 是静态方法

%title插图%num

它实际调用的仍然是比较的类中所编写的 equals 方法;
但它的 equals 会先进行非空判断,避免空指针异常,再进行 equals 比较。
空指针异常:null.equals(obj)
另外 Objects.isNull(Object obj) 用于判断变量是否为 null

Math 类

Math 类提供了执行基本数学运算的方法,它是一个工具类(没有构造函数,所有方法被 static 修饰),具体方法查阅文档

Math.abs(Int a); 绝对值

返回该数值的 int 绝对值(转正整数)

注意:若 a 是 Integer.MIN_VALUE(-2³¹),结果仍为负数(溢出问题)

Math.ceil(double a) 向上取整

将小数部分剔除,只要存在小数部分则整数部分 +1,返回值为 double

  • Math.ceil(2.3) → 3.0
  • Math.ceil(-2.3) → -2.0(注意负数方向)

Math.floor(double a) 向下取整

将小数部分剔除,只返回整数部分,返回值为 double

  • Math.floor(2.7) → 2.0
  • Math.floor(-2.7) → -3.0

Math.round(float a) 四舍五入

对浮点数的数值进行四舍五入运算,返回整数 int,若传入的是 double 返回 long

  • Math.round(double a) 返回 long(参数为 double 时)。
  • 四舍五入规则:当小数部分为 0.5 时,向正无穷方向舍入

Math.max(int a, int b) 比较返回较大值

比较两个整数,返回它们之中最大的值,返回整数 int

Math.max 支持所有数值类型(如 doublelong

Math.pow(double a, double b) 次幂运算

返回 ab 次幂运算,返回值 double

  • Math.pow(2, 3) → 8.0
  • 负数的非整数次幂可能返回 NaN(如 Math.pow(-2, 0.5)

Math.random() 随机小数 0-1区间

返回为 double 的随机值,范围 0.0 ≤ x < 1.0(不含 1.0)

底层调用 Random.nextDouble(),建议直接使用 Random 类更灵活

System 类

System 类的功能都是静态的。也是工具类,都是直接类名调用即可

%title插图%num

exit() 说明

System.exit(); 在终止虚拟机时可以传一个状态码参数,0 表示正常退出,其他值表示异常终止

currentTimeMillis() 说明

返回的值是 1970年1月1日0时0分0秒 到现在所经历的毫秒值,又叫计算机中的时间原点,是 C语言 生日

arraycopy() 说明

通过该方法,可以拷贝数组,底层通过本地方法(Native Code)实现,性能远高于手动循环复制,但是注意拷贝的方式是浅拷贝

Java
public class equalsDemo {
    public static void main(String[] args) {
        int[] a = {1, 2, 3, 4};
        int[] b = {5, 6, 7, 8};
        // a 数组中 1 - 2 下标的内容覆盖 b 下标 0 起始往后的位置
        System.arraycopy(a, 1, b, 0, 2);
        System.out.println(Arrays.toString(b)); // [2, 3, 7, 8]
    }
}
BigDecimal 类

BigDecimal类 用于解决小数运算中出现不精确的问题

参数

直接传入 double 值仍然会造成精度问题,因此推荐使用 String 数值表示来传参,或者 BigDecimal.valueOf(double val) 传参

%title插图%num

常用方法

加法public BigDecimal add(BigDecimal b);
减法public BigDecimal subtract(BigDecimal b);
乘法public BigDecimal multiply(BigDecimal b);
除法public BigDecimal divide(BigDecimal b,精确几位,舍入方式);

小数精度问题说明

受二进制储存小数的逻辑和的影响,难以直接精确计算小数部分的加减

Java
public class equalsDemo {
    public static void main(String[] args) {
        System.out.println(0.1 + 0.2); // 0.30000000000000004
    }
}

BigDecimal 解决方案

通过 BigDecimal 方法传入字符串数值,实现小数的精确计算

Java
public class equalsDemo {
    public static void main(String[] args) {
        BigDecimal num1 = new BigDecimal("1.44");
        BigDecimal num2 = new BigDecimal("1.66");
        double d1 = num1.add(num2).doubleValue();
        System.out.println(d1); // 3.1
    }
}

通过 BigDecimal.valueOf(double a) 静态方法也能实现小数的精确计算

Java
import java.math.BigDecimal;

public class bigDecimalDemo {
    public static void main(String[] args) {
        BigDecimal a = BigDecimal.valueOf(0.2);
        BigDecimal b = BigDecimal.valueOf(0.1);
        
        System.out.println(a.add(b).doubleValue()); // 加 0.3
        System.out.println(a.subtract(b).doubleValue()); // 减 0.1
        System.out.println(a.multiply(b).doubleValue()); // 乘 0.02
        System.out.println(a.divide(b).doubleValue()); // 除 2.0
    }
}

注意事项

BigDecimaldivide 方法可能会遇到除不尽的小数时就会抛出一个 ArithmeticException: Non-terminating decimal expansion 异常。
如果使用 divide 方法,最好添加 保留小数位 和 四舍五入 参数。

舍入方式的枚举:
RoundingMode.HALF_UP 四舍五入
RoundingMode.UP 进一(向上取整)
RoundingMode.DOWN 截断(向下取整)

Java
import java.math.BigDecimal;
import java.math.RoundingMode;

public class bigDecimalDemo {
    public static void main(String[] args) {
        BigDecimal a = BigDecimal.valueOf(0.1);
        BigDecimal b = BigDecimal.valueOf(0.03);
        // 参数 2:保留 4 位小数
        // 参数 3:采用 四舍五入 舍入方式
        System.out.println(a.divide(b, 4, RoundingMode.HALF_UP).doubleValue()); // 3.3333
    }
}

BigDecimal 类操作过的对象返回值仍然是 BigDecimal,因此不能被视为数值,也不能被 Math 操作。
这个时候需要使用到它提供的 doubleValue() 方法返回实际的 double 数值即可

Java
import java.math.BigDecimal;

public class bigDecimalDemo {
    public static void main(String[] args) {
        BigDecimal a = BigDecimal.valueOf(0.1);
        BigDecimal b = BigDecimal.valueOf(0.3);
        // Math.abs(a.subtract(b)); 报错:类型不匹配
        System.out.println(Math.abs(a.subtract(b).doubleValue())); // 0.2
    }
}
包装类

将基本数据类型包装成类(引用数据类型),使之支持调用对应的类方法;但从 JDK 5 版本开始已经实现自动拆装箱

%title插图%num

使用方式

‘Integer(int)’ 自 JDK 版本 9 起已弃用并标记为移除

包装类的使用有手动装箱和手动拆箱环节,例如 int

  • 手动装箱:调用提供的 构造 或 静态 方法,手动将基本数据类型包装成类
Java
int a = 100;
Integer num = new Integer(a); // 通过构造方法包装(过时 不推荐)
Integer num1 = Integer.valueOf(a); // 通过静态方法
  • 手动拆箱:通过 intValue()int 类型返回该 Integer 的值
Java
Integer num = Integer.valueOf(100); // 通过静态方法
int num2 = num.intValue(); // 返回 int 值 -> 100
  • 自动拆装箱:从 JDK5 版本开始,已经实现自动装箱/拆箱操作,无需考虑转换问题
Java
int a = Integer.valueOf(233); // 自动拆箱
Integer b = 666; // 自动装箱

提供功能

封装成包装类的基本数据类型就有了对应包装类提供的方法,这里例如 Integer 提供的方法例如:

  • public static String toBinaryString(int i) 转二进制
  • public static String toOctalString(int i) 转八进制
  • public static String toHexString(int i) 转十六进制
  • public static int parseInt(String s) 将 数字字符串 转为 数字
Java
int num = 10;
String a = Integer.toBinaryString(num); // 转二进制表示 1010
String b = Integer.toOctalString(num); // 转八进制表示 12
String c = Integer.toHexString(num); // 转十六进制表示 a
int d = Integer.parseInt("1") + 4; // 转为 int 数值 并执行 数字运算 -> 5

注意事项

使用 Integer 类型变量接收整数的数据时,会自动调用 Integer.valueOf() 方法进行封装,这是自动装箱的逻辑。

%title插图%num
%title插图%num

但自动装箱时,如果数据范围是 -128 ~ 127 之内,则使用的是缓存中存在的 Integer 对象地址,当相同传入的数据进行比较,地址相同,它们比较为 true
但如果数据范围是 -128 ~ 127 之外,会重新创建新的对象。此时它们的对象地址不同,如果进行比较则为 false

Java
public class bigDecimalDemo {
    public static void main(String[] args) {
        Integer a = 127;
        Integer b = 127;
        System.out.println(a == b); // true

        Integer c = 129;
        Integer d = 129;
        System.out.println(c == d); // false
    }
}

Long 等包装类也有类似这样的缓存机制

Arrays 类

Arrays 类用于数组操作的工具类,专门用于操作数组的元素

%title插图%num

toString() 转带数组格式的字符串

该方法重写了 ObjecttoString 方法,意为了以字符串更简洁的展示数组的元素

Java
import java.util.Arrays;

public class bigDecimalDemo {
    public static void main(String[] args) {
        int[] numList = {1, 2, 3, 4, 5, 6};
        // 打印 toString 返回的数组格式字符串
        System.out.println(Arrays.toString(numList));
    }
}

效果展示

%title插图%num

equals(Object[] a,Object[] b) 比较两个数组内容是否相同

该方法重写了 ObjecttoString 方法,意为了比较两个数组的内容是否相同

Java
import java.util.Arrays;

public class bigDecimalDemo {
    public static void main(String[] args) {
        int[] numList = {1, 2, 3, 4};
        int[] anotherList = {1, 2, 3, 4};
        int[] nextList = {1, 2, 4, 4};
        // Arrays.equals 比较数组每个内容是否一致
        System.out.println(Arrays.equals(numList, anotherList)); // true
        System.out.println(Arrays.equals(numList, nextList)); // false
    }
}

效果展示

%title插图%num

binarySearch(int[] a, int val) 查找索引

使用二分搜索法去查询指定元素在数组中索引的下标位置(二分查找法:需要保证数组的元素是排好序的)

Java
import java.util.Arrays;

public class Demo {
    public static void main(String[] args) {
        int[] intList = {1, 3, 5, 7};
        System.out.println(Arrays.binarySearch(intList, 3)); // 1

        int[] intList2 = {3, 5, 1, 7};
        // 当使用 binarySearch 去查找乱序的数组中值时 会出现意料之外的结果
        System.out.println(Arrays.binarySearch(intList2, 1)); // -1
    }
}

sort() 对数组默认升序排序

改变数组内所有元素的位置,默认进行升序排序

Java
public class bigDecimalDemo {
    public static void main(String[] args) {
        int[] intList = {3, 41, 1, 1, 24, 5, 52, 32};
        Arrays.sort(intList);
        System.out.println(Arrays.toString(intList)); // [1, 1, 3, 5, 24, 32, 41, 52]
    }
}

【拓展】二分查找

二分查找(Binary Search)是一种在有序数组中查找特定元素的搜索算法。

搜索过程从数组的中间元素开始,如果中间元素正好是目标值,则搜索过程结束;
如果目标值大于或小于中间元素,则在数组大于或小于中间元素的那一半中查找,而不是查找整个数组。
通过每次排除一半的元素,这样可以每次都能将搜索范围减半,这就是二分查找的思想。

%title插图%num

代码实现效果如下

Java
public static int BinarySearch(int[] arr, int target) {
    // 数组没有内容 直接返回 -1
    if (arr == null || arr.length == 0) {
        return -1;
    }
    // min 和 max 用于跟踪要搜索的数组部分的起始和结束索引
    int min = 0;
    int max = arr.length - 1;
    // 中心点 查找的目标
    int mid;
    // 当 min 和 max 没有重叠时,继续搜索
    while (min <= max) {
        // 计算中间索引
        mid = min + (max - min) / 2;
        // 如果中间元素正好是目标值,则返回该元素的索引
        if (arr[mid] == target) {
            return mid;
        }
        // 如果中间元素小于目标值,则调整 min 索引来搜索数组的右半部分
        if (arr[mid] < target) {
            min = mid + 1;
        }
        // 如果中间元素大于目标值,则调整 max 索引来搜索数组的左半部分
        if (arr[mid] > target) {
            max = mid - 1;
        }
    }
    // 如果循环结束还没找到目标值,则返回 -1 表示未找到
    return -1;
}

调用演示

Java
public class bigDecimalDemo {
    public static void main(String[] args) {
        int[] arr = {13, 23, 35, 64, 75, 96, 117}
        // 二分查找
        System.out.println(BinarySearch(arr, 96)); // 5
    }
}

【拓展】正则表达式

本质上说 正则表达式 就是一个字符串,来验证其他字符串。规则可参考如下笔记:正则表达式

%title插图%num
%title插图%num
%title插图%num

使用 .matches(String telRegex) 验证内容

Java
public class RegexDemo {
    public static void main(String[] args) {
        // 手机号验证规则
        String telRegex = "1[3-9]\\d{9}";
        // 验证环节
        System.out.println("1203QDK1334".matches(telRegex)); // false
        System.out.println("17620334513".matches(telRegex)); // true
    }
}

【拓展】实现内容爬取

使用 Pattern 类的 complie 静态方法去传入正则规则,再使用 Matcher 去获取符合正则要求的内容,
先使用 find 方法验证是否还存在符合要求的内容,再使用 group 抽出符合要求的内容,以此循环。

findgroup 是实现类似迭代器的效果,当使用了 find 后,group 才能获取数据,
find() 用于定位下一个匹配项,group() 依赖于 find() 的成功调用,二者结合可以像迭代器一样遍历所有匹配项

Java
public class PatternDemo {
    public static void main(String[] args) {

        // 需要进行爬取的文章
        String data = "腐喵学校学摸鱼,热线电话:17680444041、15911011101。" +
                "邮箱:smmcat@qq.com,座机电话:010-12321022," +
                "热线电话:400-111-1111;搞快报名吧~";
        // 将正则表达式封装成 Pattern 对象
        Pattern pattern = Pattern.compile(regex);
        // 传入需要爬取的文章
        Matcher matcher = pattern.matcher(data);
        ArrayList<String> strList = new ArrayList<>();
        // 循环查找 若无内容 find 返回 false
        while (matcher.find()) {
            // 提取符合规则的内容
            String str = matcher.group();
            strList.add(str);
        }
        System.out.println(strList);
    }
}

效果展示

%title插图%num
时间 类

时间类分为 JDK8 之前有的,和 JDK8 新增的,内容如下:

%title插图%num
Date 类

Date 类

Date 为日期和时间类,提供了例如 getTimesetTime 功能

%title插图%num
Java
import java.util.Date;

public class DateDemo {
    public static void main(String[] args) {
        // 当前时间 封装成 Date对象
        Date date = new Date();
        System.out.println(date);

        // 把时间毫秒值转换成 Date对象
        Date date1 = new Date(0L);
        System.out.println(date1);

        // 获取时间戳
        System.out.println(date.getTime());
        System.out.println(date1.getTime());

        // 设置毫秒值
        date.setTime(10000L);
        System.out.println(date.getTime());
    }
}

效果展示

%title插图%num
SimpleDateFormat 类

SimpleDateFormat 类

用于时间格式化的类,优化了时间样式的展示

%title插图%num

SimpleDateFormat 的构造方法允许通过传入字符串指定展示日期格式,标识如下:

%title插图%num

通过 SimpleDateFormat 类,可以将 Date类 的对象转为自定义样式的时间字符串进行显示

Java
import java.text.SimpleDateFormat;
import java.util.Date;

public class DateDemo {
    public static void main(String[] args) {
        // 当前时间 封装成 Date对象
        Date date = new Date();
        System.out.println(date);

        // 默认展示的格式
        SimpleDateFormat sdf1 = new SimpleDateFormat();
        System.out.println(sdf1.format(date));
        // 自定义展示的格式
        SimpleDateFormat sdf2 = new SimpleDateFormat("yyyy年MM月dd日 HH:mm:ss");
        System.out.println(sdf2.format(date));
    }
}

效果展示

%title插图%num

同样,也可以通过 SimpleDateFormat 类,将指定规则的时间字符串转为 Date类 对象

Java
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;

public class DateDemo {
    public static void main(String[] args) throws ParseException {
        String dateStr = "2025年02月20日";
        // 指定接收规则
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy年MM月dd日");
        // 通过 parse() 方法返回 Date 对象
        Date date = sdf.parse(dateStr);
        // 显示时间戳
        System.out.println(date.getTime());
    }
}

效果展示

%title插图%num

使用了 throws ParseException 语法用于捕获异常

Calendar 类

Calendar 类提供了日历类的功能

获取系统此刻时间对应的日历,通过它可以单独获取、修改时间中的年、月、日、时、分、秒数据

%title插图%num

Calendar 类的常用方法

%title插图%num

案例演示

通过 .get(intValues) 传入对应的数值。为了方便阅读在 Calendar 类中也提供对应数值绑定的静态常量的解释

常量名说明
Calendar.YEAR1年份字段
Calendar.MONTH2月份字段(0 表示 1 月)
Calendar.DATE5日期字段(月中的某一天)
Calendar.DAY_OF_MONTH5同 Calendar.DATE
Calendar.DAY_OF_YEAR6年份中的某一天
Calendar.DAY_OF_WEEK7星期几(1 表示星期日)
Calendar.DAY_OF_WEEK_IN_MONTH8当前月中的第几个星期几
Calendar.WEEK_OF_YEAR3当前年份中的第几周
Calendar.WEEK_OF_MONTH4当前月份中的第几周
Calendar.HOUR1012 小时制的小时字段
Calendar.HOUR_OF_DAY1124 小时制的小时字段
Calendar.MINUTE12分钟字段
Calendar.SECOND13秒字段
Calendar.MILLISECOND14毫秒字段
Java
import java.text.ParseException;
import java.util.Calendar;

public class TestDemo {
    public static void main(String[] args) throws ParseException {
       // Calendar 是抽象类 需要其子类去实现
        Calendar calendar = Calendar.getInstance();
        System.out.println(calendar.get(Calendar.YEAR)); // 2025 (年)
        System.out.println(calendar.get(Calendar.MONTH)); //  2 (月)
        System.out.println(calendar.get(Calendar.DAY_OF_MONTH)); // 5 (日)
        System.out.println(calendar.get(Calendar.HOUR_OF_DAY)); // 18 (时)
        System.out.println(calendar.get(Calendar.MINUTE)); // 53 (分)
        System.out.println(calendar.get(Calendar.SECOND)); // 0 (秒)
    }
}

通过 .set(int field,int value) 修改日历类中的某个字段或者多个字段。

Java
import java.util.Calendar;

public class TestDemo {
    public static void main(String[] args) throws ParseException {
        Calendar calendar = Calendar.getInstance(); 
        calendar.set(Calendar.YEAR, 2026); // 单个修改
        calendar.set(2026, Calendar.APRIL, 12); // 修改多个(有对应规则)
        System.out.println(calendar.get(Calendar.YEAR)); // 2026
    }
}

set 可以传入多个数据实现自由配置

%title插图%num

通过 .add(int field,int amount) 为某个字段 增加/减少 指定的值

Java
public class TestDemo {
    public static void main(String[] args) throws ParseException {
        Calendar calendar = Calendar.getInstance();
        System.out.println(calendar.get(Calendar.YEAR)); // 2025 (年)
        calendar.add(Calendar.YEAR, 1); // 相当于 年的值加 1 => 2026
    }
}
区别

JDK8版本后新增功能

%title插图%num

功能概述

%title插图%num

区别

JDK8 版本前的时间对象例如:Date(),当修改时间对象信息后,就会改变之前的事件对象,而
JDK8 版本后的时间对象为不可变对象,当修改后都会返回一个新的时间对象,不影响当前时间对象

Java
    public static void main(String[] args) throws ParseException {
        // JDK8 之前的时间对象
        Date date = new Date();
        System.out.println(new SimpleDateFormat().format(date)); // 2025/3/10 下午10:52
        date.setTime(1000);
        System.out.println(new SimpleDateFormat().format(date)); // 1970/1/1 上午8:00

        // JDK8 版本之后的时间对象 返回对象接收 不影响原对象数据
        LocalDateTime now = LocalDateTime.now(); // 2025-03-10T22:55:34.334938500
        System.out.println(now);
        LocalDateTime before = now.withYear(2008);
        System.out.println(before); // 2008-03-10T22:55:34.334938500
        System.out.println(now); // 2025-03-10T22:56:21.609711900
    }
JDK8 时间类
%title插图%num

LocalDate 代表本地日期(年、月、日、星期

LocalTime 代表本地时间(时、分、秒、纳秒

LocalDateTime 代表本地日期、时间(年、月、日、星期、时、分、秒、纳秒)

想获取当前时间使用 .now();想获取指定时间则使用 .of(...)

Java
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;

public class TestDemo {
    public static void main(String[] args) throws ParseException {
        LocalTime ld = LocalTime.now();
        System.out.println(ld); // 15:16:57.300794600
        LocalDate lt = LocalDate.now();
        System.out.println(lt); // 2025-03-13
        LocalDateTime ldt = LocalDateTime.now();
        System.out.println(ldt); // 2025-03-13T15:16:57.301791200
        
        LocalDateTime ldt2 = LocalDateTime.of(2008, 8, 8, 8, 8);
        System.out.println(ldt2); // 2008-08-08T08:08
        
        // 获取当前时间信息
        System.out.println(LocalDateTime.now().getMonthValue()); // 当前月(值) 3
        System.out.println(LocalDateTime.now().getYear()); // 2025
        System.out.println(LocalDateTime.now().getHour()); // 15
    }
}

LocalDateTime 的方法是 LocalDateLocalTime 的总和,以下是常用方法

%title插图%num

LocalDateTime 也提供了写入对应时间数据的方法,但不修改原对象而是返回新对象

%title插图%num

修改多个配置用 of ,修改单个使用 with 开头,增加用 plus 开头,减少用 minus 开头的函数

判断指定日期是否在当前对象之前可以使用 isBefore,之后可以使用 isAfter

判断两个时间是否相同使用 equals

DateTimeFormatter 日期格式化

通过 DateTimeFormatter 类,对时间类的内容进行格式化和解析

Java
// 格式化
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy年M月d日");
System.out.println(formatter.format(LocalDate.now())); // 2025年3月14日

// 解析
String time = "2025年3月14日";
LocalDate parse = LocalDate.parse(time,formatter);
System.out.println(parse); // 2025-03-14

Instant 时间戳

Inatant.now() 获取当前时间戳,但是默认获取的时间戳默认获取的时区是UTC

  • 可以使用 Instant.ofEpochMilli(毫秒) 或者 Instant.ofEpochSecond(秒) 去指定时间戳距离时间原点的时间
%title插图%num

ZoneId 时区

of 用于指定时间类的时区,例如:Instant.now().atZone(ZoneId.of("Asia/Shanghai")).toInstant()
也可以获取系统上默认的时区:ZoneId.systemDefault()
通过 ZoneId.getAvailableZoneIds() 可以获得所有支持时区的 Set<String> 集合,长度为 600

%title插图%num

ZonedDateTime 带时区的时间

ZonedDateTime 是 Java 8 引入的日期时间 API 中的一个类,用于表示带有时区的日期和时间。
它结合了 LocalDateTime 和 ZoneId,能够处理不同时区的日期和时间转换。

%title插图%num
  • ZonedDateTime.now():获取当前时区的当前时间。
  • ZonedDateTime.now(ZoneId):获取指定时区的当前时间。
  • ZonedDateTime.of(...):通过指定年、月、日、时、分、秒、纳秒和时区创建 ZonedDateTime
Java
import java.time.ZonedDateTime;
import java.time.ZoneId;

public class ZonedDateTimeDemo {
    public static void main(String[] args) {
        // 获取当前时区的当前时间
        ZonedDateTime now = ZonedDateTime.now();
        System.out.println("当前时间: " + now);

        // 指定时区获取当前时间
        ZonedDateTime nowInNewYork = ZonedDateTime.now(ZoneId.of("America/New_York"));
        System.out.println("纽约当前时间: " + nowInNewYork);

        // 通过 LocalDateTime 和 ZoneId 创建 ZonedDateTime
        ZonedDateTime specificTime = ZonedDateTime.of(2023, 10, 1, 12, 0, 0, 0, ZoneId.of("Asia/Shanghai"));
        System.out.println("指定时间: " + specificTime);
    }
}

通过 .isAfter.isBefore 也可以实现比较时间效果

  • isAfter(ZonedDateTime):判断当前时间是否在指定时间之后。
  • isBefore(ZonedDateTime):判断当前时间是否在指定时间之前。
  • isEqual(ZonedDateTime):判断当前时间是否与指定时间相等。
Java
import java.time.ZonedDateTime;

public class ZonedDateTimeDemo {
    public static void main(String[] args) {
        ZonedDateTime now = ZonedDateTime.now();
        ZonedDateTime futureTime = now.plusDays(5);

        // 比较时间
        if (futureTime.isAfter(now)) {
            System.out.println("futureTime 在 now 之后");
        }

        if (now.isBefore(futureTime)) {
            System.out.println("now 在 futureTime 之前");
        }

        if (now.isEqual(now)) {
            System.out.println("now 和 now 相等");
        }
    }
}

Duration、Period、ChronoUnit 工具类

  • Duration 用于计算两个“时间”间隔(秒、纳秒)
  • Period 用于计算两个“日期”间隔(年、月、日)
  • ChronoUnit 用于计算两个“日期”间隔

Duration 用于表示两个时间点之间的时间差异,基于秒和纳秒。它适用于处理小时、分钟、秒和纳秒的差异

Java
import java.time.Duration;
import java.time.LocalTime;

public class DurationDemo {
    public static void main(String[] args) {
        LocalTime startTime = LocalTime.of(10, 0); // 10:00
        LocalTime endTime = LocalTime.of(12, 30); // 12:30

        // 计算时间差
        Duration duration = Duration.between(startTime, endTime);
        System.out.println("时间差: " + duration);

        // 获取小时、分钟、秒
        System.out.println("小时: " + duration.toHours());
        System.out.println("分钟: " + duration.toMinutes());
        System.out.println("秒: " + duration.getSeconds());

        // 增加时间
        Duration addedDuration = duration.plusHours(1).plusMinutes(15);
        System.out.println("增加 1 小时 15 分钟后的时间差: " 
        + addedDuration
        );
    }
}

效果展示

%title插图%num

Period 用于表示两个日期之间的差异,基于年、月、日。它适用于处理日期的差异。

Java
import java.time.LocalDate;
import java.time.Period;

public class PeriodDemo {
    public static void main(String[] args) {
        LocalDate startDate = LocalDate.of(2020, 1, 1);
        LocalDate endDate = LocalDate.of(2023, 10, 1);

        // 计算日期差
        Period period = Period.between(startDate, endDate);
        System.out.println("日期差: " + period);

        // 获取年、月、日
        System.out.println("年: " + period.getYears());
        System.out.println("月: " + period.getMonths());
        System.out.println("日: " + period.getDays());

        // 增加时间
        Period addedPeriod = period.plusYears(1).plusMonths(2);
        System.out.println("增加 1 年 2 个月后的日期差: " + addedPeriod);
    }
}

效果演示

%title插图%num

ChronoUnit 是一个枚举类,用于表示时间单位(如天、小时、分钟等)。它通常用于计算两个时间点之间的差异

Java
import java.time.LocalDateTime;
import java.time.temporal.ChronoUnit;

public class ChronoUnitDemo {
    public static void main(String[] args) {
        LocalDateTime startDateTime = LocalDateTime.of(2023, 1, 1, 10, 0);
        LocalDateTime endDateTime = LocalDateTime.of(2023, 10, 1, 12, 30);

        // 计算差异
        long daysBetween = ChronoUnit.DAYS.between(startDateTime, endDateTime);
        long hoursBetween = ChronoUnit.HOURS.between(startDateTime, endDateTime);
        long minutesBetween = ChronoUnit.MINUTES.between(startDateTime, endDateTime);

        System.out.println("天数差: " + daysBetween);
        System.out.println("小时数差: " + hoursBetween);
        System.out.println("分钟数差: " + minutesBetween);
    }
}

效果演示

%title插图%num
递归 & 异常
递归

递归

递归就是方法直接或者间接调用自身。递归如果没有控制好终止时会出现递归死循环,导致栈内存溢出现象

Java
public class demo {
    public static void main(String[] args) {
        int result = jc(5); // 120
        System.out.println(result);
    }

    // 计算 num 的阶乘
    private static int jc(int num) {
        if (num == 1) {
            return 1;
        } else {
            return num * jc(num - 1);
        }
    }
}

运行原理

%title插图%num
异常

异常

Java 异常(Exception)是程序运行时发生的非预期事件,它会中断正常的指令流。异常的类型在 java 里面也是类

%title插图%num

异常分类(Deepseek提供)

类型特点常见示例
ErrorJVM 系统级错误,程序无法处理(通常不捕获)OutOfMemoryErrorStackOverflowError
Exception程序可处理的异常,分为两类:
  ↳
Checked
编译时强制检查(必须处理)IOExceptionSQLException
 ↳ Unchecked运行时异常(不强制处理,通常为逻辑错误)NullPointerExceptionArrayIndexOutOfBoundsException

以下是 Java 中常见异常的表格说明,包含异常名称、触发场景和典型示例:

异常名称触发场景示例代码解决方法
NullPointerException尝试访问或调用null对象的成员String str = null; str.length();添加非空检查:if(str != null) {...}
ArrayIndexOutOfBoundsException数组访问越界(索引<0或≥数组长度)int[] arr = {1,2}; System.out.println(arr[2]);检查索引范围:if(index >=0 && index < arr.length)
ClassCastException不安全的强制类型转换Object obj = "hello"; Integer num = (Integer) obj;使用instanceof检查:if(obj instanceof Integer)
ArithmeticException算术运算错误(如除以零)int x = 5 / 0;添加除数检查:if(divisor != 0) {...}
NumberFormatException字符串转换为数字时格式不符int num = Integer.parseInt("abc");捕获异常或正则校验:str.matches("-?\\d+")
IllegalArgumentException方法接收到非法参数List.subList(-1, 3);方法入口参数校验
IllegalStateException对象状态不符合方法调用要求Iterator.next()hasNext()=false时调用检查对象状态:if(iterator.hasNext()) {...}
IndexOutOfBoundsException集合/字符串索引越界(List, String等)String s = "hi"; s.charAt(2);同数组越界处理
ConcurrentModificationException集合在迭代时被修改(非迭代器的remove()方法)for(String s : list) { list.remove(s); }使用迭代器的remove()CopyOnWriteArrayList
NoSuchElementException调用不存在的元素(如空Optional.get()Optional.empty().get();使用Optional.orElse()等安全方法
UnsupportedOperationException调用不支持的操作(如Arrays.asList()返回不可变列表的add()Arrays.asList(1,2).add(3);使用可变集合:new ArrayList<>(Arrays.asList(1,2))
FileNotFoundException文件路径不存在或不可访问new FileInputStream("nonexist.txt");检查文件路径和权限
IOExceptionI/O操作失败(通用父类)socket.getInputStream().read()时连接断开资源清理+重试机制
SQLException数据库操作错误stmt.executeQuery("SELECT * FROM non_exist_table");检查SQL语句+连接状态
ClassNotFoundException类加载失败(JVM找不到类文件)Class.forName("com.example.NonExistClass");检查类路径和依赖
NoClassDefFoundError编译时存在但运行时缺少类定义(与ClassNotFoundException区别在于编译阶段)运行时删除了已编译依赖的jar包确保运行时类路径完整

补充说明:

  1. 检查型异常(Checked):必须捕获或声明抛出(如IOException
  2. 非检查型异常(Unchecked)RuntimeException的子类,通常由编程错误导致
  3. Error:系统级错误(如OutOfMemoryError),通常不可恢复

异常处理机制

Java
try {
    // 可能抛出异常的代码
} catch (NullPointerException e) {
    // 针对性处理空指针
} catch (IllegalArgumentException e) {
    // 处理参数错误
} catch (Exception e) {
    // 通用异常处理
    logger.error("Operation failed", e);
} finally {
    // 资源释放
}
方法作用
e.printStackTrace()打印异常堆栈跟踪(调试用)
e.getMessage()获取异常描述信息
throw new Exception("msg")主动抛出异常

自定义异常

Java
class MyException extends Exception {
    public MyException(String message) {
        super(message); // 调用父类构造
    }
}

异常说明

  • 优先处理具体异常:避免直接捕获 Exception
  • 早抛出,晚捕获:在合适层级处理异常
  • 避免空 catch 块:至少记录日志(如 log.error(e)
  • 资源管理:使用 try-with-resources(Java 7+)

Java 异常是程序错误处理的标准化机制,通过 try-catch-finally 结构实现可控的流程中断和恢复,
区分检查型和非检查型异常使错误处理更灵活

编译时异常

编译时的异常是即使你语法正确,但是仍然以为你代码运行时可能存在错误所做的一个异常提示;为了程序的运行严谨,需要提供解决方案。

Java
import java.text.SimpleDateFormat;
import java.util.Date;

public class demo {
    public static void main(String[] args) {
        int result = jc(5);
        System.out.println(result);
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy年MM月dd日");
        // 即使语法错误 依然提示 未处理 异常: java.text.ParseException
        Date date = sdf.parse("2025年12月11日");
        System.out.println(date);
    }
}
添加异常方法签名后解决问题
import java.text.SimpleDateFormat;
import java.util.Date;

public class demo {
    public static void main(String[] args) throws ParseException {
        int result = jc(5);
        System.out.println(result);
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy年MM月dd日");
        Date date = sdf.parse("2025年12月11日");
        System.out.println(date);
    }
}

报错提示

%title插图%num

异常的默认处理流程

  1. 虚拟机会在出现异常的代码那里自动的创建一个异常对象: ArithmeticException
  2. 异常会从方法中出现的点这里抛出给调用者,调用者最终抛出给JVM 虚拟机
  3. 虚拟机接收到异常对象后,先在控制台直接输出异常信息数据
  4. 终止 Java 程序的运行

如果异常捕获抓到错误后,将直接结束后续的传递。使程序继续往下执行

Java
    public static void main(String[] args) {
        try{
            // 可能报错的代码
        }catch (Exception e){
            // Exception 父类实现多态 可以捕获更多错误
            // 捕获后的代码
        }
    }

throws 抛出异常

throws 是 Java 中用于 声明方法可能抛出的异常 的关键字,它告诉调用者该方法可能会抛出哪些异常,以便调用者进行相应的处理。

用途说明
声明方法可能抛出的异常方法内部不直接处理异常,而是由调用者处理
强制调用者处理异常适用于 Checked Exception(如 IOExceptionSQLException
提高代码可读性明确方法可能抛出的异常类型,便于维护

throws 的基本语法

返回类型 方法名(参数列表) throws 异常类型1, 异常类型2, ... {
    // 方法体(可能抛出异常)
}

示例

Java
```java
public void readFile(String filePath) throws FileNotFoundException, IOException {
    FileInputStream fis = new FileInputStream(filePath); // 可能抛出 FileNotFoundException
    // 其他 IO 操作可能抛出 IOException
}

throwstry-catch 的区别

方式适用场景特点
throws方法内部不处理异常,交给调用者处理适用于 Checked Exception
try-catch方法内部自行处理异常适用于 RuntimeException 或需要立即处理的异常

示例

Java
// 方式1:使用 throws 让调用者处理异常
public void methodA() throws IOException {
    // 可能抛出 IOException
}

// 方式2:使用 try-catch 自行处理异常
public void methodB() {
    try {
        // 可能抛出 IOException
    } catch (IOException e) {
        e.printStackTrace();
    }
}

业务用途

throws 也适用于主动抛出异常的情况。如果当前代码别人调用时存在可能报错的问题,可以主动 throw 一个错误类,方便他人调用时注意

Java
public class demo {
    // 调用方可以选择继续抛出或者使用 try{}catch{} 捕获
    public static void main(String[] args) throws Exception {
        putAge(-12);
    }

    public static void putAge(int age) throws Exception {
        if (age <= 0 || age > 100) {
            throw new Exception("年龄传入有误,需要在 1-100 之间");
        }
        // 执行传参业务
        // ...
        System.out.println("完成");
    }
}

效果展示

%title插图%num

也可以通过 try catch 捕获异常,并使用 e.getMessage() 获取报错的消息

Java
public class demo {
    // 调用方可以选择继续抛出或者使用 try{}catch{} 捕获
    public static void main(String[] args) {
        try {
            putAge(-12);
        } catch (Exception e) {
            System.out.println(e.getMessage());
        }
    }

    public static void putAge(int age) throws Exception {
        if (age <= 0 || age > 100) {
            throw new Exception("年龄传入有误,需要在 1-100 之间");
        }
        // 执行传参业务
        // ...
        System.out.println("完成");
    }
}

效果展示

%title插图%num

每个异常的类都不同,因此 catch 只会捕获对应支持的异常的值,当有多个异常需要捕获需要注意最大的异常排后

Java
    public static void main(String[] args) {
        try {
            int x = 5 / 0;
            putAge(-12);
        } catch (ArithmeticException e) {
            System.out.println("运算异常");
        } catch (Exception e) {
            System.out.println(e.getMessage());
        }
    }

另外,还有 RuntimeException 异常,属于运行时异常。在运行时可能发生的错误,无需声明 throws

Java
public class demo {
    public static void main(String[] args) {
        try {
            putAge(-12);
        } catch (RuntimeException e) {
            System.out.println(e.getMessage());
        }
    }

    public static void putAge(int age) {
        if (age <= 0 || age > 100) {
            throw new RuntimeException("传入的值范围不对");
        }
        // 执行传参业务
        // ...
        System.out.println("完成");
    }
}

效果展示

%title插图%num
集合 进阶

除了 ArrayList 以外,还有很多不同种类的集合。它们可以如下分类(实现了 Collection 接口的、实现了 Map 接口的)

%title插图%num
单列集合(实现 Collection 接口)双列集合(实现 Map 接口)

单列集合:

  • Collection 接口又分为 List 接口(ArrayListLinkedList) 和 Set 接口(TreeMapHashMapLinkedHashMap
    • List 接口的特点是 存取有序、有索引、可以存储重复的
    • Set 接口的特点是 存储无序、没有索引、不可以存储重复的

Collection 通用接口方法

%title插图%num

通过多态实现调用拥有方法,发现 SetList 接口的不同

Java
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;

public class test1 {
    public static void main(String[] args) {
        // 以多态的形式创建集合对象 调用集合中的共有方法
        Collection<String> a = new ArrayList<>();
        boolean b1 = a.add("a");
        boolean b2 = a.add("a");
        boolean b3 = a.add("a");
        System.out.println(b1); // true
        System.out.println(b2); // true
        System.out.println(b3); // true
        System.out.println(a); // ["a","b","c"]
        Collection<String> b = new HashSet<>();
        boolean b4 = b.add("a");
        boolean b5 = b.add("a");
        boolean b6 = b.add("a");
        System.out.println(b4); // true
        System.out.println(b5); // false
        System.out.println(b6); // false
        System.out.println(b); // ["a"]
    }
}

Collection

通过调用 `Collection