当前位置 博文首页 > 沉默王二:GitHub 上 1.3k Star 的 strman-java 项目有值得学习

    沉默王二:GitHub 上 1.3k Star 的 strman-java 项目有值得学习

    作者:[db:作者] 时间:2021-07-05 16:20

    大家好,我是沉默王二。

    很多初学编程的同学,经常给我吐槽,说:“二哥,你在敲代码的时候会不会有这样一种感觉,写着写着看不下去了,觉得自己写出来的代码就好像屎一样?”

    这里我必须得说一句,初入“江湖”的时候,确实会觉得自己的代码写得很烂,但这么多年下来,这种感觉已经荡然无存了。

    (吹嘛,我也会,哈哈)

    那,怎么才能让写出来的代码不那么烂呢?

    我的一个经验就是,“拿来主义”,尽量不去重复造轮子。使用那些已经被验证过,足够优质的开源库不仅能够让我们的代码变得优雅,还能够让我们在不断的使用过程当中,学习到编程的精髓。

    洋务运动的时候,有一句很响亮的口号叫做,“师夷长技以制夷”。先去用,再去学,自然而然就会变得牛逼。同学们,你们说,是不是这个理?

    我今天推荐的这款开源库,名字叫做 strman-java,GitHub 上标星 1.3k,一款超赞的字符串处理工具库,基于 Java 8,语法非常简洁。

    接下来,我们来看看怎么用。

    Maven 项目只需要在 pom.xml 文件中添加以下依赖即可。

    <dependency>
        <groupId>com.shekhargulati</groupId>
        <artifactId>strman</artifactId>
        <version>0.4.0</version>
    </dependency>
    

    好了,可以肆无忌惮地调用 strman-java 的 API 了。我会在介绍的时候插入一些源码的介绍,方便同学们更深一步的学习,尽量做到“知其然知其所以然”。

    01、append

    把可变字符串参数添加到指定的字符串尾部。

    Strman.append("沉","默","王","二");
    

    结果如下所示:

    沉默王二
    

    append 对应的方法是 prepend,把可变字符串参数前置到指定的字符串前面,使用方法如下。

    Strman.prepend("沉","默","王","二");
    

    结果如下所示:

    默王二沉
    

    02、appendArray

    把字符串数组添加到指定的字符串尾部。

    String [] strs = {"默","王","二"};
    Strman.appendArray("沉",strs);
    

    结果如下所示:

    沉默王二
    

    append 内部其实调用的 appendArray,来看一下源码:

    public static String append(final String value, final String... appends) {
        return appendArray(value, appends);
    }
    

    当使用可变参数的时候,实际上是先创建了一个数组,该数组的大小就是可变参数的个数,然后将参数放入数组当中,再将数组传递给被调用的方法

    通过观察反编译后的字节码,就能看得到。

    Strman.append("沉","默","王","二");
    

    实际等同于:

    Strman.append("沉", new String[]{"默", "王", "二"});
    

    再来看一下 appendArray 方法的源码:

    public static String appendArray(final String value, final String[] appends) {
        StringJoiner joiner = new StringJoiner("");
        for (String append : appends) {
            joiner.add(append);
        }
        return value + joiner.toString();
    }
    

    内部用的 StringJoiner,Java 8 时新增的一个类。构造方法有两种。

    第一种,指定分隔符:

    public StringJoiner(CharSequence delimiter) {
        this(delimiter, "", "");
    }
    

    第二种,指定分隔符、前缀、后缀:

    public StringJoiner(CharSequence delimiter,
                        CharSequence prefix,
                        CharSequence suffix) {
        this.prefix = prefix.toString();
        this.delimiter = delimiter.toString();
        this.suffix = suffix.toString();
    }
    

    虽然也可以在 StringBuilder 类的帮助下在每个字符串之后附加分隔符,但 StringJoiner 提供了更简单的方法来实现,无需编写大量的代码。

    03、at

    获取指定索引处上的字符。

    Strman.at("沉默王二", 0);
    Strman.at("沉默王二", -1);
    Strman.at("沉默王二", 4);
    

    结果如下所示:

    Optional[沉]
    Optional[二]
    Optional.empty
    

    也就是说,at 可以处理 -(length-1)(length-1) 之内的索引(当索引为负数的时候将从末尾开始查找),如果超出这个范围,将会返回 Optional.empty,避免发生空指针。

    来看一下源码:

    public static Optional<String> at(final String value, int index) {
        if (isNullOrEmpty(value)) {
            return Optional.empty();
        }
        int length = value.length();
        if (index < 0) {
            index = length + index;
        }
        return (index < length && index >= 0) ? Optional.of(String.valueOf(value.charAt(index))) : Optional.empty();
    }
    

    本质上,是通过 String 类的 charAt() 方法查找的,但包裹了一层 Optional,就巧妙地躲开了烦人的空指针。

    Optional 是 Java 8 时新增的一个类,该类提供了一种用于表示可选值而非空引用的类级别解决方案。

    04、between

    按照指定起始字符和截止字符来返回一个字符串数组。

    String [] results = Strman.between("[沉默王二][一枚有趣的程序员]","[", "]");
    System.out.println(Arrays.toString(results));
    

    结果如下所示:

    [沉默王二, 一枚有趣的程序员]
    

    来看一下源码:

    public static String[] between(final String value, final String start, final String end) {
        String[] parts = value.split(end);
        return Arrays.stream(parts).map(subPart -> subPart.substring(subPart.indexOf(start) + start.length()))
                .toArray(String[]::new);
    }
    

    java.util.Arrays 类是为数组而生的专用工具类,基本上常见的对数组的操作,Arrays 类都考虑到了,stream() 方法可以将数组转换成流:

    String[] intro = new String[] { "沉", "默", "王", "二" };
    Arrays.stream(intro);
    

    Java 8 新增的 Stream 流在很大程度上提高了开发人员在操作集合(Collection)时的生产力。要想操作流,首先需要有一个数据源,可以是数组或者集合。每次操作都会返回一个新的流对象,方便进行链式操作,但原有的流对象会保持不变。

    map() 方法可以把一个流中的元素转化成一个新流中的元素,它可以接收一个 Lambda 表达式作为参数。Lambda 表达式描述了一个代码块(或者叫匿名方法),可以将其作为参数传递给构造方法或者普通方法以便后续执行。

    考虑下面这段代码:

    () -> System.out.println("沉默王二")
    

    来从左到右解释一下,() 为 Lambda 表达式的参数列表(本例中没有参数),-> 标识这串代码为 Lambda 表达式(也就是说,看到 -> 就知道这是 Lambda),System.out.println("沉默王二") 为要执行的代码,即将“沉默王二”打印到标准输出流。

    toArray() 方法可以将流转换成数组,你可能比较好奇的是 String[]::new,它是什么东东呢?来看一下 toArray() 方法的源码。

    <A> A[] toArray(IntFunction<A[]> generator);
    

    也就是说 String[]::new 是一个 IntFunction,一个可以产生所需的新数组的函数,可以通过反编译字节码看看它到底是什么:

    String[] strArray = (String[])list.stream().toArray((x$0) -> {
        return new String[x$0];
    });
    

    也就是相当于返回了一个指定长度的字符串数组。

    05、chars

    返回组成字符串的单个字符的数组。

    String [] results = Strman.chars("沉默王二");
    System.out.println(Arrays.toString(results));
    

    结果如下所示:

    [沉, 默, 王, 二]
    

    来看一下源码:

    public static String[] chars(final String value) {
        return value.split("");
    }
    

    内部是通过 String 类的 split() 方法实现的。

    06、charsCount

    统计字符串中每个字符出现的次数。

    Map<Character, Long> map = Strman.charsCount("沉默王二的妹妹叫沉默王三");
    System.out.println(map);
    

    结果如下所示:

    {的=1, 默=2, 三=1, 妹=2, 沉=2, 叫=1, 王=2, 二=1}
    

    是不是瞬间觉得这个方法有意思多了,一步到位,统计出字符串中各个字符出现的次数,来看一下源码吧。

    public static Map<Character, Long> charsCount(String input) {
        return input.chars().mapToObj(c -> (char) c).collect(groupingBy(identity(), counting()));
    }
    

    String 类的 chars() 方法是 Java 9 新增的,它返回一个针对基本类型 int 的流:IntStream。

    mapToObj() 方法主要是将 Stream 中的元素进行装箱操作, 转换成一个引用类型的值, 它接收一个 IntFunction 接口, 它是一个 int -> R 的函数接口。

    collect() 方法可以把流转成集合 Map。

    07、collapseWhitespace

    用单个空格替换掉多个连续的空格。

    Strman.collapseWhitespace("沉默王二       一枚有趣的程序员");
    

    结果如下所示:

    Strman.collapseWhitespace("沉默王二       一枚有趣的程序员")
    

    来看一下源码:

    public static String collapseWhitespace(final String value) {
        return value.trim().replaceAll("\\s\\s+", " ");
    }
    

    内部先用 trim() 方法去掉两侧的空格,然后再用正则表达式将多个连续的空格替换成单个空格。

    08、contains

    验证指定的字符串是否包含某个字符串。

    System.out.println(Strman.contains("沉默王二", "沉"));
    System.out.println(Strman.contains("Abbc", "a", false));
    

    结果如下所示: