Guava入门

Google Guava

Guava概述

Guava是Google提供的一个核心Java类库,其中包含:集合【collections】、缓存【caching】、原生类型支持【primitives support】、并发库【concurrency libraries】、通用注解【common annotations】、字符串处理【string processing】、I/O 等等。

Guava 是Java的工具集,提供了一些常用的便利的操作工具类,减少因为空指针、异步操作等引起的问题BUG,提高开发效率。

使用Guava的好处:

​ 标准化:Guava库是由谷歌托管。

​ 高效可靠:快速和有效的扩展JAVA标准库

​ 优化:Guava 库经过高度的优化。

​ 函数式编程:增加JAVA功能和处理能力。

​ 实用程序:提供了经常需要在应用程序开发的许多实用程序类。

​ 最佳实践:强调最佳的做法。

中文网址:https://ifeve.com/google-guava/

引入依赖

Guava 库提供了两种不同的风格,比如 31.0.1-jre31.0.1-android。JRE 风格需要 JDK 1.8 或更高版本,JDK1.7或者Android开发时,我们可以使用android风格。

Maven 添加依赖

<dependency>
    <groupId>com.google.guavagroupId>
    <artifactId>guavaartifactId>
    <version>31.0.1-jreversion>
dependency>

原生类型支持

Java的原生类型就是指基本类型:byte、short、int、long、float、double、char和boolean。

原生类型不能当作对象或泛型的类型参数使用,这意味着许多通用方法都不能应用于它们。Guava提供了若干通用工具,包括原生类型数组与集合API的交互,原生类型和字节数组的相互转换,以及对某些原生类型的无符号形式的支持。

简单来讲,Guava让我们可以把原生类型当成对象一样使用。

原生类型 Guava工具类
byte Bytes, SignedBytes, UnsignedBytes
short Shorts
int Ints, UnsignedInteger, UnsignedInts
long Longs, UnsignedLong, UnsignedLongs
float Floats
double Doubles
char Chars
boolean Booleans

比如,我们看看Ints的一些方法

如下:

Ints static方法 解释
int checkedCast(long value) long转换为int,给定的值超过了int最大值,最小值则IllegalArgumentException
int saturatedCast(long value) long转换为int。超过最大值为Integer.MAX_VALUE,小于最小值Integer.MIN_VALUE
int compare(int a, int b) 比较
boolean contains(int[] array, int target) 是否包含
int indexOf(int[] array, int target) 数组下标
int indexOf(int[] array, int[] target) 数组下标
int lastIndexOf(int[] array, int target) 最后一次出现的下标
int min(int… array) 最小值
int max(int… array) 最大值
int constrainToRange(int value, int min, int max) [min,max]区间里面最接近value的值
int[] concat(int[]… arrays) 多个数组组合成一个数组
byte[] toByteArray(int value) int转byte数组
int fromByteArray(byte[] bytes) byte数组转int
int fromBytes(byte b1, byte b2, byte b3, byte b4) byte转int
Converter stringConverter() String转int
int[] ensureCapacity(int[] array, int minLength, int padding) 返回一个包含与array相同值的数组,如果原数组长度小于minLength则进行拷贝操作并且保证新数组长度为minLength + padding,否则直接返回原数组
String join(String separator, int… array) 多个int拼接成一个字符串
Comparator lexicographicalComparator() 返回一个比较int数组的比较器
void sortDescending(int[] array) 降序排序
void reverse(int[] array) 反转
int[] toArray(Collection collection) 转换成数组
List asList(int… backingArray) 转换成list
public class PrimitivesTest {
    public static void main(String[] args) {

        /*返回数组下标*/
        int[] array = new int[]{1,1,4,8,6,5};
        int a = Ints.indexOf(array, 1);
        int b = Ints.indexOf(array, 9);
        int c = Ints.indexOf(array, 6);
        int d = Ints.indexOf(array,new int[]{8,6,5});
        int e= Ints.indexOf(array,new int[]{8,2});
        System.out.println(a);      //0
        System.out.println(b);      //-1
        System.out.println(c);      //4
        System.out.println(d);      //3
        System.out.println(e);      //-1


        /*最大值*/
        int l = Ints.max(10,84,6,23);
        System.out.println(l);      //84

        /*比较*/
        int k = Ints.compare(15,20);
        System.out.println(k);      //-1

        
        int j = Ints.checkedCast((long)Integer.MAX_VALUE+1L);//	报错
    }
}

基本工具

Optional

null为一个模糊概念,很多时候我们无法第一时间明确一个null值所代表的含义。比如我们通过一个Key向一个Map当中查找Value,如果结果返回null,其代表的含义可能是该键指向的value值是null,亦或者该键在map中并不存在。

通过学习Google底层代码库,我们发现95%的集合类不接受null值作为元素。我们认为, 相比默默地接受null,使用快速失败操作拒绝null值对开发者更有帮助。

鉴于此Google的Guava库中提供了Optional接口来使null快速失败,即在可能为null的对象上做了一层封装。一个Optional实例可能包含非null的引用(我们称之为引用存在),也可能什么也不包括(称之为引用缺失)。它从不说包含的是null值,而是用存在或缺失来表示。Optional从不会包含null值引用。

1、创建Optional实例

方法名 方法描述
Optional.of(T) 获得一个Optional对象,其内部包含了一个非null的T数据类型实例,若T=null,则立刻报错。
Optional.absent() 获得一个Optional对象,其内部包含了空值
Optional.fromNullable(T) 将一个T的实例转换为Optional对象,T的实例可以不为空,也可以为空

2、Optional中实例方法

boolean isPresent() 如果Optional包含非null的引用(引用存在),返回true
T get() 返回Optional所包含的引用,若引用缺失,则抛出java.lang.IllegalStateException
T or(T) 返回Optional所包含的引用,若引用缺失,返回指定的值
T orNull() 返回Optional所包含的引用,若引用缺失,返回null
Set asSet() 返回Optional所包含引用的单例不可变集,如果引用存在,返回一个只有单一元素的集合,如果引用缺失,返回一个空集合。
    public static void main(String[] args) {

        Integer i1 = null;
        Integer i2 =new Integer(5);


//        Optional o1 = Optional.of(i1);/*报错,那么我们很快就知道了i1在赋值给o1之前就已经为null了,这样有利于我们排查错误*/

        Optional<Integer> o1 = Optional.fromNullable(i1);
        Optional<Integer> o2 = Optional.of(i2);
        Optional<Integer> o3 = Optional.absent();



        System.out.println("o1 is present: " + o1.isPresent());

        System.out.println("o2 is present: " + o2.isPresent());

        System.out.println("o3 is present: " + o3.isPresent());

        //Optional.or - returns the value if present otherwise returns
        //the default value passed.
        Integer value1 = o1.or(new Integer(0));     //给null一个默认值


        //Optional.get - gets the value, value should be present
        Integer value2 = o2.get();
        Integer value3 = o3.orNull();

        System.out.println("i1:"+value1+"   |i2:"+value2+"   |i1+i2:"+(value1+value2));
        System.out.println(value3);

    }

}

我们以Optional.of方法为例,如果传入值为空,则立刻抛出异常。这样我们很快就知道了问题所在,这是很有用的。因为在某些情况下,比如后台通过前台获取Parameter时,我们有时会在获取了Parameter值时并不知道其为null,而在后面的运行中才爆出了空指针异常。

3.其它方法

方法类型 方法描述
boolean equals(Object object) 如果object是Optional实例,并且包含的引用彼此相等或两者都不存在,则返回true。
int hashCode() 返回此实例的哈希码。
String toString() 返回此实例的字符串表示形式。
Optional transform(Function function) 如果实例存在,则使用给定的函数进行转换;否则返回 absent()。

使用Optional除了赋予null语义,增加了可读性,最大的优点在于它是一种傻瓜式的防护。Optional迫使你积极思考引用缺失的情况,因为你必须显式地从Optional获取引用。

 List<Integer> list1 = Lists.newArrayList(1, 2, 3);
        Optional<List<Integer>> listOptional = Optional.fromNullable(list1);
        List<Integer> list2 = listOptional.transform(object -> {
            for (int i = 0; i < object.size(); i++) {
                object.set(i, object.get(i) + 1);
           }
            return object; // Optional.of([2, 3, 4])
        }).get();
        for (Integer i:list2) {
            System.out.println(i);
        }


        List<String> list = null;
        Optional<List<String>> listOptional1 = Optional.fromNullable(list);
        List<String> list3 =listOptional1.transform(object -> {
            return object; // Optional.absent()
        }).get();       //报错
        System.out.println(list3);

Preconditions

Preconditions 提供静态方法来检查方法或构造函数被调用时是否给定适当的参数,它检查先决条件,其方法失败抛出异常,大大地简化在代码中对于参数的预判断和处理

方法声明 描述 检查失败时抛出的异常
checkArgument( boolean ) 检查boolean是否为true,用来检查传递给方法的参数。 IllegalArgumentException
checkNotNull(T) 检查值是否为null,不为null则返回该值。 NullPointerException
checkState(boolean) 用来检查对象的某些状态。 IllegalStateException
checkElementIndex(int index,int size) 检查index作为索引值对某个列表、字符串或数组是否有效。index>=0 && index IndexOutOfBoundsException
checkPositionIndex(int index,int size) 检查index作为位置值对某个列表、字符串或数组是否有效。index>=0 && index<=size IndexOutOfBoundsException
checkPositionIndexes(int start,int end,int size) 检查[start, end]表示的位置范围对某个列表、字符串或数组是否有效 IndexOutOfBoundsException

索引值常用来查找列表、字符串或数组中的元素,如List.get(int),String.charAt(int)

位置值和位置范围常用来截取列表、字符串或数组,如List.subList(int,int),String.substring(int)

另外Guava提供更方便的异常信息打印(Preconditions提供可变参数列表,可供我们打印多余信息,比如可以在条件不满足时打印上下文变量值,下文可看到在条件不满足时打印出方法所有参数值)。

public class preconditionsTest {


