Guava中基础工具类Joiner的使用&字符串拼接方法 joiner.on

Guava 中有一些基础的工具类,如下所列:

1,Joiner 类:根据给定的分隔符把字符串连接到一起。MapJoiner 执行相同的操作,但是针对 Map 的 key 和 value。

2,Splitter 类:与 Joiner 操作相反的类,是根据给定的分隔符,把一个字符串分隔成若个子字符串。

3,CharMatcher,Strings 类:对字符串通用的操作,例如移除字符串的某一部分,字符串匹配等等操作。

4,其他类:针对Object操作的方法,例如 toString 和 hashCode 方法等。

Joiner是guava.jar包下的一个类,将数组,集合,map等类型用指定的字符进行分割。

Joiner的用法

<!-- https://mvnrepository.com/artifact/com.google.guava/guava -->
<dependency>
  <groupId>com.google.guava</groupId>
  <artifactId>guava</artifactId>
  <version>23.0</version>
</dependency>

1.对数组进行分割-----join

@Test
public void testStringJoin() {
  String str[] = { "aaa", "vbbb", "ccc", "ddd" };
  String ss = Joiner.on("==").join(str);

  System.out.println(ss);//aaa==vbbb==ccc==ddd
}

2.对List进行分割,替换集合中的Null值—useForNull

@Test
public void testUseForNull() {
  List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, null, 6);

  String str = Joiner.on(";").useForNull("*").join(list);

  System.out.println(str);//1;2;3;4;5;*;6
}

3.对List进行分割,消除集合中的Null值----skipNulls

@Test
public void testSkipNulls() {
  List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, null, 6);

  String str = Joiner.on("==").skipNulls().join(list);

  System.out.println(str);//1==2==3==4==5==6
}

4.对StringBuilder或StringBuffer进行追加----appendTo

@Test
public void testAppendTo() {
  StringBuilder stringBuilder = new StringBuilder("aaa_");
  List<String> strs = Arrays.asList("bbb", "ccc", "ddd");

  StringBuilder sb = Joiner.on("_").appendTo(stringBuilder, strs);

  System.out.println(sb.toString());//aaa_bbb_ccc_ddd
}

5.对Map进行分割-----withKeyValueSeparator

@Test
public void testMapJoiner() {
  Map<String, String> map = new HashMap<String, String>();
  map.put("name", "张三");
  map.put("age", "13");
  map.put("sex", "M");
  String str = Joiner.on("&").withKeyValueSeparator("=").join(map);

  System.out.println(str);//sex=M&name=张三&age=13
}

使用 Joiner 类

我们通常根据指定的分隔符来连接字符串是这样做的。

 public String buildString(List<String> stringList, String delimiter){
        StringBuilder builder = new StringBuilder();
          for (String s : stringList) {
               if(s != null){
                    builder.append(s).append(delimiter);
              }
          }
          builder.setLength(builder.length() – delimiter.length());
          return builder.toString();
}

上面的代码注意的一点就是我们要移除字符串最后的一个分隔符。虽然不难,但是很无聊,下面用 Joiner 类来实现同样的功能:

Joiner.on("|").skipNulls().join(stringList); // 默认使用“|”作为分隔符

是不是很简洁,如果你想替换为 null 的字符串,使用下面的方法:

Joiner.on("|").useForNull("no value").join(stringList);

需要注意的是,Joiner 不仅可以操作字符串,还可以是数组,迭代器,可变参数。Joiner 类是不可变类,因此是线程安全的,它可以处理 static final 的变量,考虑到这一点,我们看下面的代码:

 Joiner stringJoiner = Joiner.on("|").skipNulls();
 //the useForNull() method returns a new instance of the Joiner!
 stringJoiner.useForNull("missing");
 stringJoiner.join("foo","bar",null);

在上面的代码中,useForNull()方法没有起作用,null值还是被忽略了。

Joiner 不仅可以返回string ,还有方法能够处理StringBuilder类:

StringBuilder stringBuilder = new StringBuilder();
Joiner joiner = Joiner.on("|").skipNulls();
//returns the StringBuilder instance with the values foo,bar,baz appeneded with "|" delimiters
joiner.appendTo(stringBuilder,"foo","bar","baz");

