实战案例:用 Kotlin 重写一个 Java Android 工具类

1.4 实战案例:用 Kotlin 重写一个 Java Android 工具类

1.4.1 需求背景

在 Android 开发中,我们经常需要一些工具类来处理常见任务,如:

  • 字符串处理
  • 日期时间转换
  • 密度转换(dp、px、sp)
  • 输入验证

这些工具类在 Java 中通常写成静态方法,但在 Kotlin 中可以更加优雅。

1.4.2 Java 版本的工具类

java 复制代码
// Java 版本的工具类
package com.example.tools;

import java.util.regex.Pattern;
public class StringUtils {
    // 判断字符串是否为空
    public static boolean isEmpty(String str) {
        return str == null || str.length() == 0;
    }

    // 判断字符串是否不为空
    public static boolean isNotEmpty(String str) {
        return !isEmpty(str);
    }

    // 判断字符串是否为空或空白
    public static boolean isBlank(String str) {
        return str == null || str.trim().length() == 0;
    }

    // 邮箱验证
    public static boolean isEmail(String email) {
        if (isEmpty(email)) {
            return false;
        }
        String emailRegex = "^[A-Za-z0-9+_.-]+@(.+)$";
        Pattern pattern = Pattern.compile(emailRegex);
        return pattern.matcher(email).matches();
    }

    // 手机号验证(简单版)
    public static boolean isPhoneNumber(String phone) {
        if (isEmpty(phone)) {
            return false;
        }
        String phoneRegex = "^1[3-9]\\d{9}$";
        Pattern pattern = Pattern.compile(phoneRegex);
        return pattern.matcher(phone).matches();
    }

    // 首字母大写
    public static String capitalize(String str) {
        if (isEmpty(str)) {
            return str;
        }
        return str.substring(0, 1).toUpperCase() + str.substring(1);
    }
}

1.4.3 Kotlin 版本的重写

方式一:扩展函数版本(推荐)

kotlin 复制代码
// Kotlin 扩展函数版本
import java.util.regex.Pattern

// 扩展 String 的 isEmpty 函数
fun String?.isEmptyOrNull(): Boolean {
    return this == null || this.isEmpty()
}

// 扩展 String 的 isBlank 函数
fun String?.isBlankOrNull(): Boolean {
    return this == null || this.isBlank()
}

// 扩展 String 的邮箱验证
fun String?.isEmail(): Boolean {
    if (this.isNullOrBlank()) return false
    val emailRegex = "^[A-Za-z0-9+_.-]+@(.+)$"
    val pattern = Pattern.compile(emailRegex)
    return pattern.matcher(this).matches()
}

// 扩展 String 的手机号验证
fun String?.isPhoneNumber(): Boolean {
    if (this.isNullOrBlank()) return false
    val phoneRegex = "^1[3-9]\\d{9}$"
    val pattern = Pattern.compile(phoneRegex)
    return pattern.matcher(this).matches()
}

// 扩展 String 的首字母大写
fun String.capitalize(): String {
    if (this.isEmpty()) return this
    return this.substring(0, 1).toUpperCase() + this.substring(1)
}

使用对比

java 复制代码
// Java 使用方式
String email = "test@example.com";
if (StringUtils.isEmpty(email)) {
    // 处理空字符串
}
if (StringUtils.isEmail(email)) {
    // 处理邮箱验证通过
}
kotlin 复制代码
// Kotlin 使用方式
val email: String? = "test@example.com"
if (email.isNullOrBlank()) {
    // 处理空字符串
}
if (email.isEmail()) {
    // 处理邮箱验证通过
}

方式二:工具类版本(兼容现有代码)

kotlin 复制代码
// Kotlin 工具类版本(使用伴生对象) 直接通过AS通过Java转Kotlin代码
package com.example.tools
import java.util.Locale
import java.util.regex.Pattern

object StringUtils {
    // 判断字符串是否为空
    fun isEmpty(str: String?): Boolean {
        return str == null || str.length == 0
    }

    // 判断字符串是否不为空
    fun isNotEmpty(str: String?): Boolean {
        return !isEmpty(str)
    }

    // 判断字符串是否为空或空白
    fun isBlank(str: String?): Boolean {
        return str == null || str.trim { it <= ' ' }.length == 0
    }

    // 邮箱验证
    fun isEmail(email: String?): Boolean {
        if (isEmpty(email)) {
            return false
        }
        val emailRegex = "^[A-Za-z0-9+_.-]+@(.+)$"
        val pattern = Pattern.compile(emailRegex)
        return pattern.matcher(email).matches()
    }

