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 values | all() |
上面的 a
、b
称为 端点;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 |
boolean | apply(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). |
boolean | contains(C value) 判断Range中是否包含指定的value |
boolean | containsAll(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比较后的最小范围区间 |
boolean | encloses(Range<C> other) 返回传入Range是否包含在调用此方法的Range中 |
boolean | equals(@Nullable Object object) 如果object是具有与此范围相同的端点和绑定类型的范围,则返回true |
Range<C> | gap(Range<C> otherRange) 返回两个Range之间的最大范围 |
static <C extendsComparable<?>> Range<C> | greaterThan(C endpoint) 返回严格大于endpoint的所有值Range |
int | hashCode() 返回此范围的哈希码。 |
boolean | hasLowerBound() 返回Range是否存在下限 |
boolean | hasUpperBound() 返回Range是否存在上限 |
Range<C> | intersection(Range<C> connectedRange) 返回两个Range的最大交集,如果Range无交集,抛出异常IllegalArgumentException. |
boolean | isConnected(Range<C> other) 返回两个Range是否能够连续上. |
boolean | isEmpty() 判断Range是否为空,即上下限是否相等,例如 [v..v) or (v..v]. |
static <C extendsComparable<?>> Range<C> | lessThan(C endpoint) 返回严格小于endpoint的所有值Range |
BoundType | lowerBoundType() 返回Range的下限类型BoundType,即开闭性 |
C | lowerEndpoint() 返回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的并集 |
String | toString() 返回此范围的字符串表示形式,例如 "[3..5)" |
BoundType | upperBoundType() 返回Range的上限类型BoundType,即开闭性 |
C | upperEndpoint() 返回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
提供的离散域实例包括:
类型 | 离散域 |
---|---|
Integer | integers() |
Long | longs() |
一旦获取了 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 提供了 integers
、longs
和 bigIntegers
三个 static
方法,如果我们需要调整字符,可以建立自己的 DiscreteDomain
,例如,新建一个小写字母的 LowerCaseDomain
。
自定义方式,继承 DiscreteDomain<T>
抽象类,实现 next
、previous
、distance
三个方法,其他方法可选,比如 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 类