细微之处见真章之字符串超长省略功能

简介: 细微之处见真章之字符串超长省略功能

一、背景

有这样一个需求:如果一个字符串超过某个长度,则超过该长度的部分用省略号代替。


很多人会觉得这 so easy,有点 Java基础的同学都可以简单编写出来。


那么我们来分析这个简单的问题。


二、编码


2.1 思路

思路很简单,判断size 是否小于字符串长度,如果小于,则超过部分替换为 ... 即可。


2.2 编码

我们编码要多考虑一些:


为了健壮性,我们要进行参数校验;

另外如果想写一个完善的工具类,可以支持自定义省略符;


我们来编写工具类:


import com.google.common.base.Preconditions;

import org.apache.commons.lang3.StringUtils;

public class StringUtil {

   /**

    * 超过 maxSize 的部分用省略号代替

    *

    * @param originStr 原始字符串

    * @param maxSize   最大长度

    */

   public static String abbreviate(String originStr, int maxSize) {

       return abbreviate(originStr, maxSize, null);

   }

   /**

    * 超过 maxSize 的部分用省略号代替

    *

    * @param originStr    原始字符串

    * @param maxSize      最大长度

    * @param abbrevMarker 省略符

*/
    public static String abbreviate(String originStr, int maxSize, String abbrevMarker) {
        Preconditions.checkArgument(maxSize > 0, "size 必须大于0");
        if (StringUtils.isEmpty(originStr)) {
            return StringUtils.EMPTY;
        }
        String defaultAbbrevMarker = "...";
        if (originStr.length() < maxSize) {
            return originStr;
        }
        return originStr.substring(0, maxSize) + StringUtils.defaultIfEmpty(abbrevMarker, defaultAbbrevMarker);
    }
}

这里借助了 commons-lang3 包里的StringUtils,和guava 包的Preconditions,如果项目里没引入这些包,可以自己手动实现也很简单。


写完了怎么验证正确性呢?


作为一个合格的程序,肯定要写单元测试的嘛!


public class StringUtilTest {
   @Test
    public void abbreviateLess() {
        String input = "123456789";
        String abbreviate = StringUtil.abbreviate(input, 11);
        Assert.assertEquals(input, abbreviate);
    } 
    @Test
    public void abbreviateCommon() {
        String input = "123456789";
        String abbreviate = StringUtil.abbreviate(input, 3);
        Assert.assertEquals("123...", abbreviate);
    }
    @Test
    public void abbreviateWithMarker() {
        String input = "123456789";
        String abbreviate = StringUtil.abbreviate(input, 3, "***");
        Assert.assertEquals("123***", abbreviate);
    }
    @Test(expected = IllegalArgumentException.class)
    public void abbreviateWithNegativeSize() {
        String input = "123456789";
        String abbreviate = StringUtil.abbreviate(input, -3);
    }
}

发现功能通过。


2.3 思考问题?

如果就这么完了,是不是也没太大价值呢?


2.3.1 如果是emoji 表情,占两个字符,如果截取到了第一个字符,会不会有问题?


写单测验证一下,果然有问题。


作为优秀的程序员,我们是不是应该和产品交流一下这种情况该怎么办呢?


假设产品说:这种情况就把整个表情不要了。


我们对此工具函数做出修改:


/**

    * 超过 maxSize 的部分用省略号代替

    *

    * @param originStr    原始字符串

    * @param maxSize      最大长度

    * @param abbrevMarker 省略符

    */

   public static String abbreviate(String originStr, int maxSize, String abbrevMarker) {

       Preconditions.checkArgument(maxSize > 0, "size 必须大于0");

       if (StringUtils.isEmpty(originStr)) {

           return StringUtils.EMPTY;

       }

       String defaultAbbrevMarker = "...";

       if (originStr.length() < maxSize) {

           return originStr;

       }

       // 截取前maxSize 个字符

       String head = originStr.substring(0, maxSize);

       // 最后一个字符是高代理项,则移除掉

       char lastChar = head.charAt(head.length() - 1);

       if (Character.isHighSurrogate(lastChar)) {

           head = head.substring(0, head.length() - 1);

       }

       return head + StringUtils.defaultIfEmpty(abbrevMarker, defaultAbbrevMarker);

   }

重新运行单元测试,发现得到了我们想要的效果。


关于  unicode 详细内容参考维基百科,相关字符的用法参考下面的文章:


https://www.ibm.com/developerworks/cn/java/j-unicode/


2.3.2 如何可以写的更完善?

上面的做法看似很完美了,但是如何写的更完善呢? what? 这还不行吗?!


我们看下 commons-lang3 的 StringUtils 工具类的源码:


/**
     * 
Returns either the passed in CharSequence, or if the CharSequence is
     * empty or {@code null}, the value of {@code defaultStr}.
     *
     * 
     * StringUtils.defaultIfEmpty(null, "NULL")  = "NULL"
     * StringUtils.defaultIfEmpty("", "NULL")    = "NULL"
     * StringUtils.defaultIfEmpty(" ", "NULL")   = " "
     * StringUtils.defaultIfEmpty("bat", "NULL") = "bat"
     * StringUtils.defaultIfEmpty("", null)      = null
     * 
     * @param  the specific kind of CharSequence
     * @param str  the CharSequence to check, may be null
     * @param defaultStr  the default CharSequence to return
     *  if the input is empty ("") or {@code null}, may be null
     * @return the passed in CharSequence, or the default
     * @see StringUtils#defaultString(String, String)
     */
    public static  T defaultIfEmpty(final T str, final T defaultStr) {
        return isEmpty(str) ? defaultStr : str;
    }

