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之前就设置空指针保护是等价的用户无需去关心顺序问题。
前些天赵容分享过DogYun(狗云)香港BGP线路AMD 5950X经典低价云服务器的信息(点击查看),刚好账户还有点余额够开个最低配,所以手贱尝试下,这些贴上简单测试信息,方便大家参考。官方网站:www.dogyun.com主机配置我搞的是最低款优惠后14.4元/月的,配置单核,512MB内存,10GB硬盘,300GB/50Mbps月流量。基本信息DogYun的VPS主机管理集成在会员中心,包括...
全新PHP短网址系统URL缩短器平台,它使您可以轻松地缩短链接,根据受众群体的位置或平台来定位受众,并为缩短的链接提供分析见解。系统使用了Laravel框架编写,前后台双语言使用,可以设置多域名,还可以开设套餐等诸多功能,值得使用。链接: https://pan.baidu.com/s/1ti6XqJ22tp1ULTJw7kYHog?pwd=sarg 提取码: sarg文件解压密码 www.wn7...
EtherNetservers是一家成立于2013年的英国主机商,提供基于OpenVZ和KVM架构的VPS,数据中心包括美国洛杉矶、新泽西和杰克逊维尔,商家支持使用PayPal、支付宝等付款方式,提供 60 天退款保证,这在IDC行业来说很少见,也可见商家对自家产品很有信心。有需要便宜VPS、多IP VPS的朋友可以关注一下。优惠码SUMMER-VPS-15 (终身 15% 的折扣)SUMMER-...