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 = {
            {"&", "&amp;"}, {"<", "&lt;"}, {">", "&gt;"},
            {"\"", "&quot;"}, {"'", "'"}
    };

    // ========================= 日期格式化(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;
    }
}

验证与使用建议

  1. 兼容性验证 :所有 API 均基于 Android 16(API 16)及以下已支持的类 / 方法(如 ThreadLocalSimpleDateFormatPattern 等),无高版本依赖,可直接在低版本设备上运行。

  2. 调用示例

    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
相关推荐
猪哥帅过吴彦祖3 小时前
Flutter 系列教程:常用基础组件 (上) - `Text`, `Image`, `Icon`, `Button`
android·flutter·ios
恋猫de小郭4 小时前
Fluttercon EU 2025 :Let's go far with Flutter
android·前端·flutter
诺诺Okami4 小时前
Android Framework- AMS 之 Activity-暂停
android
2501_916013744 小时前
App 上架服务全流程解析,iOS 应用代上架、ipa 文件上传工具、TestFlight 测试与苹果审核实战经验
android·ios·小程序·https·uni-app·iphone·webview
建群新人小猿6 小时前
客户标签自动管理:标签自动化运营,画像持久保鲜
android·java·大数据·前端·git
一直向钱6 小时前
android 自定义样式 Toast 实现(兼容 Android 4.1+~Android 16(API 16))
android
一直向钱6 小时前
android SharedPreferences 工具类 * 兼容 Android 16+ (API 16)
android
2501_915909066 小时前
App Store 上架完整流程解析,iOS 应用发布步骤、ipa 文件上传工具、TestFlight 测试与苹果审核经验
android·macos·ios·小程序·uni-app·cocoa·iphone
2501_916008897 小时前
iOS 26 全景揭秘,新界面、功能创新、兼容挑战与各种工具在新版系统中的定位
android·macos·ios·小程序·uni-app·cocoa·iphone