1. Java时间与日期处理实战
Java 8之前的日期时间API存在诸多设计缺陷,比如java.util.Date类既包含日期又包含时间,而且月份从0开始计数,这些反人类的设计让开发者苦不堪言。直到Java 8引入了全新的java.time包,才真正解决了这些问题。
1.1 LocalDate/LocalTime核心用法
LocalDate代表不带时区的日期,LocalTime代表不带日期的时间,这两个类是我们日常开发中最常用的基础类:
java复制// 获取当前日期
LocalDate today = LocalDate.now();
System.out.println("当前日期: " + today); // 输出格式:2023-08-20
// 构造指定日期
LocalDate nationalDay = LocalDate.of(2023, 10, 1);
System.out.println("国庆节: " + nationalDay);
// 获取时间分量
int year = today.getYear();
Month month = today.getMonth(); // 返回Month枚举
int day = today.getDayOfMonth();
DayOfWeek dow = today.getDayOfWeek(); // 返回星期枚举
// 时间计算
LocalDate nextWeek = today.plusWeeks(1);
LocalDate prevYear = today.minusYears(1);
注意:所有
java.time类都是不可变的(immutable),每次操作都会返回新的实例,因此线程安全。
1.2 时区处理与ZonedDateTime
当应用需要处理跨时区的时间时,必须使用ZonedDateTime:
java复制// 获取系统默认时区的当前时间
ZonedDateTime now = ZonedDateTime.now();
// 指定时区创建时间
ZoneId tokyoZone = ZoneId.of("Asia/Tokyo");
ZonedDateTime tokyoTime = ZonedDateTime.now(tokyoZone);
// 时区转换
ZonedDateTime newYorkTime = tokyoTime.withZoneSameInstant(ZoneId.of("America/New_York"));
System.out.println("东京时间: " + tokyoTime);
System.out.println("纽约时间: " + newYorkTime);
1.3 格式化与解析
DateTimeFormatter替代了老旧的SimpleDateFormat,它是线程安全的:
java复制// 预定义格式器
DateTimeFormatter isoFormatter = DateTimeFormatter.ISO_LOCAL_DATE_TIME;
DateTimeFormatter customFormatter = DateTimeFormatter.ofPattern("yyyy年MM月dd日 HH:mm:ss");
// 格式化
String formatted = LocalDateTime.now().format(customFormatter);
System.out.println(formatted); // 输出:2023年08月20日 14:30:45
// 解析
LocalDateTime parsed = LocalDateTime.parse("2023-08-20T14:30:45", isoFormatter);
1.4 时间差计算
Period用于计算日期间隔,Duration用于计算时间间隔:
java复制LocalDate startDate = LocalDate.of(2023, 1, 1);
LocalDate endDate = LocalDate.of(2023, 12, 31);
Period period = Period.between(startDate, endDate);
System.out.println("间隔: " + period.getYears() + "年"
+ period.getMonths() + "月"
+ period.getDays() + "天");
LocalTime startTime = LocalTime.of(9, 0);
LocalTime endTime = LocalTime.of(17, 30);
Duration duration = Duration.between(startTime, endTime);
System.out.println("工作时长: " + duration.toHours() + "小时");
2. 包装类深度解析
2.1 自动装箱与拆箱机制
Java的自动装箱(Autoboxing)和拆箱(Unboxing)是编译器提供的语法糖:
java复制// 自动装箱
Integer num = 100; // 编译器实际执行:Integer num = Integer.valueOf(100);
// 自动拆箱
int n = num; // 编译器实际执行:int n = num.intValue();
但要注意自动装箱的性能问题和一些陷阱:
java复制Integer a = 100;
Integer b = 100;
System.out.println(a == b); // true,因为-128到127有缓存
Integer c = 200;
Integer d = 200;
System.out.println(c == d); // false,超出缓存范围
2.2 数值类型转换
包装类提供了丰富的类型转换方法:
java复制// 字符串转数值
int num1 = Integer.parseInt("123");
double num2 = Double.parseDouble("3.14");
// 数值转字符串
String s1 = Integer.toString(123);
String s2 = Double.toString(3.14);
// 进制转换
String binary = Integer.toBinaryString(255); // "11111111"
String hex = Integer.toHexString(255); // "ff"
2.3 特殊数值处理
包装类提供了一些有用的常量和方法:
java复制// 最大值/最小值
System.out.println("int最大值: " + Integer.MAX_VALUE);
System.out.println("double最小值: " + Double.MIN_VALUE);
// 特殊值判断
Double d = Double.NaN;
System.out.println(Double.isNaN(d)); // true
// 无符号处理
int unsigned = Short.toUnsignedInt((short)-1);
System.out.println(unsigned); // 65535
3. 正则表达式实战指南
3.1 基础匹配模式
Java中使用java.util.regex包处理正则表达式,核心类是Pattern和Matcher:
java复制// 简单匹配
Pattern pattern = Pattern.compile("a*b");
Matcher matcher = pattern.matcher("aaaaab");
boolean matches = matcher.matches(); // true
// 查找匹配
String text = "The quick brown fox jumps over the lazy dog";
Pattern wordPattern = Pattern.compile("\\w+");
Matcher wordMatcher = wordPattern.matcher(text);
while (wordMatcher.find()) {
System.out.println("找到单词: " + wordMatcher.group());
}
3.2 常用元字符与量词
| 元字符 | 说明 | 示例 |
|---|---|---|
| . | 匹配任意单个字符 | a.c → abc |
| \d | 数字[0-9] | \d\d → 42 |
| \w | 单词字符[a-zA-Z0-9_] | \w+ → Hello |
| \s | 空白字符[\t\n\x0B\f\r] | \s+ → " " |
| ^ | 行开始 | ^Java → 匹配行首的Java |
| $ | 行结束 | end$ → 匹配行尾的end |
| * | 0次或多次 | a*b → b, aab |
| + | 1次或多次 | a+b → ab, aab |
| ? | 0次或1次 | a?b → b, ab |
| 恰好n次 | a{3} → aaa | |
| 至少n次 | a{2,} → aa, aaa | |
| n到m次 | a{2,4} → aa, aaa |
3.3 分组与捕获
分组是正则表达式中强大的功能:
java复制// 简单分组
Pattern groupPattern = Pattern.compile("(\\d{4})-(\\d{2})-(\\d{2})");
Matcher groupMatcher = groupPattern.matcher("2023-08-20");
if (groupMatcher.matches()) {
System.out.println("年: " + groupMatcher.group(1));
System.out.println("月: " + groupMatcher.group(2));
System.out.println("日: " + groupMatcher.group(3));
}
// 命名分组(Java 7+)
Pattern namedPattern = Pattern.compile("(?<year>\\d{4})-(?<month>\\d{2})-(?<day>\\d{2})");
Matcher namedMatcher = namedPattern.matcher("2023-08-20");
if (namedMatcher.matches()) {
System.out.println("年: " + namedMatcher.group("year"));
System.out.println("月: " + namedMatcher.group("month"));
}
3.4 实用正则表达式示例
- 邮箱验证:
java复制String emailRegex = "^[a-zA-Z0-9_+&*-]+(?:\\.[a-zA-Z0-9_+&*-]+)*@(?:[a-zA-Z0-9-]+\\.)+[a-zA-Z]{2,7}$";
Pattern emailPattern = Pattern.compile(emailRegex);
System.out.println(emailPattern.matcher("test@example.com").matches());
- 手机号验证(中国大陆):
java复制String phoneRegex = "^1[3-9]\\d{9}$";
Pattern phonePattern = Pattern.compile(phoneRegex);
System.out.println(phonePattern.matcher("13812345678").matches());
- 提取HTML标签内容:
java复制String html = "<div>Hello <b>World</b></div>";
Pattern tagPattern = Pattern.compile("<[^>]+>([^<]+)</[^>]+>");
Matcher tagMatcher = tagPattern.matcher(html);
while (tagMatcher.find()) {
System.out.println("内容: " + tagMatcher.group(1)); // 输出: World
}
4. Lambda表达式精要
4.1 函数式接口基础
Lambda表达式需要配合函数式接口使用,常见的函数式接口:
java复制// 自定义函数式接口
@FunctionalInterface
interface MyFunction {
String apply(String s);
}
// 使用Lambda实现
MyFunction toUpper = s -> s.toUpperCase();
System.out.println(toUpper.apply("hello")); // 输出: HELLO
JDK内置的常用函数式接口:
Predicate<T>: 接受一个参数,返回booleanFunction<T,R>: 接受一个参数,返回一个结果Consumer<T>: 接受一个参数,无返回值Supplier<T>: 无参数,返回一个结果BiFunction<T,U,R>: 接受两个参数,返回一个结果
4.2 方法引用与构造器引用
方法引用可以进一步简化Lambda表达式:
java复制// 静态方法引用
Function<String, Integer> parser = Integer::parseInt;
// 实例方法引用
Consumer<String> printer = System.out::println;
// 对象方法引用
Function<String, String> upper = String::toUpperCase;
// 构造器引用
Supplier<List<String>> listSupplier = ArrayList::new;
4.3 Stream API结合Lambda
Lambda表达式与Stream API是天作之合:
java复制List<String> names = Arrays.asList("Alice", "Bob", "Charlie", "David");
// 过滤和映射
List<String> result = names.stream()
.filter(name -> name.length() > 3)
.map(String::toUpperCase)
.collect(Collectors.toList());
// 分组
Map<Integer, List<String>> groupByLength = names.stream()
.collect(Collectors.groupingBy(String::length));
// 并行处理
long count = names.parallelStream()
.filter(name -> name.startsWith("A"))
.count();
4.4 实战注意事项
- 变量捕获:Lambda表达式只能捕获final或等效final的局部变量
java复制int num = 10; // 等效final
Runnable r = () -> System.out.println(num); // 合法
// num++; // 如果取消注释会导致编译错误
- this关键字:Lambda表达式中的this指向外围实例,而不是Lambda本身
java复制public class MyClass {
public void doSomething() {
Runnable r = () -> {
System.out.println(this.toString()); // 指向MyClass实例
};
r.run();
}
}
- 性能考虑:简单操作使用Lambda可能比传统方式慢,因为涉及更多方法调用
java复制// 传统方式可能更快
for (String name : names) {
if (name.length() > 3) {
System.out.println(name.toUpperCase());
}
}
// 但Stream API提供了更好的可读性和并行处理能力
names.stream()
.filter(name -> name.length() > 3)
.map(String::toUpperCase)
.forEach(System.out::println);