    // 手机号验证(简单版)
    fun isPhoneNumber(phone: String?): Boolean {
        if (isEmpty(phone)) {
            return false
        }
        val phoneRegex = "^1[3-9]\\d{9}$"
        val pattern = Pattern.compile(phoneRegex)
        return pattern.matcher(phone).matches()
    }

    // 首字母大写 可以增加注解@JvmStatic
    @JvmStatic
    fun capitalize(str: String?): String? {
        if (isEmpty(str)) {
            return str
        }
        return str!!.substring(0, 1).uppercase(Locale.getDefault()) + str.substring(1)
    }
}

@JvmStatic 注解:让 Java 代码可以像调用静态方法一样调用 Kotlin 的伴生对象方法。

1.4.4 Android 密度转换工具类

需求:在 Android 开发中,经常需要在 dp、px、sp 之间进行转换。

kotlin 复制代码
import android.content.Context
import android.util.TypedValue

// 扩展 Context 的密度转换函数
fun Context.dpToPx(dp: Float): Float {
    return TypedValue.applyDimension(
        TypedValue.COMPLEX_UNIT_DIP,
        dp,
        this.resources.displayMetrics
    )
}

fun Context.pxToDp(px: Float): Float {
    return px / this.resources.displayMetrics.density
}

fun Context.spToPx(sp: Float): Float {
    return TypedValue.applyDimension(
        TypedValue.COMPLEX_UNIT_SP,
        sp,
        this.resources.displayMetrics
    )
}

fun Context.pxToSp(px: Float): Float {
    return px / this.resources.displayMetrics.scaledDensity
}

// 扩展 Int 的密度转换函数
fun Int.dpToPx(context: Context): Int {
    return TypedValue.applyDimension(
        TypedValue.COMPLEX_UNIT_DIP,
        this.toFloat(),
        context.resources.displayMetrics
    ).toInt()
}

fun Int.spToPx(context: Context): Int {
    return TypedValue.applyDimension(
        TypedValue.COMPLEX_UNIT_SP,
        this.toFloat(),
        context.resources.displayMetrics
    ).toInt()
}

使用示例

kotlin 复制代码
// 在 Activity 或 Fragment 中使用
val marginInDp = 16
val marginInPx = marginInDp.dpToPx(this)

val textSizeInSp = 14
val textSizeInPx = textSizeInSp.spToPx(this)

// 或者直接使用 Context 扩展函数
val pxValue = this.dpToPx(16f)
val dpValue = this.pxToDp(48f)

1.4.5 完整的工具类封装

kotlin 复制代码
package com.example.androidmoderndev.utils

import android.content.Context
import android.util.TypedValue
import java.text.SimpleDateFormat
import java.util.*

/**
 * 字符串扩展函数
 */

// 判断字符串是否为空或 null
fun String?.isEmptyOrNull(): Boolean = this.isNullOrEmpty()

// 判断字符串是否为空白或 null
fun String?.isBlankOrNull(): Boolean = this.isNullOrBlank()

// 邮箱验证
fun String?.isEmail(): Boolean {
    if (this.isNullOrBlank()) return false
    val emailRegex = "^[A-Za-z0-9+_.-]+@(.+)$"
    val pattern = Regex(emailRegex)
    return pattern.matches(this)
}

// 手机号验证(中国大陆)
fun String?.isPhoneNumber(): Boolean {
    if (this.isNullOrBlank()) return false
    val phoneRegex = "^1[3-9]\\d{9}$"
    val pattern = Regex(phoneRegex)
    return pattern.matches(this)
}

// 首字母大写
fun String.capitalize(): String {
    if (this.isEmpty()) return this
    return this.substring(0, 1).toUpperCase() + this.substring(1)
}

// 验证密码强度(至少8位,包含字母和数字)
fun String?.isStrongPassword(): Boolean {
    if (this.isNullOrBlank() || this.length < 8) return false
    val hasLetter = this.any { it.isLetter() }
    val hasDigit = this.any { it.isDigit() }
    return hasLetter && hasDigit
}

/**
 * 日期时间扩展函数
 */

// Date 转格式化字符串
fun Date.toFormattedString(pattern: String = "yyyy-MM-dd HH:mm:ss"): String {
    val sdf = SimpleDateFormat(pattern, Locale.getDefault())
    return sdf.format(this)
}

