Guava 是个风火轮之基础工具(4)
前言
Guava 是 Java 开发者的好朋友。虽然我在开发中使用 Guava 很长时间了,Guava API 的身影遍及我写的生产代码的每个角落,但是我用到的功能只是 Guava 的功能集中一个少的可怜的真子集,更别说我一直没有时间认真的去挖掘 Guava 的功能,没有时间去学习 Guava 的实现。直到最近,我开始阅读Getting Started with Google Guava,感觉有必要将我学习和使用 Guava 的一些东西记录下来。
Preconditions
Precondition 是先决条件的意思,也叫前置条件,可以人为是使函数正常执行的参数需要满足的条件。在 Preconditions 这个静态工厂中,Guava 为我们提供了一系列的静态方法,用于帮助我们在函数执行的开始检查参数,函数执行的过程中检查状态等等。
Preconditions.checkArgument(5 < 3);//IllegalArgumentException
Preconditions.checkState(5 < 3);//IllegalStateException
Preconditions.checkNotNull(null);//NullPointerException
Preconditions.checkElementIndex(4, 4);//IndexOutOfBoundsException
Preconditions.checkPositionIndex(5, 4);//IndexOutOfBoundsException
源码分析
源码来自 Guava 18.0。Preconditions 类代码约 440 行,大部分是 JavaDoc 和函数重载,那些真正干活的代码大部分也是先 if 然后 throw 的模式。
public static void checkArgument(boolean expression) {if (!expression) {throw new IllegalArgumentException();}
}
大约在 255 行处有一大段的注释,讲了一个有趣的事情。
大概从 2009 年开始,由于 Hotspot 虚拟机优化器的一个 bug,对于抛异常的代码,直接在初始化异常时传入字符串常量反而导致效率低下,效率远远不如在初始化前调用一个类型是 String 的函数来获取字符串,而且这个性能差距不是 10% 或者 20%,而是可怕的 2 倍到 8 倍。于是我们看到的 JDK 类库的抛异常代码,就从
if (guardExpression) {throw new BadException(messageExpression);
}
变成了下面这样。
if (guardExpression) {throw new BadException(badMsg(...));
}
Objects
我们在定义一个类的时候,免不了会去覆盖 toString 方法;如果要把这个类的对象放到 HashMap 中,还得去覆盖 hashCode 方法;如果对象之间需要比较大小,那么还得实现 Comparable 接口的 compareTo 方法。
Guava 为我们提供了方便的实现这些方法的工具。虽然优秀的 IDE 比如 IntelliJ IDEA 能够自动帮我们生成 toString 和 hashCode,但是依赖代码生成器始终不是一个科学的开发方式。
需要说明的一点是,Objects 类中用于帮助实现 toString 方法的内部类 ToStringHelper,已经被标记为过时,在 Guava 18.0 中迁移到 MoreObjects 中了,而用于帮助实现 compareTo 的则是 ComparisonChain 类,稍后会解读这个类的用法和代码。
现在的 Objects 中硕果仅存的两个函数,分别是 Objects#equal 和 Objects#hashCode,分别用于判断两个对象是否相等,和生成对象的 hashCode。
Objects.equal(new Object(), new Object());//false
Objects.hashCode("", new Object());//340664367
源码分析
源码来自 Guava 18.0。Objects 类代码约 320 行,刨除过时代码之后,也没剩几行了。
硕果仅存的两个函数,实现比想象中还简单。
public static boolean equal(@Nullable Object a, @Nullable Object b) {return a == b || (a != null && a.equals(b));
}public static int hashCode(@Nullable Object... objects) {return Arrays.hashCode(objects);
}
我好奇的跟到 Arrays#hashCode 里面看了看,发现这段计算 hashCode 的代码,和 String 类里面的算法几乎一样,31 据说是一个经验值,反正无论如何必须是个质数。
public static int hashCode(Object a[]) {if (a == null)return 0;int result = 1;for (Object element : a)result = 31 * result + (element == null ? 0 : element.hashCode());return result;
}
MoreObjects
MoreObjects 是从 18.0 版本开始出现的一个新类,从 Objects 中分裂出来的,主要剥离了内部类 ToStringHelper 以及一系列的包装函数。
至于那个顺便一起迁移过来的 MoreObjects#firstNonNull 函数,功能和实现都过分简单,这里就不展开了,有兴趣的可以查看源码。
下面是 ToStringHelper 的简单用法,通过调用 ToStringHelper#omitNullValues 来配置 ToStringHelper 使得生成的字符串中不含 null 值。
public class Player {private String name = "Underwood";private String sex;@Overridepublic String toString() {return MoreObjects.toStringHelper(this).omitNullValues().add("name", name).add("sex", sex).toString();//Player{name=Underwood}}
}
源码分析
源码来自 Guava 18.0。MoreObjects 类代码约 390 行,甚至比 Objects 还要多。其中 ToStringHelper 代码约 240 行,这里我们主要看看 ToStringHelper 的实现。
从 ToStringHelper 的属性可以看出,它内部维护着一个链表。
public static final class ToStringHelper {private final String className;private ValueHolder holderHead = new ValueHolder();private ValueHolder holderTail = holderHead;private boolean omitNullValues = false;//some codesprivate static final class ValueHolder {String name;Object value;ValueHolder next;}
}
为了保持插入结点后链表结点顺序和代码调用的顺序一致,ToStringHelper 还额外维护了一个尾指针,在链表尾插入新结点。
private ValueHolder addHolder() {ValueHolder valueHolder = new ValueHolder();holderTail = holderTail.next = valueHolder;return valueHolder;
}
private ToStringHelper addHolder(String name, @Nullable Object value) {ValueHolder valueHolder = addHolder();valueHolder.value = value;valueHolder.name = checkNotNull(name);return this;
}
最后的最后,ToStringHelper#toString 就是遍历对象内部维护的链表,拼接字符串了。说道字符串拼接,之前在Guava 是个风火轮之基础工具(1)中,我们看到 Joiner 使用 if 和 while 来实现了比较优雅的分隔符拼接,避免了在末尾插入分隔符的尴尬。在这里,Guava 的作者展示了另一个技巧,用更少的代码实现同样的效果。
@Override public String toString() {// create a copy to keep it consistent in case value changesboolean omitNullValuesSnapshot = omitNullValues;String nextSeparator = "";StringBuilder builder = new StringBuilder(32).append(className).append('{');for (ValueHolder valueHolder = holderHead.next; valueHolder != null;valueHolder = valueHolder.next) {if (!omitNullValuesSnapshot || valueHolder.value != null) {builder.append(nextSeparator);nextSeparator = ", ";if (valueHolder.name != null) {builder.append(valueHolder.name).append('=');}builder.append(valueHolder.value);}}return builder.append('}').toString();
}
一开始的时候,先把分隔符置为空字符串,完成分隔符拼接之后,将分隔符置为逗号,这样就实现了从第二个元素开始,每个元素前面拼接分隔符的效果。这样子就不用去判断当前元素是不是第一个元素,代价仅仅是每次循环多出一次冗余的赋值,完全可以忽略不计。
ComparisonChain
ComparisonChain 可以帮助我们优雅地实现具有短回路功能链式比较,然后我们可以借助 ComparisonChain 来实现 compareTo 方法。先看看这个类的用法。
public class Player implements Comparable<Player> {private String name = "Underwood";private String sex;public int compareTo(Player that) {return ComparisonChain.start().compare(this.name, that.name).compare(this.sex, that.sex).result();}
}
美中不足的是,比较链的参数,基本不能有空指针,不然当场就 NPE 了。虽然我们可以通过自定义比较器去兼容空指针,但是这样一来代码就变得一点都不优雅了。
源码分析
带着对 ComparisonChain 空指针处理不力的不满,我们来看看它的实现,如果可能就动手实现我们需要的特性。
源码来自 Guava 18.0。ComparisonChain 类代码约 220 行,大部分是注释和 ComparisonChain#compare 函数的各种重载。看到 ComparisonChain 是一个抽象类,各种 ComparisonChain#compare 都是虚函数,返回结果的 ComparisonChain#result 也是虚函数,我以为有希望继承它然后做些改造。不过看到代码里那个私有的构造函数之后,我打消了继承它的念头。
ComparisonChain 内部维护着 3 个 ComparisonChain 类型的变量,ACTIVE、LESS、GREATER,容易知道这代表着链式比较的状态,ACTIVE 还需要继续比较,其他两个则是已经知道最终结果了。
LESS 和 GREATER 状态其实是 InactiveComparisonChain 类的对象,这个类内部有一个属性维护比较链的结果,然后各种 compare 函数都是直接返回 this 指针,着就是所谓的短回路了,能够避免调用被比较对象的 compareTo 函数。
private static final class InactiveComparisonChain extends ComparisonChain {final int result;InactiveComparisonChain(int result) { this.result = result; }@Override public ComparisonChain compare(int left, int right) { return this; }//other compare functions@Override public int result() { return result; }
}
最后,我对 ComparisonChain 稍作改动,增强了它对空指针的容忍,可以通过 ComparisonChain#nullValueLess 来设置 null 字段在比较的时候小于非 null 字段,访问 Gist查看代码片段。
Guava 是个风火轮之基础工具(4)相关推荐
- [Google Guava] 2.3-强大的集合工具类:java.util.Collections中未包含的集合工具
原文链接 译文链接 译者:沈义扬,校对:丁一 尚未完成: Queues, Tables工具类 任何对JDK集合框架有经验的程序员都熟悉和喜欢java.util.Collections包含的工具方法.G ...
- mysql分析sql语句基础工具 —— explain
转载自 https://segmentfault.com/a/1190000009724144 立即登录 [笔记] mysql分析sql语句基础工具 -- explain mysql wateran ...
- 初学者也能看懂的 Vue2 源码中那些实用的基础工具函数
1. 前言 大家好,我是若川.最近组织了源码共读活动,感兴趣的可以加我微信 ruochuan12 想学源码,极力推荐之前我写的<学习源码整体架构系列>jQuery.underscore.l ...
- Docker安装ssh,supervisor等基础工具
2019独角兽企业重金招聘Python工程师标准>>> Docker安装ssh,supervisor等基础工具 需要提前下载好官方的ubuntu镜像,我这里使用的是ubuntu:14 ...
- ps文字换行_零基础一周内熟悉使用PS基础工具【Photoshop教程二】
零基础一周内熟悉使用PS基础工具[Photoshop教程一]这篇的后台数据显示有很多知友都有收藏了.由此可见现在的视频教程,网络上太多太多但,但好多知识都太"碎片化"今天学习这个技 ...
- 手机app逆向、渗透测试基础工具介绍
手机app逆向基础工具介绍 1.夜神模拟器 2.Androidkiller --apk反编译工具 3.burpsuite --代理工具 4.jeb --Android 反编绎工具 安装 使用 5.dd ...
- CTF 六大方向基础工具合集
本文中提到的所有工具在ctf部落中均有,加入方式见文末. CTF 六大方向基础工具合集 今天来为大家分享CTF 六大方向基础工具简介集合. 一.MISC方向 杂项往往是不能被归到其他类别里的题目,所以 ...
- 【MAPBOX基础功能】25、mapbox地图基础工具 - 缩放
前言 官网指引,生成accesstoken,下载相关依赖请翻阅[https://blog.csdn.net/weixin_44402694/article/details/125414381?spm= ...
- 【MAPBOX基础功能】29、mapbox地图基础工具 - 获取当前地图层级
前言 官网指引,生成accesstoken,下载相关依赖请翻阅[https://blog.csdn.net/weixin_44402694/article/details/125414381?spm= ...
最新文章
- android双重for循环,Android实现ViewPager无限循环效果(二)
- CSS之box-shadow
- 757计算机电子元件,飞行员的好帮手 波音757的发动机指示与机组报警系统简介...
- 第三节 计算机体系结构,计算机系统结构 第三节 输入输出系统.pdf
- xml转svg_C# Excel 转PDF/图片/HTML/TXT/XML/XPS/CSV/ODS/SVG/EMF
- python示例apk_Python获取apk文件URL地址实例
- 一步一步写算法(之线性结构的处理)
- Java设计模式学习02——工厂模式
- design expert响应面分析_第01组(17)需求分析报告 - yuqiao1120
- SpringBoot+SpringAOP+Java自定义注解+mybatis实现切库读写分离
- 鸿蒙2.0正式开源,华为重磅押注开发者生态
- 实现简单的增删改查(Asp.Net MVC+Layui)
- 仿大逃杀源码_破咒不是您的典型大逃杀
- RENIX_802.3ah功能介绍(下)——网络测试仪实操
- 360安全卫士断网急救箱系统服务
- 真实评测:华为mate30epro和mate30的区别-哪个好
- A Blog of WEB
- USDP使用笔记(三)大数据集群及组件启停
- 设备以国标GB28181协议接入视频平台时可能会遇到的问题
- C4D模型工具—缝合
热门文章
- 火箭技术术语_仿真优化火箭发动机 3D 打印制造工艺
- java难学还是pythonnanxue_关于python:为什么numpy中的“ NaN”比“ -np.inf”更小?
- android包结构规范,【Android】Android产品-开发规范
- php ip2long mysql,PHP基于ip2long实现IP转换整形
- mysql 两张表差集_mysql中两张表使用left join on 求差集详解
- dvt高危患者的护理措施_dvt的预防及护理
- SVN登录时不断弹出用户名密码输入
- linux命令wget下载jdk(完整解决诸多异常)
- 2022年中国美妆护肤品行业投资研究报告
- 华语乐坛趋势报告(2022)