package com.nyw.mvvmmode.utils;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.Locale;
import java.util.TimeZone;
import java.util.regex.Pattern;
/**
* 字符串工具类(兼容 Android 16+ / API 16,无报错版)
* 功能:空值判断、格式校验、类型转换、日期处理、字符串加工、字节数组转换
*/
public final class StringUtils {
// ========================= 正则常量(修复手机号正则,适配全号段)=========================
private static final Pattern PATTERN_EMAIL = Pattern.compile("\\w+([-+.]\\w+)*@\\w+([-.]\\w+)*\\.\\w+([-.]\\w+)*");
private static final Pattern PATTERN_PHONE = Pattern.compile("^1[3-9]\\d{9}$"); // 支持13-19全号段
private static final Pattern PATTERN_PURE_NUMBER = Pattern.compile("^\\d+$");
private static final Pattern PATTERN_CHINESE = Pattern.compile("[\u4e00-\u9fa5]");
private static final String[][] HTML_ESCAPE_PAIRS = {
{"&", "&"}, {"<", "<"}, {">", ">"},
{"\"", """}, {"'", "'"}
};
// ========================= 日期格式化(ThreadLocal保证线程安全,补充Locale)=========================
private static final ThreadLocal<SimpleDateFormat> SDF_FULL = new ThreadLocal<SimpleDateFormat>() {
@Override
protected SimpleDateFormat initialValue() {
return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.getDefault());
}
};
private static final ThreadLocal<SimpleDateFormat> SDF_DATE = new ThreadLocal<SimpleDateFormat>() {
@Override
protected SimpleDateFormat initialValue() {
return new SimpleDateFormat("yyyy-MM-dd", Locale.getDefault());
}
};
// ========================= 私有构造(防止实例化)=========================
private StringUtils() {
throw new AssertionError("StringUtils cannot be instantiated");
}
// ========================= 1. 空值判断(修复逻辑效率,避免冗余判断)=========================
public static boolean isEmpty(CharSequence input) {
if (input == null || input.length() == 0) {
return true;
}
for (int i = 0; i < input.length(); i++) {
if (!Character.isWhitespace(input.charAt(i))) {
return false;
}
}
return true;
}
public static boolean hasEmpty(CharSequence... strs) {
if (strs == null || strs.length == 0) {
return true;
}
for (CharSequence str : strs) {
if (isEmpty(str)) {
return true;
}
}
return false;
}
public static String emptyReplace(CharSequence input, String defaultValue) {
return isEmpty(input) ? defaultValue : input.toString();
}
// ========================= 2. 格式校验(修复原正则缺陷,补充容错)=========================
public static boolean isEmail(CharSequence email) {
return !isEmpty(email) && PATTERN_EMAIL.matcher(email).matches();
}
public static boolean isPhone(CharSequence phoneNum) {
return !isEmpty(phoneNum) && PATTERN_PHONE.matcher(phoneNum).matches();
}
public static boolean isPureNumber(CharSequence str) {
return !isEmpty(str) && PATTERN_PURE_NUMBER.matcher(str).matches();
}
public static boolean containsChinese(CharSequence str) {
return !isEmpty(str) && PATTERN_CHINESE.matcher(str).find();
}
// ========================= 3. 类型转换(修复空指针风险,明确异常捕获)=========================
public static int toInt(CharSequence str, int defValue) {
if (isEmpty(str)) {
return defValue;
}
try {
return Integer.parseInt(str.toString());
} catch (NumberFormatException e) {
return defValue;
}
}
public static int toInt(Object obj) {
if (obj == null) {
return 0;
}
return toInt(obj.toString(), 0);
}
public static long toLong(CharSequence str) {
if (isEmpty(str)) {
return 0L;
}
try {
return Long.parseLong(str.toString());
} catch (NumberFormatException e) {
return 0L;
}
}
public static double toDouble(CharSequence str) {
if (isEmpty(str)) {
return 0.0D;
}
try {
return Double.parseDouble(str.toString());
} catch (NumberFormatException e) {
return 0.0D;
}
}
public static boolean toBool(CharSequence str) {
return !isEmpty(str) && Boolean.parseBoolean(str.toString().toLowerCase(Locale.getDefault()));
}
// ========================= 4. 日期处理(修复时区判断逻辑,避免空指针)=========================
public static String getCurrentTime(String format) {
if (isEmpty(format)) {
return "";
}
try {
SimpleDateFormat sdf = new SimpleDateFormat(format, Locale.getDefault());
return sdf.format(new Date());
} catch (IllegalArgumentException e) {
return ""; // 格式非法时返回空
}
}
public static Date toDate(String sdate) {
return toDate(sdate, SDF_FULL.get());
}
public static Date toDate(String sdate, SimpleDateFormat sdf) {
if (isEmpty(sdate) || sdf == null) {
return null;
}
try {
return sdf.parse(sdate);
} catch (ParseException e) {
return null;
}
}
public static String friendlyTime(String sdate) {
Date time = toDate(sdate);
if (time == null) {
return "未知时间";
}
// 处理时区(修复原代码冗余判断)
if (!isInEasternEightZones()) {
time = transformTime(time, TimeZone.getTimeZone("GMT+08"), TimeZone.getDefault());
}
Calendar cal = Calendar.getInstance();
long currentTime = cal.getTimeInMillis();
long targetTime = time.getTime();
String curDate = SDF_DATE.get().format(cal.getTime());
String targetDate = SDF_DATE.get().format(time);
// 当天时间处理
if (curDate.equals(targetDate)) {
long hourDiff = (currentTime - targetTime) / 3600000L;
if (hourDiff == 0) {
long minuteDiff = (currentTime - targetTime) / 60000L;
return Math.max(minuteDiff, 1) + "分钟前";
} else {
return hourDiff + "小时前";
}
}
// 非当天时间处理
long dayDiff = (currentTime / 86400000L) - (targetTime / 86400000L);
if (dayDiff == 1) {
return "昨天";
} else if (dayDiff == 2) {
return "前天";
} else if (dayDiff > 2 && dayDiff < 31) {
return dayDiff + "天前";
} else if (dayDiff >= 31 && dayDiff <= 62) {
return "1个月前";
} else if (dayDiff > 62 && dayDiff <= 93) {
return "2个月前";
} else if (dayDiff > 93 && dayDiff <= 124) {
return "3个月前";
} else {
return targetDate;
}
}
public static boolean isInEasternEightZones() {
// 修复原代码equals判断问题(通过时区ID比较,更可靠)
String timeZoneId = TimeZone.getDefault().getID();
return "GMT+08".equals(timeZoneId) || "Asia/Shanghai".equals(timeZoneId);
}
public static Date transformTime(Date date, TimeZone oldZone, TimeZone newZone) {
if (date == null || oldZone == null || newZone == null) {
return null;
}
int offset = oldZone.getOffset(date.getTime()) - newZone.getOffset(date.getTime());
return new Date(date.getTime() - offset);
}
// ========================= 5. 字符串加工(新增实用功能,无高版本API)=========================
public static String desensitizePhone(CharSequence phoneNum) {
if (!isPhone(phoneNum)) {
return emptyReplace(phoneNum, "");
}
String phone = phoneNum.toString();
return phone.substring(0, 3) + "****" + phone.substring(7);
}
public static String substringWithEllipsis(CharSequence str, int maxLength) {
if (isEmpty(str) || maxLength <= 0) {
return emptyReplace(str, "");
}
String s = str.toString();
return s.length() <= maxLength ? s : s.substring(0, maxLength) + "...";
}
public static String escapeHtml(CharSequence html) {
if (isEmpty(html)) {
return "";
}
String result = html.toString();
for (String[] pair : HTML_ESCAPE_PAIRS) {
result = result.replace(pair[0], pair[1]);
}
return result;
}
// ========================= 6. 字节数组与十六进制(保留原功能,修复语法错误)=========================
public static String byteArrayToHexString(byte[] data) {
if (data == null || data.length == 0) {
return "";
}
StringBuilder sb = new StringBuilder(data.length * 2);
for (byte b : data) {
int v = b & 0xFF; // 修复原代码强转问题,确保无符号
if (v < 16) {
sb.append('0');
}
sb.append(Integer.toHexString(v));
}
return sb.toString().toUpperCase(Locale.getDefault());
}
public static byte[] hexStringToByteArray(String s) {
if (isEmpty(s) || s.length() % 2 != 0) {
return new byte[0]; // 长度为奇数时返回空数组,避免崩溃
}
int len = s.length();
byte[] data = new byte[len / 2];
for (int i = 0; i < len; i += 2) {
// 修复原代码Character.digit可能返回-1的问题,补充容错
int high = Character.digit(s.charAt(i), 16);
int low = Character.digit(s.charAt(i + 1), 16);
if (high == -1 || low == -1) {
return new byte[0]; // 非法十六进制字符时返回空数组
}
data[i / 2] = (byte) ((high << 4) + low);
}
return data;
}
}
验证与使用建议
-
兼容性验证 :所有 API 均基于 Android 16(API 16)及以下已支持的类 / 方法(如
ThreadLocal
、SimpleDateFormat
、Pattern
等),无高版本依赖,可直接在低版本设备上运行。 -
调用示例 :
java
运行
// 1. 手机号脱敏 String desensitized = StringUtils.desensitizePhone("13800138000"); // 结果:138****8000 // 2. 友好时间显示 String friendly = StringUtils.friendlyTime("2024-05-20 10:30:00"); // 结果:如"1小时前" // 3. 字节数组转十六进制 byte[] bytes = "test".getBytes(); String hex = StringUtils.byteArrayToHexString(bytes); // 结果:54657374