拼接guava是个风火轮之基础工具(1)-java开发java经验技巧

java开发工具  时间:2021-02-26  阅读:()

Gu ava是个风火轮Z基础工具(1)-编程开发技术

Guava是个风火轮之基础工具(1)

原文出处潘家邦的博客

・ 、 F4a

刖5

Guava是Java开发者的好朋友。虽然我在开发屮使用Guava很长时间了 Guava API的身影遍及我写的生产代码的每个角落但是我用到的功能只是

Guava的功能集中一个少的可怜的真子集更别说我一直没有吋间认真的去挖掘Guava的功能没有时间去学习Guava的实现。直到最近我开始阅读^GettingStarted withGoogle Gua va,感觉有必耍将我学习和使用Guava的一些东四记录下来。

Joiner

我们经常需要将几个字符串或者字符串数组、列表之类的东西拼接成一个以指定符号分隔各个元素的字符串比如把[1, 2, 3]拼接成“1 2 3” 。

在Python中我只需要简单的调用str. join函数就可以了就像这样。

' ' ・ join(map(str, [1, 2, 3] ) )

到了Java屮如果你不知道Guava的存在基木上就得手写循环去实现这个功能代码瞬间变得±1陋起來。

Guava为我们提供了一套优雅的API,让我们能够轻而易举的完成字符串拼接这一简单任务。还是上面的例子借助Guava的Joiner类,代码瞬间变得优雅起来。Join er. on(' ' ) . jo in(l, 2, 3) ;

被拼接的对象集可以是硬编码的少数几个对象可以是实现了Tterable接口的集合也可以是迭代器对象。

除了返回一个拼接过的字符串 Joiner述可以在实现了Appendable接口的对象所维护的内容的末尾追加字符串拼接的结果。

Str in gBuilder sb二new Str in gBuilder ( zresult:/z) ;

Joiner, on ( z “) . appendTo(sb, 1, 2, 3) ;

System.out.println(sb) ;//rcsult: 1 2 3

Guava对空指针冇着严格的限制如果传入的对象屮包含空指针Joiner会直接抛出NPEo与此同时 Joiner提供了两个方法让我们能够优雅的处理待拼接集合屮的空

指针。

如果我们希望忽略空指针那么可以调用skipNulls方法得到一个会跳过空指针的Joiner实例。如果希望将空指针变为某个指定的值那么可以调用useForNull方法指定用来替换空指针的字符串。

Joiner. on( ' ) ・ skipNulls() ・ join(l, null, 3) ;//l 3

Join er. on(' ' ) . useForNull ("Non e〃 ) .jo in(l, null, 3) ;//l None 3

需要注意的是,Joiner实例是不可变的,skipNulls和useForNul 1都不是在原实例上修改某个成员变量而是生成一个新的Joiner实例。

Joiner. Mapjoiner

Mapjoiner是Joiner的内部静态类用于帮助将Map对彖拼接成字符串。

Joiner. on( . withKeyValueSeparator( 〃二“) .join(ImmutableMap .of仃 2, 3,

4) ) ;//l二2#3二4withKeyValueScparator方法指定了键与值的分隔符同时返回一个Mapjoiner实例。有些家伙会往Map里插入键或值为空指针的键值对如果我们要拼接这种Map,千万记得要用useForNull对Mapjoiner做保护不然NPE妥妥的。

源码分析

源码來口Guava 1& 0 。 Joiner类的源码约450行其中大部分是注释、函数重载常用手法是先实现一个包含完整功能的函数然后通过各种封装把不常用的功能隐藏起来提供优雅简介的接口。这样子的好处显而易见用户可以使用简单接口解决80%的问题那些罕见而复杂的需求交给全功能函数去支持。

初始化方法

由于构造函数被设置成了私冇Joiner只能通过Joiner#on函数来初始化。最基础的Joiner#on接受一个字符串入参作为分隔符而接受字符入参的Joiner# 。 n方法是前者的重载内部使用StringttvalueOf函数将字符变成字符串后调用前者完成初始化。或许这是一个利于字符串内存回收的优化。

追加拼接结果整个Joiner类最核心的函数莫过于 〈A extends Appendable>Joiner#appendTo(A, lterator<?» ,一切的字符串拼接操作最后都会调用到这个函数。这就是所谓的全功能函数其他的一切appendTo只不过是它的重载,一切的join不过是它和它的重载的封装。public <A extends Appendable> A appendTo(A appendable, Iterator<?> parts) throwsIOExccption {checkNotNull (appendable) ;if (parts. hasNext() ) {appendable. append(loString(parts. next () ) ) ;while (parts.hasNextO) { appendablc. append(scparator) ; appendable, append

(toString(parts. next () ) ) ;

}

}return appendable;

}

