1、Guava-连接器Joiner使用和源码分析

article/2025/9/11 9:02:04

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:大家觉得如何实现这几个方法那?


http://chatgpt.dhexx.cn/article/BW6k0VGR.shtml

相关文章

如何在Linux中使用命令行卸载软件

所有的Linux系统中都带有包管理器命令&#xff0c;比如Ubuntu发行版里的dpkg命令&#xff0c;CentOS/RHEL发行版里带的yum和rpm命令&#xff0c;下面我就以自己比较熟悉的CentOS/RHEL系列发行版来讲解&#xff0c;以nginx这个常用的web软件为例。 查看系统上是否安装了nginx …

Linux如何使用命令行卸载安装包

严格地说&#xff0c;Linux是内核。Linux发行版由Linux内核、安装脚本、shell、编译器、桌面和其他组件组成。因此&#xff0c;卸载包或软件的Linux命令取决于Linux发行版的名称和类型。本文说明如何使用命令行在各种Linux发行版上卸载软件包或软件。 首先&#xff0c;您需要找…

Linux中使用rpm命令卸载软件

1、先使用rpm -qa | grep 软件包名称 例如卸载mysql&#xff1a; rpm -qa | grep mysql 2、使用rpm -e --nodeps 文件包名称 rpm -e --nodeps mysql-5.0.77-4.el5_6.6 rpm -e --nodeps libdbi-dbd-mysql-0.8.1a-1.2.2 rpm -e --nodeps mysql-5.0.77-4.el5_6.6 rpm -e --node…

Linux下如何彻底删除(卸载)MySQL?

首先连接操作系统&#xff0c;切换到root用户。 如果是使用yum安装的mysql&#xff0c;使用如下命令进行卸载&#xff08;不能确定使用何种方式安装的mysql情况下&#xff0c;按后续步骤一一进行处理即可&#xff09;&#xff1a; yum remove mysql mysql-server mysql-libs c…

Linux rpm命令详解,Linux安装、卸载、更新软件

「作者主页」&#xff1a;士别三日wyx 「作者简介」&#xff1a;CSDN top100、阿里云博客专家、华为云享专家、网络安全领域优质创作者 rpm命令详解 一、常用操作1、安装软件1.1、默认安装路径 2、更新软件3、卸载软件4、查询已经安装的软件 二、实用技巧三、rpm包命名规则 作用…

Linux 卸载程序

windows中安装了软件&#xff0c;可以通过图形化界面很方便的卸载&#xff0c;那么linux命令行的方式如何卸载程序呢&#xff1f; 首先看看安装了哪些程序 rpm -q -a #查询所有已安装的软件-q:query 查询 -a:all 所有 查询出了所有已安装的程序 所以需要过滤多自己需要卸载…

Linux软件的卸载

1.安装说明 configure作用:是源码安装软件时配置环境用的 他根据你的配置选项和你的系统情况生成makefile文件 为make 做准备。 最常用的参数: ./configure –prefix 作用: 不指定prefix&#xff0c;则可执行文件默认放在/usr/local/bin&#xff0c;库文件默认放在/usr/local…

Linux 卸载软件

一、卸载软件 1.输入命令 dpkg --list 查看已安装的软件 记住要卸载的软件的名字 sudo apt-get remove –-purge 要卸载的软件的名字 #卸载软件同时删除配置文件 sudo apt-get remove 要卸载的软件的名字 #卸载该软件 出现此界面为卸载并删除配置文件成功。 二、apt缓存删…

【Ubuntu】【Linux】命令卸载软件

Ubuntu命令卸载软件_李柏林的博客-CSDN博客_ubuntu卸载程序1.打开一个终端&#xff0c;输入dpkg --list ,按下Enter键&#xff0c;终端输出以下内容&#xff0c;显示的是你电脑上安装的所有软件。 2.在终端中找到你需要卸载的软件的名称&#xff0c;列表是按照首字母排序的。 3…

用sizeof来计算数组元素个数

