android 字符串工具类(兼容 Android 16+ / API 16,无报错版)
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>() {@Overrideprotected SimpleDateFormat initialValue() {return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.getDefault());}};private static final ThreadLocal<SimpleDateFormat> SDF_DATE = new ThreadLocal<SimpleDateFormat>() {@Overrideprotected 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