    public static void main(String[] args) {

        /*checkNotNull,抛出NullPointerException*/
//            Preconditions.checkNotNull(null);         //抛出异常
//            Preconditions.checkNotNull(null,"有错");       //抛出异常,并且指定提示语

        
//        int i = Preconditions.checkNotNull(1,"有错");   //checkNotNull的返回值类型即为传入参数的类型,没有报错则返回参数给返回值
//        System.out.println(i);


        
        
//        try{
//            Preconditions.checkNotNull(null,"有错");
//        }catch (Exception e){
//            System.out.println(e.getClass()==NullPointerException.class);
//            System.out.println(e.getMessage().equals("有错"));
//        }

        
        
//        Preconditions.checkNotNull(null,"请填入至少%s个元素",1);    


        /*checkArgument,抛出IllegalArgumentException*/

//        try{
//            Preconditions.checkArgument("a".equals("b"));
//            }catch (Exception e){
//                System.out.println(e.getClass()==NullPointerException.class);
//                System.out.println(e.getClass()==IllegalArgumentException.class);
//        }


        /*checkState,抛出IllegalStateException*/
//        try{
//            Preconditions.checkState("a".equals("b"));
//            System.out.println("执行到这了");
//        }catch (Exception e){
//            System.out.println(e.getClass()==IllegalStateException.class);
//        }

        /*checkElementIndex,抛出IndexOutOfBoundsException*/
//        try{
//            List list = Arrays.asList("aaa","bbb","ccc","ddd","eee");
            Preconditions.checkElementIndex(10,list.size());
//            Preconditions.checkElementIndex(4,list.size());
//            System.out.println("执行到这了");
//        }catch (Exception e){
//            System.out.println(e.getClass()==IndexOutOfBoundsException.class);
//        }



    }
}

反射

TypeToken

Guava TypeToken类是用来解决java运行时泛型类型被擦除的问题。当我们拿到一个ArrayList的时候,我们无法通过getClass方法获取到ArrayList里的泛型。而存有不同类型的ArrayList调用getClass方法的返回结果是一样的。

ArrayList<String> stringList = Lists.newArrayList();
ArrayList<Integer> intList = Lists.newArrayList();
System.out.println("stringList type is : " + stringList.getClass());
System.out.println("intList type is : " + intList.getClass());

使用TypeToken

TypeToken<ArrayList<String>> typeToken = new TypeToken<ArrayList<String>>() {
};
//TypeToken解析出泛型参数的具体类型
//resolveType是一个功能强大但复杂的查询操作,可用于“替换”上下文标记中的类型参数。
TypeToken<?> typeToken1 = typeToken.resolveType(ArrayList.class.getTypeParameters()[0]);            //ArrayList中只有一个泛型,通过ArrayList.class.getTypeParameters()[0]获取到.resolveType方法会根据传入的参数去解析typeToken里所存的类型,然后解析出相应的泛型,如果传入类型不一致则无法查出结果.
System.out.println(typeToken1.getType());//class java.lang.String
Invokable

Guava的Invokable是对java.lang.reflect.Method和java.lang.reflect.Constructor的流式包装,包装起来后,用更简洁的代码去调用反射。

public class InvokableTest {
    public static void main(String[] args) throws InvocationTargetException, IllegalAccessException {
        // 对象
        InvokableInstance object = new InvokableInstance(10);
        // 获去对象对应的类
        Class clazz = object.getClass();
        Method[] invokableSourceList = clazz.getMethods();
//        Constructor[] invokableSourceList =  clazz.getConstructors();
        if (invokableSourceList.length > 0) {
            for (Method item : invokableSourceList) {
                System.out.println("========================================");
                // 方法名字
                System.out.println("方法名字:" + item.getName());
                // 把Method包装成Invokable
                Invokable methodInvokable = Invokable.from(item);
                // getDeclaringClass() 获取定义该方法的类
                System.out.println("定义该方法的类:" + methodInvokable.getDeclaringClass());
                // getOwnerType() 获取定义该方法的class的包装对象Type
                System.out.println("定义该方法的类:" + methodInvokable.getOwnerType().getType());
                // isOverridable() 该方法是否可以重写
                System.out.println("是否可以重写:" + methodInvokable.isOverridable());
                // isVarArgs() 该方法是否可变参数
                System.out.println("是否可变参数:" + methodInvokable.isVarArgs());
                // getReturnType() 该方法返回值类型
                System.out.println("返回值类型:" + methodInvokable.getReturnType().getType());
                // getParameters() 获取参数
                ImmutableList<Parameter> parameterList = methodInvokable.getParameters();
                for (int index = 0; index < parameterList.size(); index++) {
                    System.out.println("方法参数" + index + ": " + parameterList.get(index).getType());
                }
                // getExceptionTypes() 获取异常类
                ImmutableList<TypeToken> exceptionList = methodInvokable.getExceptionTypes();
                for (int index = 0; index < exceptionList.size(); index++) {
                    System.out.println("异常类" + index + ": " + exceptionList.get(index).getType());
                }
            }
        }

        //调用方法
        Invokable methodInvokable1 = Invokable.from(invokableSourceList[0]);
        methodInvokable1.invoke(object,1,2);
    }
}

Guava对Object类方法的重写

Guava提供了一些工具类,帮助我们对Object对象的方法进行重写。

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

String object = null;
System.out.println("test".equals(object));    //false
//System.out.println(object==null);
//System.out.println(object.equals("test"));        //报错
    if(object == null){							//null检查
      System.out.println("不可为空");
    }else{
      System.out.println(object.equals("test"));  //不可为空
    }


//Objects的equal方法
System.out.println(Objects.equal("a","a"));   //true
System.out.println(Objects.equal(object,"b"));//false
System.out.println(Objects.equal("b",object));//false
System.out.println(Objects.equal(null,null)); //true

System.out.println(Objects.equal(new Employee("liu",20),new Employee("liu",20)));  //false
Employee person = new Employee("liu",20);
System.out.println(Objects.equal(person,person));   //true

Object类中的hashCode和equal都是用于判断两个对象是否相等。对象通过调用Object.hashCode()生成哈希值,由于不可避免地会存在哈希值冲突,因此hashCode相同时,还需要再调用equals进行比较,但若是hashCode不同,将直接判断两个对象不同,跳过equal,这加快了冲突处理效率。

Object类定义中对hashCode和equals要求:

  • 如果两个对象的equals的结果是相等的,则两个对象的hashCode返回值也必相同。
  • 任何时候重写equals,都必须同时重写hashCode。

Guava的Objects.hashCode(Object…)会对传入的字段序列计算出合理的、顺序敏感的散列值,代替手动计算每一个散列值。这样我们可以方便的进行大量数据的hashcode比较。

public class hashCodeTest {
    public static void main(String[] args) {
        Integer integer = new Integer(1);
        Integer i = 1;
        System.out.println(integer.hashCode());
        System.out.println(i.hashCode());

        String string = new String("two");
        String s = "two";
        System.out.println(string.hashCode());
        System.out.println(s.hashCode());

        Double aDouble = new Double(3.0);
        Double d = 3.0;
        System.out.println(aDouble.hashCode());
        System.out.println(d.hashCode());

        int h1 = Objects.hashCode(1, "two", 3.0);
        int h2 = Objects.hashCode(new Integer(1), new String("two"), new Double(3.0));
        System.out.println("h1 = " + h1);
        System.out.println("h2 = " + h2);
        System.out.println(Objects.equal(h1,h2));   // true
    }
}

Guava提供MoreObjects.toStringHelper()可以轻松编写有用的toString方法。

@Override
public String toString() {
   // System.out.println(MoreObjects.toStringHelper(this).omitNullValues());      //  输出Guava1{}

    return MoreObjects.toStringHelper(this).omitNullValues()        //tostring时忽略属性中的null值
            .add("manufacturer",this.manufacturer)
            .add("version",this.version)
            .add("releaseDate",this.releaseDate).toString();
}

对于compareTo方法的重写,Guava中提供了一个ComparisonChain方法,该方法执行一种懒比较:它执行比较操作直至发现非零的结果,在那之后的比较输入将被忽略。

 @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;
        } else if(score > other.score){
            return 1;
        } else if(score < other.score){
            return -1;
        }
        return 0;
    }
@Override
public int compareTo(Person other){
    return ComparisonChain.start()
            .compare(name,other.name)
            .compare(age,other.age)
            .compare(score,other.score)
            .result();
}

数学运算Math

为什么使用Guava Math

  • Guava Math针对各种不常见的溢出情况有充分的测试;对溢出语义,Guava文档也有相应的说明;如果运算的溢出检查不能通过,将导致快速失败;
  • Guava Math的性能经过了精心的设计和调优;虽然性能不可避免地依据具体硬件细节而有所差异,但Guava Math的速度通常可以与Apache Commons的MathUtils相比,在某些场景下甚至还有显著提升;
  • Guava Math在设计上考虑了可读性和正确的编程习惯;IntMath.log2(x, CEILING) 所表达的含义,即使在快速阅读时也是清晰明确的。而32-Integer.numberOfLeadingZeros(x – 1)对于阅读者来说则不够清晰。
整数运算

Guava Math主要处理三种整数类型:int、long和BigInteger。这三种类型的运算工具类分别叫做IntMath、LongMath和BigIntegerMath。

有溢出检查的运算

有溢出检查的运算,如果计算结果有溢出的情况下(上溢,下溢),就会抛出ArithmeticException异常。

运算(有溢出检查) IntMath里面方法 LongMath里面方法
加法 checkedAdd(int a, int b) checkedAdd(int a, int b)
减法 checkedSubtract(int a, int b) checkedSubtract(int a, int b)
乘法 checkedMultiply(int a, int b) checkedMultiply(int a, int b)
checkedPow(int b, int k) checkedPow(int b, int k)
上溢,下溢返回最大值最小值

如果对应的运算发生溢出,上溢则返回对应类型的最大值(Integer.MAX_VALUE、Long.MAX_VALUE )、下溢则返回对应类型的最小值(Integer.MIN_VALUE、Long.MIN_VALUE)。

