前言

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)相关推荐

  1. [Google Guava] 2.3-强大的集合工具类:java.util.Collections中未包含的集合工具

    原文链接 译文链接 译者:沈义扬,校对:丁一 尚未完成: Queues, Tables工具类 任何对JDK集合框架有经验的程序员都熟悉和喜欢java.util.Collections包含的工具方法.G ...

  2. mysql分析sql语句基础工具 —— explain

    转载自 https://segmentfault.com/a/1190000009724144 立即登录 [笔记] mysql分析sql语句基础工具 -- explain  mysql wateran ...

  3. 初学者也能看懂的 Vue2 源码中那些实用的基础工具函数

    1. 前言 大家好,我是若川.最近组织了源码共读活动,感兴趣的可以加我微信 ruochuan12 想学源码,极力推荐之前我写的<学习源码整体架构系列>jQuery.underscore.l ...

  4. Docker安装ssh,supervisor等基础工具

    2019独角兽企业重金招聘Python工程师标准>>> Docker安装ssh,supervisor等基础工具 需要提前下载好官方的ubuntu镜像,我这里使用的是ubuntu:14 ...

  5. ps文字换行_零基础一周内熟悉使用PS基础工具【Photoshop教程二】

    零基础一周内熟悉使用PS基础工具[Photoshop教程一]这篇的后台数据显示有很多知友都有收藏了.由此可见现在的视频教程,网络上太多太多但,但好多知识都太"碎片化"今天学习这个技 ...

  6. 手机app逆向、渗透测试基础工具介绍

    手机app逆向基础工具介绍 1.夜神模拟器 2.Androidkiller --apk反编译工具 3.burpsuite --代理工具 4.jeb --Android 反编绎工具 安装 使用 5.dd ...

  7. CTF 六大方向基础工具合集

    本文中提到的所有工具在ctf部落中均有,加入方式见文末. CTF 六大方向基础工具合集 今天来为大家分享CTF 六大方向基础工具简介集合. 一.MISC方向 杂项往往是不能被归到其他类别里的题目,所以 ...

  8. 【MAPBOX基础功能】25、mapbox地图基础工具 - 缩放

    前言 官网指引,生成accesstoken,下载相关依赖请翻阅[https://blog.csdn.net/weixin_44402694/article/details/125414381?spm= ...

  9. 【MAPBOX基础功能】29、mapbox地图基础工具 - 获取当前地图层级

    前言 官网指引,生成accesstoken,下载相关依赖请翻阅[https://blog.csdn.net/weixin_44402694/article/details/125414381?spm= ...

最新文章

  1. android双重for循环,Android实现ViewPager无限循环效果(二)
  2. CSS之box-shadow
  3. 757计算机电子元件,飞行员的好帮手 波音757的发动机指示与机组报警系统简介...
  4. 第三节 计算机体系结构,计算机系统结构 第三节 输入输出系统.pdf
  5. xml转svg_C# Excel 转PDF/图片/HTML/TXT/XML/XPS/CSV/ODS/SVG/EMF
  6. python示例apk_Python获取apk文件URL地址实例
  7. 一步一步写算法(之线性结构的处理)
  8. Java设计模式学习02——工厂模式
  9. design expert响应面分析_第01组(17)需求分析报告 - yuqiao1120
  10. SpringBoot+SpringAOP+Java自定义注解+mybatis实现切库读写分离
  11. 鸿蒙2.0正式开源,华为重磅押注开发者生态
  12. 实现简单的增删改查(Asp.Net MVC+Layui)
  13. 仿大逃杀源码_破咒不是您的典型大逃杀
  14. RENIX_802.3ah功能介绍(下)——网络测试仪实操
  15. 360安全卫士断网急救箱系统服务
  16. 真实评测:华为mate30epro和mate30的区别-哪个好
  17. A Blog of WEB
  18. USDP使用笔记(三)大数据集群及组件启停
  19. 设备以国标GB28181协议接入视频平台时可能会遇到的问题
  20. C4D模型工具—缝合

热门文章

  1. 火箭技术术语_仿真优化火箭发动机 3D 打印制造工艺
  2. java难学还是pythonnanxue_关于python:为什么numpy中的“ NaN”比“ -np.inf”更小?
  3. android包结构规范,【Android】Android产品-开发规范
  4. php ip2long mysql,PHP基于ip2long实现IP转换整形
  5. mysql 两张表差集_mysql中两张表使用left join on 求差集详解
  6. dvt高危患者的护理措施_dvt的预防及护理
  7. SVN登录时不断弹出用户名密码输入
  8. linux命令wget下载jdk(完整解决诸多异常)
  9. 2022年中国美妆护肤品行业投资研究报告
  10. 华语乐坛趋势报告(2022)