Guava-连接器Joiner使用和源码分析
- 1.Guava-连接器Joiner使用和源码分析
- 1.1 使用版本
- 1.2 代码示例
- 1.2.1 基本使用
- 1.2.2 集合中Null导致空指针异常
- 1.2.3 忽略集合中的null
- 1.2.4 用默认值代替集合中的null
- 1.2.5 添加至Appendable中
- 1.2.6 连接Map中的key和value
- 1.2.7 使用Stream流进行拼接
- 1.3 源码分析
- 1.3.1 Appendable接口
- 1.3.2 Joiner的属性和构造器
- 1.3.3 join方法源码
- 1.3.4 skipNulls()方法源码
- 1.3.5 useForNull("")方法源码
- 1.3.6 MapJoiner类源码
- 1.4 使用的坑
- 1.4.1 连接器对于Map中的null无能为力
- 1.5 思考
- 1.5.1 Joiner的设计的巧妙点
- 1.5.2 使用原生StringBuilder处理Map
- 1.5.3 对于map容器中Null,如果自己设计如何处理那?
1.Guava-连接器Joiner使用和源码分析
Guava 是一套由Google开源的java库,包含集合、字符串处理、缓存、并发等实用工具,在企业中使用非常广泛,十分学习必要。
1.1 使用版本
<dependency><groupId>com.google.guava</groupId><artifactId>guava</artifactId><version>30.1.1-jre</version>
</dependency>
1.2 代码示例
1.2.1 基本使用
//1.最基本的连接示例
@Test
public void joinerQuickStart(){List<String> stringList = Arrays.asList("java","spring","kafka");String res = Joiner.on("=").join(stringList);Assertions.assertEquals(res, "java=spring=kafka");
}
1.2.2 集合中Null导致空指针异常
//2.集合中Null导致空指针异常
@Test
public void joinerNPE(){List<String> stringList = Arrays.asList("java","spring","kafka",null);try{String res = Joiner.on("=").join(stringList);}catch (NullPointerException e){System.out.println("空指针异常");}
}
1.2.3 忽略集合中的null
//3.忽略集合中的null
@Test
public void joinerSkipNulls(){List<String> stringList = Arrays.asList("java","spring",null,"kafka");String res = Joiner.on("=").skipNulls().join(stringList);Assertions.assertEquals(res, "java=spring=kafka");
}
1.2.4 用默认值代替集合中的null
//4.用默认值代替集合中的null
@Test
public void joinerUseForNull(){List<String> stringList = Arrays.asList("java","spring",null,"kafka");String res = Joiner.on("=").useForNull("default_value").join(stringList);Assertions.assertEquals(res, "java=spring=default_value=kafka");
}
1.2.5 添加至Appendable中
作用:将集合中的元素通过连接器处理后加入到appendable
eg:加入文件输出流中:
//5.添加到本地文件中@Testpublic void joinerAppendToWriter() throws IOException {FileWriter fileWriter = new FileWriter(new File("E://file.txt"));Joiner.on("=").appendTo(fileWriter, Arrays.asList("java", "spring", "kafka"));fileWriter.close();}
1.2.6 连接Map中的key和value
//5.连接Map中的key和value
@Test
public void joinerMap(){ImmutableMap<String, String> map = ImmutableMap.of("java", "后端", "javascript", "前端");//用=连接map中的Entry,用->连接每个entry的key和valueString res = Joiner.on("=").withKeyValueSeparator("->").join(map);Assertions.assertEquals(res, "java->后端=javascript->前端");
}
1.2.7 使用Stream流进行拼接
@Test
public void streamToJoin(){List<String> stringList = Arrays.asList("java", "spring", null,"kafka");String res = stringList.stream().filter(item->item != null).collect(Collectors.joining("="));Assertions.assertEquals(res, "java=spring=kafka");
}
1.3 源码分析
1.3.1 Appendable接口
Appendable含义是可追加的,该接口提供了可追加char序列的操作(append方法),返回值都是对象本身,是链式编程的体现。
public interface Appendable {Appendable append(CharSequence csq) throws IOException;Appendable append(CharSequence csq, int start, int end) throws IOException;Appendable append(char c) throws IOException;
}
两点说明:
- 任何意图从java.util.Formatter接受格式化流的类必须实现该接口
- 是否线程安全尤其实现类决定
eg:StringBuilder和StringBuffer均间接实现了Appendable接口
1.3.2 Joiner的属性和构造器
Joiner中定一个字段separator,用来表示连接符,并且私有化构造方法,对外统一提供获得实例对象的静态方法。
public class Joiner {//连接符private final String separator;//提供静态方法,获取Joiner实例对象public static Joiner on(String separator) {return new Joiner(separator);}public static Joiner on(char separator) {return new Joiner(String.valueOf(separator));}//私有化构造器private Joiner(String separator) {this.separator = (String)Preconditions.checkNotNull(separator);}private Joiner(Joiner prototype) {this.separator = prototype.separator;}
}
1.3.3 join方法源码
join源码最终是使用StringBuilder类和传入集合的迭代器Iterator实现的:
public final String join(Iterator<?> parts) {return this.appendTo(new StringBuilder(), parts).toString();}
底层appendTo方法实现逻辑:遍历容器的迭代器Iterator,然后依次调用Appendable.append方法添加元素和分割符
public <A extends Appendable> A appendTo(A appendable, Iterator<?> parts) throws IOException {Preconditions.checkNotNull(appendable);if (parts.hasNext()) {appendable.append(this.toString(parts.next()));while(parts.hasNext()) {appendable.append(this.separator);appendable.append(this.toString(parts.next()));}}return appendable;}
1.3.4 skipNulls()方法源码
skipNulls()的实现方式根据原Joiner创建一个新的Joiner对象,并重新定义Joiner中新的appendTo()方法(添加了过滤null的功能)
public Joiner skipNulls() {return new Joiner(this) {public <A extends Appendable> A appendTo(A appendable, Iterator<?> parts) throws IOException {Preconditions.checkNotNull(appendable, "appendable");Preconditions.checkNotNull(parts, "parts");//过滤掉集合前面n个null值,append集合中第一个不为null的元素即可退出Object part;while(parts.hasNext()) {part = parts.next();if (part != null) {appendable.append(Joiner.this.toString(part));break;}}//依次append分割符和集合中的元素while(parts.hasNext()) {part = parts.next();if (part != null) {appendable.append(Joiner.this.separator);appendable.append(Joiner.this.toString(part));}}return appendable;}//使用skipNulls就不必使用默认值代替Null值了public Joiner useForNull(String nullText) {throw new UnsupportedOperationException("already specified skipNulls");}//map容器不能使用skipNullspublic Joiner.MapJoiner withKeyValueSeparator(String kvs) {throw new UnsupportedOperationException("can't use .skipNulls() with maps");}};}
1.3.5 useForNull(“”)方法源码
useForNull(“”)的实现原理也是重新返回一个新的Joiner,并且重写toString()方法
public Joiner useForNull(final String nullText) {Preconditions.checkNotNull(nullText);return new Joiner(this) {CharSequence toString(@Nullable Object part) {return (CharSequence)(part == null ? nullText : Joiner.this.toString(part));}
由于appendTo方法中对每一个集合中的元素都调用了toString()方法,即可实现用默认值代替null的效果:
public <A extends Appendable> A appendTo(A appendable, Iterator<?> parts) throws IOException {checkNotNull(appendable);if (parts.hasNext()) {//每次append的时候会对每一个元素调用Joiner对象的toString()方法appendable.append(toString(parts.next()));while (parts.hasNext()) {appendable.append(separator);appendable.append(toString(parts.next()));}}return appendable;}
1.3.6 MapJoiner类源码
MapJoiner类是Joiner类中定义的静态内部类,通过该类实现对Map容器中元素的连接处理
MapJoiner引用了Joiner并且加入和key和value的分割符keyValueSeparator
即可在appendTo方法中使用两种分割符:
①各个entry之间的分割符:separator
②每个entry中key和value的分割符keyValueSeparator
当Joiner对象调用withKeyValueSeparator(“”)方法时,就会new出MapJoiner对象
public Joiner.MapJoiner withKeyValueSeparator(String keyValueSeparator) {return new Joiner.MapJoiner(this, keyValueSeparator);}
MapJoiner的appendTo()方法,是将Map中的Entry对象作为迭代器进行迭代拼接:
public <A extends Appendable> A appendTo(A appendable, Iterator<? extends Entry<?, ?>> parts)throws IOException {checkNotNull(appendable);if (parts.hasNext()) {Entry<?, ?> entry = parts.next();//append第一个enteryappendable.append(joiner.toString(entry.getKey()));appendable.append(keyValueSeparator);appendable.append(joiner.toString(entry.getValue()));//依次append分隔符和entrywhile (parts.hasNext()) {//entry之间的连接符appendable.append(joiner.separator);Entry<?, ?> e = parts.next();appendable.append(joiner.toString(e.getKey()));appendable.append(keyValueSeparator);appendable.append(joiner.toString(e.getValue()));}}return appendable;
}
1.4 使用的坑
1.4.1 连接器对于Map中的null无能为力
如下示例代码NPE:
//MapJoiner对于Map中的null无能为力
@Test
public void MapJoinerNPE(){HashMap<String, String> map = new HashMap<>();map.put("java","后端");map.put(null,null);map.put("javaScript","前端");String res = Joiner.on("=").withKeyValueSeparator("->").join(map);
}
前面提过,skipNulls()方法会将将withKeyValueSeparator()重写为:
@Override
public MapJoiner withKeyValueSeparator(String kvs) {throw new UnsupportedOperationException("can't use .skipNulls() with maps");
}
需要在使用Joiner处理map容器时注意
1.5 思考
1.5.1 Joiner的设计的巧妙点
(1)静态内部类MapJoiner
将MapJoiner设计为Joiner的静态内部类,对外仅提供Joiner类的API,让使用者处理两种容器(Collection和Map)的连接无任何区别,对使用者友好。
(2)链式操作重新new Joiner对象
例如useForNull(“”)操作,其对Joiner对象的影响仅仅是toString方法改变了,因此通过返回重写了方法的Joiner对象实现不同的处理。其实就是一种策略模式的体现,对于使用不同api后,提供不同的实现策略,避免了代码中大量的if/else。
1.5.2 使用原生StringBuilder处理Map
作用:跳过map中key和value为null的Entry
//9.使用StringBuilder连接有null的Map
@Test
public void userStringBuilderSolveNullMap(){//Entry之间的分割符String separator = "->";//每个Entry中key和value的分割符String keyValueSeparator = ":";HashMap<String, String> map = new HashMap<>();map.put(null,"aaa");map.put("aaa",null);map.put("java","后端");map.put(null,null);map.put("javaScript","前端");StringBuilder sb = new StringBuilder();Iterator<String> iterator = map.keySet().iterator();//append第一个不为null的元素while (iterator.hasNext()){String next = iterator.next();if (next != null && map.get(next)!=null) {sb.append(next);sb.append(keyValueSeparator);sb.append(map.get(next));break;}}//依次append元素和连接符while (iterator.hasNext()){String next = iterator.next();if (next != null && map.get(next)!=null) {sb.append(separator);sb.append(next);sb.append(keyValueSeparator);sb.append(map.get(next));}}Assertions.assertEquals(sb.toString(),"java:后端->javaScript:前端");
}
1.5.3 对于map容器中Null,如果自己设计如何处理那?
若JoinerMap要增加处理null的能力,需要定义函数:
skipKeyNulls()、skipValueNulls()、useForKeyNull(“”)、userForValueNull(“”)方法
TODO:大家觉得如何实现这几个方法那?