运算 IntMath里面方法 LongMath里面方法
加法 saturatedAdd(int a, int b) saturatedAdd(int a, int b)
减法 saturatedSubtract(int a, int b) saturatedSubtract(int a, int b)
乘法 saturatedMultiply(int a, int b) saturatedMultiply(int a, int b)
saturatedPow(int b, int k) saturatedPow(int b, int k)
实数运算

IntMath、LongMath和BigIntegerMath提供了很多实数运算的方法,并把最终运算结果舍入成整数。这些方法需要指定一个java.math.RoundingMode枚举值来作为舍入的模式。RoundingMode的取值如下:

RoundingMode枚举值 解释
RoundingMode.DOWN 向零方向舍入(去尾法),保留整数部分
RoundingMode.UP 远离零方向舍入,向上取整
RoundingMode.FLOOR 向负无限大方向舍入
RoundingMode.CEILING 向正无限大方向舍入
RoundingMode.UNNECESSARY 不需要舍入,如果用此模式进行舍入,应直接抛出ArithmeticException
RoundingMode.HALF_UP 向最近的整数舍入,其中x.5远离零方向舍入
RoundingMode.HALF_DOWN 向最近的整数舍入,其中x.5向零方向舍入
RoundingMode.HALF_EVEN 向最近的整数舍入,其中x.5向相邻的偶数舍入
实数运算方法
运算 IntMath里面方法 LongMath里面方法
除法 divide(int, int, RoundingMode) divide(long, long, RoundingMode)
2为底的对数 log2(int, RoundingMode) log2(long, RoundingMode)
10为底的对数 log10(int, RoundingMode) log10(long, RoundingMode)
平方根 sqrt(int, RoundingMode) sqrt(long, RoundingMode)

除了上述方法,Guava还另外提供了一些有用的运算函数

运算 IntMath里面方法 LongMath里面方法 BigIntegerMath里面方法
最大公约数 gcd(int, int) gcd(long, long) gcd(BigInteger)
取模 mod(int, int) mod(long, long) mod(BigInteger)
取幂 pow(int, int) pow(long, int) pow(int)
是否2的幂 isPowerOfTwo(int) isPowerOfTwo(long) isPowerOfTwo(BigInteger)
阶乘 factorial(int) factorial(int) factorial(int)
二项式系数 binomial(int, int) binomial(int, int) binomial(int, int)

阶乘和二项式系数的运算结果如果溢出,则返回MAX_VALUE

区间(Ranges)

Ranges:Guava强大的API,用于处理连续和离散的可Comparable比较类型的区间。区间(称为间隔)是特定域的凸(非正式地称为"连续的"或"不间断的")部分。区间可以『扩展到无穷大』或可以受到『有限约束』,如x > 3、2 <= x < 5。

为了提高一致性,Guava的Range概念要求上端点不能小于下端点。仅当至少一个边界闭合时,上下端点才可能相等:

  • [a..a]: 单重区间
  • [a..a); (a..a]: 空,但有效
  • (a..a): 无效

Guava中区间的类型为Range,所有区间都是不可变的

1、构建区间

Range上的静态方法获得区间:

区间类型 方法
(a…b) open(C, C)
[a…b] closed(C, C)
[a…b) closedOpen(C, C)
(a…b] openClosed(C, C)
(a…+∞) greaterThan©
[a…+∞) atLeast©
(-∞…b) lessThan©
(-∞…b] atMost©
(-∞…+∞) all()
有界区间 range(C, BoundType, C, BoundType)
无上界区间:((a…+∞) 或[a…+∞)) downTo(C, BoundType)
无下界区间:((-∞…b) 或(-∞…b]) upTo(C, BoundType)

这里的BoundType是一个枚举类型,包含CLOSED和OPEN两个值。

2、区间运算

Range的基本运算是它的contains©方法,用来区间判断是否包含某个值。任何Range实例也都支持containsAll(Iterable)方法。

System.out.println("判断2是否在[1,3]中:" + Range.closed(1, 3).contains(2));  //true
System.out.println("判断5是否在(-∞,5)中:" + Range.lessThan(5).contains(5));  //false
boolean b = Range.range(1, BoundType.CLOSED, 4, BoundType.OPEN)
        .containsAll(Lists.newArrayList(1, 2, 3));
System.out.println("判断1,2,3是否在[1,4)中:" + b);  //true

2.1 查询运算

方法 描述
hasLowerBound() hasUpperBound() 判断区间是否有特定边界,或是无限的;
lowerBoundType() upperBoundType() 返回区间边界类型,CLOSED或OPEN;如果区间没有对应的边界,抛出IllegalStateException;
lowerEndpoint()和upperEndpoint() 返回区间的端点值;如果区间没有对应的边界,抛出IllegalStateException
isEmpty() 判断是否为空区间
System.out.println("判断是否是空区间:" + Range.closedOpen(4, 4).isEmpty());  //true	
System.out.println("判断是否有上界:" + Range.lessThan(5).hasUpperBound());  //true
System.out.println("返回左边界类型:" + Range.closedOpen(1, 3).lowerBoundType()); //CLOSE
System.out.println("返回右边界类型:" + Range.closedOpen(1, 3).upperBoundType()); //OPEN
System.out.println("返回左端点:" + Range.closedOpen(3, 5).lowerEndpoint());  //3
System.out.println("返回右端点:" + Range.closedOpen(3, 5).upperEndpoint());  //5
System.out.println("左端点无界,抛出异常::" + Range.lessThan(5).lowerEndpoint());

2.2 关系运算

包含[encloses]

一个range里面的值都在另一个range里面。

Range<Integer> open = Range.open(3, 5);
Range<Integer> closed = Range.closed(2, 7);
System.out.println(closed.encloses(open));  //true

相连[isConnected]

两个区间是否相连,指的是两个区间有相交的情况或者两个区间的元素正好可以拼起来。

System.out.println(Range.closed(1, 3).isConnected(Range.closed(3, 5)));  //true
System.out.println(Range.closed(1, 3).isConnected(Range.closed(4, 5)));  //false
System.out.println(Range.closed(1, 6).isConnected(Range.open(2, 8)));    //true
System.out.println(Range.open(1, 3).isConnected(Range.open(3, 6)));      //false

交集[intersection]

返回两个区间的交集:既包含于第一个区间,又包含于另一个区间的最大区间。当且仅当两个区间是相连的,它们才有交集。如果两个区间没有交集,该方法将抛出IllegalArgumentException。

Range<Integer> intersection = Range.closed(3, 5).intersection(Range.closed(4, 6));
System.out.println("交集范围: " + intersection);  //[4..5]
System.out.println(Range.closed(3, 5).intersection(Range.open(5, 10)));  //(5..5]
System.out.println(Range.closed(0, 9).intersection(Range.closed(3, 4)));  //[3..4]
System.out.println(Range.open(3, 5).intersection(Range.open(5, 10)));  //抛出异常

两个区间之间的范围(gap)

返回原区间范围与目标区间之间的最大范围。这个是有条件的:区间不能有交集。

System.out.println(Range.closed(1, 5).gap(Range.closed(7, 10)));  //(5..7)
System.out.println(Range.closed(1, 6).gap(Range.closed(4, 8)));   //有交集报错

跨区间[span]

返回”同时包括两个区间的最小区间”,如果两个区间相连,那就是它们的并集。

System.out.println(Range.closed(0, 9).span(Range.closed(3, 4)));  //[0..9]
System.out.println(Range.closed(0, 5).span(Range.closed(3, 9)));  //[0..9]
System.out.println(Range.open(1, 3).span(Range.open(5, 10)));     //(1,10)

字符串处理工具

joiner

所谓连接器,就是对字符串进行拼接的操作,可对数组、List、Map等连接为字符串。其中不乏要进行判空、空字符串转换、分隔符添加等处理,使用Joiner会极大的解决繁杂的处理过程:

方法 使用
on() 静态方法用于创建对象,唯一的参数是连接符
skipNulls() 过滤集合中为null的元素,然后返回一个新的Joiner对象实例;
useForNull() 将集合中为null的元素替换成指定的字符串,并返回新的Joiner对象实例;
withKeyValueSeparator() 键值对中(map中)key和value之间的分隔符
join() 需连接的对象(list、map)等,返回String对象
    private static final List<String> stringList = Arrays.asList("aaa","bbb","ccc","ddd","eee");

    private static final List<String> stringListWithNull = Arrays.asList("aaa","bbb","ccc","ddd",null);       



	/*用java来拼接一个字符串*/
//        String result = stringListWithNull.stream().filter(item -> item != null).collect(Collectors.joining("#"));      //断言         还有collect方法
//        System.out.println(result);


        String resultwithdefault = stringListWithNull.stream().map(item -> item==null?"alla":item).collect(Collectors.joining("#"));      //断言      
        System.out.println(resultwithdefault);


        /*此处起为join一个可迭代的对象*/

//        String result = Joiner.on("#").join(stringList);
//        System.out.println(result);




//        String result1 = Joiner.on("#").join(stringListWithNull);       //报错

//        String result1 = Joiner.on("#").skipNulls().join(stringListWithNull);         //跳过null

//        String result1 = Joiner.on("#").useForNull("alla").join(stringListWithNull);    //代替空值

//        System.out.println(Joiner.on("#").skipNulls().getClass());      //Join类对象 Joiner.on方法以及后续方法分别返回的对象

//        System.out.println(result1);
splitter

JDK中,使用String.split()对字符串进行拆分,但该方法会悄悄丢弃了尾部的分隔符。

String string = ",a,,b,";
String[] split = string.split(",");
for (String s : split) {
    System.out.println(s + '!');
}  //! a! ! b!

Splitter中的方法匹配所有的分隔符

Iterable split1 = Splitter.on(",")
        .split(string);
for (String s : split1) {
    System.out.println(s + '!');
} //! a! ! b! !

Splitter可以被设置为按照任何模式、字符、字符串或字符匹配器进行拆分,常见方法有:

