guava之基本工具

1、guava Optional

Doug Lea 说,“Null 真糟糕。”

Null的含糊语义让人很不舒服。Null很少可以明确地表示某种语义,例如,Map.get(key)返回Null时,可能表示map中的值是null,亦或map中没有key对应的值。Null可以表示失败、成功或几乎任何情况。使用Null以外的特定值,会让你的逻辑描述变得更清晰。

Guava用Optional<T>表示可能为null的T类型引用。一个Optional实例可能包含非null的引用(我们称之为引用存在),也可能什么也不包括(称之为引用缺失)。它从不说包含的是null值,而是用存在或缺失来表示。但Optional从不会包含null值引用。

1.1)创建Optional实例(以下都是静态方法):

Optional.of(T)

创建指定引用的Optional实例,若引用为null则快速失败(抛NPT异常)

Optional.absent()

创建引用缺失的Optional实例

Optional.fromNullable(T)

创建指定引用的Optional实例,若引用为null则表示缺失(不会跑异常)

1.2)用Optional实例查询引用(以下都是非静态方法):

boolean isPresent()

如果Optional包含非null的引用(引用存在),返回true

T get()

返回Optional所包含的引用,若引用缺失,则抛出java.lang.IllegalStateException

T or(T)

返回Optional所包含的引用,若引用缺失,返回指定的值

T orNull()

返回Optional所包含的引用,若引用缺失,返回null

Set<T> asSet()

返回Optional所包含引用的单例不可变集,如果引用存在,返回一个只有单一元素的集合,如果引用缺失,返回一个空集合。

示例:

使用Optional除了赋予null语义,增加了可读性,最大的优点在于它是一种傻瓜式的防护。Optional迫使你积极思考引用缺失的情况,因为你必须显式地从Optional获取引用。直接使用null很容易让人忘掉某些情形,尽管FindBugs可以帮助查找null相关的问题,但是我们还是认为它并不能准确地定位问题根源。

如同输入参数,方法的返回值也可能是null。和其他人一样,你绝对很可能会忘记别人写的方法method(a,b)会返回一个null,就好像当你实现method(a,b)时,也很可能忘记输入参数a可以为null。将方法的返回类型指定为Optional,也可以迫使调用者思考返回的引用缺失的情形。

补充:guava optional vs java optional:

在jdk8中也提供了Optional类用来优雅处理NPE,主要应用在lambada表达式中。二者的区别:

  • Optional in Guava is abstract which means objects are subclasses. In Java Optional is final which means there are no sub classes.
  • In guava Optional is Serializable. in Java Optional is not Serializable.
  • Methods are not exactly the same.

2、Preconditions(前置条件)

Guava在Preconditions类中提供了若干前置条件判断的实用方法,每个方法都有三个变种:

  • 没有额外参数:抛出的异常中没有错误消息;
  • 有一个Object对象作为额外参数:抛出的异常使用Object.toString() 作为错误消息;
  • 有一个String对象作为额外参数,并且有一组任意数量的附加Object对象:这个变种处理异常消息的方式有点类似printf,但考虑GWT的兼容性和效率,只支持%s指示符。例如:checkArgument(i < j, "Expected i < j, but %s > %s", i, j);

2.1)常用方法:

方法声明(不包括额外参数)

描述

检查失败时抛出的异常

void checkArgument(boolean)

检查boolean是否为true,用来检查传递给方法的参数。

IllegalArgumentException

T checkNotNull(T)

检查value是否为null,该方法直接返回value,因此可以内嵌使用checkNotNull。返回参数本身

NullPointerException

checkState(boolean)

用来检查对象的某些状态。

IllegalStateException

int checkElementIndex(int index, int size)

检查index作为索引值对某个列表、字符串或数组是否有效。index>=0 && index<size,返回index

IndexOutOfBoundsException

int checkPositionIndex(int index, int size)

检查index作为位置值对某个列表、字符串或数组是否有效。index>=0 && index<=size,返回index

IndexOutOfBoundsException

void checkPositionIndexes(int start, int end, int size)

检查[start, end]表示的位置范围对某个列表、字符串或数组是否有效*

IndexOutOfBoundsException

2.2)示例:

1)通常在构造函数中使用checkArgument(boolean)、checkNotNull(T)

注:一般使用import static 引入Preconditions下的方法。

2)检查下标值:

  • 索引值常用来查找列表、字符串或数组中的元素,如List.get(int), String.charAt(int)
  • 位置值和位置范围常用来截取列表、字符串或数组,如List.subList(int,int), String.substring(int)

补充:相比Apache Commons提供的类似方法,Guava的Preconditions仍然作为首选,理由:

  • 使用import static后,Guava方法非常清楚明晰。checkNotNull清楚地描述做了什么,会抛出什么异常;
  • checkNotNull方法直接返回检查的参数,让你可以在构造函数中保持字段的单行赋值风格:this.field = checkNotNull(field)
  • 简单的、参数可变的printf风格异常信息。鉴于这个优点,在JDK7已经引入Objects.requireNonNull的情况下,我们仍然建议你使用checkNotNull。

3、Objects类

在jdk7中也引入了Objects类。

3.1)equals():

equals是一个经常需要覆写的方法, 可以查看Object的equals方法注释, 对equals有几个性质的要求:

  1. 自反性reflexive:任何非空引用x,x.equals(x)返回为true;
  2. 对称性symmetric:任何非空引用x和y,x.equals(y)返回true当且仅当y.equals(x)返回true;
  3. 传递性transitive:任何非空引用x和y,如果x.equals(y)返回true,并且y.equals(z)返回true,那么x.equals(z)返回true;
  4. 一致性consistent:两个非空引用x和y,x.equals(y)的多次调用应该保持一致的结果,(前提条件是在多次比较之间没有修改x和y用于比较的相关信息);
  5. 对于所有非null的值x, x.equals(null)都要返回false。

当一个对象中的字段可以为null时,实现Object.equals方法会很痛苦,因为不得不分别对它们进行null检查。使用Objects.equal帮助你执行null敏感的equals判断,从而避免抛出NullPointerException。

3.2)hashCode():

当覆写(override)了equals()方法之后,必须也覆写hashCode()方法,反之亦然。这个方法返回一个整型值(hash code value),如果两个对象被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 ...), 这是个可变参数的方法,参数列表可以是任意数量,所以可以像这样使用Objects.hashCode(field1, field2, ..., fieldn)。非常方便和简洁。

3.3)toString():

因为每个类都直接或间接地继承自Object,因此每个类都有toString()方法。这个方法是用得最多的, 覆写得最多, 一个好的toString方法对于调试来说是非常重要的。复写toString常用方式有:

  1. 重写Object的toString()方法;
  2. 使用lombok注解;
  3. 使用org.apache.commons.lang3.builder.ToStringBuilder
public class MyBean {
        private Integer id;
        private String name;
        
        public MyBean(Integer id, String name) {
                super();
                this.id = id;
                this.name = name;
        }
        
        @Override
        public String toString() {
                 return ToStringBuilder.reflectionToString(this);
        }
        //使用org.apache.commons.lang3.builder.ToStringBuilder
        @Override
        public String toString() {
                 return "MyBean [id=" + id + ", name=" + name + "]";
        }
        
        public static void main(String...strings) {
                MyBean myBean = new MyBean(1,"test");
                System.out.println(myBean.toString());
        }
}

使用 MoreObjects.toStringHelper() 可以很方便的实现重写toString():

public static void main(String... strings) {
        System.out.println(MoreObjects.toStringHelper(this).add("x", 1).toString()); //ObjectTest{x=1}
        System.out.println(MoreObjects.toStringHelper(Person.class).add("x", 1).toString()); //Person{x=1}
        
        Person person=new Person("peida",23);
        String result = MoreObjects.toStringHelper(Person.class)
            .add("name", person.name)
            .add("age", person.age).toString();      
        System.out.print(result); //Person{name=peida, age=23}
    }
}

class Person {
    public String name;
    public int age;

    Person(String name, int age) {
        this.name = name;
        this.age = age;
    }
}

3.4)compare/compareTo方法:

1)java中的Comparable和Comparator接口:

  • Comparable 是排序接口,若一个类实现了 Comparable 接口(compareTo(T o);方法),就意味着 “该类支持排序”;即可通过 Collections.sort(或 Arrays.sort)进行排序。
  • Comparator接口:是一个比较器接口,不需要对原来的类进行结构上的变化,属于无侵入式的;一个类实现了 Comparator 接口(compareTo();方法),那么它就是一个 “比较器”。其它的类,可以根据该比较器去排序;即它可以直接通过 Arrays.sort() 或 Collections.sort() 进行排序。
public class Student implements Comparable {
     String name;
     public int compareTo(Student another) {
         return name.compareTo(another.name); 
     }
}

// 比较两个对象
student1.compareTo(student2);

// 排序数组或集合
Arrays.sort(students);
Collections.sort(collection);

//Comparator
class LengthComparator implements Comparator<String> {
    public int compare(String f, String s) {
        return f.length() - s.length();
    }
}

// 比较两个对象
LengthComparator comparator = new LengthComparator();
comparator.compare(person1,person2);

// 排序数组或集合
Arrays.sort(arr, new LengthComparator());
Collections.sort(collection, new LengthComparator());

2)guava提供的比较器:

我们先看一个例子

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方法:

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:

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();  
    }  
  }  
}

 

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

赶路人儿

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值