这段代码的笫一个技巧是使用if和while来实现了比较优雅的分隔符拼接避免了在末尾插入分隔符的尴尬第二个技巧是使用了自定义的toString方法而不是Object#toString来将对象序列化成字符串为后续的各种空指针保护开了方便之门。注意到一个比较有意思的appendTo重载。public final StringBuilder appendToCStringBuilder builder, Iterator<?> parts) {try {appendTo( (Appendable) builder, parts) ;

} catch (IOExccption impossible) {throw new AssertionError(impossible) ;

}return builder;

}

在Appendable接口屮 append方法是会抛出lOException的。然而StringBuilder虽然实现了Appendable,但是它覆盖实现的append方法却是不抛出lOException的。于是就出现了明知不可能抛异常却乂不得不去捕获异常的尴尬。

这里的异常处理手法十分机智异常变量命名为impossible,我们一看就明白这里是不会抛出IOExccption的。但是如杲catch块里面什么都不做又好像不合适于是抛出一个AssertionError,表示对于这里不抛异常的断言失败了。

另一个比较有意思的appendTo重载是关于可变长参数。public final <A cxtcnds Appcndablc> A appcndTo(

Aappendable, @Nullable Object first, @Nullable Object second, Object. . . rest)throws IOException {return appendTo(appendoble, iterable(first, second, rest) ) ;

}

注意到这里的iterable方法它把两个变量和一个数组变成了一个实现了Iterable接口的集合,手法精妙!private static Iterable<Object> iterable(final Object first, final Object second, final Object[] rest)

{ chcckNotNull (rest) ;return new AbstractList<Object>() {

©Override public int size() {return rest, length + 2;

}

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

}

}

} ;

}

如果是我来实现可能是简单粗暴的创建一个ArrayList的实例然后把这两个变量一个数组的全部元索放到ArrayIJst里面然后返回。这样子代码虽然短了但是代价却不小为了一个小小的重载调用而产生了0(n)的时空复杂度。

看看人家G社的做法。要想写岀这样的代码需要熟悉顺序表迭代器的实现。迭代器内部维护着一个游标 cursoro迭代器的两大关键操作 hasNext判断是否还有没遍历的元素 next获取下一个元素它们的实现是这样的。public boolean hasNext() {return cursor != sizc() ;public E next () { checkForComodi fi cati on() ;try {int i = cursor;

E next = get (i) ;lastRet二i;cursor二i + 1;rcturn next;

} catch (IndexOutOfBoundsException e) {checkForComodif ication() ;throw new NoSuchElementException() ;

}

}hasNext屮关键的函数调用是size,获取集合的大小。 next方法屮关键的函数调用是get,获取第i个元素oGuava的实现返冋了一个被覆盖了size和get方法的AbstractList,巧妙的复用了由编译器生成的数组避免了新建列表和増加元素的开销。

空指针处理

当待拼接列表中可能包含空指针时我们用useForNul l将空指针替换为我们指定的字符串。它是通过返回一个覆盖了方法的Joiner实例来实现的。public Joiner useForNul1 (final String nullText) {chcckNotNull (nullTcxt) ;return new Joiner(this) {

©Override CharSequenee toString (©Nullable Object part) {return (part二二null) ? rmllText : Joiner, this. toString(part) ; }

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

}

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

}

} 

}

首先是使用复制构造函数保留先前初始化吋候设置的分隔符然后覆盖了之前提到的toString方法。为了防止重复调用useForNull和skipNulls,还特意覆盖了这两个方法一旦调用就抛出运行时异常。为什么不能重复调用useForNull ?因为覆盖了toString方法而覆盖实现屮需要调用覆盖前的toStringo在不支持的操作中抛出UnsupportedOperat ionExcept ion是Guava的常见做法可以在第一时间纠正不科学的调用方式。skipNulls的实现就相对要复杂一些覆盖了原先全功能appendTo中使用if和while的优雅实现变成了2个wh i l e先后执行。第一个wh i l e找到第一个不为空指针的元素起到之前的if的功能笫二个while功能和之前的一致。