方法 描述
Splitter.on(char) 按单个字符拆分
Splitter.on(CharMatcher) 按字符匹配器拆分
Splitter.on(String) 按字符串拆分
Splitter.on(Pattern) Splitter.onPattern(String) 按正则表达式拆分
Splitter.fixedLength(int) 按固定长度拆分;最后一段可能比给定长度短,但不会为空。
splitToList() 切分,返回List对象
split() 切分,返回Iterable对象

Splitter中的修饰符:

方法 描述
omitEmptyStrings() 从结果中自动忽略空字符串
trimResults() 去掉首尾空白符
trimResults(CharMatcher) 给定匹配器,移除结果字符串的首部匹配字符和尾部匹配字符
limit(int) 限制拆分出的字符串数量
withKeyValueSeparator(s) 通过参数s将List中的元素拆分为map的key和value

splitter实例总是不可变的。用来定义splitter目标语义的配置方法总会返回一个新的splitter实例。这使得splitter实例都是线程安全的,可以将其定义为static final常量。

public class splitterTest {

    public static void main(String[] args) {

//        System.out.println(Splitter.on(" ").getClass());            //Splitter对象

        /*分割字符串*/

//        List result = Splitter.on("|").splitToList("hello|world");
//        for (String s: result) {
//            System.out.println(s);
//        }


        /*去除空字符*/
//        List result = Splitter.on("|").splitToList("hello|world||||");

//        List result = Splitter.on("|").omitEmptyStrings().splitToList("hello|world||||");
//        for (String s: result) {
//            System.out.println(s);
//        }
//        System.out.println(result.size());

        /*去除空字符,同时去除非空选项内边缘的空格*/
//        List result = Splitter.on("|").omitEmptyStrings().splitToList("hello | world||||");
//        List result = Splitter.on("|").trimResults().omitEmptyStrings().splitToList("hello | world||||");
//        for (String s: result) {
//            System.out.println(s);
//        }
//        System.out.println(result.size());

        /*固定长度切片*/
//        List result = Splitter.fixedLength(4).splitToList("abcdefghhijklmn");
//        for (String s: result) {
//            System.out.println(s);
//        }
//        System.out.println(result.size());


        /*限定分割次数*/
//        List result = Splitter.on("|").limit(3).splitToList("hello|world|how|are|you|?");
//        List result = Splitter.fixedLength(4).limit(3).splitToList("abcdefghhijklmn");
//        for (String s: result) {
//            System.out.println(s);
//        }
//        System.out.println(result.size());

        /*Splitter使用正则表达式*/
//        List result = Splitter.onPattern("\\d").trimResults().omitEmptyStrings().splitToList("how3are9 you8");
//        for (String s: result) {
//            System.out.println(s);
//        }
//        System.out.println(result.size());


//        List result = Splitter.on(Pattern.compile("\\d")).trimResults().omitEmptyStrings().splitToList("how3are9 you8");
//        for (String s: result) {
//            System.out.println(s);
//        }
//        System.out.println(result.size());

        /*Splitter拆分成Map*/
        Map<String,String> map = Splitter.on(Pattern.compile("\\d")).trimResults().omitEmptyStrings().withKeyValueSeparator("=").split("how=ok3are=yes9 you=right8");       //先split,再把split结果中划分成key和Value;trimResults()或者omitEmptyStrings()是修改Splitter属性并返回本身的方法
        System.out.println(map.get("how"));
        System.out.println(map.get("are"));
        System.out.println(map.get("you"));
        System.out.println(map.get(" you"));
        System.out.println(map.keySet());
    }
}

集合(collections)

集合工具类

Lists

asList()

将array转化为List,并在list头部插入值,不支持基本类型array。

List<String> stringList = Arrays.asList("aaa","bbb","ccc","ddd","eee");
List<String> list = Lists.asList("a", new String[]{"b", "c", "d"});
System.out.println("list = " + list);
List<String> list1 = Lists.asList("a", "b", new String[]{"c", "d", "e"});
System.out.println("list1 = " + list1);

newArrayList()

创建数组集合

Lists.newArrayList("lele","kaka").forEach(System.out::println);

newArrayListWithCapacity(int initialArraySize)

用于确定list装多少个元素,不会改变。如果容器超过定义size,它会自动扩容。扩容后,会将原来的数组复制到新的数组中,但扩容会带来一定的性能影响:包括开辟新空间,copy数据,耗时,耗性能

ArrayList<String> arrayList3 = Lists.newArrayListWithCapacity(3);
arrayList3.add("q");
arrayList3.add("w");
arrayList3.add("e");
System.out.println("arrayList3 = " + arrayList3);

newArrayListWithExpectedSize(int estimatedSize)

用于不确定list装多少个元素。使用该方法,会直接创建一个指定size的容器,通过5L + (long)arraySize + (long)(arraySize / 10)公式来进行扩容。它的底层就是newArrayListWithCapacity(int initialArraySize)实现的。

ArrayList<String> arrayList4 = Lists.newArrayListWithExpectedSize(3);
arrayList4.add("a");
arrayList4.add("b");
System.out.println("arrayList4 = " + arrayList4);

cartesianProduct(List… lists)

计算笛卡尔积,可以传多个List

List<List<String>> result = Lists.cartesianProduct(Lists.newArrayList("1","2"),Lists.newArrayList("A","B"));
List<List<String>> result1 = Lists.cartesianProduct(Lists.newArrayList("1","2"),Lists.newArrayList("A","B"),Lists.newArrayList("k","y"));
System.out.println(result);
System.out.println(result1);

transform(List source,Function function)

对集合中的元素进行转换

ArrayList<String> source = Lists.newArrayList("kaka","xixi","hehe");
Lists.transform(source,e -> e.toUpperCase(Locale.ROOT)).forEach(System.out::println);         //也可以用FluentIterable的transform方法

reverse()

反转list集合中的元素

ArrayList<String> result = Lists.newArrayList("1","2","3");
System.out.println(Lists.reverse(result));

partition(List list, int size)

将list按指定大小分隔成多个list,返回List集合,其中元素为List集合

/*Pratiton,分区,用于查询时提高效率,比如我们关系型数据库中就使用了分区*/
List<List<String>> res = Lists.partition(Lists.newArrayList("1","2","3","4"),2);
System.out.println(res.get(0));
System.out.println(res.get(1));

Sets

JDK中一般我们以如下方式创建一个hashSet

Set<Integer> s1 = new HashSet<>();
s1.add(1);
s1.add(2);
s1.add(3);
System.out.println(s1);

现在我们可以这样去创建

newHashSet()

HashSet<Integer> s1 = Sets.newHashSet(1,2,3); 

此外我们还可以进行元素去重

HashSet<Integer> set = Sets.newHashSet(Lists.newArrayList(1,2,3,1));  

cartesianProduct()

笛卡尔积

Set<List<Integer>> set = Sets.cartesianProduct(Sets.newHashSet(1,2),Sets.newHashSet(3,4),Sets.newHashSet(1,5));           //笛卡尔积
        System.out.println(set);

combinations()

求出Set的所有规定长度的子集

        HashSet<Integer> s1 = Sets.newHashSet(1,2,3);
//        Set> comb = Sets.combinations(s1,1);
        Set<Set<Integer>> comb = Sets.combinations(s1,2);                 //子集
        comb.forEach(System.out::println);

difference()

求差集

HashSet<Integer> s1 = Sets.newHashSet(1,2,3);               //差集
HashSet<Integer> s2 = Sets.newHashSet(1,2,5);
System.out.println(Sets.difference(s1,s2));             //s1有s2没有
System.out.println(Sets.difference(s2,s1));             //s2有s1没有
Sets.SetView<Integer> d1 ;

intersection()

union()

求交集和并集

HashSet<Integer> s1 = Sets.newHashSet(1,2,3);
HashSet<Integer> s2 = Sets.newHashSet(1,2,5);
System.out.println(Sets.intersection(s1,s2));           //交集
System.out.println(Sets.union(s1,s2));           //并集

Maps

静态工厂方法——创建Map集合

Map<String,Integer> map = Maps.newHashMap();
map.put("a",1);
map.put("b",2);
System.out.println("map = " + map);  //map = {a=1, b=2}
ConcurrentMap<String, Integer> map1 = Maps.newConcurrentMap();
System.out.println("map1 = " + map1);   //map1 = {}

uniqueIndex()和asMap()

通过这两种方法,我们分别可以使用List和Set创建一个Map。并且这两个方法可以传入一个函数式接口,这个传入的方法分别在这两个方法中用于创建key和value

ArrayList<String> valueList = Lists.newArrayList("1","2","3");
ImmutableMap<String,String> map = Maps.uniqueIndex(valueList,v -> v+"_key");                //通过List创建
System.out.println(map);
Map<String,String> map2 = Maps.asMap(Sets.newHashSet("1","2,","3"),k -> k+"_value" );           //通过Set创建
System.out.println(map2);

transformValues()

对Map的value进行统一处理

Map map2 = Maps.asMap(Sets.newHashSet("1","2,","3"),k -> k+"_value" );
System.out.println(map2);
Map newmap = Maps.transformValues(map2,v -> v + "_trans");
System.out.println(newmap);

filterKeys()

过滤key

Map map3 = Maps.asMap(Sets.newHashSet(1,2,3),k -> k+"_value" );
Map newmap3 = Maps.filterKeys(map3, k -> k>1);
System.out.println(newmap3);

Guava新增集合类型

MultiMap

java中的Map维护的是键值一对一的关系,如果要将一个键映射到多个值上,那么就只能把值的内容设为集合形式,简单实现如下:

Map<String, List<Integer>> map=new HashMap<>();
List<Integer> list=new ArrayList<>();
list.add(1);
list.add(2);
map.put("a",list);

guava中的Multimap提供了将一个键映射到多个值的形式,使用起来无需定义复杂的内层集合,可以像使用普通的Map一样使用它,定义及放入数据如下:

Multimap<String, Integer> multimap = ArrayListMultimap.create();
multimap.put("day",1);
multimap.put("day",2);
multimap.put("day",8);
multimap.put("month",3);