一般大家常见的是用sizeof来求不同数据类型的空间大小&#xff0c;如&#xff1a; 但是我们也可以用其来计算数组元素个数&#xff0c;如下&#xff1a; 从数据可得该数组元素个数为8个。

详解strlen和sizeof在数组中的使用(四)

目录 一、前言 二、sizeof在指针中的试题&#xff1a; 解析&#xff1a; vs编辑器验证&#xff1a; 32位平台&#xff1a; 64位平台&#xff1a; 一、前言 前面一节我们已经讲过了有关于strlen在字符数组中的使用&#xff0c;以及strlen使用时候的注意事项 譬如只能在字符…

C++基础(十五)sizeof的用法 计算数组长度

sizeof本身是C语言的一个运算符&#xff0c;但也被C支持&#xff0c;且很多C代码中经常会出现。 sizeof可以很容易计算一个数组的长度&#xff0c;这在数组作为参数的函数中很有用(数组作为函数参数&#xff0c;传入的其实是首元素的地址&#xff0c;必须带上数组的实际长度作…

C语言中 sizeof(数组名) 和 strlen(数组名) 的意义

数组名是什么呢&#xff1f; 数组名通常来说是数组首元素的地址 但是有2个例外&#xff1a; 1. sizeof(数组名)&#xff0c;这里的数组名表示整个数组&#xff0c;计算的是整个数组的大小 2. &数组名&#xff0c;这里的数组名表示整个数组&#xff0c;取出的是整个数组的地…

c语言在函数中使用sizeof求数组长度

c语言sizeof的重要用途之一就是求数组长度&#xff0c;但是sizeof必须在数组定义的位置使用。不能作为参数传入函数并在函数中使用sizeof&#xff1b; 比如下边这个例子 函数中打印结果为&#xff1b; 数组的长度应该是5但是结果却是2&#xff0c;这是为什么呢&#xff1f;因为…

sizeof 数组与指针

sizeof的定义&#xff1a; sizeof是C/C中的一个操作符&#xff08;operator&#xff09;&#xff0c;简单的说其作用就是返回一个对象或者类型所占的内存字节数。 MSDN上的解释为&#xff1a; The sizeof keyword gives the amount of storage, in bytes, associated with a…

sizeof数组名

数组名到底是不是指针&#xff1f; 数组名在某些使用场合的时候的确可以看做指针处理&#xff0c;之前用c52开发的时候碰到一个问题&#xff0c;大概的意思就是sizeof&#xff08;函数指针数组名&#xff09;/ sizeof&#xff08;数组首元素[0]&#xff09;&#xff0c;结果有…

详解strlen和sizeof在数组中的使用(二)

目录 一、前言 二、sizeof在字符数组中的题目 这整题sizeof的答案是&#xff1a; 32位平台下&#xff1a; 64位平台下&#xff1a; ​ 一、前言 前面我们已经提到了sizeof在整形数组的使用&#xff0c;因为strlen只能在字符数组中使用&#xff0c;所以上一 没涉及strlen…

4.1 数组

数组是用来存储多个相同类型数据的内存分配方法。 1.语法&#xff1a;元素数据类型 数组名[元素个数] [初始值] 例如&#xff1a;int a[5] {1,2,3,4,5} 2.多个、相同类型 3.连续的内存区域 4.数组名是数组首元素的符号地址&#xff0c;即数组的首地址 5.数组元素就是数组…

stm32外部中断实验

目录 &#xff08;一&#xff09;外部中断简介 3.GPIO 跟中断线的映射关系图​ &#xff08;二&#xff09;软件设计 1.函数说明 三&#xff1a;代码 exti.c main.c &#xff08;一&#xff09;外部中断简介 1.实验效果&#xff1a;通过板载的 4 个按键&#xff0c;控制…

stm32学习笔记-外部中断

文章目录 1、stm32f103外部中断控制器EXTI。1.1外部中断的映像1.2 外部中断/事件的框图1.3 外部中断的编程。外部中断配置思路相关寄存器相关库函数 1.4 按键中断实例。 2、按键按下的触发方式编程思路2.1 支持连续按下2.2 不支持连续 2.3 连续&#xff0c;不连续二合一遇到的问…