// 时间戳转格式化字符串
fun Long.toFormattedString(pattern: String = "yyyy-MM-dd HH:mm:ss"): String {
    val sdf = SimpleDateFormat(pattern, Locale.getDefault())
    return sdf.format(Date(this))
}

/**
 * Context 扩展函数(密度转换)
 */

fun Context.dpToPx(dp: Float): Float {
    return TypedValue.applyDimension(
        TypedValue.COMPLEX_UNIT_DIP,
        dp,
        this.resources.displayMetrics
    )
}

fun Context.pxToDp(px: Float): Float {
    return px / this.resources.displayMetrics.density
}

fun Context.spToPx(sp: Float): Float {
    return TypedValue.applyDimension(
        TypedValue.COMPLEX_UNIT_SP,
        sp,
        this.resources.displayMetrics
    )
}

fun Context.pxToSp(px: Float): Float {
    return px / this.resources.displayMetrics.scaledDensity
}

/**
 * Int 扩展函数(密度转换)
 */

fun Int.dpToPx(context: Context): Int {
    return context.dpToPx(this.toFloat()).toInt()
}

fun Int.spToPx(context: Context): Int {
    return context.spToPx(this.toFloat()).toInt()
}

/**
 * SharedPreferences 扩展函数
 */

import android.content.SharedPreferences

fun SharedPreferences.putString(key: String, value: String?) {
    edit().putString(key, value).apply()
}

fun SharedPreferences.getInt(key: String, defaultValue: Int = 0): Int {
    return getInt(key, defaultValue)
}

fun SharedPreferences.getBoolean(key: String, defaultValue: Boolean = false): Boolean {
    return getBoolean(key, defaultValue)
}

fun SharedPreferences.putBoolean(key: String, value: Boolean) {
    edit().putBoolean(key, value).apply()
}

1.4.6 代码对比总结

方面 Java 版本 Kotlin 版本
代码量 约 80 行 约 120 行(但功能更丰富)
调用方式 StringUtils.isEmpty(str) str.isNullOrEmpty()
空安全 需要手动检查 null 编译器自动处理
可读性 一般 优秀(更接近自然语言)
扩展性 需要修改工具类 可以随时添加扩展函数
兼容性 仅 Java Java + Kotlin(@JvmStatic)

Kotlin 版本的优势

  1. 调用更自然str.isEmail()StringUtils.isEmail(str) 更直观
  2. 空安全String?. 类型系统保证空安全
  3. 易于扩展:可以随时为任何类添加扩展函数
  4. 代码组织:相关功能可以按类组织,而不是全部放在工具类中

本章小结

核心要点回顾

  1. Kotlin 的核心优势

    • 空安全机制:从编译时杜绝 NPE
    • 代码简洁性:数据类、扩展函数等语法糖
    • 与 Java 100% 互操作:增量迁移可行
    • Google 官方推荐:Android 开发的未来
  2. Kotlin 基础语法

    • val vs var:优先使用不可变变量
    • 函数定义:支持默认参数、命名参数、可变参数
    • 类与继承:open 关键字、override 关键字
    • 接口与抽象类:接口定义行为,抽象类定义模板
  3. Kotlin 特有语法糖

    • 数据类(data class):自动生成常用方法
    • 密封类(sealed class):受限的类继承结构
    • 对象表达式与单例:object 关键字
    • 扩展函数与属性:不修改原类添加新功能
  4. 实战案例

    • 用 Kotlin 扩展函数重写 Java 工具类
    • Android 密度转换工具类的 Kotlin 实现
    • SharedPreferences 操作的简化
相关推荐
前端技术2 小时前
[特殊字符]️ Spring AI Alibaba Advisor基础应用
java·人工智能·spring
Jet7692 小时前
2026年API中转平台选型笔记:稳定性、兼容性、成本怎么一起看
java·网络·笔记
Fate_I_C2 小时前
Kotlin 特有语法糖
android·开发语言·kotlin
糯米团子7492 小时前
蓝桥杯javaB组赛前四天复习-1
java·windows·蓝桥杯
莫逸风2 小时前
【java-core-collections】集合框架深度解析
java·开发语言
小江的记录本2 小时前
【分布式】分布式系统核心知识体系:CAP定理、BASE理论与核心挑战
java·前端·网络·分布式·后端·python·安全
Fate_I_C2 小时前
Kotlin 为什么是 Android 开发的首选语言
android·开发语言·kotlin
黄林晴2 小时前
Android CLI 来了!终端一键建项目、控模拟器、给 Agent 喂官方规范
android