Google Guava 快速入门 —— 【基础】区间范围 Range 类

Google Guava 快速入门.jpg

Guava Range 类

一、Range 简介

Range 表示一个间隔或一个序列。它被用于获取一组数字/串在一个特定范围之内。可比较类型的区间API,包括连续和离散类型。

Range 定义了连续跨度的范围边界,这个连续跨度是一个可以比较的类型(Comparable type)。比如1到100之间的整型数据。

在数学里面的范围是有边界和无边界之分的;同样,在Guava中也有这个说法。如果这个范围是有边界的,那么这个范围又可以分为包括 开集(不包括端点)和 闭集(包括端点);如果是无解的可以用 +∞ 表示。

Guava 用更紧凑的方法表示范围,如果枚举的话,一共有九种范围表示。

概念表示范围对应方法
(a..b){x | a < x < b}open(C, C)
[a..b]{x | a <= x <= b}closed(C, C)
[a..b){x | a <= x < b}closedOpen(C, C)
(a..b]{x | a < x <= b}openClosed(C, C)
(a..+∞){x | x > a}greaterThan(C)
[a..+∞){x | x >= a}atLeast(C)
(-∞..b){x | x < b}lessThan(C)
(-∞..b]{x | x <= b}atMost(C)
(-∞..+∞)all valuesall()

上面的 ab 称为 端点;a 为 下端点,b 为 上端点。

Guava 中的 Range 要求:

  • 上端点不能小于下端点。

  • 极端情况下,上下端点有可能是相等的,但要求区间是闭区间或半开半闭区间(至少有一个端点是包含在区间中的)

如下示例:

  • [a..a]:单元素区间

  • [a..a)(a..a]:空区间,但它们是有效的

  • (a..a):无效区间,构造这样的 Range 将会抛出异常

Guava 用类型 Range<C> 表示区间。所有区间实现都是不可变类型。

二、类声明

以下是 com.google.common.collect.Range<C> 类的声明:

@GwtCompatible
@SuppressWarnings("rawtypes")
public final class Range<C extends Comparable> extends RangeGwtSerializationDependencies
    implements Predicate<C>, Serializable {

三、类方法

官方文档:https://google.github.io/guava/releases/27.0.1-jre/api/docs/com/google/common/collect/Range.html

方法类型方法描述
static <C extendsComparable<?>> Range<C>all() 
返回包含类型C所有值的Range
booleanapply(C input) 
已过时。 仅提供满足Predicate接口
static <C extendsComparable<?>> Range<C>atLeast(C endpoint) 
返回大于等于endpoint的所有值Range
static <C extendsComparable<?>> Range<C>atMost(C endpoint) 
返回小于等于endpoint的所有值Range
Range<C>canonical(DiscreteDomain<C> domain) 
返回在给定domain离散域下Range的规范形式
static <C extendsComparable<?>> Range<C>closed(C lower, C upper) 
返回一个Range,包含大于等于lower小于等于upper范围的所有值,数学表示 [lower,upper]
static <C extendsComparable<?>> Range<C>closedOpen(C lower, C upper) 
返回一个Range,包含大于等于lower严格小于upper范围的所有值,数学表示 [lower,upper).
booleancontains(C value) 
判断Range中是否包含指定的value
booleancontainsAll(Iterable<? extends C> values) 
判断指定values中是否所有值都包含在Range中
static <C extendsComparable<?>> Range<C>downTo(C endpoint, BoundType boundType) 
返回下限临界值为endpoint的区间,下限开闭性由boundType指定
static <C extendsComparable<?>> Range<C>encloseAll(Iterable<C> values) 
返回Range与传入values比较后的最小范围区间
booleanencloses(Range<C> other) 
返回传入Range是否包含在调用此方法的Range中
booleanequals(@Nullable Object object) 
如果object是具有与此范围相同的端点和绑定类型的范围,则返回true
Range<C>gap(Range<C> otherRange) 
返回两个Range之间的最大范围
static <C extendsComparable<?>> Range<C>greaterThan(C endpoint) 
返回严格大于endpoint的所有值Range
inthashCode() 
返回此范围的哈希码。
booleanhasLowerBound() 
返回Range是否存在下限
booleanhasUpperBound() 
返回Range是否存在上限
Range<C>intersection(Range<C> connectedRange) 
返回两个Range的最大交集,如果Range无交集,抛出异常IllegalArgumentException.
booleanisConnected(Range<C> other) 
返回两个Range是否能够连续上.
booleanisEmpty() 
判断Range是否为空,即上下限是否相等,例如 [v..v) or (v..v].
static <C extendsComparable<?>> Range<C>lessThan(C endpoint) 
返回严格小于endpoint的所有值Range
BoundTypelowerBoundType() 
返回Range的下限类型BoundType,即开闭性
ClowerEndpoint() 
返回Range下限的临界点值
static <C extendsComparable<?>> Range<C>open(C lower, C upper) 
返回一个Range,包含严格大于lower小于upper范围的所有值,数学表示(lower,upper)
static <C extendsComparable<?>> Range<C>openClosed(C lower, C upper) 
返回一个Range,包含严格大于lower小于等于upper范围的所有值,数学表示 (lower,upper]
static <C extendsComparable<?>> Range<C>range(C lower, BoundType lowerType, C upper, BoundType upperType) 
返回一个Range,包含lower和upper范围的所有值,临界值的开闭可以通过BoundType设置,BoundType 是枚举类型,标识开闭.
static <C extendsComparable<?>> Range<C>singleton(C value)
返回唯一包含传入value的Range
Range<C>span(Range<C> other) 
返回两个Range的并集
StringtoString() 
返回此范围的字符串表示形式,例如 "[3..5)"
BoundTypeupperBoundType() 
返回Range的上限类型BoundType,即开闭性
CupperEndpoint() 
返回Range上限的临界点值
static <C extendsComparable<?>> Range<C>upTo(C endpoint, BoundType boundType) 
返回上限临界值为endpoint的区间,上限开闭性由boundType指定

四、方法介绍

1、构建区间

区间实例可以由 Range 类的静态方法获取:

区间实例方法
(a..b)open(C, C)
[a..b]closed(C, C)
[a..b)closedOpen(C, C)
(a..b]openClosed(C, C)
(a..+∞)greaterThan(C)
[a..+∞)atLeast(C)
(-∞..b)lessThan(C)
(-∞..b]atMost(C)
(-∞..+∞)all()

此外,也可以明确地指定边界类型来构造区间:

区间实例方法
有界区间range(C, BoundType, C, BoundType)
无上界区间:(a..+∞) 或 [a..+∞)downTo(C, BoundType)
无下界区间:(-∞..b) 或 (-∞..b]upTo(C, BoundType)

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

Range.downTo(3, BoundType.OPEN);    // (3..+∞)
Range.upTo(3, BoundType.CLOSED);    // (-∞..3]
Range.range(1, BoundType.CLOSED, 6, BoundType.OPEN);  // [1..6) 等同于 Range.closedOpen(1, 6)

2、区间运算

Range 的基本运算是它的 contains(C) 方法,和你期望的一样,它用来区间判断是否包含某个值。此外,Range 实例也可以当作 Predicate 断言,并且在函数式编程中使用。任何 Range 实例也都支持 containsAll(Iterable<? extends C>) 方法:

Range.closed(1, 3).contains(2);     // return true
Range.closed(1, 3).contains(4);     // return false
Range.lessThan(5).contains(5);      // return false
Range.closed(1, 4).containsAll(Ints.asList(1, 2, 3));   // return true

3、查询运算

Range 类提供了以下方法来 查看区间的端点:

方法描述
hasLowerBound() 和 hasUpperBound()判断区间是否有特定边界,或是无限的
lowerBoundType() 和 upperBoundType()返回区间边界类型,CLOSED 或 OPEN;如果区间没有对应的边界,抛出 IllegalStateException
lowerEndpoint() 和 upperEndpoint()返回区间的端点值;如果区间没有对应的边界,抛出 IllegalStateException
isEmpty()判断是否为空区间。
Range.closedOpen(3, 3).hasLowerBound(); // return true
Range.closedOpen(3, 3).hasUpperBound(); // return true
Range.closedOpen(4, 4).isEmpty();       // return true
Range.openClosed(4, 4).isEmpty();       // return true
Range.closed(4, 4).isEmpty();           // return false
Range.closed(3, 10).lowerEndpoint();    // return 3
Range.open(3, 10).lowerEndpoint();      // return 3
Range.closed(3, 10).lowerBoundType();   // return CLOSED
Range.open(3, 10).upperBoundType();     // return OPEN
Range.open(4, 4).isEmpty();             // Range.open throws IllegalArgumentException

4、关系运算

(1)包含 [enclose]

区间之间的最基本关系就是包含[encloses(Range)]:如果内区间的边界没有超出外区间的边界,则外区间包含内区间。包含判断的结果完全取决于区间端点的比较!

包含:是一种偏序关系。基于包含关系的概念。

  • [3..6] 包含 [4..5]

  • (3..6) 包含 (3..6)

  • [3..6] 包含 [4..4),虽然后者是空区间

  • (3..6] 不包含 [3..6]

  • [4..5] 不包含 (3..6),虽然前者包含了后者的所有值,离散域[discrete domains]可以解决这个问题

  • [3..6] 不包含 (1..1],虽然前者包含了后者的所有值

(2)相连 [isConnected]

Range.isConnected(Range) 判断区间是否是相连的。具体来说,isConnected 测试是否有区间同时包含于这两个区间,这等同于数学上的定义”两个区间的并集是连续集合的形式”(空区间的特殊情况除外)。

相连:是一种自反的、对称的关系。

Range.closed(3, 5).isConnected(Range.open(5, 10));      // return true
Range.closed(0, 9).isConnected(Range.closed(3, 4));     // return true
Range.closed(0, 5).isConnected(Range.closed(3, 9));     // return true
Range.open(3, 5).isConnected(Range.open(5, 10));        // return false
Range.closed(1, 5).isConnected(Range.closed(6, 10));    // return false
(3)交集 [intersection]

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

交集:是可互换的、关联的运算。

Range.closed(3, 5).intersection(Range.open(5, 10));     // return (5, 5]
Range.closed(0, 9).intersection(Range.closed(3, 4));    // return [3, 4]
Range.closed(0, 5).intersection(Range.closed(3, 9));    // return [3, 5]
Range.open(3, 5).intersection(Range.open(5, 10));       // throws IAE
Range.closed(1, 5).intersection(Range.closed(6, 10));   // throws IAE
(4)跨区间 [span]

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

跨区间:是可互换的、关联的、闭合的运算。

Range.closed(3, 5).span(Range.open(5, 10));     // return [3, 10)
Range.closed(0, 9).span(Range.closed(3, 4));    // return [0, 9]
Range.closed(0, 5).span(Range.closed(3, 9));    // return [0, 9]
Range.open(3, 5).span(Range.open(5, 10));       // return (3, 10)
Range.closed(1, 5).span(Range.closed(6, 10));   // return [1, 10]

五、离散域 DiscreteDomain

部分(但不是全部)可比较类型是离散的,即区间的上下边界都是可枚举的。

在Guava中,用 DiscreteDomain<C> 实现类型 C 的离散形式操作。一个离散域总是代表某种类型值的全集;它不能代表类似”素数”、”长度为5的字符串”或”午夜的时间戳”这样的局部域。

DiscreteDomain 提供的离散域实例包括:

类型离散域
Integerintegers()
Longlongs()

一旦获取了 DiscreteDomain 实例,你就可以使用下面的 Range 运算方法:

  • ContiguousSet.create(range, domain):用 ImmutableSortedSet<C> 形式表示 Range<C> 中符合离散域定义的元素,并增加一些额外操作,实际返回 ImmutableSortedSet 的子类 ContiguousSet。(对无限区间不起作用,除非类型C本身是有限的,比如int就是可枚举的)

  • canonical(domain):把离散域转为区间的”规范形式”。如果 ContiguousSet.create(a, domain).equals(ContiguousSet.create(b, domain)) 并且 !a.isEmpty(),则有 a.canonical(domain).equals(b.canonical(domain)),(这并不意味着 a.equals(b)

ImmutableSortedSet set = ContigousSet.create(Range.open(1, 5), iscreteDomain.integers());   // set包含[2, 3, 4]
ContiguousSet.create(Range.greaterThan(0), DiscreteDomain.integers());                      // set包含[1, 2, ..., Integer.MAX_VALUE]

注意: ContiguousSet.create 并没有真的构造了整个集合,而是返回了 set 形式的区间视图,但是可以进行遍历,从 toString 和 forEach 的使用上可以显示出区别。

示例1

/**
 * 离散域 DiscreteDomain
 */
public void test6() {
    Range<Integer> range = Range.closed(20, 30);
    print("closed", ContiguousSet.create(range, DiscreteDomain.integers()));

    range = Range.open(20, 30);
    print("open", ContiguousSet.create(range, DiscreteDomain.integers()));

    range = Range.openClosed(20, 30);
    print("openClosed", ContiguousSet.create(range, DiscreteDomain.integers()));

    range = Range.closedOpen(20, 30);
    print("closedOpen", ContiguousSet.create(range, DiscreteDomain.integers()));

    range = Range.greaterThan(20);
    System.out.println("greaterThan: " + ContiguousSet.create(range, DiscreteDomain.integers()).toString());

    range = Range.atLeast(20);
    System.out.println("atLeast: " + ContiguousSet.create(range, DiscreteDomain.integers()).toString());

    range = Range.lessThan(30);
    System.out.println("lessThan: " + ContiguousSet.create(range, DiscreteDomain.integers()).toString());

    range = Range.atMost(30);
    System.out.println("atMost: " + ContiguousSet.create(range, DiscreteDomain.integers()).toString());

    range = Range.all();
    System.out.println("all: " + ContiguousSet.create(range, DiscreteDomain.integers()).toString());
}

public static void print(String type, Set<Integer> ranges) {
    System.out.println(type + ":" + ranges + "      list:" + Lists.newArrayList(ranges));
}

执行结果

closed:[20..30]      list:[20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30]
open:[21..29]      list:[21, 22, 23, 24, 25, 26, 27, 28, 29]
openClosed:[21..30]      list:[21, 22, 23, 24, 25, 26, 27, 28, 29, 30]
closedOpen:[20..29]      list:[20, 21, 22, 23, 24, 25, 26, 27, 28, 29]
greaterThan: [21..2147483647]
atLeast: [20..2147483647]
lessThan: [-2147483648..29]
atMost: [-2147483648..30]
all: [-2147483648..2147483647]

示例2 自定义离散域 DiscreteDomain

由于经常取值是整数,因此 DiscreteDomain 提供了 integerslongs 和 bigIntegers 三个 static 方法,如果我们需要调整字符,可以建立自己的 DiscreteDomain,例如,新建一个小写字母的 LowerCaseDomain

自定义方式,继承 DiscreteDomain<T> 抽象类,实现 nextpreviousdistance 三个方法,其他方法可选,比如 minValue 和 macValue

import com.google.common.collect.DiscreteDomain;

public class LowerCaseDomain extends DiscreteDomain<Character> {
    private static LowerCaseDomain domain = new LowerCaseDomain();

    public static DiscreteDomain letters() {
        return domain;
    }

    @Override
    public Character next(Character value) {
        return (char) (value + 1);
    }

    @Override
    public Character previous(Character value) {
        return (char) (value - 1);
    }

    @Override
    public long distance(Character start, Character end) {
        return end - start;
    }

    @Override
    public Character minValue() {
        return 'a';
    }

    @Override
    public Character maxValue() {
        return 'z';
    }
}

调用方式

// a b c d e f g h i j k l m n o p q r s t u v w x y z 
for (Object i : ContiguousSet.create(Range.closed('a', 'z'), LowerCaseDomain.letters())) {
    System.out.print(i + " ");
}

System.out.println();

// m n o p q r s t u v w x y z 
for(Object i : ContiguousSet.create(Range.atLeast('m'), LowerCaseDomain.letters())) {
    System.out.print(i + " ");
}

六、相关文章



未经允许请勿转载:程序喵 » Google Guava 快速入门 —— 【基础】区间范围 Range 类

点  赞 (1) 打  赏
分享到: