Guava的使用指南之Basic Utilities

从避免使用null开始

由于null的根本问题在于含糊而不明确,要避免使用null的方式,就是确认过去使用null的时机与目的,并使用明确的语义。在过去使用null的情况中,开发者在方法中传回null,通常代表着客户端必须检查是否为null,并在null的情况下使用预设值,以便程序能够继续执行。举个例子来说,如果原先有getNickName方法可以传回String,调用它如下:

1
2
3
4
5
String nickName = getNickName("Duke");
if(nickName == null){
  nickName = "Ajia's User";
}
out.println(nickName);

如果客户端忘了检查null,那么就会直接显示null,在这个简单的例子中并不会怎么样,只是显示的结果令人困惑罢了,但如果后面的执行流程牵涉到至关重要的结果,程序快乐的继续执行下去,错误可能到最后才会呈现发生。

那么可将getNickName修改使一定传回Optional(com.google.common.base)实例,但绝对不要传回null。Optional的语义是它可能包含也可能不包括值,如果你呼叫直接呼叫它的get方法:

1
2
String nickName = getNickName("Duke").get();
out.println(nickName);

在Optional没有包含值的情况下,就会直接抛出IllegalStateException,这实现了速错的概念,开发者可以立即发现错误,并了解到必须作出些检查,可能的方式之一像是:

1
2
3
Optional<String> nick = getNickName("Duke");
String nickname = nick.isPresent()?nick.get():"CodeData User";
out.println(nickName);

不过这看来有点啰嗦,一个比较好的方式可以是:

1
2
String nickName = getNickName("Duke").or("Ajia's User");
out.println(nickName);

在getNickName方法内部,原先如果是这样传回值:

1
return rs.next()?rs.getString("nickname"):null;

则可以使用Optional改为:

1
return rs.next()?Optional.of(rs.getString("nickname")):Optional.absent();

Optional.of方法用来建立Optional对象包含传入的值,而Optional.absent建立的对象不包含任何值,也就是如果你直接调用后者建立的Optional对象上get方法,就会抛出异常。在有值的情况下使用Optional.of,在原本会传回null的情况下使用Optional.absent。

当然,过去许多程序库中使用了不少的null,这些程序无法说改就改,Guava提供了一些衔接程序库中null的方法。例如,如果原先的getNickName是你无法修改的,那么可以这么修改客户端:

1
2
String nickName = Optional.fromNullable(getNickName("Duke")).or("Ajia's User");
out.println(nickName);

Optional.fromNullable在传入值为null的情况下,传回的Optional实例调用其get方法,就会抛出错误。

使用Preconditions作参数的与判断

Guava中提供了一个工作参数检查的工具类-Preconditions,静态导入这个类,可以大大地简化代码中对于参数的预判断和处理。

1
import static com.google.common.base.Preconditions.*;

在以前,我们需要判断一个参数不为空需要像下面这样写:

1
2
3
4
5
6
public void testMethod(Object obj){
  if(obj == null){
      throw new NullPointerException();
  }
  //... other operations
}

每次都要添加if语句来做判断,重复的工作会做好多次,使用Preconditions可以简化成下面这样

1
2
3
4
public void testMethod(Object obj){
  Object other = checkNotNull(obj);
  //... other operations
}

checkNotNull会检查参数是否为null,当为null的时候会抛出NullPointerException,否则直接返回参数。

checkNotNull, checkArgument和checkState,都有三种形式的参数: * public static T checkNotNull(T reference),只包含需要判断的对象,无其他多余的参数,抛出的异常不带有任何异常信息 * public static T checkNotNull(T reference, @Nullable Object errorMessage),只包含一个错误信息的额外参数,抛出的异常带有errorMessage.toString()的异常信息 * public static T checkNotNull(T reference, @Nullable String errorMessageTemplate, @Nullable Object…errorMessageArgs),这种是printf风格的错误信息,后面是变参,errorMessageTemplate可以使用一些占位符,例如可以这样写

