引言
这篇文章集合了一些我在日常 Java 开发中真实遇到的痛点 —— 以及我是如何将它们重写得更智能、更精炼、更整洁的。不玩什么花里胡 secretário的技巧,就是实实在在地把代码写得更好。
1. 手动进行对象映射
- • 一般写法 :
// 从 User 实体手动映射到 UserDTO UserDTO dto = new UserDTO(); dto.setId(user.getId()); dto.setEmail(user.getEmail()); dto.setActive(user.isActive()); dto.setRole(user.getRole().name()); // 假设 Role 是枚举
- • 高级写法:
import org.mapstruct.Mapper; @Mapper(componentModel = "spring") // 声明为 MapStruct Mapper,并集成 Spring public interface UserMapper { UserDTO toDto(User user); // 定义映射方法 // MapStruct 会在编译时自动生成这个接口的实现 } // 使用时: UserDTO dto = userMapper.toDto(user);
-
• 为啥这样更高级? 你只需要编写接口,MapStruct 会在编译时为你生成具体的实现代码。这意味着零运行时开销,不需要反射,也告别了手动映射可能带来的维护地狱。
-
• 拓展阅读:什么是 MapStruct?为何每个 Java 开发者都在谈论它
-
**2. 链式空指针检查和条件判断 **
- • 一般写法 :
// 一层又一层的 null 检查,非常丑陋且容易出错 if (order != null && order.getCustomer() != null && order.getCustomer().getEmail() != null) { sendEmail(order.getCustomer().getEmail()); }
- • 高级写法 :
import java.util.Optional; // 使用 Optional 进行链式调用,优雅处理潜在的 null 值 Optional.ofNullable(order) // order 可能为 null .map(Order::getCustomer) // 如果 order 不为 null,则获取 customer .map(Customer::getEmail) // 如果 customer 不为 null,则获取 email .ifPresent(this::sendEmail); // 如果 email 不为 null,则执行 sendEmail
-
• 为啥这样更高级? 再也不会掉进
NullPointerException
的坑了!代码可读性强,而且即使链条中的某个环节是null
,程序也不会直接崩溃。
**3. 编写“傻瓜式”循环 **
- • 一般写法 :
// 手动遍历列表并将每个名字转换为大写 List<String> upper = new ArrayList<>(); for (String name : names) { upper.add(name.toUpperCase()); }
- • 高级写法:
// 使用 Stream API 进行函数式转换 List<String> upper = names.stream() .map(String::toUpperCase) // 映射操作:每个元素转大写 .toList(); // 收集结果为 List (Java 16+) // .collect(Collectors.toList()); // Java 8 - 15 写法
-
• 为啥这样更高级? 代码更少,表达力更强。函数式风格更容易理解其背后的逻辑 —— 而且 Stream 的转换操作可以轻松地进行链式调用。
4. 手动构建 JSON 响应
- • 一般写法 :
// 手动用 Map 构建 JSON 结构 Map<String, Object> map = new HashMap<>(); map.put("id", user.getId()); map.put("email", user.getEmail()); map.put("active", user.isActive()); // 如果字段很多,这里会非常冗长 return new ResponseEntity<>(map, HttpStatus.OK);
- • 高级写法 :
或者,如果 Controller 方法的返回类型就是// 直接返回 DTO 对象,让 Jackson (Spring Boot 默认的 JSON 库) 自动序列化 return ResponseEntity.ok(new UserDTO(user)); // 假设 UserDTO 有合适的构造函数或用 UserMapper 转换
UserDTO
,可以直接返回 DTO 实例,Spring MVC 和 Jackson 会帮你处理剩下的序列化工作。 -
• 为啥这样更高级? 你很可能已经在项目中间接或直接地使用了 Jackson。让它去干这些序列化和反序列化的脏活累活吧,别自己费劲了。
**5. 充满 Getter 和 Setter 的样板式 DTO **
- • 一般写法 :
public class ProductDTO { private String name; private BigDecimal price; // 大量的 Getter 和 Setter 方法... public String getName() { return name; } public void setName(String name) { this.name = name; } public BigDecimal getPrice() { return price; } public void setPrice(BigDecimal price) { this.price = price; } // 可能还有 equals(), hashCode(), toString()... }
- • 高级写法 :
// 使用 record 定义不可变的数据传输对象 public record ProductDTO(String name, BigDecimal price) {} // 编译器会自动生成构造函数、getter、equals()、hashCode() 和 toString()
-
• 为啥这样更高级? Java
record
非常适合用作不可变的数据传输对象。代码更简洁、更安全,而且完全没有样板代码的烦恼。
**6. 过度使用 if-else
和 switch
语句 **
- • 一般写法 :
// 一长串的 if-else if if ("ADMIN".equals(role)) { return "拥有所有权限"; } else if ("USER".equals(role)) { return "拥有有限权限"; } else { return "无权限"; }
- • 高级写法 :
// 使用 switch 表达式,更简洁、更安全 return switch (role) { case "ADMIN" -> "拥有所有权限"; case "USER" -> "拥有有限权限"; default -> "无权限"; }; // 注意这里是表达式,可以返回值,末尾可能有分号
-
• 为啥这样更高级?
switch
表达式更简洁、更不容易出错(比如忘记break
导致的穿透问题),并且不需要break
语句。
**7. 仅仅为了关闭资源而编写 try-catch-finally
**
- • 一般写法 :
Connection conn = null; Statement stmt = null; try { conn = dataSource.getConnection(); stmt = conn.createStatement(); // ... 使用 conn 和 stmt ... } catch (SQLException e) { // 处理异常 } finally { // 繁琐的资源关闭逻辑,还可能嵌套 try-catch if (stmt != null) { try { stmt.close(); } catch (SQLException e) { /* log or ignore */ } } if (conn != null) { try { conn.close(); } catch (SQLException e) { /* log or ignore */ } } }
- • 高级写法 :
// 使用 try-with-resources 语句,资源会自动关闭 try (Connection conn = dataSource.getConnection(); Statement stmt = conn.createStatement()) { // 实现了 AutoCloseable 接口的资源都可以放这里 // ... 使用 conn 和 stmt ... } catch (SQLException e) { // 处理异常 } // conn 和 stmt 会在这里被自动关闭,即使发生异常
-
• 为啥这样更高级? Try-with-resources 结构能自动处理资源的清理工作,即使在发生异常的情况下也能保证资源被正确关闭,代码也更简洁。
8. 重复发明工具函数
- • 一般写法 :
// 自己写一个判断字符串是否为 null 或空的工具方法 public static boolean isNullOrEmpty(String s) { return s == null || s.trim().isEmpty(); }
- • 高级写法:
// 使用 Apache Commons Lang 库中的 StringUtils.isBlank() // 它能处理 null、空字符串以及只包含空白字符的情况 boolean isEmpty = StringUtils.isBlank(s); // 或者 Guava 的 Strings.isNullOrEmpty(s)
-
• 为啥这样更高级? 多多利用那些稳定、开源的库(如 Apache Commons, Guava)。停止编写那些已经被成千上万次编写和测试过的代码,不要重复造轮子。
**9. 自己动手实现对象缓存 **
- • 一般写法:
// 手动用 HashMap 实现简单的缓存 Map<String, User> userCache = new HashMap<>(); public User getUserById(String id) { if (userCache.containsKey(id)) { return userCache.get(id); } else { User user = fetchUserFromDatabase(id); // 假设这是从数据库获取用户的方法 if (user != null) { userCache.put(id, user); } return user; } }
- • 高级写法 :
对于原文示例,如果// 使用 Java 8 Map 的 computeIfAbsent 方法,更简洁且线程安全 (对于 ConcurrentMap) // private ConcurrentMap<String, User> userCache = new ConcurrentHashMap<>(); // return userCache.computeIfAbsent(id, key -> fetchUserFromDatabase(key)); // 或者使用更专业的缓存库如 Caffeine / Guava Cache // LoadingCache<String, User> userCache = Caffeine.newBuilder() // .maximumSize(100) // .expireAfterWrite(10, TimeUnit.MINUTES) // .build(key -> fetchUserFromDatabase(key)); // return userCache.get(id);
cache
是ConcurrentMap
:// 假设 cache 是一个 ConcurrentMap<String, User> return cache.computeIfAbsent(id, this::fetchUser); // 或者 key -> fetchUser(key)
-
• 为啥这样更高级?
computeIfAbsent
更简洁,并且对于ConcurrentMap
来说是原子操作,因此线程安全。而使用像 Caffeine 或 Guava Cache 这样的专业缓存库,你甚至不用操心缓存大小、过期策略、淘汰策略等复杂问题。
很高兴你觉得这些内容有用!让我们继续探讨更多真实场景下的智能 Java 代码片段 —— 这些例子几乎在每个项目中都会遇到,但往往被用“一般写法”编写。这些例子都源于实际应用,而非空谈理论。
10. 手动解析和格式化日期
- • 一般写法 :
// SimpleDateFormat 是可变的,且线程不安全 SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd"); try { Date date = sdf.parse(inputString); // 解析字符串为 Date String outputString = sdf.format(date); // 格式化 Date为字符串 } catch (ParseException e) { e.printStackTrace(); }
- • 高级写法:
import java.time.LocalDate; import java.time.format.DateTimeFormatter; // 解析字符串 (假设 input 是 "yyyy-MM-dd" 格式,这是 LocalDate 的默认格式) LocalDate date = LocalDate.parse(inputString); // 格式化为字符串 (默认也是 "yyyy-MM-dd") String output = date.toString(); // 或者使用自定义格式 DateTimeFormatter customFormatter = DateTimeFormatter.ofPattern("dd/MM/yyyy"); String formattedDate = LocalDate.now().format(customFormatter);
-
• 为啥这样更高级? 旧的
java.util.Date
和SimpleDateFormat
类是可变的、易出错,并且不是线程安全的。而java.time
包(如LocalDate
,LocalDateTime
,DateTimeFormatter
)是现代、整洁、安全且线程安全的选择。
**11. 手动过滤和排序列表 **
- • 一般写法 :
// 手动循环过滤 List<User> activeUsers = new ArrayList<>(); for (User user : allUsers) { if (user.isActive()) { activeUsers.add(user); } } // 手动排序 Collections.sort(activeUsers, new Comparator<User>() { @Override public int compare(User u1, User u2) { return u1.getName().compareTo(u2.getName()); } });
- • 高级写法:
import java.util.Comparator; import java.util.stream.Collectors; // 使用 Stream API 进行链式操作 List<User> result = allUsers.stream() .filter(User::isActive) // 过滤:只保留 active 的用户 .sorted(Comparator.comparing(User::getName)) // 排序:按名字排序 .toList(); // 收集结果 (Java 16+) // .collect(Collectors.toList()); // Java 8 - 15
-
• 为啥这样更高级? Stream API 使得数据转换操作更具可读性且易于组合 —— 特别是当你需要链式地进行多个过滤(filter)、映射(map)和收集(collect)操作时。
12. 仅仅为了静态常量而编写嵌套类 (Writing Nested Classes Just for Static Constants)
- • 一般写法 (The Normal Way):
// 用一个类专门存放常量字符串 public class AppConstants { public static final String ROLE_ADMIN = "ADMIN"; public static final String ROLE_USER = "USER"; // ... 可能还有更多 } // 使用时:if (userRole.equals(AppConstants.ROLE_ADMIN))
-
• 高级写法 (The Advanced Way - 使用枚举
enum
或 Java 17+ 的sealed interface
+record
):- • 使用枚举 (Enum - 通常是最佳选择):
public enum Role { ADMIN, USER // 枚举实例本身就是类型安全的常量 } // 使用时:if (userRole == Role.ADMIN)
- • 使用
sealed interface
和record
(Java 17+,适用于更复杂的常量结构):// 定义一个密封接口 public sealed interface Role permits Role.Admin, Role.User { // 定义实现了该接口的 record 作为具体常量类型 record Admin() implements Role {} record User() implements Role {} } // 使用时:if (userRole instanceof Role.Admin)
- • 使用枚举 (Enum - 通常是最佳选择):
-
• 为啥这样更高级? 枚举 (Enum) 提供了类型安全(你不能把一个不相关的字符串赋给
Role
类型的变量)、清晰的序列化行为,并且能很好地配合现代switch
表达式使用。普通的字符串常量可没这些好处。sealed interface
+record
提供了更强的模式匹配能力。
13. 手动对数字列表求和或求平均值 (Summing or Averaging a List of Numbers)
- • 一般写法 (The Normal Way):
// 手动循环累加 int totalAmount = 0; for (Order order : orders) { totalAmount += order.getAmount(); // 假设 getAmount() 返回 int }
- • 高级写法 (The Advanced Way - 使用 Stream API):
// 使用 Stream API 的 sum() 方法 int total = orders.stream() .mapToInt(Order::getAmount) // 将 Order 流映射为 IntStream .sum(); //直接求和 // 或者求平均值 double averageAmount = orders.stream() .mapToDouble(Order::getAmount) // 映射为 DoubleStream .average() // 计算平均值,返回 OptionalDouble .orElse(0.0); // 如果列表为空,则返回 0.0
-
• 为啥这样更高级? 不需要显式的累加变量,减少了可变状态,代码更简洁。Stream API 提供了内置的聚合函数,如
sum()
,average()
,count()
,max()
,min()
等。
14. 用策略模式或多态替换嵌套的 If 语句 (Replacing Nested Ifs With Strategy or Polymorphism)
- • 一般写法 (The Normal Way):
// 一长串的 if-else if 根据类型执行不同操作 if ("paypal".equals(paymentType)) { processPaypalPayment(); } else if ("credit_card".equals(paymentType)) { processCreditCardPayment(); } else if ("bank_transfer".equals(paymentType)) { processBankTransferPayment(); } else { // 默认处理或报错 }
- • 高级写法 (The Advanced Way - 使用 Map 存储策略实现):
(这实际上是策略模式的一种简单实现。在 Spring 中,通常会将策略实现为 Bean,并通过 Bean Name 或其他标识从应用上下文中获取。)// 假设 PaymentStrategy 是一个接口,PaypalStrategy 等是其实现类 // interface PaymentStrategy { void process(); } // class PaypalStrategy implements PaymentStrategy { @Override public void process() {/* ... */} } // ... // 使用 Map 来存储和分发策略 Map<String, PaymentStrategy> paymentStrategies = Map.of( "paypal", new PaypalStrategy(), "credit_card", new CreditCardStrategy(), "bank_transfer", new BankTransferStrategy() ); // 获取并执行策略,如果找不到则使用默认策略 PaymentStrategy strategy = paymentStrategies.getOrDefault(paymentType, new DefaultPaymentStrategy()); strategy.process();
-
• 为啥这样更高级? 避免了冗长的
if-else
或switch
代码块。添加新的支付策略时,通常只需要增加一个新的策略实现类并注册到 Map(或 Spring 容器)中,而不需要修改现有的分发逻辑(符合开闭原则)。
15. 将 JSON 反序列化为对象 (Deserializing JSON to Objects)
- • 一般写法 (The Normal Way):
这种写法本身没大问题,但问题在于,如果项目中到处都这样写,并且每个地方都单独处理// ObjectMapper mapper = new ObjectMapper(); // 每次都 new 一个?或者到处注入? // User user = mapper.readValue(jsonString, User.class);
IOException
,代码会变得重复且难以管理。 - • 高级写法 (The Advanced Way - 封装成工具方法):
首先,封装一次:
然后复用这个工具方法:import com.fasterxml.jackson.databind.ObjectMapper; import java.io.IOException; public class JsonUtil { // ObjectMapper 实例是线程安全的,可以静态共享 private static final ObjectMapper MAPPER = new ObjectMapper(); // 可以在这里对 MAPPER 进行全局配置,例如日期格式、忽略未知属性等 public static <T> T fromJson(String json, Class<T> clazz) { try { return MAPPER.readValue(json, clazz); } catch (IOException e) { // 将受检异常转换为运行时异常,或者抛出自定义的业务异常 throw new RuntimeException("JSON 解析错误: " + e.getMessage(), e); } } public static String toJson(Object object) { try { return MAPPER.writeValueAsString(object); } catch (IOException e) { throw new RuntimeException("JSON 序列化错误: " + e.getMessage(), e); } } }
// User user = JsonUtil.fromJson(jsonString, User.class);
-
• 为啥这样更高级? 将 JSON (反)序列化逻辑、错误处理以及
ObjectMapper
的配置集中管理。代码更符合 DRY (Don't Repeat Yourself) 原则,也更整洁。
16. 避免冗余的线程管理 (Avoiding Verbose Thread Management)
- • 一般写法 (The Normal Way - 手动创建和管理线程池):
// ExecutorService executor = Executors.newFixedThreadPool(10); // executor.submit(() -> { // // 执行耗时任务... // doWork(); // }); // // 还需要考虑 executor 的关闭等问题
-
• 高级写法 (The Advanced Way - 在 Spring Boot 中使用
@Async
或CompletableFuture
):- • 使用 Spring Boot 的
@Async
(需要在配置类启用@EnableAsync
):import org.springframework.scheduling.annotation.Async; import org.springframework.stereotype.Service; import java.util.concurrent.CompletableFuture; @Service public class MyAsyncService { @Async // 标记此方法为异步执行 (由 Spring 管理的线程池执行) public CompletableFuture<String> doWorkAsync() { // 执行耗时任务... try { Thread.sleep(1000); } catch (InterruptedException e) {} return CompletableFuture.completedFuture("异步任务完成"); } }
- • 或者直接使用
CompletableFuture
(Java 8+):// CompletableFuture.runAsync(() -> doWork()); // 如果没有返回值 // CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> { // // 执行耗时任务并返回结果... // return "结果"; // });
- • 使用 Spring Boot 的
-
• 为啥这样更高级? Spring Boot 的
@Async
提供了声明式的异步处理,能利用其管理的内置线程池,通常比手动管理ExecutorService
更简单、更易于配置,也更利于扩展。CompletableFuture
则是 Java 现代并发编程的强大工具。
✨ 总结:更高级的 Java 写法,其实就是现代 Java 的标准写法
这些并非什么“奇技淫巧”,它们是现代 Java 开发中**“更好的默认做法”**。
Java 已经进化良多 —— 如果你还固守着 5-10 年前学习 Java 时的方式,那你很可能写了太多不必要的代码,干了太多不必要的活儿,效率也大打折扣。