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;
    @Override
    public 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 codes
  private 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 changes
  boolean 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查看代码片段。

时间: 2024-12-02 08:04:36

Guava 是个风火轮之基础工具(4)的相关文章

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

前言 Guava 是 Java 开发者的好朋友.虽然我在开发中使用 Guava 很长时间了,Guava API 的身影遍及我写的生产代码的每个角落,但是我用到的功能只是 Guava 的功能集中一个少的可怜的真子集,更别说我一直没有时间认真的去挖掘 Guava 的功能,没有时间去学习 Guava 的实现.直到最近,我开始阅读 Getting Started with Google Guava,感觉有必要将我学习和使用 Guava 的一些东西记录下来. Splitter Guava 提供了 Join

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

前言 Guava 是 Java 开发者的好朋友.虽然我在开发中使用 Guava 很长时间了,Guava API 的身影遍及我写的生产代码的每个角落,但是我用到的功能只是 Guava 的功能集中一个少的可怜的真子集,更别说我一直没有时间认真的去挖掘 Guava 的功能,没有时间去学习 Guava 的实现.直到最近,我开始阅读 Getting Started with Google Guava,感觉有必要将我学习和使用 Guava 的一些东西记录下来. Joiner 我们经常需要将几个字符串,或者字

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

前言 Guava 是 Java 开发者的好朋友.虽然我在开发中使用 Guava 很长时间了,Guava API 的身影遍及我写的生产代码的每个角落,但是我用到的功能只是 Guava 的功能集中一个少的可怜的真子集,更别说我一直没有时间认真的去挖掘 Guava 的功能,没有时间去学习 Guava 的实现.直到最近,我开始阅读 Getting Started with Google Guava,感觉有必要将我学习和使用 Guava 的一些东西记录下来. Charsets Charsets 是一个常量

PS基础工具操作方式与方法介绍

本教程像飞特的PS初学者介绍一下ps软件的基础工具的操作方法.如果你耐心的多做几遍这个练习.         三联推荐:photoshop7.0迷你版免费下载    |  Photoshop CS5 中文免费下载  |  photoshop免费下载 我相信你会有很大的提高.这时介绍了"创建文件"."裁切方法""移动工具"."裁切工具". "对齐方式"."复制图层"."合并图层&

命令行基础工具的更佳替代品

命令行基础工具的更佳替代品 命令行听起来有时候会很吓人,特别是在刚刚接触的时候,你甚至可能做过有关命令行的噩梦.然而渐渐地,我们都会意识到命令行实际上并不是那么吓人,反而是非常有用.实际上,没有命令行正是每次我使用 Windows 时让我感到崩溃的地方.这种感觉上的变化是因为命令行工具实际上是很智能的. 你在任何一个 Linux 终端上所使用的基本工具功能都是很强大的, 但还远说不上是足够强大. 如果你想使你的命令行生涯更加愉悦, 这里有几个程序你可以下载下来替换原来的默认程序, 它还可以给你提

Guava 是个风火轮之函数式编程(1)

前言 函数式编程是一种历久弥新的编程范式,比起命令式编程,它更加关注程序的执行结果而不是执行过程.Guava 做了一些很棒的工作,搭建了在 Java 中模拟函数式编程的基础设施,让我们不用多费手脚就能享受部分函数式编程带来的便利. Java 始终是一个面向对象(命令式)的语言,在我们使用函数式编程这种黑魔法之前,需要确认:同样的功能,使用函数式编程来实现,能否在健壮性和可维护性上,超过使用面向对象(命令式)编程的实现? Function Function 接口是我们第一个介绍的 Guava 函数

如何在两小时内搞懂PHOTOSHOP基础工具?

  初学者面对Photoshop这个庞然大物,第一直观感受是不知道从何下手.那么多工具,学完这个忘那个,有木有什么教程可以一次性学完呢?今天这篇好文,只要童鞋们打起精神来,两个小时就可以搞懂基础的工具使用!绝对是新手速成Photoshop的一个好机会!赶紧来学习! 教程很有意思,从A--Z 依次讲解每个工具的使用,精辟彻底,不拖泥带水呦. 3D面板:作为Adobe Photoshop Extended的一部分,你可以在Adobe Photoshop CC中找到它.3D面板中展示了所有3D文件中允

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

原文链接 译文链接 译者:沈义扬,校对:丁一 尚未完成: Queues, Tables工具类 任何对JDK集合框架有经验的程序员都熟悉和喜欢java.util.Collections包含的工具方法.Guava沿着这些路线提供了更多的工具方法:适用于所有集合的静态方法.这是Guava最流行和成熟的部分之一. 我们用相对直观的方式把工具类与特定集合接口的对应关系归纳如下: 集合接口 属于JDK还是Guava 对应的Guava工具类 Collection JDK Collections2:不要和jav

Guava 是个风火轮之函数式编程(2)

前言 函数式编程是一种历久弥新的编程范式,比起命令式编程,它更加关注程序的执行结果而不是执行过程.Guava 做了一些很棒的工作,搭建了在 Java 中模拟函数式编程的基础设施,让我们不用多费手脚就能享受部分函数式编程带来的便利. Java 始终是一个面向对象(命令式)的语言,在我们使用函数式编程这种黑魔法之前,需要确认:同样的功能,使用函数式编程来实现,能否在健壮性和可维护性上,超过使用面向对象(命令式)编程的实现? Predicate Predicate 接口是我们第二个介绍的 Guava