1
2
checkArgument(i>=0, "Argument was %s but expected nonnegative", i);
checkArgument(i<j, "Expected i<j, but %s>%s", i, j);

捕获异常后可以获取自定义的详细错误信息,对于调试来说很有帮助,而且代码也很简洁。例如,

1
2
3
4
5
6
Object obj = null;
try{
  checkNotNull(obj, "cannot be null");
}catch(Exception e){
  System.out.println(e.getMessage());
}

运行后可以获得自定义的异常信息“cannot be null”

Preconditions里面的方法还有下面几个

  • checkArgument(boolean) 检查boolean是否为真,用作方法中检查参数
  • checkNotNull(T)检查value不为null,直接返回value
  • checkState(boolean)检查对象的一些状态,不依赖方法参数。例如,Iterator可以用来next是否在remove之前被调用
  • checkElementIndex(int index, int size)检查index是否为在一个长度为size的list、string或array合法的范围。index的范围区间是[0,size)(包含0不包含size)。无需直接传入list、string或array,只需传入大小。 返回index
  • checkPositionIndex(int index, int size)检查位置index是否为在一个长度为size的list、string或array合法的范围。index的范围区间是[0, size)(包含0不包含size). 无需直接传入list, string或array, 只需传入大小
  • checkPositionIndexes(int start, int end, int size),检查[start, end)是一个长度为size的list, string或array合法的范围子集.伴随着错误信息.

Guava的preconditions有这样几个有点: * 在静态导入后,方法很明确无歧义,checkNotNull可以清楚地告诉你他是干什么的,它会抛出怎样的异常 * checkNotNull在验证通过后直接返回,可以这样方便地写代码:this.field = checkNotNull(field). * 简单而又强大的可变参数printf风格的自定义错误信息。

建议将preconditions放置在单独的行上,这样可以在调试的时候清楚地指明哪个precondition出现错误。另外,提供有帮助的错误信息也非常有用。

Ordering简介

Ordering是Guava最常用的Comparator类,可以用来操作、扩展和使用comparators。自定义比较器,进行排序判断,进行排序,获取最大值最小值,获取最大最小的前几个值等方法。

几个常用的static方法,提供了三种常用的比较器

  • natural() 使用Comparable类型的自然排序,例如,整数从小到大,字符串是按字典顺序
  • usingToString() 使用toString()返回的字符串按字典顺序进行排序

使用Ordering.from(Comparator)从一个已经存在的Comparator来构建Ordering实例

自定义Ordering

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import java.util.Arrays;
import java.util.List;
import com.google.common.collect.Ordering;
 import com.google.common.primitives.Ints;
public class Demo {
     public static void main(String[] args) {
         Ordering<String> byLengthOrdering = new Ordering<String>() {
             public int compare(String left, String right) {
                 return Ints.compare(left.length(), right.length());
             }
         };
         List<String> strList = Arrays.asList("abc", "a", "bcd");
System.out.println(byLengthOrdering.reverse().isOrdered(strList));
         List<String> strList1 = Arrays.asList("a", "ab", "bcd");
         System.out.println(byLengthOrdering.isOrdered(strList1));
     }
 }

操作方法

reverse()方法,获取了Ordering的反排序。使用自定义的Ordering判断collection是否符合自定义顺序。

  • reverse() 返回与当前Ordering相反的排序
  • nullsFirst() 返回一个将null放在non-null元素之前的Ordering,其他的和原始的Ordering一样
  • compound(Comparator)返回一个使用Comparator的Ordering, Comparator作为第二排序元素,例如对bug列表进行排序,先根据bug的级别,再根据优先级进行排序。
  • lexicographical() Returns an Ordering that orders iterables lexicographically by their elements.
  • onResultOf(Function)将function应用在各个元素上之后,再使用原始ordering进行排序。

假如我们希望按sortedBy字段进行排序,

1
2
3
4
5
6
7
8
9
class Foo {
  @Nullable String sortedBy;
  int notSortedBy;
}
Ordering<Foo> ordering = Ordering.natural().nullsFirst().onResultOf(new Function<Foo, String>() {
    public String apply(Foo foo) {
      return foo.sortedBy;
    }
  });

还有一些很有用的方法 * greatestOf(Iterable iterable, int k) Returns the k greatest elements of the specified iterable, according to this ordering, in order from greatest to least. Not necessarily stable. * isOrdered(Iterable) Tests if the specified Iterable is in nondecreasing order according to this ordering. * sortedCopy(Iterable) Returns a sorted copy of the specified elements as a List. * min(E, E) Returns the minimum of its two arguments according to this ordering. If the values compare as equal, the first argument is returned. * min(E, E, E, E…) Returns the minimum of its arguments according to this ordering. If there are multiple least values, the first is returned. * min(Iterable) Returns the minimum element of the specified Iterable. Throws a NoSuchElementException if the Iterable is empty.

复写Object的常用方法

在Java中Object类是所有类的父类,其中有几个需要override的方法比如equals,hashCode和toString等方法。每次写这几个方法都要做很多重复性的判断,很多类库提供了复写这几个方法的工具类,Guava也提供了类似的方式。下面我们来看看Guava中这几个方法简单使用。

equals

equals是一个经常需要覆写的方法,可以查看Object的equals方法注释,对equals有几个性质的要求: * 1.自反性reflexive: 任何非空引用x,x.equals(x)返回为true; * 2.对称性symmetric: 任何非空引用x和y,x.equals(y)返回true当且仅当y.equals(x)返回true; * 传递性transitive:任何非空引用x和y,如果x.equals(y)返回true,并且y.equals(z)返回true,那么x.equals(z)返回true; * 一致性consistent:两个非空引用x和y,x.equals(y)的多次调用应该保持一致的结果,(前提条件是在多次比较之前没有修改x和y用于比较的相关信息); * 对于所有非null的值x,x.equals(null)都要返回false

当我们要覆写的类中某些值可能为null的时候,就需要对null做很多判断和分支处理。使用Guava的Object.equal方法可以避免这个问题,使得equals的方法的覆写变得更加容易,而且可读性强,简洁优雅。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import org.junit.Test;
import com.google.common.base.Objects;
public class ObjectTest {
    @Test
    public void equalTest() {
System.out.println(Objects.equal("a", "a"));
System.out.println(Objects.equal(null, "a"));
System.out.println(Objects.equal("a", null));
System.out.println(Objects.equal(null, null));
    }
    @Test
    public void equalPersonTest() {
 System.out.println(Objects.equal(new Person("peida",23), new Person("peida",23)));
        Person person=new Person("peida",23);
System.out.println(Objects.equal(person,person));
    }
}
class Person {
    public String name;
    public int age;
  Person(String name, int age) {
        this.name = name;
        this.age = age;
    }
}

运行输出:

true
false
false
true
false
true

hashCode

当覆写(override)了equals()方法之后,必须也覆写hashCode()方法,反之亦然。这个方法返回一个整型值,如果两个对象被equals()方法判断为相等,那么它们就应该拥有同样的hash code。Object类的hashCode()方法为不同的对象返回不同的值,Object类的hashCode值表示的是对象的地址。

hashCode的一般性契约(需要满足的条件)如下: * 1.在Java应用的一次执行过程中,如果对象用于equals比较的信息没有被修改,那么同一个对象多次调用hashCode()方法应该返回同一个整型值。应用的多次执行中,这个值不需要保持一致,即每次执行都是保持着各自不同的值。 * 2.如果equals()判断两个对象相等,那么它们的hashCode()方法应该返回同样的值。 * 3.并没有强制要求如果equals()判断两个对象不相等,那么它们的hashCode()方法就应该返回不同的值。即,两个对象用equals()方法比较返回false,它们的hashCode可以相同也可以不同。但是,应该意识到,为两个不相等的对象产生两个不同的hashCode可以改善哈希表的性能。

