【JavaSE全面教学】Java常用类与包装类Day10(2026年)
写在前面:这是JavaSE系列的第10篇,也是面向对象篇的最后一篇。今天我们学习Java中最常用的几个类——Object、Math、日期类,以及包装类。这些是Java开发中每天都会用到的,必须掌握。

文章目录
一、Object类:所有类的父类
1.1 为什么Object类如此重要?
在实际开发中,你是否遇到过这样的场景?
// 场景:打印对象信息,输出的是乱码
Person p = new Person("张三", 20);
System.out.println(p); // 输出:Person@15db9742
// 场景:比较两个对象,明明内容一样却返回false
Person p1 = new Person("张三", 20);
Person p2 = new Person("张三", 20);
System.out.println(p1 == p2); // false
这些问题都源于Object类的方法。Java中所有类都直接或间接继承Object类,理解它是写出高质量代码的基础。
class Person { } // 相当于 class Person extends Object { }
class Student extends Person { } // Student间接继承Object
1.2 Object的常用方法
| 方法 | 说明 |
|---|---|
toString() |
返回对象的字符串表示 |
equals(Object obj) |
比较两个对象是否相等 |
hashCode() |
返回对象的哈希码值 |
getClass() |
返回对象的运行时类型 |
clone() |
创建对象的副本 |
finalize() |
垃圾回收时调用(已废弃) |
1.3 toString方法
踩坑提醒:很多新手直接打印对象,看到Person@15db9742这样的输出一脸懵,其实这是Object默认的toString实现。
class Person {
String name;
int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
}
public class Test {
public static void main(String[] args) {
Person p = new Person("张三", 20);
// 不重写toString,默认输出:类名@哈希码
System.out.println(p.toString());
// 输出:Person@15db9742
// 快捷键:Alt + Insert → toString()
// 重写后输出:Person{name='张三', age=20}
}
}
经验之谈:在实际项目中,我习惯为每个实体类都重写toString,这样在日志输出和调试时能快速定位问题。IDEA的自动生成快捷键Alt + Insert可以一键生成,非常方便。
IDEA自动生成toString:
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
1.4 equals方法
踩坑提醒:==和equals的区别是面试必考点,也是新手最容易混淆的地方。
class Person {
String name;
int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
// IDE自动生成equals(Alt + Insert → equals and hashCode)
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Person person = (Person) o;
return age == person.age && Objects.equals(name, person.name);
}
}
public class Test {
public static void main(String[] args) {
Person p1 = new Person("张三", 20);
Person p2 = new Person("张三", 20);
System.out.println(p1 == p2); // false(==比较地址)
System.out.println(p1.equals(p2)); // true(比较内容)
}
}
经验之谈:在集合操作中(如HashMap、HashSet),equals和hashCode必须同时重写,这是Java的约定。如果只重写equals而不重写hashCode,会导致集合行为异常。
1.5 hashCode方法
为什么equals和hashCode要一起重写?
在HashMap中,key的查找过程是:先通过hashCode定位桶,再通过equals比较。如果两个对象equals相等但hashCode不同,会导致同一个key存进两个不同的桶,违背HashMap的设计。
// hashCode的约定:
// 1. 同一个对象多次调用hashCode,返回值必须相同
// 2. 两个对象equals返回true,hashCode必须相同
// 3. 两个对象equals返回false,hashCode可以相同(但最好不同)
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Person person = (Person) o;
return age == person.age && Objects.equals(name, person.name);
}
@Override
public int hashCode() {
return Objects.hash(name, age);
}
踩坑提醒:曾经遇到过一个Bug,Person对象作为HashMap的key,存进去后却取不出来。排查半天发现只重写了equals没重写hashCode,导致hashCode不一致,定位到了错误的桶。
二、Math类:数学工具
2.1 常用方法
// 取整
Math.ceil(3.14); // 4.0,向上取整
Math.floor(3.14); // 3.0,向下取整
Math.round(3.14); // 3,四舍五入
Math.round(3.54); // 4
// 绝对值
Math.abs(-5); // 5
Math.abs(-3.14); // 3.14
// 最大最小值
Math.max(10, 20); // 20
Math.min(10, 20); // 10
// 幂运算
Math.pow(2, 3); // 8.0,2的3次方
Math.sqrt(16); // 4.0,平方根
Math.cbrt(27); // 3.0,立方根
// 对数
Math.log(2.71828); // 1.0,自然对数
Math.log10(100); // 2.0,底数为10的对数
// 三角函数
Math.sin(Math.PI / 2); // 1.0
Math.cos(0); // 1.0
Math.tan(Math.PI / 4); // 1.0
2.2 随机数
实际应用场景:验证码生成、抽奖系统、测试数据构造都需要用到随机数。
// 1. Math.random():返回[0, 1)之间的随机数
double random = Math.random(); // 0.0 ~ 0.999...
// 生成指定范围的随机整数
int num = (int)(Math.random() * 100); // 0 ~ 99
int num2 = (int)(Math.random() * 100) + 1; // 1 ~ 100
// 2. Random类(更灵活)
import java.util.Random;
Random r = new Random(); // 默认种子(当前时间)
Random r2 = new Random(100); // 指定种子(种子相同,产生的随机数序列相同)
r.nextInt(); // 任意int值
r.nextInt(100); // [0, 100)之间的int值
r.nextDouble(); // [0.0, 1.0)之间的double值
r.nextBoolean(); // true或false
经验之谈:在需要可重复随机序列的场景(如游戏存档、测试用例),使用指定种子的Random非常重要。例如游戏地图生成,相同种子生成相同的地图,便于玩家分享。
三、日期时间类
3.1 Date类
import java.util.Date;
// 当前时间
Date now = new Date();
System.out.println(now); // Thu Jan 15 10:30:45 CST 2024
// 毫秒值转Date
long time = 1704067200000L;
Date date = new Date(time);
// Date转毫秒值
long timestamp = now.getTime();
// 日期比较
Date d1 = new Date();
Date d2 = new Date();
d1.before(d2); // d1是否在d2之前
d1.after(d2); // d1是否在d2之后
d1.compareTo(d2); // 返回负数、0、正数
3.2 SimpleDateFormat:日期格式化
踩坑提醒:SimpleDateFormat不是线程安全的!在高并发场景下,多个线程共享同一个SimpleDateFormat实例会导致日期解析错误。
import java.text.SimpleDateFormat;
import java.util.Date;
Date now = new Date();
// 格式化:Date → String
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String dateStr = sdf.format(now);
System.out.println(dateStr); // 2024-01-15 10:30:45
String dateStr2 = new SimpleDateFormat("yyyy年MM月dd日 E").format(now);
System.out.println(dateStr2); // 2024年01月15日 周一
// 解析:String → Date
String str = "2024-01-15";
SimpleDateFormat sdf2 = new SimpleDateFormat("yyyy-MM-dd");
Date date = sdf2.parse(str);
解决方案:
- 每次使用都new一个SimpleDateFormat(简单但性能差)
- 使用ThreadLocal为每个线程创建一个实例
- 升级到Java 8,使用DateTimeFormatter(线程安全,推荐)
格式化符号:
| 符号 | 含义 | 示例 |
|---|---|---|
| yyyy | 年 | 2024 |
| MM | 月 | 01~12 |
| dd | 日 | 01~31 |
| HH | 小时(24) | 00~23 |
| hh | 小时(12) | 01~12 |
| mm | 分钟 | 00~59 |
| ss | 秒 | 00~59 |
| E | 星期 | 周一 |
3.3 LocalDateTime(Java 8+,推荐)
为什么推荐LocalDateTime?
- 线程安全:DateTimeFormatter是线程安全的,可以在多线程环境下共享
- 不可变对象:所有修改操作都返回新对象,不会出现意外修改
- API设计清晰:日期和时间分开处理,语义明确
import java.time.LocalDate;
import java.time.LocalTime;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
// LocalDate:只有日期
LocalDate date = LocalDate.now();
System.out.println(date); // 2024-01-15
// LocalTime:只有时间
LocalTime time = LocalTime.now();
System.out.println(time); // 10:30:45.123456
// LocalDateTime:日期+时间(常用)
LocalDateTime ldt = LocalDateTime.now();
System.out.println(ldt); // 2024-01-15T10:30:45.123456
// 格式化
DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy年MM月dd日 HH:mm:ss");
String str = ldt.format(dtf);
System.out.println(str); // 2024年01月15日 10:30:45
// 解析
LocalDateTime ldt2 = LocalDateTime.parse("2024-01-15 10:30:45",
DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
// 计算
LocalDateTime future = ldt.plusDays(7); // 7天后
LocalDateTime past = ldt.minusHours(2); // 2小时前
// 日期比较
LocalDate d1 = LocalDate.of(2024, 1, 1);
LocalDate d2 = LocalDate.of(2024, 12, 31);
System.out.println(d1.isBefore(d2)); // true
System.out.println(d2.isAfter(d1)); // true
经验之谈:在新项目中,我坚决使用LocalDateTime替代Date。曾经维护过一个老项目,里面Date和LocalDateTime混用,转换起来非常痛苦。建议新项目统一使用Java 8时间API。
四、包装类:基本类型的"对象包装"
4.1 为什么要包装类?
// 基本类型不是对象,不能用于泛型
// List<int> list = new ArrayList<>(); // 错误!
// 包装类是对象,可以用于泛型
List<Integer> list = new ArrayList<>(); // 正确!
4.2 基本类型对应的包装类
| 基本类型 | 包装类 |
|---|---|
| byte | Byte |
| short | Short |
| int | Integer |
| long | Long |
| float | Float |
| double | Double |
| char | Character |
| boolean | Boolean |
4.3 装箱和拆箱
// 手动装箱
Integer a = Integer.valueOf(10); // 方式1
Integer b = new Integer(10); // 方式2(已废弃)
// 手动拆箱
int c = a.intValue(); // 方式1
// 自动装箱(编译时自动转换为valueOf)
Integer d = 10; // 自动装箱:Integer.valueOf(10)
// 自动拆箱
int e = d; // 自动拆箱:d.intValue()
// 实际应用
int sum = 0;
for (Integer i : list) { // 自动拆箱
sum += i; // 自动拆箱
}
4.4 缓存机制
面试高频考点:Integer缓存是面试官最爱问的知识点之一,务必理解透彻。
// Integer缓存:-128 ~ 127
Integer a = 127;
Integer b = 127;
System.out.println(a == b); // true(缓存范围内)
Integer c = 128;
Integer d = 128;
System.out.println(c == d); // false(超出缓存范围)
// Byte、Short、Long也有缓存
// Character缓存:0 ~ 127
// Boolean:TRUE和FALSE两个常量
原理揭秘:Integer.valueOf()方法内部使用了IntegerCache,-128到127之间的整数会被缓存起来,多次获取返回同一个对象。超出这个范围就会new一个新的Integer对象。
踩坑提醒:比较包装类一定要用equals,不要用==。虽然缓存范围内的==可能返回true,但这是不可靠的实现细节,不同JDK版本可能不同。
4.5 字符串转换
// String → 基本类型
int i = Integer.parseInt("100"); // 100
double d = Double.parseDouble("3.14"); // 3.14
boolean b = Boolean.parseBoolean("true"); // true
// String → 包装类
Integer i = Integer.valueOf("100");
// 基本类型 → String
String s1 = String.valueOf(100); // "100"
String s2 = Integer.toString(100); // "100"
String s3 = 100 + ""; // "100"
// 包装类 → String
String s4 = new Integer(100).toString(); // "100"
4.6 注意事项
踩坑提醒:这三个坑我在实际开发中都踩过,血泪教训!
// ⚠️ 自动拆箱可能报NullPointerException
Integer i = null;
int j = i; // 自动拆箱:i.intValue() → NullPointerException!
// ⚠️ 包装类的==比较
Integer a = 100;
Integer b = 100;
System.out.println(a == b); // true(缓存范围内)
Integer c = 200;
Integer d = 200;
System.out.println(c == d); // false(超出缓存)
// ⚠️ 建议:比较包装类用equals
System.out.println(c.equals(d)); // true
经验之谈:
- 数据库查询返回的Integer可能为null,赋值给int前一定要判空
- 三目运算符中如果有一个操作数是基本类型,另一个会自动拆箱,也容易NPE
- 团队规范建议:所有包装类比较统一使用equals,避免==的迷惑行为
五、System类
5.1 常用方法
性能测试必备:System.currentTimeMillis()和System.nanoTime()是性能测试的常用工具。
// 1. System.currentTimeMillis():获取当前时间毫秒值
long start = System.currentTimeMillis();
// 执行代码...
long end = System.currentTimeMillis();
System.out.println("耗时:" + (end - start) + "ms");
// 2. System.arraycopy():数组拷贝
int[] src = {1, 2, 3, 4, 5};
int[] dest = new int[5];
System.arraycopy(src, 0, dest, 0, 5);
// 3. System.gc():建议垃圾回收器回收(不保证立即执行)
System.gc();
// 4. System.exit(0):退出JVM
// System.exit(0); // 0表示正常退出,非0表示异常退出
// 5. System.getProperty():获取系统属性
System.out.println(System.getProperty("java.version")); // Java版本
System.out.println(System.getProperty("os.name")); // 操作系统
System.out.println(System.getProperty("user.dir")); // 当前目录
经验之谈:System.arraycopy()是native方法,比手动for循环拷贝数组快得多。在需要高性能数组操作的场景下优先使用。
六、String类的高级操作
6.1 String的split方法
String s = "Java,Python,JavaScript";
// 按逗号分割
String[] arr = s.split(","); // ["Java", "Python", "JavaScript"]
// 限制分割次数
String[] arr2 = s.split(",", 2); // ["Java", "Python,JavaScript"]
// 按正则分割
String s2 = "Java1Python2JavaScript3Go";
String[] arr3 = s2.split("\\d"); // 按数字分割
6.2 String的matches方法
// 判断是否匹配正则
String phone = "13812345678";
boolean valid = phone.matches("1[3-9]\\d{9}");
System.out.println(valid); // true
String email = "test@example.com";
boolean valid2 = email.matches("\\w+@\\w+\\.\\w+");
System.out.println(valid2); // true
6.3 String的replaceAll
String s = "Java123Python456";
// 替换所有数字为#
String s2 = s.replaceAll("\\d", "#"); // Java###Python###
// 删除所有数字
String s3 = s.replaceAll("\\d", ""); // JavaPython
// 替换字符串(不支持正则)
String s4 = s.replace("Java", "C++"); // C++123Python456
七、面试高频考点
考点1:Integer缓存机制
Integer a = 127;
Integer b = 127;
System.out.println(a == b); // true
Integer c = 128;
Integer d = 128;
System.out.println(c == d); // false
延伸问题:缓存范围可以调整吗?
答案:可以通过-XX:AutoBoxCacheMax=<size>参数调整Integer缓存上限。
考点2:==和equals的区别
// 基本类型:==比较值
// 引用类型:==比较地址,equals比较内容(需要重写)
追问:String的equals是怎么实现的?
答案:String重写了equals,先比较地址,再比较长度,最后逐个字符比较。
考点3:自动拆箱的NPE问题
Integer i = null;
int j = i; // NullPointerException!
实际场景:从数据库查询返回Integer,直接赋值给int类型字段时容易触发。
考点4:Date和LocalDateTime
// Date:旧API,线程不安全
// LocalDateTime:Java 8+,线程安全,推荐使用
延伸:SimpleDateFormat为什么线程不安全?
答案:SimpleDateFormat内部使用Calendar对象存储中间状态,多线程并发修改会导致数据混乱。
八、总结
今天我们学习了:
- ✅ Object类的常用方法
- ✅ Math类的数学运算
- ✅ 日期时间类的使用
- ✅ 包装类的装箱拆箱
- ✅ System类的常用方法
重点记忆:
- Object是所有类的父类
- Integer缓存范围是-128~127
- 日期格式化用SimpleDateFormat或LocalDateTime
- 自动拆箱注意NPE问题
下一步预告:
Day11我们将学习异常处理——try-catch、自定义异常、异常体系等。
参考资料
互动话题:你在使用日期类的时候,有没有被Date和LocalDateTime搞混过?欢迎在评论区分享!
如果这篇文章对你有帮助,欢迎点赞、收藏!这是【JavaSE全面教学】系列的第10篇,关注我看完整套教程 👇
本文为【JavaSE全面教学】系列第10篇,持续更新中…
更多推荐

所有评论(0)