LinkedListMultimap<Integer,String> multimap = LinkedListMultimap.create();
multimap.put(1,"lala");
multimap.put(1,"lolo");
multimap.put(2,"haha");

打印这个Multimap的内容,可以直观的看到每个key对应的都是一个集合:

{1=[lala, lolo], 2=[haha]}
[lala, lolo]

创建的普通Multimapget(key)方法将返回一个Collection类型的集合:

Collection day = multimap.get("day");

如果在创建时指定为ArrayListMultimap或者LinkedListMultimap类型,那么get方法将返回一个List

 List list =multimap.get(1);

同理,你还可以创建HashMultimapTreeMultimap等类型的Multimap

使用get方法返回的集合也不是一个独立的对象,可以理解为集合视图的关联,对这个新集合的操作仍然会作用于原始的Multimap

LinkedListMultimap<Integer,String> multimap1 = LinkedListMultimap.create();
multimap1.put(1,"lala");
multimap1.put(1,"lolo");
multimap1.put(2,"haha");
multimap1.put(2,"heihei");
List<String> c= multimap1.get(1);
c.add("yeye");
c.remove(0);
System.out.println(1+" : "+multimap1.get(1));

我们可以将MultiMap转为普通Map

LinkedListMultimap<Integer,String> multimap1 = LinkedListMultimap.create();
multimap1.put(1,"lala");
multimap1.put(1,"lolo");
multimap1.put(2,"haha");
multimap1.put(2,"heihei");
Map<Integer, Collection<String>> map= multimap1.asMap();
for (Integer key : map.keySet()) {
    System.out.println(key+" : "+map.get(key));
}

MultiMap的Size

System.out.println(multimap.size());					//所有key到单个value的映射
System.out.println(multimap.entries().size());			//所有键值对
System.out.println(multimap.keySet().size());           //不同key的个数
Map<Integer, Collection<String>> map= multimap.asMap();
System.out.println(map.entrySet().size());				//key到Value的映射,只不过此时的value已经是一个集合
BiMap

BiMap()提供了一种新的集合类型,它提供了key和value的双向关联的数据结构。

BiMap是一种特殊的Map,可以用inverse()反转BiMap的键值映射,同时保证值是唯一的。

HashBiMap<String,String> biMap = HashBiMap.create();
biMap.put("1","xixi");
biMap.put("1","hehe");      //  覆盖
System.out.println(biMap);
biMap.put("2","hehe");      //报错

键值反转

HashBiMap<String,String> biMap = HashBiMap.create();
biMap.put("1","xixi");
biMap.put("2","hehe");
biMap.put("3","koko");
BiMap<String, String> biMap1 = biMap.inverse();
System.out.println(biMap1);

当一个value已经存在于BiMap的某一个键值对中时,使用forcePut可以强制插入一个相同value的键值对,但同样的,之前的键值对会被覆盖掉。

HashBiMap<String,String> biMap = HashBiMap.create();
biMap.put("1","hehe");
System.out.println(biMap);
biMap.forcePut("2","hehe");         //覆盖
System.out.println(biMap);
Table

Table是Guava提供的一个双值Map,即Table的一个value所对应的key是由两个值构成的Table中的两个key分别被称为rowKeycolumnKey,也就是行和列(既然如此,我们可以把Table想象成一个围棋的棋盘)。

假设我要找地球上某个位置(经纬度定位)站着的某个人,那么用普通Java的Map大概会是这样:

Map<Integer,Map<Integer,String>> map=new HashMap<>();
//存放元素
Map<Integer,String> workMap=new HashMap<>();
workMap.put(120,"张三");
workMap.put(-60,"李四");
map.put(30,workMap);

//取出元素
String person = map.get(30).get(-60);
String person1 = map.get(30).get(120);
System.out.println(person);
System.out.println(person1);

我们可以实现这个功能,但代码看上去有点繁琐,尤其是添加的时侯,不方便看出具体添加的经纬度坐标。

那么我们可以用Guava中的Table来实现一次。

Table<Integer,Integer,String> table= HashBasedTable.create();
table.put(30,120,"张三");
table.put(30,-60,"李四");
System.out.println(table.get(30,-60));

显然,这样的方式让代码看起来简洁明了多了。

现在,我们可以使用rowKeySet(),columnKeySet(),values()分别获取到所有的rowKey、columnKey和所有的值。

注意,由于rowKeySet(),columnKeySet()的返回类型为Set,所以我们不会重复获取到相同的rowKey、columnKey。但Value会。

Table table= HashBasedTable.create();
table.put(30,120,"张三");
table.put(30,-60,"李四");
table.put(30,120,"王二");
table.put(70,-60,"麻子");
table.put(-10,-60,"赵四");
table.put(70,45,"王二");
Set rowKeys = table.rowKeySet();
Set columnKeys = table.columnKeySet();
Collection values = table.values();
System.out.println(rowKeys);            //set
System.out.println(columnKeys);         //set
System.out.println(values);             //collection

现在我们继续上面的Table,查看一下同一纬度下住着那些人

for (Integer key : table.rowKeySet()) {
    System.out.println(key+":");
    Set<Map.Entry<Integer, String>> rows = table.row(key).entrySet();           //table.row(key)返回一个Map
    for (Map.Entry<Integer, String> row : rows) {
        System.out.println(row.getValue());
    }
    System.out.println("=================");
}

Table的行列转置

Set<Table.Cell<Integer, Integer, String>> cells1 = table.cellSet();         //cellSet方法可以得到所有的数据行,打印结果
cells1.forEach(cell->
        System.out.println(cell.getRowKey()+","+cell.getColumnKey()+":"+cell.getValue())
);

System.out.println("===============");
Table<Integer, Integer, String> table2 = Tables.transpose(table);
Set<Table.Cell<Integer, Integer, String>> cells2 = table2.cellSet();         //cellSet方法可以得到所有的数据行,打印结果
cells2.forEach(cell->
        System.out.println(cell.getRowKey()+","+cell.getColumnKey()+":"+cell.getValue())
);

我们可以将Table转为普通的Map。而这个Map的结构就跟上面我们使用Java模拟Table的Map结构是一样的。

Map<Integer, Map<Integer, String>> rowMap = table.rowMap();
Map<Integer, Map<Integer, String>> columnMap = table.columnMap();
System.out.println(rowMap);
System.out.println(columnMap);
MultiSet

Multiset和Set的区别就是可以保存多个相同的对象。在JDK中,List和Set有一个基本的区别,就是List可以包含多个相同对象,且是有顺序的,而Set不能有重复,且不保证顺序(有些实现有顺序,例如LinkedHashSet和SortedSet等),所以Multiset占据了List和Set之间的一个灰色地带:允许重复,但是不保证顺序。

Multiset继承于JDK的Collection接口,而不是Set接口。它和set最大的区别就是它可以对相同元素做一个计数的功能,普通的 Set 就像这样:[car, ship, bike],而 Multiset 会是这样:[car x 2, ship x 6, bike x 3],所以可以用来进行数字统计,这比用for循环往Map添加元素和计数方便多了。

public class MultiSetExample {
    public static void main(String[] args) {
        String[] words = new String[]{"li","lin","liu","lu","luo","li"};
        //传统方式
        Map<String,Integer> countMap = new HashMap<String,Integer>();
        for(String word : words){
            Integer count = countMap.get(word);
            if(count == null){
                countMap.put(word,1);
            }else{
                countMap.put(word,count + 1);
            }
        }
        System.out.println(countMap);
        
        
//使用multiSet方式
        List<String> list = Lists.newArrayList(words);
//创建一个HashMultiset集合,并将words集合数据放入
        HashMultiset<String> multiset = HashMultiset.create();
//可以添加Collection中的所有元素并进行计数
        multiset.addAll(list);
//将不同的元素放在一个集合set中
        for(String key : multiset.elementSet()){
            System.out.println(key + "-->" + multiset.count(key));
        }
    }
}
方法 描述
count(E) 返回给定参数元素的个数
elementSet() Multiset中不重复元素的集合,类型为Set
entrySet() 和Map的entrySet类似,其中包含的Entry支持getElement()和getCount()方法
add(E, int) 增加给定元素在Multiset中的计数
remove(E,int) 减少给定元素在Multiset中的计数
setCount(E,int) 设置给定元素在Multiset中的计数,不可以为负数
size() 返回集合元素的总个数(包括重复的元素)
retainAll(Collection c) 保留出现在给定集合参数的所有的元素
removeAll(Collection c) 去除出现在给定集合参数的所有的元素

Multiset与Map区别

  • Multiset中的元素出现的次数只能为正数,任何元素的计数都不能为负,也不能是0。
  • Multiset.size()返回集合的大小,等同于所有元素计数的总和,而Map.size()是元素个数的总和。
  • Multiset.iterator()可以遍历multiset中的所有元素,遍历次数等同Multiset.size()。
  • Multiset支持直接添加、删除元素,设置元素出现的次数;setCount(elem, 0)相当于移除elem的所有元素。
  • Multiset.count(elem)方法中的elem如果没有出现在Multiset中,那么它的返回值永远都是0。
RangeMap

假设我们要为一个班级的学生成绩进行评级,那么按照一般的写法,我们可能会写成下面的这个方法。

有一说一,这个代码是真的丑。

public static String check(int score){
    if (0<=score && score<60)
        return "不及格";
    else if (60<=score && score<80)
        return "及格";
    else if (80<=score && score<=90)
        return "良好";
    else if (90<score && score<=100)
        return "优秀";
    return null;
}

那么我们可以使用RangeMap试试。RangeMap可以将一个区间内的键映射到某一个值上,从而我们可以实现一个区间的成绩对应一个评级。

RangeMap<Integer, String> rangeMap = TreeRangeMap.create();             //静态方法
rangeMap.put(Range.closedOpen(0,60),"不及格");            //closedOpen是左闭右开
rangeMap.put(Range.closedOpen(60,80),"及格");
rangeMap.put(Range.closed(80,90),"良好");                 //closed闭区间
rangeMap.put(Range.openClosed(90,100),"优秀");