写一个hashCode本来也不是很难,但是Guava提供给我们一个更加简单的方法–Objects.hashCode(Object …),这是个可变参数的方法,参数列表可以是任意数量,所以可以像这样使用Object.hashCode(field1, field2, … ,fieldn)。非常方便和简洁。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import org.junit.Test;
import com.google.common.base.Objects;
public class ObjectTest {
    @Test
    public void hashcodeTest() {
System.out.println(Objects.hashCode("a"));
System.out.println(Objects.hashCode("a"));
System.out.println(Objects.hashCode("a","b"));
System.out.println(Objects.hashCode("b","a"));
System.out.println(Objects.hashCode("a","b","c"));
Person person=new Person("peida",23);
System.out.println(Objects.hashCode(person));
System.out.println(Objects.hashCode(person));
    }
}
class Person {
    public String name;
    public int age;
  Person(String name, int age) {
        this.name = name;
        this.age = age;
    }
}

toString()

因为每个类都直接或间接地继承自Object,因此每个类都有toString()方法。这个方法是用得最多的,覆写得最多,一个好的toString方法对于调试来说是非常重要的,但是写起来确实很不爽。Guava也提供了toString()方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
import org.junit.Test;
import com.google.common.base.Objects;
public class ObjectTest {
    @Test
    public void toStringTest() {
System.out.println(Objects.toStringHelper(this).add("x", 1).toString());
System.out.println(Objects.toStringHelper(Person.class).add("x", 1).toString());
        Person person=new Person("peida",23);
        String result = Objects.toStringHelper(Person.class)
        .add("name", person.name)
        .add("age", person.age).toString();
        System.out.print(result);
    }
}
class Person {
    public String name;
    public int age;
  Person(String name, int age) {
        this.name = name;
        this.age = age;
    }
}
//============输出===============
ObjectTest{x=1}
Person{x=1}
Person{name=peida, age=23}

compare/compareTo

compareTo(Object o)方法是java.lang.Comparable接口中的方法,当需要对某个类的对象进行排序时,该类需要实现Comparable接口的,必须重写public int compareTo(T o)方法。java规定,若a,b是两个对象,当a.compareTo(b)>0时,则a大于b,a.compareTo(b)<0时,a<b,即规定对象的比较大小的规则;

compare(Object o1, Object o2)方法是java.util.Comparator接口的方法,compare方法内主要靠定义的compareTo规定的对象大小关系规则来确定对象的大小。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
import org.junit.Test;
public class ObjectTest {
@Test
    public void compareTest(){
        Person person=new Person("peida",23);
        Person person1=new Person("aida",25);
        Person person2=new Person("aida",25);
        Person person3=new Person("aida",26);
        Person person4=new Person("peida",26);
System.out.println(person.compareTo(person1));
System.out.println(person1.compareTo(person2));
System.out.println(person1.compareTo(person3));
System.out.println(person.compareTo(person4));
System.out.println(person4.compareTo(person));
    }
}
class Person implements Comparable<Person>{
    public String name;
    public int age;
  Person(String name, int age) {
        this.name = name;
        this.age = age;
    }
    @Override
    public int compareTo(Person other) {
        int cmpName = name.compareTo(other.name);
        if (cmpName != 0) {
            return cmpName;
        }
        if(age>other.age){
            return 1;
        }
        else if(age<other.age){
            return -1;
        }
        return 0;
    }
}

上面的compareTo方法,代码看上去并不是十分优雅,如果实体属性很多,数据类型丰富,代码可读性将会很差。在guava里, 对所有原始类型都提供了比较的工具函数来避免这个麻烦. 比如对Integer, 可以用Ints.compare()。利用guava的原始类型的compare,我们对上面的方法做一个简化,实现compare方法:

1
2
3
4
5
6
7
8
9
10
class PersonComparator implements Comparator<Person> {
    @Override
    public int compare(Person p1, Person p2) {
      int result = p1.name.compareTo(p2.name);
      if (result != 0) {
        return result;
      }
      return Ints.compare(p1.age, p2.age);
    }
  }

