Guava 是一套由Google开源的java库,包含集合、字符串处理、缓存、并发等实用工具,在企业中使用非常广泛,十分学习必要。
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>30.1.1-jre</version>
</dependency>
//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");
}
//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("空指针异常");
}
}
//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");
}
//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");
}
作用:将集合中的元素通过连接器处理后加入到appendable
eg:加入文件输出流中:
//5.添加到本地文件中
@Test
public void joinerAppendToWriter() throws IOException {
FileWriter fileWriter = new FileWriter(new File("E://file.txt"));
Joiner.on("=").appendTo(fileWriter, Arrays.asList("java", "spring", "kafka"));
fileWriter.close();
}
//5.连接Map中的key和value
@Test
public void joinerMap(){
ImmutableMap<String, String> map = ImmutableMap.of("java", "后端", "javascript", "前端");
//用=连接map中的Entry,用->连接每个entry的key和value
String res = Joiner.on("=").withKeyValueSeparator("->").join(map);
Assertions.assertEquals(res, "java->后端=javascript->前端");
}
@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");
}
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;
}
两点说明:
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;
}
}
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;
}
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容器不能使用skipNulls
public Joiner.MapJoiner withKeyValueSeparator(String kvs) {
throw new UnsupportedOperationException("can't use .skipNulls() with maps");
}
};
}
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;
}
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第一个entery
appendable.append(joiner.toString(entry.getKey()));
appendable.append(keyValueSeparator);
appendable.append(joiner.toString(entry.getValue()));
//依次append分隔符和entry
while (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;
}
如下示例代码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)静态内部类MapJoiner
将MapJoiner设计为Joiner的静态内部类,对外仅提供Joiner类的API,让使用者处理两种容器(Collection和Map)的连接无任何区别,对使用者友好。
(2)链式操作重新new Joiner对象
例如useForNull(“”)操作,其对Joiner对象的影响仅仅是toString方法改变了,因此通过返回重写了方法的Joiner对象实现不同的处理。其实就是一种策略模式的体现,对于使用不同api后,提供不同的实现策略,避免了代码中大量的if/else。
作用:跳过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:前端");
}
若JoinerMap要增加处理null的能力,需要定义函数:
skipKeyNulls()、skipValueNulls()、useForKeyNull(“”)、userForValueNull(“”)方法
TODO:大家觉得如何实现这几个方法那?