public Joiner skipNul1s() {return new Joincr(this) {

©Override public <A extends Appendable> A appendTo(A appendable, Iterator<?>parts)throws IOException {checkNotNull (appendable, 〃 appendable〃 ) ;chcckNotNull (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)

{appendablc. appcnd (separator) ;appendable.append (Joiner, this. toString(part) ) ;

}

}return appendable;

}

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

}

©Override public Mapjoiner withKeyValueSeparator(String kvs) { throw newUnsupportedOperationException(//can t use . skipNulls() with maps") ;

 

拼接键值对

Mapjoiner实现为Joiner的一个静态内部类它的构造函数和JoineT样也是私冇只能通过Joiner#wi thKeyValueSepeirdtor來生成实例。类似地 Mapjoincr也实现了appendTo方法和一系列的重载还用join方法对appendTo做了封装。Mapjoiner整个实现和Joiner大同小异,在实现中大量使用Joiner的toString方法来保证空指针保护彳亍为和初始化时的语义一致。

Mapjoiner也实现了一个useForMull方法这样的好处是在获取Mapjoiner之后再去设置空指针保护和获取Mapjoiner之前就设置空指针保护是等价的用户无需去关心顺序问题。

硅云香港CN2+BGP云主机仅188元/年起(香港云服务器专区)

硅云怎么样?硅云是一家专业的云服务商,硅云的主营产品包括域名和服务器,其中香港云服务器、香港云虚拟主机是非常受欢迎的产品。硅云香港可用区接入了中国电信CN2 GIA、中国联通直连、中国移动直连、HGC、NTT、COGENT、PCCW在内的数十家优质的全球顶级运营商,是为数不多的多线香港云服务商之一。目前,硅云香港云服务器,CN2+BGP线路,1核1G香港云主机仅188元/年起,域名无需备案,支持个...

Atcloud:全场8折优惠,美国/加拿大/英国/法国/德国/新加坡vps,500g大硬盘/2T流量/480G高防vps,$4/月

atcloud怎么样?atcloud刚刚发布了最新的8折优惠码,该商家主要提供常规cloud(VPS)和storage(大硬盘存储)系列VPS,其数据中心分布在美国(俄勒冈、弗吉尼亚)、加拿大、英国、法国、德国、新加坡,所有VPS默认提供480Gbps的超高DDoS防御。Atcloud高防VPS。atcloud.net,2020年成立,主要提供基于KVM虚拟架构的VPS、只能DNS解析、域名、SS...

NameCheap优惠活动 新注册域名38元

今天上午有网友在群里聊到是不是有新注册域名的海外域名商家的优惠活动。如果我们并非一定要在国外注册域名的话,最近年中促销期间,国内的服务商优惠力度还是比较大的,以前我们可能较多选择海外域名商家注册域名在于海外商家便宜,如今这几年国内的商家价格也不贵的。比如在前一段时间有分享到几个商家的年中活动:1、DNSPOD域名欢购活动 - 提供域名抢购活动、DNS解析折扣、SSL证书活动2、难得再次关注新网商家...

java开发工具为你推荐
文件夹删不掉为什么文件夹会删不掉?解压程序下RAR那个解压软件推广方法营业推广的方式有哪些真正免费的网络电话谁知道哪个真正免费的网络电话啊?告诉我把3?太感谢了依赖注入什么是侵入性?还有依赖注入?今日热点怎么删除今日热点怎么卸载删除 今日热点新闻彻底卸载删办公协同软件oa办公系统软件有哪些神雕侠侣礼包大全神雕侠侣手游每天送的元宝买什么合适2012年正月十五2012年正月十五 几月几号ios系统苹果手机的系统是什么?
asp虚拟空间 黑龙江域名注册 网站备案域名查询 cloudstack la域名 商家促销 亚洲小于500m 150邮箱 毫秒英文 泉州移动 美国堪萨斯 支付宝扫码领红包 银盘服务是什么 in域名 阿里云邮箱登陆 江苏徐州移动 美国主机侦探 restart 月付空间 性能测试工具 更多