System.out.println(rangeMap.get(10));
System.out.println(rangeMap.get(60));
System.out.println(rangeMap.get(85));
System.out.println(rangeMap.get(95));

去除区间

rangeMap.remove(Range.open(70,80));
System.out.println(rangeMap.get(75));           //去除的区间
System.out.println(rangeMap.get(105));          //未定义区间
ClassToInstanceMap

我们可以用一个Map来存储一个实例以及相应的类对象。但是这种方法带来两个问题:

1.不能保证类对象和实例是一一对应的。

2.去处实例时,还需要进行强制类型转换。

Map<Class,Object> map=new HashMap<>();
map.put(E1.class,new E1());
map.put(E2.class,new E2());
E1 e1a = (E1) map.get(E1.class);
System.out.println(e1a);        //不为null,说明取到了

map.put(E2.class,new E1());
E1 e1b = (E1) map.get(E2.class);        //类和对象无法对应
System.out.println(e1b);

因此,Guava为我们提供了一个ClassToInstanceMap,这是一个专门存储类对象和实例的Map。并且,解决了以上的问题。

        ClassToInstanceMap<Object> instanceMap;
        instanceMap = MutableClassToInstanceMap.create();
        E1 e1 = new E1();
        E2 e2 = new E2();

        instanceMap.putInstance(E1.class,e1);
        instanceMap.putInstance(E2.class,e2);
//        instanceMap.putInstance(E1.class,e2);//类和对象无法对应.报错.而用普通的Map不会.

        E1 e1a =  instanceMap.getInstance(E1.class);          //不需要强制类型转换
//        System.out.println(e1a == e1);			//true

不可变集合

不可变集合,顾名思义就是说集合是不可被修改的。集合的数据项是在创建的时候提供,并且在整个生命周期中都不可改变。

JDK中实现immutable集合

public void JDKImmutable(){
    List<String> list = new ArrayList<>();
    list.add("a");
    list.add("b");
    list.add("c");
    System.out.println(list);  //a b c

    List<String> unmodifiableList = Collections.unmodifiableList(list);
    System.out.println(unmodifiableList);  //a b c

    list.add("d");
  	//unmodifiableList.add("d");   //抛出不可修改的错误
    System.out.println("list add a item after list:"+list);  //a b c d
    System.out.println("list add a item after unmodifiableList:"+unmodifiableList);  //a b c d
}

可见,jdk提供的不可变集合方法是不安全的,如果原有集合的引用在其它地方被持有,那么就不是真的不可变,可以通过原有集合的引用改变该“不可变集合”。

Guava的immutable集合

public void GuavaImmutable(){
    List<String> list = Lists.newArrayList();
    list.add("a");
    list.add("b");
    list.add("c");
    System.out.println("list:" + list);  //a b c

    ImmutableList<String> imList = ImmutableList.copyOf(list);
    System.out.println("imList:" + imList);   //a b c

    list.add("d");
    System.out.println("list add a item after list:" + list);  //a b c d
    System.out.println("list add a item after imlist:" + imList);  //a b c
}

创建Immutable集合的方法:

  • copyOf方法,如ImmutableSet.copyOf(set)

    public void copyOfTest(){
        ImmutableSet<String> imSert = ImmutableSet.of("copyof","of","build","list");
        System.out.println("imSert = " + imSert);
        ImmutableList<String> imList = ImmutableList.copyOf(imSert);
        System.out.println("imList = " + imList);
        ImmutableSortedSet<String> imSortSet = ImmutableSortedSet.copyOf(imSert);
        System.out.println("imSortSet = " + imSortSet);
    
        List<String> list = Lists.newArrayList();
        for (int i = 0; i < 10; i++) {
            list.add( i + "x");
        }
        System.out.println("list = " + list);
      	//subList返回集合的其中一部分视图
        ImmutableList<String> imInfoList = ImmutableList.copyOf(list.subList(2,8));
        System.out.println("imInfoList = " + imInfoList);
        int imInfoListSize = imInfoList.size();
        ImmutableSet<String> imInfoSet = ImmutableSet.copyOf(imInfoList.subList(2,imInfoListSize - 2));
        System.out.println("imInfoSet = " + imInfoSet);
    }
    /*
    imSert = [copyof, of, build, list]
    imList = [copyof, of, build, list]
    imSortSet = [build, copyof, list, of]
    list = [0x, 1x, 2x, 3x, 4x, 5x, 6x, 7x, 8x, 9x]
    imInfoList = [2x, 3x, 4x, 5x, 6x, 7x]
    imInfoSet = [4x, 5x]
    */
    
  • of方法,如ImmutableSet.of(“a”, “b”, “c”)或 ImmutableMap.of(“a”, 1, “b”, 2)

    ImmutableList<String> imOflist=ImmutableList.of("peida","jerry","harry");
    ImmutableSortedSet<String> imSortList=ImmutableSortedSet.of("a", "b", "c", "a", "d", "b");
    System.out.println("imSortList:"+imSortList);//[a, b, c, d]
    ImmutableMap<String, Integer> of = ImmutableMap.of("a", 1, "b", 2);
    System.out.println("of:"+of);//{a=1, b=2}
    
  • Builder工具

    ImmutableList<Object> build = ImmutableList.builder().add("a","b","c").build();
    System.out.println("build = " + build);
    

所有Guava不可变集合的实现都不接受null值

函数式编程

函数式接口,在Java里,我们通过使用函数式接口将一段参数(包括重写的方法)作为参数进行传入,以达到灵活编写和使用代码的目的。我们的Lambda表达式作为函数,而函数式接口的一个重要作用在于规定好我们Lambda表达式的参数(返回类型)。