只要是实现了 Appendble 接口的类都可以用 Joiner 来处理。

FileWriter fileWriter = new FileWriter(new File("path")):
List<Date> dateList = getDates();
Joiner joiner = Joiner.on("#").useForNulls(" ");
//returns the FileWriter instance with the values appended into it
joiner.appendTo(fileWriter,dateList);

上面的例子中,我们传递了 FileWriter 实例 和 Data 对象给 Joiner,Joiner 将连接list中所有的数据给 FileWriter 实例并返回。

如上所示,Joiner 是一个非常有用的类,很容易处理日常的一些任务。还有一个特殊的方法,它和 Joiner 的工作方式是一样,但是它连接的是根据指定的分隔符连接 Map 的 key 和 value。

mapJoiner = Joiner.on("#").withKeyValueSeparator("=");

下面的单元测试类展示了如何连接 Map 的 key-value。

Map<String, String> testMap = Maps.newLinkedHashMap();
    testMap.put("Washington D.C", "Redskins");
    testMap.put("New York City", "Giants");
    testMap.put("Philadelphia", "Eagles");
    testMap.put("Dallas", "Cowboys");
    String returnedString = Joiner.on("#").withKeyValueSeparator("=").join(testMap);

    System.out.println(returnedString);

运行结果:Washington D.C=Redskins#New York City=Giants#Philadelphia=Eagles#Dallas=Cowboys

使用示例

以下参考:官方文档

开发过程中,用分隔符连接字符串序列可能是一个比较繁琐的过程,但本不应该如此。Joiner 可以简化这个操作。

如果序列中包含 null 值,那么可以使用 Joiner 跳过 null 值:

    // 跳过 null 值
    result = Joiner.on("; ").skipNulls().join("Harry", null, "Ron", "Hermione");
    Assert.assertEquals(result, "Harry; Ron; Hermione");

也可以通过 useForNull(String) 来将 null 值替换为指定的字符串。

    // 替换 null 值
    result = Joiner.on("; ").useForNull("null").join("Harry", null, "Ron", "Hermione");
    Assert.assertEquals(result, "Harry; null; Ron; Hermione");

同样可以在对象上使用 Joiner,最终会调用对象的 toString() 方法。

    // 使用在对象上,会调用对象的 toString() 函数
    result = Joiner.on(",").join(Arrays.asList(1, 5, 7));
    Assert.assertEquals(result, "1,5,7");

对于 Map ,可以使用这样的代码:

    // MapJoiner 的使用,将 map 转换为字符串
    Map map = ImmutableMap.of("k1", "v1", "k2", "v2");
    result = Joiner.on("; ").withKeyValueSeparator("=").join(map);
    Assert.assertEquals(result, "k1=v1; k2=v2");

源码分析

初始化方法

Joiner 的构造方法被设置成了私有,需要通过静态的 on(String separator) 或者 on(char separator) 函数初始化。

拼接基本函数

Joiner 了中最为核心的函数就是 A appendTo(A appendable, Iterator parts)。作为全功能函数,其它所有的字符串拼接最终都会调用这个函数。

  public <A extends Appendable> A appendTo(A appendable, Iterator<?> parts) throws IOException {
    checkNotNull(appendable);
    if (parts.hasNext()) {
      appendable.append(toString(parts.next()));
      while (parts.hasNext()) {
        appendable.append(separator);
        appendable.append(toString(parts.next()));
      }
    }
    return appendable;
  }

这段代码的分析如下:

  • 这里的 Appendable 源码中传入的是实现该接口的 StringBuilder
  • 因为是公共方法,无法保证 appendable 值不为空,所以要先检查该值是否为空。
  • if ... while ... 的结构确保末尾不会添加多余的分隔符。
  • 通过本地 toString 方法,而不是直接调用对象的 toString 方法,这种做法提供了空指针保护。

不可能发生的异常

