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)及以下已支持的类 / 方法(如 ThreadLocal、SimpleDateFormat、Pattern 等),无高版本依赖,可直接在低版本设备上运行。

  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
相关推荐
千里马学框架4 天前
一起学 Android 14:ShellTransition 屏幕旋转过程深度剖析
android·智能手机·性能优化·framework·性能·屏幕旋转·rotation
美狐美颜SDK开放平台4 天前
开发直播APP时如何接入视频美颜SDK?开发流程与注意事项
android·人工智能·计算机视觉·音视频·直播美颜sdk
AFinalStone4 天前
Android7 SystemUI源码解析(七)Keyguard锁屏模块深度解析
android·systemui
致远ccc4 天前
Google Play 上架前如何测试 App?多国家 Android 环境测试
android·app测试·googleplay·多国家应用测试
ttyyttemo4 天前
Kotlin 协程中的 Job 结构化并发与取消
android
sun0077004 天前
tbox 4g/5g切换,导致wan ip 改变,导致车机旧网络不可用。需要重启车机才行
android
其实防守也摸鱼4 天前
内网穿透与反向代理:原理、工具与实战指南
android·大数据·运维·安全·网络安全·自动化·渗透
AFinalStone4 天前
Android7 SystemUI 源码解析(四)NavigationBar 导航栏与 SystemBars
android·systemui
JMchen4 天前
属性动画原理与高级动画实现
android·kotlin·canvas
AFinalStone4 天前
Android7 SystemUI 源码解析(二)启动流程深度解析
android·systemui