Guava提供两个基本的函数式接口:

  • Function,它声明了单个方法B apply(A input)。Function对象通常被预期为引用透明的——没有副作用——并且引用透明性中的”相等”语义与equals一致,如a.equals(b)意味着function.apply(a).equals(function.apply(b))。

  • Predicate,它声明了单个方法boolean apply(T input)。Predicate对象通常也被预期为无副作用函数,并且”相等”语义与equals一致。

  • 此外,还有一个Supplier。

    Function函数式接口初体验

            Function<String,Integer> function = new Function<String,Integer>(){
    
                @Override
                public Integer apply(String input) {
                    Preconditions.checkNotNull(input,"不能为null");
                    return input.length();
                }
            };
            System.out.println(function.apply("hello"));
    //        System.out.println(function.apply(null));
    

    Predicate函数式接口初体验

    /*函数式接口Predicate初体验,Predicate接口中有一个apply方法,实现接口后就可以调用,Predicate是接受参数并返回boolean的函数式接口*/
    Predicate p = new Predicate() {
        @Override
        public boolean apply(String s) {
            return false;
        }
    };
    System.out.println(p.apply("s"));
    

    函数式接口Supplier初体验,Supplier接口中有一个get方法,实现接口后就可以调用,Supplier是不传入参数的函数式接口

    System.out.println(new Supplier<String>() {
        @Override
        public String get() {
            return "你好";
        }
    }.get());
    

    Functions提供简便的Function构造和操作方法,包括:

    forMap(Map) [compose(Function, Function)](http://docs.guava-libraries.googlecode.com/git/javadoc/com/google/common/base/Functions.html#compose(com.google.common.base.Function, com.google.common.base.Function)) constant(T)
    identity() toStringFunction()

    相应地,Predicates提供了更多构造和处理Predicate的方法,下面是一些例子:

    instanceOf(Class) assignableFrom(Class) contains(Pattern)
    in(Collection) isNull() alwaysFalse()
    alwaysTrue() equalTo(Object) [compose(Predicate, Function)](http://docs.guava-libraries.googlecode.com/git/javadoc/com/google/common/base/Predicates.html#compose(com.google.common.base.Predicate, com.google.common.base.Function))
    and(Predicate...) or(Predicate...) not(Predicate)

Functions.compose返回一个函数式接口Function类型的值,该方法传入两个Function,最后compose的apply方法执行为:将自己apply调用时传入的参数给后一个Function,其apply的计算结果作为参数传给前一个Function的apply并最终计算出结果后返回。

Boolean b = Functions.compose(new Function<Integer, Boolean>() {            //Functions.compose返回一个函数式接口Function类型的值,该方法传入两个Function,最后compose的apply方法执行为:将自己apply调用时传入的参数给后一个Function,其apply的计算结果作为参数传给前一个Function的apply并最终计算出结果后返回
    @Override
    public Boolean apply(Integer integer) {
        if(integer>3)
        return false;
        return true;
    }
}, new Function<String, Integer>() {
    @Override
    public Integer apply(String s) {
        return s.length();
    }
}).apply("我是奇葩");

Functions.toStringFunction()调用了Functions类当中的一个枚举,这个枚举依然时Function函数式接口,所以依然可以调用apply方法,它的apply可以将传入的参数转成字符串

System.out.println(Functions.toStringFunction().apply(new Date()));         //通过枚举做单例

Functions.forMap(map)的apply方法可以通过key来查找Map中的value

Map<String,Integer> map = new HashMap<>();
map.put("张三",1);
map.put("李四",2);
System.out.println(Functions.forMap(map).apply("张三"));		//1
System.out.println(Functions.forMap(map).apply("李四"));		//2
System.out.println(Functions.forMap(map).apply("王二"));		//报错

Predicates部分方法

System.out.println(Predicates.isNull().apply(new Integer(10)));		//true
System.out.println(Predicates.isNull().apply(null));		//false

        Integer i = new Integer(5);
        Integer j = i ;
        System.out.println(Predicates.equalTo(i).apply(j));		//true
        System.out.println(Predicates.equalTo(i).apply(new Integer(5)));//true
        System.out.println(Predicates.equalTo(i).apply(new Integer(10)));//false

IO

Files文件操作

现在,我们有一个文件,想实现从一个文件到另一个路径下的拷贝。

String sourceFile = "D:\\work\\java\\guava——programming\\src\\main\\resources\\source";
String tartgetFile = "D:\\work\\java\\guava——programming\\src\\main\\resources\\target";

jdk是这么做的

Files.copy(Paths.get(sourceFile),Paths.get(tartgetFile), StandardCopyOption.REPLACE_EXISTING);

而Guava可以这样做,显然要简洁不少。

Files.copy(new File(sourceFile),new File(tartgetFile));

Files.readlines()从文件中以字符流的形式读取内容,每一行被读取到后作为一个String存入List中。

        List list = Files.readLines(new File(sourceFile), Charsets.UTF_8);            //字符流输入

        String s1 = "hello\n" +
                "how are you\n" +
                "fine thank you\n" +
                "\n" +
                "byebye";
        String s2=Joiner.on("\n").join(list);       //验证文件内容
        System.out.println(s1.equals(s2));

我们还可以对读取内容加以某些限制,这里就要用到一个解析器的接口了

LineProcessor<List<Integer>> lineProcessor = new LineProcessor<List<Integer>>() {       //解析器,帮助我们自主定义读取文件的规则

    private final List<Integer> lengthlist= new ArrayList<>();

    @Override
    public boolean processLine(String line) throws IOException {            //每读取到一行,该方法被调用。当解析器的processLine方法返回false,则不继续读取。该方法对可读取到的每一行进行解析判断
        if(line.length() == 0)
        return false;
        lengthlist.add(line.length());
        return true;
    }

    @Override
    public List<Integer> getResult() {                  //getResult方法的返回值可以被其它方法调用
        return lengthlist;
    }
};

List<Integer> result = Files.asCharSource(new File(sourceFile),Charsets.UTF_8).readLines(lineProcessor);        //readLines的返回值为解析器的getResult方法返回值
System.out.println(result);

Guava已经删除了读取字节的readBytes方法,当然我们可以手动实现

File f1 = new File(sourceFile);
String s = Files.asByteSource(f1).read(new ByteProcessor<String>() {
        private byte[] b=new byte[0] ;
    @Override
    public boolean processBytes(byte[] bytes, int i, int i1) throws IOException {
        byte[] bl = new byte[b.length+ bytes.length];
        int j = 0;
        for(;j<b.length;j++)
            bl[j]=b[j];
        for(int k = j;k< bl.length;k++)
            bl[k] = bytes[k-j];
        b=bl;
        return true;
    }

    @Override
    public String getResult() {
        try {
            String s = new String(b,"UTF-8");
            Pattern pattern = Pattern.compile("([\u0000]*)");
            Matcher matcher = pattern.matcher(s);
            s=matcher.replaceAll("");
            return s;
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
        return null;
    }
});
System.out.println(s);          //字节数组多出来的部分读成了空值

Files输出文件

String s = "你好";
        Files.write(s.getBytes(Charsets.UTF_8), new File(tartgetFile));         //字节流输出  asByteSink(to).write(from)
        Files.asCharSink(new File("D:\\work\\java\\guava——programming\\src\\main\\resources\\compareone"),Charsets.UTF_8).write("及你太美"); 

Files追加写入

Files.asCharSink(new File(tartgetFile), Charsets.UTF_8, FileWriteMode.APPEND).write("我是练习时长两年半的");

Files创建一个空文件

        File touchFile = new File(tartgetFile);
        Files.touch(touchFile);
        System.out.println(touchFile.exists());

获取到path下的全目录

Iterable<File> files = Files.fileTraverser().depthFirstPostOrder(file); 
        for (File file1 : files) {
            System.out.println("全目录: " + file1);
        }

CharSource、CharSink

Guava的CharSource、CharSink分别相当于Java的reader和writer。

CharSource不仅可以通过Files.asasCharSource()方法创建,还可以通过CharSource.wrap()方法创建。

read()方法

CharSource charSource = CharSource.wrap("i am litidy");
String result = charSource.read();
System.out.println(result);

readlines()方法

CharSource charSource = CharSource.wrap("i am litidy");
ImmutableList<String> list = charSource.readLines();
for (String s: list) {
    System.out.println(s);
}

copyTo方法

CharSource charSource = CharSource.concat(CharSource.wrap("aaaaaaaa"),CharSource.wrap("bbbbbb"),CharSource.wrap("ccccccccccc"));
CharSink charSink = Files.asCharSink(new File("D:\\work\\java\\guava——programming\\src\\main\\resources\\target"), Charsets.UTF_8);
charSource.copyTo(charSink);

其它方法

        CharSource charSource = CharSource.wrap("i am litidy");
//        CharSource charSource = CharSource.wrap("");
        System.out.println(charSource.length());
        System.out.println(charSource.isEmpty());
        System.out.println(charSource.lengthIfKnown().get());
//        CharSource charSource = CharSource.concat(CharSource.wrap("aaaaaaaa"),CharSource.wrap("bbbbbb"),CharSource.wrap("ccccccccccc"));//1
//        CharSource charSource = CharSource.concat(CharSource.wrap("aaaaaaaa\n"),CharSource.wrap("bbbbbb\n"));   //2
        CharSource charSource = CharSource.concat(CharSource.wrap("aaaaaaaa\r"),CharSource.wrap("bbbbbb\r"));   //2
        ImmutableList<String> list = charSource.readLines();
        System.out.println(list.size());
        for (String s: list) {
            System.out.println(s);
        }

ByteSource、ByteSink

Guava的ByteSource、ByteSink分别相当于Java的InputStream和OutputStream。

read(),wrap(),slice()

ByteSource byteSource = ByteSource.wrap(new byte[]{0x00,0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08,0x09}); //16进制
ByteSource sliceByteSource = byteSource.slice(5L,5L);
ByteSource byteSource1 = ByteSource.concat(ByteSource.wrap(new byte[]{0x09,0x08,0x02,0x03}),sliceByteSource);
byte[] b = byteSource1.read();
//        byte[] b = sliceByteSource.read();
for(byte bt : b )
    System.out.println(bt);

copyTo方法,hash()

File source =new File("D:\\work\\java\\guava——programming\\src\\main\\resources\\more\\more1\\780.jpg");
File target =new File("D:\\work\\java\\guava——programming\\src\\main\\resources\\more\\780.jpg");
ByteSource byteSource = Files.asByteSource(source);
byteSource.copyTo(Files.asByteSink(target));
System.out.println(target.exists());
System.out.println(Files.asByteSource(source).hash(Hashing.sha256()).toString().equals(Files.asByteSource(target).hash(Hashing.sha256()).toString()));      //文件哈希如果不同,那么文件内容必定有不同
ByteSink CharSink
void write(byte[\]) void write(CharSequence)
long writeFrom(InputStream) long writeFrom(Readable)
N/A void writeLines(Iterable)
N/A [void writeLines(Iterable, String)](http://docs.guava-libraries.googlecode.com/git/javadoc/com/google/common/io/CharSink.html#writeLines(java.lang.Iterable, java.lang.String))
ByteSource CharSource
byte[] read() String read()
N/A ImmutableList readLines()
N/A String readFirstLine()
long copyTo(ByteSink) long copyTo(CharSink)
long copyTo(OutputStream) long copyTo(Appendable)
long size() (in bytes) N/A
boolean isEmpty() boolean isEmpty()
boolean contentEquals(ByteSource) N/A
HashCode hash(HashFunction) N/A

ByteStream 和 CharStream

CharStreams和ByteStreams为操作输入输出流的两个工具类

public class ByteStreamsTest {


    public static void main(String[] args) throws IOException {
        File file =new File("D:\\work\\java\\guava——programming\\src\\main\\resources\\more\\more1\\BtyeSinkTest.dat");          //二进制文件
        File file1 =new File("D:\\work\\java\\guava——programming\\src\\main\\resources\\more\\more1\\780.jpg");
        File file2 =new File("D:\\work\\java\\guava——programming\\src\\main\\resources\\more\\more1\\7801.jpg");
        /*copy*/
//        InputStream in = Files.asByteSource(file1).openStream();
//        OutputStream o = Files.asByteSink(file2).openStream();
//        ByteStreams.copy(in,o);
        /*toByteArray*/
//        InputStream in = Files.asByteSource(file).openStream();
//        byte[] bytes = ByteStreams.toByteArray(in);
//        for (byte b:bytes) {
//            System.out.println(b);
//        }
        /*readfully 将InputStream内容读到btye数组里,如果InputStream过短而不能将btye读满,则报错*/
//        InputStream in = Files.asByteSource(file).openStream();
//        byte[] bytes = new byte[2];
        byte[] bytes = new byte[1024];      //          报错
//        ByteStreams.readFully(in , bytes);
//        for (byte b:bytes) {
//            System.out.println(b);
//        }



//        ByteSink byteSink = Files.asByteSink(file);
//        byte[] b = new byte[]{0x01,0x02};
//        byteSink.write(b);

    }
}
public class CharStreamsTest {
    public static void main(String[] args) throws IOException {
        int b;
        String sourceFile = "D:\\work\\java\\guava——programming\\src\\main\\resources\\source";
        String targetFile = "D:\\work\\java\\guava——programming\\src\\main\\resources\\target3";
        Reader reader = Files.asCharSource(new File(sourceFile), Charsets.UTF_8).openStream();
        Writer writer = Files.asCharSink(new File(targetFile), Charsets.UTF_8).openStream();
        /*readlines*/
//        List lines = CharStreams.readLines(reader);
//        System.out.println(lines);
        /*copy*/
        try {
            CharStreams.copy(reader,writer);
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            writer.close();
            reader.close();
        }

        /*skipFully每跳过多少个字符读取一个字符*/

//        CharStreams.skipFully(reader, 3L);
//        while ((b = reader.read()) != -1) {
//            System.out.println((char)b);
//            try{
//                CharStreams.skipFully(reader, 3L);
//            }catch (EOFException e){
//            }
//        }
//        System.out.println("========");
//        while ((b = reader.read()) != -1){
//            System.out.println((char)b);
//        }
    }
}
ByteStreams CharStreams
byte[] toByteArray(InputStream) String toString(Readable)
N/A List readLines(Readable)
long copy(InputStream, OutputStream) long copy(Readable, Appendable)
void readFully(InputStream, byte[]) N/A
void skipFully(InputStream, long) void skipFully(Reader, long)
OutputStream nullOutputStream() Writer nullWriter()

Closer

Closer可以保证注册的Closable对象,在Closer关闭时,注册的Closable对象也会被关闭。Closer的close方法是很安全的。

如果不使用Closer,大概我们会这么写

File destination = new File("D:\\work\\java\\guava——programming\\src\\main\\resources\\target2");
BufferedReader reader = new BufferedReader(new FileReader("D:\\work\\java\\guava——programming\\src\\main\\resources\\source"));
BufferedWriter writer = new BufferedWriter(new FileWriter(destination));
try {
    String line;
    while ((line = reader.readLine()) != null) {
        System.out.println(line);
        writer.write(line+"\n");
    }

}catch (Exception e){
    e.printStackTrace();
}finally {
    if(reader != null){
        reader.close();
    }
    if(writer != null){
        writer.close();         
    }
}

使用Closer之后,代码显得更加优雅简洁了。

Closer closer = Closer.create();
try {
    File destination = new File("D:\\work\\java\\guava——programming\\src\\main\\resources\\target1");
    BufferedReader reader = new BufferedReader(new FileReader("D:\\work\\java\\guava——programming\\src\\main\\resources\\source"));
    BufferedWriter writer = new BufferedWriter(new FileWriter(destination));
    closer.register(reader);
    closer.register(writer);
    String line;
    while ((line = reader.readLine()) != null) {
        writer.write(line+"\n");
    }
} catch (Throwable t) {
    throw closer.rethrow(t);
} finally {
    closer.close();
}

EventBus

总线(Bus)一般指计算机各种功能部件之间传送信息的公共通信干线,而EventBus是Guava库中的一个处理组件间通信的事件总线,它基于发布/订阅模式,实现了多组件之间通信的解耦合,事件产生方和事件消费方实现解耦分离,提升了通信的简洁性。

事件总线涉及三个要素:事件源订阅者通道
1、当一个模块处理逻辑时会产生事件,我们称这个模块为事件源。
2、有的模块等待事件的产生,然后去执行特定的任务,我们称这个模块为订阅者。
3、维护这些事件源和订阅者的关系的组件我们称之为通道,也就是总线。

Guava入门_第1张图片

现在我们想象一个场景,用户购买成功了一个产品,可能会产生一个事件源,事件源告知一个订阅者我需要改数据库,一个要通知仓库,一个要返回购买商品信息给用户,不论是改库,还是通知实体仓库,还是返回页面,看上去都比程序执行几句代码要慢不少,如果这几个事件是串行执行的话,那么程序执行效率就会很低。假设我们这时候又出现了一个事件源,串行执行需要等上一个事件源的所有事件执行完了之后才能够执行这一个事件源所需的任务。

那么这个时候,我们就用到EventBus。

当一个事件源产生之后,EventBus就会告知订阅者(监听者Listener,因为这相当于订阅者在监听我们的事件源是否发生。)然后订阅者会执行相应的方法。这个时候我们的事件源还可以继续执行,不需要等待第一个订阅者执行完成自己的任务。

在创建EventBus之前,我们先创建一个Listener。

public class SimpleEventListener {


    @Subscribe          //Subscribe方法只能有一个Object参数
    public void method1(final Integer event){
        System.out.println("method1:"+event);
    }
}

然后在执行代码里创建EventBus并把Listener注册进EventBus。

public class SimpleEventBusExample {
    public static void main(String[] args) {
        
        
        final EventBus eventBus = new EventBus();			//创建EventBus
        eventBus.register(new SimpleEventListener());		//注册Listener
        
        
        
        
        System.out.println("某件事源发生");
        eventBus.post(new Integer(10) );        //post方法可以传一个Object,然后调用注册的Listener的@Subscribe的方法,并传入该Object。post也可以直接传基本类型,但Listener的@Subscribe方法必须使用包装类
    }
}

现在我们看看,一个事件源触发一个Listener多个方法的情况

public class MultipleEventListener {
    @Subscribe
    public void task1(String event){
        System.out.println("task1:"+event);
    }
    @Subscribe
    public void task2(String event){
        System.out.println("task2:"+event);
    }
    @Subscribe
    public void taskint1(Integer event) throws InterruptedException {
        System.out.println("taskint1:"+event);
    }
    @Subscribe
    public void taskint2(Integer event) throws InterruptedException {
        System.out.println("taskint2:"+event);
    }
    @Subscribe
    public void taskboolean(Boolean event){
        System.out.println("taskboolean:"+event);
    }
}
public class MultipleEventBusExample {

    public static void main(String[] args) {
        final EventBus eventBus = new EventBus();
        eventBus.register(new MultipleEventListener());
        eventBus.post("I am String Event");
        System.out.println("intEvent");
        eventBus.post(1000);
        System.out.println("booleanEvent");
        eventBus.post(true);
    }

}

我们发现,eventBus.post会根据传入参数的类型来调用不同的Subscribe方法。在实际的应用当中,我们更可能会自己定义一些包装类将自己的请求或相应的参数放在自定义类中,让这些自定义类去触发不同的Subscribe方法。

既然我们的各个Subscribe方法是解耦合的,那么它们之间应该是相互独立的,如何体现呢。我们看看假设其中有个方法如果发生异常会怎么样。

package com.lisguava.guava.eventbus;

import com.google.common.eventbus.Subscribe;

public class ExceptionListener {

    @Subscribe
    public void m1(String event){
        System.out.println("m1:"+event);
    }

    @Subscribe
    public void m2(String event){
        System.out.println("m2:"+event);
    }

    @Subscribe
    public void m3(String event){
//        System.out.println("m3:"+event);
        throw new RuntimeException();
    }

    @Subscribe
    public void m4(String event){
        System.out.println("m4:"+event);
    }
}
final EventBus eventBus = new EventBus();
eventBus.register(new ExceptionListener());
eventBus.post("exception");         //在调用@subscriber的方法时,每个方法不会因为其它方法抛出异常而终止,并且这个异常不会真的抛出来,只是打印了一个堆栈信息而已,post后面的代码依然可以执行。那么如果我们想要得到这个异常,我们就需要使用到一个叫做exceptionhandler的东西
System.out.println("执行到此");

我们发现,即使有异常发生,我们的其它方法依然可以正常执行。

我们继续看,现在我们模拟有两个事件源争夺Listener的方法。

public class ThreadListener {
    @Subscribe
    public void task1(String event) throws InterruptedException {

        System.out.println("task1:"+event);

    }
    @Subscribe
    public void task2(String event) throws InterruptedException {
        Thread.sleep(5000);
        System.out.println("task2:"+event);
    }
}
public class ThreadEventBus {
    public static void main(String[] args) {
        EventBus eventBus = new EventBus();
        eventBus.register(new ThreadListener());
        Runnable r1 = new Runnable() {
            @Override
            public void run() {

                eventBus.post("事件1");
            }
        };
        Runnable r2 = new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                eventBus.post("事件2");
            }
        };
        new Thread(r1).start();
        new Thread(r2).start();
    }
}

输出结果

task1:事件1	//第一个事件源来了,执行完毕task1方法,这时候task1方法就被释放出来了。此时第一个事件源正在调用task2
task1:事件2	//第二个事件源来了,task1方法已经被释放,第二个事件源成功调用task2
task2:事件1	//五秒之后,第一个事件源释放了task2,第二个事件源执行task2
task2:事件2	//再五秒之后,第二个事件源释放了task2

现在我们看一下,Listner继承的情况

public abstract class AbstractListener {
    @Subscribe
    public void commonTask(String Event){
        System.out.println("commonYask:"+Event);
    }

}



public class BaseEventListener extends AbstractListener{

    @Subscribe
    public void baseTask(String event){
        System.out.println("baseTask:"+event);
    }
}



public class ConcreteListener extends BaseEventListener{
    @Subscribe
    public void conTask(String event){
        System.out.println("conTask:"+event);
    }
}
public class InheritEventBusExample {
    public static void main(String[] args) {
        final EventBus eventBus = new EventBus();
        eventBus.register(new ConcreteListener());
        eventBus.post("I am String Event");     //父类的方法会并行执行
    }
}

这样的话,我们就可以实现多个监听者的注册了。

那么事件继承呢?我们传递的事件参数所属的类有父类的时候,如果在@Subscribe方法中有传入它父类的参数的方法会如何呢?

public class Fruit {
    protected String name;
    public Fruit(String name) {
        this.name = name;
    }
}



public class Apple extends Fruit{
    public Apple(String name) {
        super(name);
    }
}
public class FruitListener {
    @Subscribe
    public void Task1(Fruit Event){
        System.out.println("Fruit:"+Event);
    }

    @Subscribe
    public void Task2(Apple Event){
        System.out.println("Apple:"+Event);
    }
}
public class FruitEventBus {
    public static void main(String[] args) {
        final EventBus eventBus = new EventBus();
        eventBus.register(new FruitListener());
        eventBus.post(new Fruit("Apple"));
        System.out.println("===================");
        eventBus.post(new Apple("Apple"));
    }
}

DeadEvent

有时候,@Subscribe方法需要拿到我们事件的一些信息,这时候我们需要在方法中传入一个DeadEvent

public class DeadEventListener {

    @Subscribe          //现在我们传入了一个叫DeadEvent的类的实例,现在我们把这个Subscriber方法成为DeadEvent方法.DeadEvent方法可以拿到事件源或者EventBus的一些信息
    public void handle(DeadEvent event){
        System.out.println(event.getSource());
        System.out.println(event.getEvent());
    }
}
public class DeadEventBusExample {
    public static void main(String[] args) {
        final EventBus eventBus = new EventBus("DeadEventBus"){
            @Override
            public String toString(){
                return "Dead-event-bus";
            }
        };
        eventBus.register(new DeadEventListener());
        eventBus.post("hello");
    }
}

你可能感兴趣的:(guava)