常用核心类
Java 标准库提供了丰富的核心类,涵盖字符串处理、数值操作、日期时间、随机数生成等常见场景。熟练使用这些类是高效编写 Java 程序的基础。
String 类
String 是 Java 中最常用的类之一,用于表示字符串。Java 中的字符串是不可变的(immutable),这一设计贯穿了整个 String API。
不可变性
String 类被声明为 final,其内部字符数组也是 final 的,一旦创建就不能被修改。任何对字符串的"修改"操作都会返回一个新的 String 对象。
不可变的好处:
| 好处 | 说明 |
|---|---|
| 线程安全 | 不可变对象天然线程安全,无需同步 |
| 字符串常量池 | 字符串可以被缓存复用,节省内存 |
| 哈希缓存 | String 的 hashCode 只需计算一次,适合作为 HashMap 的键 |
| 安全性 | 网络连接、文件路径等场景不会被篡改 |
public class StringImmutableExample {
public static void main(String[] args) {
String s = "Hello";
// "修改"操作会返回新对象,原字符串不变
String s2 = s.concat(" World");
System.out.println("原字符串: " + s); // Hello
System.out.println("新字符串: " + s2); // Hello World
System.out.println("是否同一个对象: " + (s == s2)); // false
}
}字符串常量池
为了节省内存,Java 维护了一个字符串常量池(String Pool)。使用字面量创建字符串时,JVM 会先在常量池中查找,如果已存在则直接引用,否则创建新对象并放入池中。
public class StringPoolExample {
public static void main(String[] args) {
// 字面量创建——会放入常量池
String s1 = "Java";
String s2 = "Java";
// new 创建——强制创建新对象,不入池
String s3 = new String("Java");
System.out.println("s1 == s2: " + (s1 == s2)); // true(同池引用)
System.out.println("s1 == s3: " + (s1 == s3)); // false(堆上新对象)
System.out.println("s1.equals(s3): " + s1.equals(s3)); // true(内容相等)
// intern() 方法:手动将字符串放入常量池并返回池中引用
String s4 = s3.intern();
System.out.println("s1 == s4: " + (s1 == s4)); // true(池中引用)
}
}常用 API
public class StringAPIExample {
public static void main(String[] args) {
String str = " Hello, Java World! ";
// 长度
System.out.println("长度: " + str.length()); // 21
// 字符与索引
System.out.println("索引 3 的字符: " + str.charAt(3)); // e
// substring —— 截取子串(左闭右开)
String sub = str.substring(2, 7); // "Hello"
System.out.println("substring(2,7): [" + sub + "]");
// indexOf / lastIndexOf —— 查找索引
System.out.println("'Java' 首次出现: " + str.indexOf("Java")); // 9
System.out.println("'o' 最后一次出现: " + str.lastIndexOf('o')); // 12
// contains / startsWith / endsWith
System.out.println("包含 'Java': " + str.contains("Java")); // true
System.out.println("以空格开头: " + str.startsWith(" ")); // true
System.out.println("以感叹号结尾: " + str.endsWith("! ")); // true
// replace / replaceAll
String replaced = str.replace("Java", "Python");
System.out.println("替换后: " + replaced); // " Hello, Python World! "
// split —— 分割
String csv = "apple,banana,orange";
String[] fruits = csv.split(",");
System.out.println("分割结果:");
for (String fruit : fruits) {
System.out.println(" - " + fruit.trim());
}
// trim —— 去除首尾空白
System.out.println("trim 后: [" + str.trim() + "]"); // [Hello, Java World!]
// toUpperCase / toLowerCase
System.out.println("大写: " + str.toUpperCase());
System.out.println("小写: " + str.toLowerCase());
// equals / equalsIgnoreCase —— 比较内容
String a = "Hello";
String b = "hello";
System.out.println("equals: " + a.equals(b)); // false
System.out.println("equalsIgnoreCase: " + a.equalsIgnoreCase(b)); // true
// compareTo —— 字典序比较
System.out.println("compareTo: " + "apple".compareTo("banana")); // 负数(a < b)
// valueOf —— 基本类型转字符串
String numStr = String.valueOf(42);
System.out.println("valueOf(42): " + numStr);
// isEmpty / isBlank
System.out.println("\"\" 是否为空: " + "".isEmpty()); // true
System.out.println("\" \" 是否空白: " + " ".isBlank()); // true(JDK 11+)
// join —— 拼接
String joined = String.join(" - ", "A", "B", "C");
System.out.println("join 结果: " + joined); // A - B - C
}
}StringBuilder 与 StringBuffer
由于 String 不可变,频繁拼接字符串会产生大量临时对象,影响性能。为此 Java 提供了两个可变字符串类:
| 特性 | StringBuffer | StringBuilder |
|---|---|---|
| 引入版本 | JDK 1.0 | JDK 5.0 |
| 线程安全 | 是(方法用 synchronized 修饰) | 否 |
| 性能 | 较慢 | 较快(无同步开销) |
使用原则: 单线程环境优先使用 StringBuilder;多线程共享可变字符串时使用 StringBuffer。
public class StringBuilderBufferExample {
public static void main(String[] args) {
// StringBuilder —— 单线程推荐
StringBuilder sb = new StringBuilder();
sb.append("Hello");
sb.append(" ");
sb.append("World");
sb.append("!");
System.out.println("StringBuilder 结果: " + sb.toString()); // Hello World!
// 链式调用
StringBuilder sb2 = new StringBuilder()
.append("A")
.append(", B")
.append(", C");
System.out.println("链式调用: " + sb2); // A, B, C
// 插入 / 删除 / 反转
sb2.insert(2, "(插入)");
System.out.println("insert 后: " + sb2); // A(插入), B, C
sb2.delete(2, 6);
System.out.println("delete 后: " + sb2); // A, B, C
sb2.reverse();
System.out.println("reverse 后: " + sb2); // C ,B ,A
// 性能对比演示
long start = System.nanoTime();
String result = "";
for (int i = 0; i < 10000; i++) {
result += i; // 每次循环都创建新 String 对象
}
long end = System.nanoTime();
System.out.println("String 拼接耗时: " + (end - start) / 1_000_000 + " ms");
start = System.nanoTime();
StringBuilder sb3 = new StringBuilder();
for (int i = 0; i < 10000; i++) {
sb3.append(i);
}
end = System.nanoTime();
System.out.println("StringBuilder 耗时: " + (end - start) / 1_000_000 + " ms");
}
}包装类
Java 是面向对象语言,但 int、double、boolean 等基本类型不是对象。包装类(Wrapper Class)将每个基本类型封装成对应的对象,使其可以参与泛型、集合等需要对象的场景。
| 基本类型 | 包装类 | 初始值 |
|---|---|---|
byte | Byte | null |
short | Short | null |
int | Integer | null |
long | Long | null |
float | Float | null |
double | Double | null |
char | Character | null |
boolean | Boolean | null |
自动装箱与拆箱
Java 5 引入的自动装箱(autoboxing)和拆箱(unboxing)让基本类型和包装类之间的转换更简洁:
public class BoxExample {
public static void main(String[] args) {
// 自动装箱:int → Integer
Integer num = 42; // 编译后相当于 Integer.valueOf(42)
// 自动拆箱:Integer → int
int value = num; // 编译后相当于 num.intValue()
// 在集合中使用(必须用包装类)
java.util.List<Integer> list = new java.util.ArrayList<>();
list.add(10); // 自动装箱
list.add(20);
int sum = 0;
for (int n : list) { // 自动拆箱
sum += n;
}
System.out.println("集合求和: " + sum); // 30
// 三元表达式中的拆箱陷阱
Integer a = null;
try {
// 三元表达式会拆箱,触发 NullPointerException
int b = (a != null) ? a : 0;
} catch (NullPointerException e) {
System.out.println("拆箱 null 抛出了 NullPointerException");
}
}
}Integer 缓存机制
为了提高性能和节省内存,Integer 内部缓存了 -128 到 127 之间的值。在这个范围内,valueOf() 返回缓存对象,== 比较结果为 true。
public class IntegerCacheExample {
public static void main(String[] args) {
// 在缓存范围内(-128 ~ 127)
Integer i1 = 100; // Integer.valueOf(100)
Integer i2 = 100;
System.out.println("i1 == i2 (100): " + (i1 == i2)); // true(缓存命中)
// 超出缓存范围
Integer i3 = 200;
Integer i4 = 200;
System.out.println("i3 == i4 (200): " + (i3 == i4)); // false(新对象)
// new 强制创建新对象,不经过缓存
Integer i5 = new Integer(100);
System.out.println("i1 == i5 (new): " + (i1 == i5)); // false
// 正确比较方式
System.out.println("正确比较: " + i3.equals(i4)); // true
// 缓存范围可配置(通过 JVM 参数 -XX:AutoBoxCacheMax=1000)
// Long 也有类似缓存,但范围固定 -128~127
Long l1 = 127L;
Long l2 = 127L;
System.out.println("Long 127 缓存: " + (l1 == l2)); // true
}
}常用方法:parseXxx / valueOf / xxxValue
public class WrapperMethodsExample {
public static void main(String[] args) {
// ----- parseXxx:字符串 → 基本类型 -----
int intVal = Integer.parseInt("42");
double dblVal = Double.parseDouble("3.14");
boolean boolVal = Boolean.parseBoolean("true");
System.out.println("parseInt: " + intVal); // 42
System.out.println("parseDouble: " + dblVal); // 3.14
// 异常处理
try {
Integer.parseInt("abc");
} catch (NumberFormatException e) {
System.out.println("parseInt 无法解析非数字字符串: " + e.getMessage());
}
// ----- valueOf:字符串/基本类型 → 包装类 -----
// 利用缓存,推荐优先使用 valueOf 而非 new
Integer a = Integer.valueOf(42);
Integer b = Integer.valueOf("42");
System.out.println("valueOf 相同值: " + a.equals(b)); // true
// ----- xxxValue:包装类 → 基本类型 -----
Integer n = 300;
byte byteVal = n.byteValue();
short shortVal = n.shortValue();
int intVal2 = n.intValue();
long longVal = n.longValue();
float floatVal = n.floatValue();
double doubleVal = n.doubleValue();
System.out.println("intValue: " + intVal2); // 300
System.out.println("doubleValue: " + doubleVal); // 300.0
// ----- toXxxString —— 进制转换 -----
System.out.println("十进制 255 转二进制: " + Integer.toBinaryString(255)); // 11111111
System.out.println("十进制 255 转十六进制: " + Integer.toHexString(255)); // ff
System.out.println("十进制 255 转八进制: " + Integer.toOctalString(255)); // 377
// ----- MAX_VALUE / MIN_VALUE -----
System.out.println("int 最大值: " + Integer.MAX_VALUE); // 2147483647
System.out.println("int 最小值: " + Integer.MIN_VALUE); // -2147483648
}
}Math 类
java.lang.Math 提供了一组静态方法,用于执行基本数学运算。所有方法都是 static,直接通过类名调用。
public class MathExample {
public static void main(String[] args) {
// ----- 绝对值 -----
System.out.println("abs(-10): " + Math.abs(-10)); // 10
System.out.println("abs(-3.14): " + Math.abs(-3.14)); // 3.14
// ----- 最大值 / 最小值 -----
System.out.println("max(10, 20): " + Math.max(10, 20)); // 20
System.out.println("min(10, 20): " + Math.min(10, 20)); // 10
System.out.println("max(3.14, 2.71): " + Math.max(3.14, 2.71)); // 3.14
// ----- 幂运算 / 平方根 -----
System.out.println("pow(2, 10): " + Math.pow(2, 10)); // 1024.0
System.out.println("sqrt(144): " + Math.sqrt(144)); // 12.0
System.out.println("cbrt(27): " + Math.cbrt(27)); // 3.0(立方根)
// ----- 舍入 -----
double d = 3.7;
System.out.println("ceil(3.7): " + Math.ceil(d)); // 4.0 (向上取整)
System.out.println("floor(3.7): " + Math.floor(d)); // 3.0 (向下取整)
System.out.println("round(3.7): " + Math.round(d)); // 4 (四舍五入返回 long)
double d2 = 3.4;
System.out.println("round(3.4): " + Math.round(d2)); // 3
// ceil/floor/round 对负数的行为
System.out.println("ceil(-3.7): " + Math.ceil(-3.7)); // -3.0
System.out.println("floor(-3.7): " + Math.floor(-3.7)); // -4.0
System.out.println("round(-3.7): " + Math.round(-3.7)); // -4
// ----- 随机数 -----
// Math.random() 返回 [0.0, 1.0) 之间的 double
double rand = Math.random();
System.out.println("随机数 [0,1): " + rand);
// 生成 [min, max] 范围内的随机整数
int min = 1, max = 100;
int randInt = (int) (Math.random() * (max - min + 1)) + min;
System.out.println("随机整数 [1,100]: " + randInt);
// ----- 三角函数 -----
double radians = Math.toRadians(90); // 角度转弧度
System.out.println("sin(90°): " + Math.sin(radians)); // 1.0
System.out.println("cos(0°): " + Math.cos(0)); // 1.0
// ----- 常量 -----
System.out.println("π: " + Math.PI); // 3.141592653589793
System.out.println("e: " + Math.E); // 2.718281828459045
// ----- log / exp -----
System.out.println("ln(e): " + Math.log(Math.E)); // 1.0(自然对数)
System.out.println("log10(100): " + Math.log10(100)); // 2.0(常用对数)
System.out.println("exp(1): " + Math.exp(1)); // e(指数运算)
// ----- copySign / signum -----
System.out.println("copySign(5, -1): " + Math.copySign(5, -1)); // -5.0(复制符号)
System.out.println("signum(-42): " + Math.signum(-42)); // -1.0(符号函数)
}
}随机数生成
Java 提供了多种生成随机数的方式,适用于不同的场景。
Random 类
java.util.Random 基于线性同余算法生成伪随机数,线程安全但存在竞争时性能较差。
import java.util.Random;
public class RandomExample {
public static void main(String[] args) {
// 默认构造器——以当前纳秒数为种子
Random random = new Random();
// 可指定种子——相同种子产生相同序列(可重复场景)
Random seededRandom = new Random(42);
System.out.println("=== 基本随机数 ===");
System.out.println("随机 int: " + random.nextInt()); // 任意 int
System.out.println("随机 int [0, 100): " + random.nextInt(100)); // 0~99
System.out.println("随机 long: " + random.nextLong());
System.out.println("随机 float [0,1): " + random.nextFloat());
System.out.println("随机 double [0,1): " + random.nextDouble());
System.out.println("随机 boolean: " + random.nextBoolean());
// 生成指定范围的随机整数
int min = 10, max = 50;
int rangeRand = random.nextInt(max - min + 1) + min;
System.out.println("随机整数 [10,50]: " + rangeRand);
// 生成随机字节数组
byte[] bytes = new byte[8];
random.nextBytes(bytes);
System.out.print("随机字节: ");
for (byte b : bytes) {
System.out.printf("%02X ", b);
}
System.out.println();
// 相同种子产生相同序列(演示可复现性)
System.out.println("\n=== 相同种子 42 的随机序列 ===");
Random r1 = new Random(42);
Random r2 = new Random(42);
for (int i = 0; i < 3; i++) {
System.out.println("r1: " + r1.nextInt(100) + " r2: " + r2.nextInt(100));
}
// Guassian 分布(均值为 0,标准差为 1)
System.out.print("\n正态分布随机数: ");
for (int i = 0; i < 5; i++) {
System.out.printf("%.2f ", random.nextGaussian());
}
System.out.println();
}
}ThreadLocalRandom
java.util.concurrent.ThreadLocalRandom 是 JDK 7 引入的随机数生成器,专为多线程环境优化。每个线程维护独立的随机数生成器实例,避免了 Random 的竞争问题。
import java.util.concurrent.ThreadLocalRandom;
public class ThreadLocalRandomExample {
public static void main(String[] args) {
System.out.println("=== ThreadLocalRandom ===");
// 获取当前线程的随机数生成器
ThreadLocalRandom random = ThreadLocalRandom.current();
// 常用方法
System.out.println("随机 int: " + random.nextInt());
System.out.println("随机 int [0, 100): " + random.nextInt(100));
System.out.println("随机 long: " + random.nextLong());
System.out.println("随机 double: " + random.nextDouble());
// 指定范围的便捷方法(闭区间)
int val = ThreadLocalRandom.current().nextInt(10, 21); // [10, 20]
System.out.println("随机整数 [10,20]: " + val);
long longVal = ThreadLocalRandom.current().nextLong(1000, 9999); // [1000, 9999]
System.out.println("随机长整数 [1000,9999]: " + longVal);
// 多线程场景演示
System.out.println("\n=== 多线程场景 ===");
Runnable task = () -> {
String name = Thread.currentThread().getName();
// 每次使用时调用 current() 获取当前线程的实例
int r = ThreadLocalRandom.current().nextInt(1, 101);
System.out.println(name + " 生成: " + r);
};
for (int i = 0; i < 3; i++) {
new Thread(task, "线程-" + i).start();
}
// 性能对比提示
System.out.println("\n性能提示:在高并发场景下,ThreadLocalRandom 的性能远优于 Random");
System.out.println("因为 ThreadLocalRandom 避免了线程间的 CAS 竞争");
}
}UUID
java.util.UUID 用于生成全局唯一的标识符(Universally Unique Identifier)。UUID 是一个 128 位长的数字,通常以 36 个字符的十六进制字符串表示(含 4 个连字符)。
import java.util.UUID;
public class UUIDExample {
public static void main(String[] args) {
System.out.println("=== UUID 生成 ===");
// 随机生成 UUID(版本 4)——最常用
UUID uuid = UUID.randomUUID();
System.out.println("随机 UUID: " + uuid.toString());
System.out.println("长度: " + uuid.toString().length()); // 36
// 从字符串解析 UUID
UUID parsed = UUID.fromString("550e8400-e29b-41d4-a716-446655440000");
System.out.println("解析 UUID: " + parsed);
// 获取 UUID 的各个字段
System.out.println("\n=== UUID 字段 ===");
System.out.println("变体 (variant): " + uuid.variant());
System.out.println("版本 (version): " + uuid.version());
System.out.println("时间戳字段 (timestamp): " + uuid.timestamp()); // 仅版本 1 有效
System.out.println("时钟序列 (clockSequence): " + uuid.clockSequence()); // 仅版本 1 有效
// UUID 比较
UUID uuid1 = UUID.randomUUID();
UUID uuid2 = UUID.randomUUID();
System.out.println("\nuuid1: " + uuid1);
System.out.println("uuid2: " + uuid2);
System.out.println("equals: " + uuid1.equals(uuid2)); // false
System.out.println("compareTo: " + uuid1.compareTo(uuid2)); // -1 或 1
// 常用场景:作为数据库主键或文件名
String fileName = "report-" + UUID.randomUUID() + ".pdf";
System.out.println("\n生成文件名: " + fileName);
// 去掉连字符(32 位十六进制字符串)
String shortUuid = UUID.randomUUID().toString().replaceAll("-", "");
System.out.println("无连字符 UUID: " + shortUuid);
System.out.println("长度: " + shortUuid.length()); // 32
}
}日期时间 API
Java 的日期时间处理经历了从 JDK 1.0 的 Date/Calendar(旧 API)到 JDK 8 的 java.time 包(新 API)的重大演进。
旧 API:Date、Calendar、SimpleDateFormat
旧 API 存在诸多设计缺陷,但在遗留代码中仍广泛使用。
主要问题:
Date月份从 0 开始计(0 = 一月),容易出错Calendar可变的,线程不安全SimpleDateFormat线程不安全,多线程共享时会出现奇怪结果- API 设计不直观,日期计算繁琐
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
public class OldDateTimeExample {
public static void main(String[] args) throws Exception {
System.out.println("=== 旧 API(Date / Calendar / SimpleDateFormat)===");
// ----- Date -----
Date now = new Date();
System.out.println("当前时间: " + now);
System.out.println("时间戳 (毫秒): " + now.getTime());
// Date 的月份从 0 开始 —— 容易踩坑
@SuppressWarnings("deprecation")
Date birth = new Date(2024 - 1900, Calendar.JANUARY, 15); // 年要减去 1900
System.out.println("Date(2024, 1, 15): " + birth); // 1 月 15 日,但 1 代表二月!
// ----- Calendar -----
Calendar cal = Calendar.getInstance();
System.out.println("\nCalendar 当前时间: " + cal.getTime());
// 获取各个字段
int year = cal.get(Calendar.YEAR);
int month = cal.get(Calendar.MONTH) + 1; // 注意:月份要 +1
int day = cal.get(Calendar.DAY_OF_MONTH);
int hour = cal.get(Calendar.HOUR_OF_DAY);
int minute = cal.get(Calendar.MINUTE);
int second = cal.get(Calendar.SECOND);
System.out.printf("当前日期: %d年%d月%d日 %d:%d:%d\n", year, month, day, hour, minute, second);
// 日期运算
cal.add(Calendar.DAY_OF_MONTH, 7); // 加 7 天
System.out.println("一周后: " + cal.getTime());
cal.add(Calendar.MONTH, -1); // 减 1 个月
System.out.println("再减一个月: " + cal.getTime());
// ----- SimpleDateFormat(线程不安全)-----
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String formatted = sdf.format(new Date());
System.out.println("\n格式化当前时间: " + formatted);
// 解析
Date parsed = sdf.parse("2024-08-15 10:30:00");
System.out.println("解析结果: " + parsed);
// SimpleDateFormat 线程安全问题演示
System.out.println("\n=== SimpleDateFormat 线程安全问题 ===");
SimpleDateFormat unsafeFormat = new SimpleDateFormat("yyyy-MM-dd");
// 多线程共享 SimpleDateFormat 实例会导致各种异常
Runnable unsafeTask = () -> {
try {
for (int i = 0; i < 10; i++) {
// 共享的 unsafeFormat 不是线程安全的
Date d = unsafeFormat.parse("2024-01-15");
String s = unsafeFormat.format(d);
if (!"2024-01-15".equals(s)) {
System.out.println(" !! 错误结果: " + s);
}
}
} catch (Exception e) {
System.out.println(" !! 异常: " + e.getClass().getSimpleName() + " - " + e.getMessage());
}
};
Thread t1 = new Thread(unsafeTask, "线程A");
Thread t2 = new Thread(unsafeTask, "线程B");
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println("提示:避免共享 SimpleDateFormat,或使用 ThreadLocal 包装");
}
}新 API:java.time 包
JDK 8 引入了全新的日期时间 API(JSR 310),位于 java.time 包下,解决了旧 API 的所有问题。
核心优势:
- 不可变且线程安全 —— 所有类都是
final的 - 设计清晰 —— 日期、时间、日期时间三类分开
- 月份从 1 开始 —— 符合常识
- 支持时区、时间间隔等复杂计算
- 与大部分第三方库(如 MyBatis、Jackson)兼容
LocalDate / LocalTime / LocalDateTime
最常用的三个类,分别表示日期、时间、日期时间,均不包含时区信息。
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.Month;
import java.time.temporal.ChronoUnit;
public class LocalDateTimeExample {
public static void main(String[] args) {
System.out.println("=== LocalDate ===");
// 获取当前日期
LocalDate today = LocalDate.now();
System.out.println("当前日期: " + today);
// 创建指定日期
LocalDate date1 = LocalDate.of(2024, Month.MARCH, 15);
LocalDate date2 = LocalDate.of(2024, 3, 15); // 月份直接用数字(从 1 开始)
System.out.println("指定日期: " + date1);
// 解析字符串
LocalDate parsed = LocalDate.parse("2024-08-15"); // 标准格式 yyyy-MM-dd
System.out.println("解析日期: " + parsed);
// 日期运算(返回新对象,原对象不变)
LocalDate tomorrow = today.plusDays(1);
LocalDate nextWeek = today.plusWeeks(1);
LocalDate nextMonth = today.plusMonths(1);
LocalDate lastYear = today.minusYears(1);
System.out.println("明天: " + tomorrow);
System.out.println("下个月: " + nextMonth);
// 获取日期字段
System.out.println("年: " + today.getYear());
System.out.println("月: " + today.getMonthValue()); // 1~12
System.out.println("日: " + today.getDayOfMonth());
System.out.println("星期: " + today.getDayOfWeek()); // MONDAY, TUESDAY, ...
System.out.println("一年中的第几天: " + today.getDayOfYear());
// 日期比较
System.out.println("isBefore: " + date1.isBefore(today));
System.out.println("isAfter: " + date1.isAfter(today));
System.out.println("isEqual: " + date1.isEqual(today));
System.out.println("相差天数: " + ChronoUnit.DAYS.between(date1, today));
// 判断闰年
System.out.println("2024 是闰年吗: " + LocalDate.of(2024, 1, 1).isLeapYear());
System.out.println("\n=== LocalTime ===");
LocalTime now = LocalTime.now();
LocalTime time1 = LocalTime.of(14, 30, 0); // 14:30:00
LocalTime time2 = LocalTime.parse("14:30:00"); // 字符串解析
System.out.println("当前时间: " + now);
System.out.println("指定时间: " + time1);
System.out.println("小时: " + now.getHour());
System.out.println("分钟: " + now.getMinute());
System.out.println("加 2 小时: " + now.plusHours(2));
System.out.println("减 30 分钟: " + now.minusMinutes(30));
System.out.println("\n=== LocalDateTime ===");
LocalDateTime ldt1 = LocalDateTime.now();
LocalDateTime ldt2 = LocalDateTime.of(2024, 12, 25, 10, 0, 0);
LocalDateTime ldt3 = LocalDateTime.parse("2024-12-25T10:00:00");
System.out.println("当前日期时间: " + ldt1);
System.out.println("圣诞: " + ldt2);
// LocalDateTime ↔ LocalDate + LocalTime
LocalDate datePart = ldt1.toLocalDate();
LocalTime timePart = ldt1.toLocalTime();
System.out.println("日期部分: " + datePart);
System.out.println("时间部分: " + timePart);
// 组合
LocalDateTime combined = LocalDateTime.of(today, now);
System.out.println("组合: " + combined);
}
}Instant
Instant 表示时间线上的一个瞬时点,精确到纳秒,通常用于记录时间戳。
import java.time.Instant;
import java.time.Duration;
public class InstantExample {
public static void main(String[] args) {
System.out.println("=== Instant ===");
// 当前时间戳(UTC)
Instant now = Instant.now();
System.out.println("当前 Instant: " + now);
// 获取时间戳
long epochSecond = now.getEpochSecond(); // 秒
long epochMilli = now.toEpochMilli(); // 毫秒
System.out.println("纪元秒: " + epochSecond);
System.out.println("纪元毫秒: " + epochMilli);
System.out.println("System.currentTimeMillis: " + System.currentTimeMillis());
// 从时间戳创建
Instant fromSeconds = Instant.ofEpochSecond(1_700_000_000L);
Instant fromMillis = Instant.ofEpochMilli(1_700_000_000_000L);
System.out.println("从秒创建: " + fromSeconds);
System.out.println("从毫秒创建: " + fromMillis);
// 运算
Instant later = now.plusSeconds(3600); // 加 1 小时
Instant earlier = now.minusMillis(5000); // 减 5 秒
System.out.println("1 小时后: " + later);
// 比较
System.out.println("isBefore: " + earlier.isBefore(now)); // true
System.out.println("isAfter: " + later.isAfter(now)); // true
// Duration 计算时间差
Duration duration = Duration.between(earlier, now);
System.out.println("时间差秒数: " + duration.getSeconds());
System.out.println("时间差毫秒: " + duration.toMillis());
// 适合用 Instant 的场景:记录 API 请求时间、日志时间戳等
long requestStart = Instant.now().toEpochMilli();
// ... 模拟处理耗时 ...
try { Thread.sleep(100); } catch (InterruptedException e) {}
long requestEnd = Instant.now().toEpochMilli();
System.out.println("请求耗时: " + (requestEnd - requestStart) + " ms");
}
}Duration 与 Period
Duration 表示时间间隔(秒/纳秒级),Period 表示日期间隔(年/月/日级)。
import java.time.Duration;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.Period;
public class DurationPeriodExample {
public static void main(String[] args) {
System.out.println("=== Duration(时间间隔)===");
// 创建 Duration
Duration d1 = Duration.ofHours(2);
Duration d2 = Duration.ofMinutes(30);
Duration d3 = Duration.ofSeconds(65);
Duration d4 = Duration.between(LocalTime.of(9, 0), LocalTime.of(17, 30));
System.out.println("2 小时: " + d1);
System.out.println("30 分钟: " + d2.toMinutes() + " 分钟");
System.out.println("65 秒: " + d3.getSeconds() + " 秒");
System.out.println("9:00~17:30: " + d4.toHours() + " 小时 " + d4.toMinutesPart() + " 分钟");
// Duration 的加减
Duration total = d1.plus(d2).plus(d3);
System.out.println("总共: " + total.toMinutes() + " 分钟");
// 判断正负
Duration negative = Duration.ofMinutes(-10);
System.out.println("isNegative: " + negative.isNegative()); // true
System.out.println("abs: " + negative.abs()); // PT10M
System.out.println("\n=== Period(日期间隔)===");
LocalDate start = LocalDate.of(2024, 1, 1);
LocalDate end = LocalDate.of(2024, 12, 25);
Period period = Period.between(start, end);
System.out.println("从 " + start + " 到 " + end);
System.out.println("间隔: " + period.getYears() + " 年 "
+ period.getMonths() + " 月 " + period.getDays() + " 天");
// 计算总天数(用 ChronoUnit)
long totalDays = java.time.temporal.ChronoUnit.DAYS.between(start, end);
System.out.println("总天数: " + totalDays);
// 创建 Period
Period p1 = Period.ofDays(30);
Period p2 = Period.ofMonths(3);
LocalDate futureDate = LocalDate.now().plus(p1).plus(p2);
System.out.println("30 天 + 3 个月后: " + futureDate);
}
}DateTimeFormatter
DateTimeFormatter 是线程安全的日期时间格式化器,替代了旧的 SimpleDateFormat。
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.time.format.FormatStyle;
import java.util.Locale;
public class DateTimeFormatterExample {
public static void main(String[] args) {
System.out.println("=== DateTimeFormatter ===");
LocalDateTime now = LocalDateTime.now();
// ----- 预定义格式器 -----
System.out.println("ISO_LOCAL_DATE: " + now.format(DateTimeFormatter.ISO_LOCAL_DATE));
System.out.println("ISO_LOCAL_TIME: " + now.format(DateTimeFormatter.ISO_LOCAL_TIME));
System.out.println("ISO_LOCAL_DATE_TIME: " + now.format(DateTimeFormatter.ISO_LOCAL_DATE_TIME));
// ----- 本地化格式 -----
System.out.println("\n本地化格式:");
System.out.println("FULL: " + now.format(DateTimeFormatter.ofLocalizedDateTime(FormatStyle.FULL)));
System.out.println("LONG: " + now.format(DateTimeFormatter.ofLocalizedDateTime(FormatStyle.LONG)));
System.out.println("MEDIUM: " + now.format(DateTimeFormatter.ofLocalizedDateTime(FormatStyle.MEDIUM)));
System.out.println("SHORT: " + now.format(DateTimeFormatter.ofLocalizedDateTime(FormatStyle.SHORT)));
// ----- 自定义格式 -----
System.out.println("\n自定义格式:");
DateTimeFormatter custom1 = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
System.out.println("yyyy-MM-dd HH:mm:ss: " + now.format(custom1));
DateTimeFormatter custom2 = DateTimeFormatter.ofPattern("yyyy年MM月dd日 EEEE HH:mm");
System.out.println("中文格式: " + now.format(custom2));
DateTimeFormatter custom3 = DateTimeFormatter.ofPattern("yyyy/MM/dd");
System.out.println("yyyy/MM/dd: " + now.format(custom3));
DateTimeFormatter custom4 = DateTimeFormatter.ofPattern("QQQ yyyy", Locale.ENGLISH);
System.out.println("季度格式: " + now.format(custom4)); // Q1 2024
// ----- 解析字符串 -----
System.out.println("\n字符串解析:");
String dateStr = "2024-08-15 14:30:00";
DateTimeFormatter parser = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
LocalDateTime parsed = LocalDateTime.parse(dateStr, parser);
System.out.println("解析结果: " + parsed);
// 只解析日期部分
LocalDate dateOnly = LocalDate.parse("2024/12/25", DateTimeFormatter.ofPattern("yyyy/MM/dd"));
System.out.println("日期部分: " + dateOnly);
// ----- 格式模式符号说明 -----
System.out.println("\n常用模式符号:");
System.out.println(" yyyy - 四位年份 yy - 两位年份");
System.out.println(" MM - 两位月份 M - 不补零月份");
System.out.println(" dd - 两位日期 d - 不补零日期");
System.out.println(" HH - 24小时制 hh - 12小时制");
System.out.println(" mm - 分钟 ss - 秒");
System.out.println(" EEEE - 完整星期名 EEE - 缩写星期名");
System.out.println(" a - AM/PM Q - 季度");
}
}小结
本文详细介绍了 Java 中常用的核心类库:
| 类别 | 核心要点 | 推荐使用 |
|---|---|---|
| String | 不可变、常量池、丰富 API | 字符串处理首选;拼接多用 StringBuilder |
| StringBuilder/StringBuffer | 可变字符串、线程安全对比 | 单线程用 StringBuilder,多线程用 StringBuffer |
| 包装类 | 自动装箱拆箱、Integer 缓存(-128~127) | 优先用 valueOf 而非 new |
| Math | 数学计算静态方法 | 注意 ceil/floor/round 对符号的行为 |
| Random / ThreadLocalRandom | 伪随机数、多线程性能 | 并发场景用 ThreadLocalRandom |
| UUID | 128 位全局唯一标识 | 数据库主键、文件名等场景 |
| 旧日期 API | Date/Calendar/SimpleDateFormat | 遗留代码维护需要了解,新代码勿用 |
| 新日期 API | LocalDate/LocalTime/LocalDateTime、Instant、Duration/Period、DateTimeFormatter | 新代码的首选,不可变且线程安全 |
关键原则:
- 使用包装类时注意
null值和拆箱陷阱 - 多线程环境下避免共享
SimpleDateFormat,改用DateTimeFormatter - JDK 8 及以上项目优先使用
java.time包处理日期时间 - 字符串拼接次数较多时(如循环中),务必使用
StringBuilder或StringBuffer