在源码中,有个地方的处理值得关注一下:

   public StringBuilder appendTo(StringBuilder builder, Iterator<? extends Entry<?, ?>> entries) {
      try {
        appendTo((Appendable) builder, entries);
      } catch (IOException impossible) {
        throw new AssertionError(impossible);
      }
      return builder;
    }

这里之所以 IOException 的变量名取名为 impossible 是因为:虽然 Appendable 接口的 append 方法会抛出 IOException,但是传入的 StringBuilder 在实现的时候并不会抛出改异常,所以为了适应这个接口,这里不得不捕捉异常。这样捕捉后的断言处理也就可以理解了。

巧妙的可变长参数转换

有一个添加的重载函数如下所示:

 public final <A extends Appendable> A appendTo(
      A appendable,  Object first,  Object second, Object... rest)
      throws IOException {
    return appendTo(appendable, iterable(first, second, rest));
  }

其中 iterable 函数将参数变为一个可以迭代的序列,该函数如下所示。

private static Iterable<Object> iterable(
      final Object first, final Object second, final Object[] rest) {
    checkNotNull(rest);
    return new AbstractList<Object>() {
      
      public int size() {
        return rest.length + 2;
      }

      
      public Object get(int index) {
        switch (index) {
          case 0:
            return first;
          case 1:
            return second;
          default:
            return rest[index - 2];
        }
      }
    };
  }

通过实现 AbstractList 的方式,巧妙地复用了编译器生成的数组,减少了对象创建的开销。这样的实现需要对迭代器有深入的了解,因为要确保实现能够满足迭代器接口各个函数的语义。

Joiner 二次创建

因为 Joiner 创建后就是不可更改的了,所以为了实现 useForNullskipNulls 等语义,源码会再次创建一个匿名类,并覆盖相应的方法。

useForNull 函数汇中为了防止重复调用 useForNullskipNulls,还特意覆盖了这两个方法,一旦调用就抛出运行时异常。为什么不能重复调用 useForNull ?因为覆盖了 toString 方法,而覆盖实现中需要调用覆盖前的 toString

  public Joiner useForNull(final String nullText) {
    checkNotNull(nullText);
    return new Joiner(this) {
      
      CharSequence toString( Object part) {
        return (part == null) ? nullText : Joiner.this.toString(part);
      }

      
      public Joiner useForNull(String nullText) {
        throw new UnsupportedOperationException("already specified useForNull");
      }

      
      public Joiner skipNulls() {
        throw new UnsupportedOperationException("already specified useForNull");
      }
    };
  }

skipNulls 函数实现如下所示。个人比较奇怪的是 skipNulls 中为什么不禁止重复调用 skipNulls 函数。

  public Joiner skipNulls() {
    return new Joiner(this) {
      
      public <A extends Appendable> A appendTo(A appendable, Iterator<?> parts) throws IOException {
        checkNotNull(appendable, "appendable");
        checkNotNull(parts, "parts");
        while (parts.hasNext()) {
          Object part = parts.next();
          if (part != null) {
            appendable.append(Joiner.this.toString(part));
            break;
          }
        }
        while (parts.hasNext()) {
          Object part = parts.next();
          if (part != null) {
            appendable.append(separator);
            appendable.append(Joiner.this.toString(part));
          }
        }
        return appendable;
      }

      
      public Joiner useForNull(String nullText) {
        throw new UnsupportedOperationException("already specified skipNulls");
      }

      
      public MapJoiner withKeyValueSeparator(String kvs) {
        throw new UnsupportedOperationException("can't use .skipNulls() with maps");
      }
    };
  }

Java字符串拼接写法 joiner.on

1、 joiner.on

String result = Joiner.on(",").join(list);

这种写法最简单,直接Joiner.on 拼接 “,” “#” “、”_" “-” 之类的

也是最常用的方法

2、 StringBuilder

StringBuilder strBur = new StringBuilder();
list.forEach(val -> {
    strBur.append(val).append("#");
});
strBur.toString();

这种就是平常StringBuffer的写法,一个个遍历循环的去append添加

3、Java8Stream的写法

String result = list.stream().collect(Collectors.joining("_"));
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Archie_java

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值