可以发现,源码给出了常见参数的返回值,用起来特别容易。


因此我们做出下面的修改:


import com.google.common.base.Preconditions;

import org.apache.commons.lang3.StringUtils;

public class StringUtil {

   /**

    * 超过 maxSize 的部分用省略号代替

    *

    * 使用范例:

    * 1 不超过取所有

    * StringUtil.abbreviate("123456789", 11) = "123456789"

    *

    * 2 超过最大长度截取并补充省略号

    * StringUtil.abbreviate("123456789", 3) = "123..."

    *

    * 3 emoji表情被截断则丢弃前面的字符(整个表情)

    * StringUtil.abbreviate("123456789??", 10) = "123456789..."

    *

    * @param originStr 原始字符串

    * @param maxSize   最大长度

    */

   public static String abbreviate(String originStr, int maxSize) {

       return abbreviate(originStr, maxSize, null);

   }

   /**

    * 超过 maxSize 的部分用省略号代替

    *

    * 使用范例:

    *

    * StringUtil.abbreviate("123456789"", 3, "***") = "123..."

    *

    * @param originStr    原始字符串

    * @param maxSize      最大长度

    * @param abbrevMarker 省略符

    */

   public static String abbreviate(String originStr, int maxSize, String abbrevMarker) {

       Preconditions.checkArgument(maxSize > 0, "size 必须大于0");

       if (StringUtils.isEmpty(originStr)) {

           return StringUtils.EMPTY;

       }

       String defaultAbbrevMarker = "...";

       if (originStr.length() < maxSize) {

           return originStr;

       }

       // 截取前maxSize 个字符

       String head = originStr.substring(0, maxSize);

       // 最后一个字符是高代理项,则移除掉

       char lastChar = head.charAt(head.length() - 1);

       if (Character.isHighSurrogate(lastChar)) {

           head = head.substring(0, head.length() - 1);

       }

       return head + StringUtils.defaultIfEmpty(abbrevMarker, defaultAbbrevMarker);

   }

}

最为优秀的程序员,我们编写工具类时,可以把工具类的常见输入和输出在注释中给出,方便使用者。



三、总结

这个简单的功能,实现很容易,写好却没那么容易。


可以加上参数校验,加上单元测试,加上注释,加上emoji表情问题处理等。



很多新手总是觉得很多问题很简单,但是简单的功能代码能否写的严谨,是一件值得思考的问题。


另外希望大家能从各方面吸收源码的精华,而不是想当然地读源码,源码的注释,源码的设计模式,源码的编写思路都是非常有价值的东西。


编程在细微之处见真章,希望大家在平时编程时能够养成好的习惯,努力做一个有追求的优秀的程序员。

相关文章
程序人生 - 燕窝等级分几 A 是什么意思?有什么差别?5A燕窝最好吗?
程序人生 - 燕窝等级分几 A 是什么意思?有什么差别?5A燕窝最好吗?
249 0
程序人生 - 燕窝等级分几 A 是什么意思?有什么差别?5A燕窝最好吗?
|
2月前
|
设计模式 安全 编译器
C++中精简艺术:省略参数名以提升代码清晰度
C++中精简艺术:省略参数名以提升代码清晰度
63 2
|
5月前
|
人工智能 自然语言处理 Java
想要搞定正则验证字串符?用这个办法最简单,质量还高!
在编程中,字符串的处理是不可避免的一部分。我们经常需要验证用户输入的数据、提取文本信息、替换特定字符等等。在这些场景中,正则验证字串符(Regex Validation)为我们提供了一种高效、灵活的处理方式。
|
8月前
|
开发者 Perl
正则表达式中的模式修正符S和M 妙用!
教你如何正确使用正则表达式中的模式修正符S和M!
39 0
正则表达式中的模式修正符S和M 妙用!
|
8月前
|
PHP 开发者
|
10月前
|
程序员
相见恨晚的Matlab编程小技巧(2)-代码怎么做到逻辑清晰?——巧用注释符“%“
        本文将以教程的形式详细介绍Matlab中两个常用符号“%”和“%%”的作用。初学者可以通过此文掌握这两个符号的用法,为Matlab编程打下坚实的基础。
一个微小的调优去掉嵌套的if,else
一个微小的调优去掉嵌套的if,else
|
Unix Apache C++
给代码写注释时有哪些讲究?
给代码写注释时有哪些讲究?
125 0
给代码写注释时有哪些讲究?
千万别再一直无脑使用ES6的箭头函数了,它虽然很有用但并不是万能的
相信很多小伙伴自从知道了ES6的箭头函数以后,都疯狂得使用,渐渐的淡忘了普通函数的使用。不过确实,箭头函数看起来比较简洁,用起来也舒服,不过它的出现是为了解决某一部分问题的,并不是用来替代普通函数的,所以我们不能在每一个地方都使用箭头函数
120 0
千万别再一直无脑使用ES6的箭头函数了,它虽然很有用但并不是万能的
《代码重构》之方法到底多长算“长”?(下)
《代码重构》之方法到底多长算“长”?
89 0