上面的代码看上去简单了一点,但还是不那么优雅简单,对此, guava有一个相当聪明的解决办法, 提供了ComparisonChain:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
class Student implements Comparable<Student>{
    public String name;
    public int age;
    public int score;
    Student(String name, int age,int score) {
        this.name = name;
        this.age = age;
        this.score=score;
    }
    @Override
    public int compareTo(Student other) {
        return ComparisonChain.start()
        .compare(name, other.name)
        .compare(age, other.age)
        .compare(score, other.score, Ordering.natural().nullsLast())
        .result();
    }
}
class StudentComparator implements Comparator<Student> {
    @Override public int compare(Student s1, Student s2) {
      return ComparisonChain.start()
          .compare(s1.name, s2.name)
          .compare(s1.age, s2.age)
          .compare(s1.score, s2.score)
          .result();
    }
  }
}

ComparisonChain是一个lazy的比较过程, 当比较结果为0的时候, 即相等的时候, 会继续比较下去, 出现非0的情况, 就会忽略后面的比较。ComparisonChain实现的compare和compareTo在代码可读性和性能上都有很大的提高。

下面是个综合应用的实例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
import java.util.Comparator;
import org.junit.Test;
import com.google.common.base.Objects;
import com.google.common.collect.ComparisonChain;
import com.google.common.collect.Ordering;
public class ObjectTest {
@Test
public void StudentTest(){
        Student student=new Student("peida",23,80);
        Student student1=new Student("aida",23,36);
        Student student2=new Student("jerry",24,90);
        Student student3=new Student("peida",23,80);
        System.out.println("==========equals===========");
        System.out.println(student.equals(student2));
        System.out.println(student.equals(student1));
        System.out.println(student.equals(student3));
        System.out.println("==========hashCode===========");
        System.out.println(student.hashCode());
        System.out.println(student1.hashCode());
        System.out.println(student3.hashCode());
        System.out.println(student2.hashCode());
          System.out.println("==========toString===========");
        System.out.println(student.toString());
        System.out.println(student1.toString());
        System.out.println(student2.toString());
        System.out.println(student3.toString());
        System.out.println("==========compareTo===========");
        System.out.println(student.compareTo(student1));
        System.out.println(student.compareTo(student2));
        System.out.println(student2.compareTo(student1));
        System.out.println(student2.compareTo(student));
    }
}
class Student implements Comparable<Student>{
    public String name;
    public int age;
    public int score;
    Student(String name, int age,int score) {
        this.name = name;
        this.age = age;
        this.score=score;
    }
    @Override
    public int hashCode() {
        return Objects.hashCode(name, age);
    }
    @Override
    public boolean equals(Object obj) {
        if (obj instanceof Student) {
            Student that = (Student) obj;
            return Objects.equal(name, that.name)
                    && Objects.equal(age, that.age)
                    && Objects.equal(score, that.score);
        }
        return false;
    }
    @Override
    public String toString() {
        return Objects.toStringHelper(this)
                .addValue(name)
                .addValue(age)
                .addValue(score)
                .toString();
    }
    @Override
    public int compareTo(Student other) {
        return ComparisonChain.start()
        .compare(name, other.name)
        .compare(age, other.age)
        .compare(score, other.score, Ordering.natural().nullsLast())
        .result();
    }
}
class StudentComparator implements Comparator<Student> {
    @Override public int compare(Student s1, Student s2) {
      return ComparisonChain.start()
          .compare(s1.name, s2.name)
          .compare(s1.age, s2.age)
          .compare(s1.score, s2.score)
          .result();
    }
  }
//=============运行输出===========================
==========equals===========
false
false
true
==========hashCode===========
-991998617
-991998617
-1163491205
==========toString===========
Student{peida, 23, 80}
Student{aida, 23, 36}
Student{jerry, 24, 90}
Student{peida, 23, 80}
==========compareTo===========
-1