001/* 002 * Copyright (c) 2023-2026, Agents-Flex (fuhai999@gmail.com). 003 * <p> 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * <p> 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * <p> 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016package com.agentsflex.core.util; 017 018import com.alibaba.fastjson2.JSON; 019import com.alibaba.fastjson2.JSONArray; 020import com.alibaba.fastjson2.JSONObject; 021import com.alibaba.fastjson2.JSONWriter; 022 023import java.lang.reflect.ParameterizedType; 024import java.lang.reflect.Type; 025import java.math.BigDecimal; 026import java.math.BigInteger; 027import java.time.Instant; 028import java.time.LocalDate; 029import java.time.LocalDateTime; 030import java.time.ZoneId; 031import java.time.format.DateTimeFormatter; 032import java.time.format.DateTimeFormatterBuilder; 033import java.time.format.DateTimeParseException; 034import java.time.temporal.ChronoField; 035import java.time.temporal.TemporalAccessor; 036import java.util.*; 037import java.util.concurrent.ConcurrentHashMap; 038import java.util.regex.Pattern; 039 040/** 041 * 高性能通用类型转换工具类 042 * <p> 043 * 转换策略优先级(按性能排序): 044 * <ol> 045 * <li>null 处理 & 类型匹配 → 直接返回</li> 046 * <li>基本类型 & 包装类型 → 直接转换(零 JSON 开销)</li> 047 * <li>String/Number/Boolean → 类型安全转换</li> 048 * <li>Date/Time/Enum → 特殊处理(多格式解析 + 缓存)</li> 049 * <li>复杂对象/泛型集合 → 走 JSON 转换(兜底)</li> 050 * </ol> 051 * <p> 052 * 线程安全:本类所有方法均为静态无状态,可安全并发调用 053 * 054 * @author Michael 055 * @since 1.0 056 */ 057public class TypeConverter { 058 059 // ========== 常量定义 ========== 060 061 /** 062 * 基本类型 → 包装类型映射 063 */ 064 private static final Map<Class<?>, Class<?>> PRIMITIVE_WRAPPER_MAP = new HashMap<>(8); 065 066 static { 067 PRIMITIVE_WRAPPER_MAP.put(boolean.class, Boolean.class); 068 PRIMITIVE_WRAPPER_MAP.put(byte.class, Byte.class); 069 PRIMITIVE_WRAPPER_MAP.put(char.class, Character.class); 070 PRIMITIVE_WRAPPER_MAP.put(short.class, Short.class); 071 PRIMITIVE_WRAPPER_MAP.put(int.class, Integer.class); 072 PRIMITIVE_WRAPPER_MAP.put(long.class, Long.class); 073 PRIMITIVE_WRAPPER_MAP.put(float.class, Float.class); 074 PRIMITIVE_WRAPPER_MAP.put(double.class, Double.class); 075 } 076 077 /** 078 * 数值类型集合 079 */ 080 private static final Set<Class<?>> NUMBER_TYPES = new HashSet<>(Arrays.asList( 081 Byte.class, Short.class, Integer.class, Long.class, 082 Float.class, Double.class, BigDecimal.class, BigInteger.class 083 )); 084 085 /** 086 * Boolean 真值集合(不区分大小写) 087 */ 088 private static final Set<String> TRUE_VALUES = new HashSet<>(Arrays.asList("true", "1", "yes", "y", "on")); 089 /** 090 * Boolean 假值集合(不区分大小写) 091 */ 092 private static final Set<String> FALSE_VALUES = new HashSet<>(Arrays.asList("false", "0", "no", "n", "off", "")); 093 094 /** 095 * 纯整数正则:可选符号 + 数字 096 */ 097 private static final Pattern PURE_INTEGER_PATTERN = Pattern.compile("^[+-]?\\d+$"); 098 099 /** 100 * 时间戳正则:10位(秒) 或 13位(毫秒) 101 */ 102 private static final Pattern TIMESTAMP_PATTERN = Pattern.compile("^\\d{10,13}$"); 103 104 /** 105 * 日期格式列表(按命中率降序排列,提升平均性能) 106 */ 107 private static final String[] DATE_PATTERNS = { 108 // 高频格式放前面 109 "yyyy-MM-dd HH:mm:ss", 110 "yyyy-MM-dd", 111 "yyyy-MM-dd HH:mm:ss.SSS", 112 "yyyy/MM/dd HH:mm:ss", 113 "yyyy/MM/dd", 114 "yyyy-MM-dd HH:mm", 115 "yyyy.MM.dd HH:mm:ss", 116 "yyyy.MM.dd", 117 "yyyy年MM月dd日", 118 "HH:mm:ss", 119 "HH:mm:ss.SSS", 120 "HH:mm", 121 "yyyy-MM-dd hh:mm:ss a", 122 "yyyy-MM-dd hh:mm:ss.SSS a", 123 "hh:mm:ss a", 124 "yyyyMMdd", 125 "yyyyMMddHHmmss", 126 "yyyyMMddHHmmssSSS", 127 "yyyy-MM-dd'T'HH:mm:ss", 128 "yyyy-MM-dd'T'HH:mm:ss.SSS", 129 "yyyy-MM-dd'T'HH:mm:ssXXX", 130 "yyyy-MM-dd'T'HH:mm:ss.SSSXXX", 131 "EEE, dd MMM yyyy HH:mm:ss zzz", 132 "EEE MMM dd HH:mm:ss zzz yyyy", 133 "dd-MM-yyyy HH:mm:ss", 134 "dd/MM/yyyy HH:mm:ss", 135 "MM-dd-yyyy HH:mm:ss", 136 "MM/dd/yyyy HH:mm:ss", 137 }; 138 139 /** 140 * DateTimeFormatter 缓存(Key: pattern) 141 */ 142 private static final Map<String, DateTimeFormatter> FORMATTER_CACHE = new ConcurrentHashMap<>(32); 143 144 145 /** 146 * JSON 序列化特性配置 147 */ 148 private static final JSONWriter.Feature[] SERIAL_FEATURES = { 149 JSONWriter.Feature.WriteMapNullValue, 150 }; 151 152 153 /** 154 * 通用类型转换入口 155 * 156 * @param value 源数据 157 * @param toType 目标类型(支持 Class / ParameterizedType) 158 * @param <T> 目标类型泛型 159 * @return 转换后的对象,失败时抛出 ConversionException 160 */ 161 @SuppressWarnings("unchecked") 162 public static <T> T convert(Object value, Type toType) { 163 if (value == null) { 164 return null; 165 } 166 167 // 1. 目标类型是 Class 且已匹配,直接返回(零开销) 168 if (toType instanceof Class) { 169 Class<?> targetClass = (Class<?>) toType; 170 if (targetClass.isInstance(value)) { 171 return (T) value; 172 } 173 return (T) convertSimpleType(value, targetClass); 174 } 175 176 // 2. 泛型类型走 JSON 转换(兜底) 177 return (T) convertComplexType(value, toType); 178 } 179 180 /** 181 * 带默认值的转换:转换失败或结果为 null 时返回默认值 182 * 183 * @param value 源数据 184 * @param toType 目标类型 185 * @param defaultValue 默认值 186 * @param <T> 目标类型泛型 187 * @return 转换结果或默认值 188 */ 189 @SuppressWarnings("unchecked") 190 public static <T> T convert(Object value, Type toType, T defaultValue) { 191 try { 192 T result = convert(value, toType); 193 return result != null ? result : defaultValue; 194 } catch (Exception e) { 195 return defaultValue; 196 } 197 } 198 199 /** 200 * 动态创建 ParameterizedType(用于运行时泛型) 201 * 202 * @param rawType 原始类型,如 List.class 203 * @param typeArgs 泛型参数,如 User.class 204 * @return ParameterizedType 实例 205 */ 206 public static Type createParameterizedType(Class<?> rawType, Type... typeArgs) { 207 return new ParameterizedType() { 208 @Override 209 public Type[] getActualTypeArguments() { 210 return typeArgs.clone(); 211 } 212 213 @Override 214 public Type getRawType() { 215 return rawType; 216 } 217 218 @Override 219 public Type getOwnerType() { 220 return null; 221 } 222 }; 223 } 224 225 // ========== 简单类型转换(零 JSON 开销)========== 226 227 private static Object convertSimpleType(Object value, Class<?> targetType) { 228 // 处理基本类型 → 包装类型 229 if (targetType.isPrimitive()) { 230 targetType = PRIMITIVE_WRAPPER_MAP.getOrDefault(targetType, targetType); 231 } 232 233 // String: 直接 toString 234 if (targetType == String.class) { 235 return value.toString(); 236 } 237 238 // 数值类型 239 if (NUMBER_TYPES.contains(targetType)) { 240 return convertNumber(value, targetType); 241 } 242 243 // Boolean 244 if (targetType == Boolean.class) { 245 return convertBoolean(value); 246 } 247 248 // Character 249 if (targetType == Character.class) { 250 return convertChar(value); 251 } 252 253 // Date / Time 254 if (targetType == Date.class) { 255 return convertToDate(value); 256 } 257 if (targetType == LocalDate.class) { 258 return convertToLocalDate(value); 259 } 260 if (targetType == LocalDateTime.class) { 261 return convertToLocalDateTime(value); 262 } 263 if (targetType == Instant.class) { 264 return convertToInstant(value); 265 } 266 267 // Enum 268 if (targetType.isEnum()) { 269 return convertToEnum(value, (Class<Enum>) targetType); 270 } 271 272 // 其他 Class 类型:兜底走 JSON 273 return convertByJson(value, targetType); 274 } 275 276 // ----- 数值转换 ----- 277 private static Number convertNumber(Object value, Class<?> targetType) { 278 // 源是 Number:直接转换 279 if (value instanceof Number) { 280 Number num = (Number) value; 281 if (targetType == Byte.class) return num.byteValue(); 282 if (targetType == Short.class) return num.shortValue(); 283 if (targetType == Integer.class) return num.intValue(); 284 if (targetType == Long.class) return num.longValue(); 285 if (targetType == Float.class) return num.floatValue(); 286 if (targetType == Double.class) return num.doubleValue(); 287 if (targetType == BigDecimal.class) { 288 if (value instanceof BigDecimal) return (BigDecimal) value; 289 if (value instanceof Double || value instanceof Float) { 290 return BigDecimal.valueOf(num.doubleValue()); 291 } 292 return new BigDecimal(num.toString()); 293 } 294 if (targetType == BigInteger.class) { 295 if (value instanceof BigInteger) return (BigInteger) value; 296 return BigInteger.valueOf(num.longValue()); 297 } 298 } 299 300 // 源是 String:校验 + 解析 301 if (value instanceof String) { 302 String str = (String) value; 303 str = str.trim(); 304 if (!isPureNumeric(str) && !isDecimalString(str)) { 305 throw new IllegalArgumentException("字符串 '" + str + "' 不是有效数值"); 306 } 307 try { 308 if (targetType == Integer.class) return Integer.parseInt(str); 309 if (targetType == Long.class) return Long.parseLong(str); 310 if (targetType == Double.class) return Double.parseDouble(str); 311 if (targetType == Float.class) return Float.parseFloat(str); 312 if (targetType == BigDecimal.class) return new BigDecimal(str); 313 if (targetType == BigInteger.class) return new BigInteger(str); 314 if (targetType == Byte.class) return Byte.parseByte(str); 315 if (targetType == Short.class) return Short.parseShort(str); 316 } catch (NumberFormatException e) { 317 throw new IllegalArgumentException("无法将 '" + str + "' 转换为 " + targetType.getSimpleName(), e); 318 } 319 } 320 321 throw new IllegalArgumentException("无法将 " + value + " (" + value.getClass().getSimpleName() + ") 转换为 " + targetType); 322 } 323 324 /** 325 * 判断是否为纯整数字符串(可选符号 + 数字) 326 */ 327 private static boolean isPureNumeric(String str) { 328 return PURE_INTEGER_PATTERN.matcher(str).matches(); 329 } 330 331 /** 332 * 判断是否为小数字符串(包含小数点) 333 */ 334 private static boolean isDecimalString(String str) { 335 if (str == null || str.isEmpty()) return false; 336 int dotCount = 0; 337 int start = (str.charAt(0) == '+' || str.charAt(0) == '-') ? 1 : 0; 338 for (int i = start; i < str.length(); i++) { 339 char c = str.charAt(i); 340 if (c == '.') { 341 if (++dotCount > 1) return false; 342 } else if (c < '0' || c > '9') { 343 return false; 344 } 345 } 346 return dotCount == 1; 347 } 348 349 // ----- Boolean 转换 ----- 350 private static Boolean convertBoolean(Object value) { 351 if (value instanceof Boolean) return (Boolean) value; 352 if (value instanceof Number) { 353 Number num = (Number) value; 354 return num.intValue() != 0; 355 } 356 if (value instanceof String) { 357 String str = (String) value; 358 String s = str.trim().toLowerCase(Locale.ROOT); 359 if (FALSE_VALUES.contains(s)) return false; 360 if (TRUE_VALUES.contains(s)) return true; 361 // 非明确真/假值:非 null 即 true(可配置) 362 return value != null; 363 } 364 return value != null; 365 } 366 367 // ----- Character 转换 ----- 368 private static Character convertChar(Object value) { 369 if (value instanceof Character) return (Character) value; 370 if (value instanceof String) { 371 String str = (String) value; 372 if (str.isEmpty()) { 373 throw new IllegalArgumentException("空字符串无法转换为 Character"); 374 } 375 return str.charAt(0); 376 } 377 if (value instanceof Number) { 378 Number num = (Number) value; 379 return (char) num.intValue(); 380 } 381 throw new IllegalArgumentException("无法将 " + value + " 转换为 Character"); 382 } 383 384 // ----- Date 转换(多格式 + 缓存 + 时间戳优先)----- 385 private static Date convertToDate(Object value) { 386 if (value == null) return null; 387 if (value instanceof Date) return (Date) value; 388 if (value instanceof Number) { 389 Number num = (Number) value; 390 return new Date(num.longValue()); 391 } 392 if (value instanceof String) { 393 String str = (String) value; 394 return parseDateFromString(str); 395 } 396 return parseDateFromString(value.toString()); 397 } 398 399 private static Date parseDateFromString(String str) { 400 if (str == null || str.isEmpty()) { 401 throw new IllegalArgumentException("日期字符串不能为空"); 402 } 403 str = str.trim(); 404 405 // 策略1: 纯数字时间戳(10位秒 / 13位毫秒) 406 if (TIMESTAMP_PATTERN.matcher(str).matches()) { 407 long ts = Long.parseLong(str); 408 if (str.length() <= 10) ts *= 1000; // 秒 → 毫秒 409 return new Date(ts); 410 } 411 412 // 策略2: ISO_INSTANT 格式(带 Z / 时区) 413 try { 414 if (str.endsWith("Z") || str.contains("+") || (str.indexOf('-', 10) > 0 && !str.contains(" "))) { 415 Instant instant = Instant.parse(str); 416 return Date.from(instant); 417 } 418 } catch (DateTimeParseException ignore) { 419 } 420 421 // 策略3: 预定义格式轮询(缓存 Formatter) 422 for (String pattern : DATE_PATTERNS) { 423 try { 424 DateTimeFormatter formatter = getCachedFormatter(pattern); 425 TemporalAccessor accessor; 426 if (pattern.contains("HH") || pattern.contains("hh") || pattern.contains("H")) { 427 accessor = LocalDateTime.parse(str, formatter); 428 return Date.from(((LocalDateTime) accessor) 429 .atZone(ZoneId.systemDefault()) 430 .toInstant()); 431 } else { 432 accessor = LocalDate.parse(str, formatter); 433 return Date.from(((LocalDate) accessor) 434 .atStartOfDay(ZoneId.systemDefault()) 435 .toInstant()); 436 } 437 } catch (DateTimeParseException ignore) { 438 // 尝试下一个格式 439 } 440 } 441 442 // 策略4: 宽松解析兜底 443 Date fuzzy = tryFuzzyParse(str); 444 if (fuzzy != null) return fuzzy; 445 446 throw new IllegalArgumentException("无法解析日期: '" + str + "'"); 447 } 448 449 private static DateTimeFormatter getCachedFormatter(String pattern) { 450 return FORMATTER_CACHE.computeIfAbsent(pattern, p -> 451 DateTimeFormatter.ofPattern(p) 452 .withLocale(Locale.CHINA) 453 .withZone(ZoneId.systemDefault()) 454 ); 455 } 456 457 private static Date tryFuzzyParse(String str) { 458 try { 459 // 移除中文、统一分隔符 460 String cleaned = str.replaceAll("[\\u4e00-\\u9fa5]", "") 461 .replaceAll("[./]", "-") 462 .trim(); 463 464 // 尝试宽松格式:yyyy-M-d [H:m:s[.S]] 465 if (cleaned.matches("\\d{4}-\\d{1,2}-\\d{1,2}( \\d{1,2}:\\d{1,2}(:\\d{1,2}(\\.\\d{1,3})?)?)?")) { 466 DateTimeFormatter flexible = new DateTimeFormatterBuilder() 467 .parseLenient() 468 .appendPattern("yyyy-M-d") 469 .optionalStart().appendLiteral(' ').appendPattern("H:m:s").optionalEnd() 470 .optionalStart().appendFraction(ChronoField.MILLI_OF_SECOND, 0, 3, true).optionalEnd() 471 .toFormatter() 472 .withZone(ZoneId.systemDefault()); 473 474 if (cleaned.contains(":")) { 475 LocalDateTime ldt = LocalDateTime.parse(cleaned, flexible); 476 return Date.from(ldt.atZone(ZoneId.systemDefault()).toInstant()); 477 } else { 478 LocalDate ld = LocalDate.parse(cleaned, flexible); 479 return Date.from(ld.atStartOfDay(ZoneId.systemDefault()).toInstant()); 480 } 481 } 482 483 // 尝试 ISO_DATE_TIME 484 try { 485 LocalDateTime ldt = LocalDateTime.parse(str, DateTimeFormatter.ISO_DATE_TIME); 486 return Date.from(ldt.atZone(ZoneId.systemDefault()).toInstant()); 487 } catch (DateTimeParseException ignore) { 488 } 489 490 } catch (Exception ignore) { 491 // 模糊解析失败 492 } 493 return null; 494 } 495 496 // ----- LocalDate 转换 ----- 497 private static LocalDate convertToLocalDate(Object value) { 498 if (value == null) return null; 499 if (value instanceof LocalDate) return (LocalDate) value; 500 if (value instanceof Date) { 501 Date date = (Date) value; 502 return Instant.ofEpochMilli(date.getTime()) 503 .atZone(ZoneId.systemDefault()) 504 .toLocalDate(); 505 } 506 if (value instanceof Long) { 507 long ts = (Long) value; 508 return Instant.ofEpochMilli(ts) 509 .atZone(ZoneId.systemDefault()) 510 .toLocalDate(); 511 } 512 if (value instanceof String) { 513 String str = (String) value; 514 // 优先尝试 ISO 格式 515 try { 516 return LocalDate.parse(str.trim(), DateTimeFormatter.ISO_DATE); 517 } catch (DateTimeParseException e) { 518 // 回退到多格式解析 519 Date date = parseDateFromString(str); 520 return date.toInstant().atZone(ZoneId.systemDefault()).toLocalDate(); 521 } 522 } 523 throw new IllegalArgumentException("无法将 " + value + " 转换为 LocalDate"); 524 } 525 526 // ----- LocalDateTime 转换 ----- 527 private static LocalDateTime convertToLocalDateTime(Object value) { 528 if (value == null) return null; 529 if (value instanceof LocalDateTime) return (LocalDateTime) value; 530 if (value instanceof Date) { 531 Date date = (Date) value; 532 return Instant.ofEpochMilli(date.getTime()) 533 .atZone(ZoneId.systemDefault()) 534 .toLocalDateTime(); 535 } 536 if (value instanceof Long) { 537 long ts = (Long) value; 538 return Instant.ofEpochMilli(ts) 539 .atZone(ZoneId.systemDefault()) 540 .toLocalDateTime(); 541 } 542 if (value instanceof String) { 543 String str = (String) value; 544 try { 545 return LocalDateTime.parse(str.trim(), DateTimeFormatter.ISO_LOCAL_DATE_TIME); 546 } catch (DateTimeParseException e) { 547 Date date = parseDateFromString(str); 548 return date.toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime(); 549 } 550 } 551 throw new IllegalArgumentException("无法将 " + value + " 转换为 LocalDateTime"); 552 } 553 554 // ----- Instant 转换 ----- 555 private static Instant convertToInstant(Object value) { 556 if (value == null) return null; 557 if (value instanceof Instant) return (Instant) value; 558 if (value instanceof Date) { 559 Date date = (Date) value; 560 return date.toInstant(); 561 } 562 if (value instanceof Long) { 563 long ts = (Long) value; 564 return Instant.ofEpochMilli(ts); 565 } 566 if (value instanceof String) { 567 String str = (String) value; 568 try { 569 return Instant.parse(str.trim()); 570 } catch (DateTimeParseException e) { 571 Date date = parseDateFromString(str); 572 return date.toInstant(); 573 } 574 } 575 throw new IllegalArgumentException("无法将 " + value + " 转换为 Instant"); 576 } 577 578 // ----- Enum 转换 ----- 579 private static <E extends Enum<E>> E convertToEnum(Object value, Class<E> enumClass) { 580 if (enumClass.isInstance(value)) { 581 return (E) value; 582 } 583 if (value instanceof String) { 584 String name = (String) value; 585 return Enum.valueOf(enumClass, name.trim()); 586 } 587 if (value instanceof Number) { 588 Number num = (Number) value; 589 E[] constants = enumClass.getEnumConstants(); 590 int index = num.intValue(); 591 if (index < 0 || index >= constants.length) { 592 throw new IllegalArgumentException("枚举索引 " + index + " 超出范围 [0, " + (constants.length - 1) + "]"); 593 } 594 return constants[index]; 595 } 596 throw new IllegalArgumentException("无法将 " + value + " 转换为枚举 " + enumClass.getSimpleName()); 597 } 598 599 // ========== 复杂类型转换(JSON 兜底)========== 600 601 private static Object convertComplexType(Object value, Type toType) { 602 try { 603 if (value instanceof JSONArray) { 604 return ((JSONArray) value).to(toType); 605 } 606 607 if (value instanceof JSONObject) { 608 return ((JSONObject) value).to(toType); 609 } 610 611 // 优化:如果 value 已是 JSON 字符串,直接解析 612 if (value instanceof String && !(toType instanceof Class)) { 613 String jsonStr = (String) value; 614 return JSON.parseObject(jsonStr, toType); 615 } 616 617 String json = JSON.toJSONString(value, SERIAL_FEATURES); 618 return JSON.parseObject(json, toType); 619 } catch (RuntimeException e) { 620 throw new ConversionException("复杂类型转换失败: " + toType.getTypeName() + ", JSON 内容: " + JSON.toJSONString(value, SERIAL_FEATURES), e); 621 } catch (Error e) { 622 // Error 不应被捕获,直接抛出 623 throw e; 624 } 625 } 626 627 private static Object convertByJson(Object value, Class<?> targetType) { 628 try { 629 if (value instanceof JSONArray) { 630 return ((JSONArray) value).to(targetType); 631 } 632 633 if (value instanceof JSONObject) { 634 return ((JSONObject) value).to(targetType); 635 } 636 String json = JSON.toJSONString(value, SERIAL_FEATURES); 637 return JSON.parseObject(json, targetType); 638 } catch (RuntimeException e) { 639 throw new ConversionException("JSON 转换失败: " + targetType.getName() + ", JSON 内容: " + JSON.toJSONString(value, SERIAL_FEATURES), e); 640 } catch (Error e) { 641 throw e; 642 } 643 } 644 645 646 /** 647 * 类型转换异常 648 */ 649 public static class ConversionException extends RuntimeException { 650 public ConversionException(String message, Throwable cause) { 651 super(message, cause); 652 } 653 654 public ConversionException(String message) { 655 super(message); 656 } 657 } 658 659}