天天看點

細微之處見真章之字元串超長省略功能 一、背景二、編碼三、總結

 一、背景

有這樣一個需求:如果一個字元串超過某個長度,則超過該長度的部分用省略号代替。

很多人會覺得這 so easy,有點 Java基礎的同學都可以簡單編寫出來。

那麼我們來分析這個簡單的問題。

二、編碼

2.1 思路

思路很簡單,判斷size 是否小于字元串長度,如果小于,則超過部分替換為 ... 即可。

2.2 編碼

我們編碼要多考慮一些:

  1. 為了健壯性,我們要進行參數校驗;
  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 工具類的源碼:

/**
     * <p>Returns either the passed in CharSequence, or if the CharSequence is
     * empty or {@code null}, the value of {@code defaultStr}.</p>
     *
     * <pre>
     * StringUtils.defaultIfEmpty(null, "NULL")  = "NULL"
     * StringUtils.defaultIfEmpty("", "NULL")    = "NULL"
     * StringUtils.defaultIfEmpty(" ", "NULL")   = " "
     * StringUtils.defaultIfEmpty("bat", "NULL") = "bat"
     * StringUtils.defaultIfEmpty("", null)      = null
     * </pre>
     * @param <T> 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 extends CharSequence> 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 的部分用省略号代替
     * <p>
     * 使用範例:
     * 1 不超過取所有
     * StringUtil.abbreviate("123456789", 11) = "123456789"
     * <p>
     * 2 超過最大長度截取并補充省略号
     * StringUtil.abbreviate("123456789", 3) = "123..."
     * <p>
     * 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 的部分用省略号代替
     * <p>
     * 使用範例:
     * <p>
     * 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表情問題處理等。

很多新手總是覺得很多問題很簡單,但是簡單的功能代碼能否寫的嚴謹,是一件值得思考的問題。

另外希望大家能從各方面吸收源碼的精華,而不是想當然地讀源碼,源碼的注釋,源碼的設計模式,源碼的編寫思路都是非常有價值的東西。

程式設計在細微之處見真章,希望大家在平時程式設計時能夠養成好的習慣,努力做一個有追求的優秀的程式員。