[Google Guava] 8-区间

原文链接 译文链接 译文:沈义扬

范例

1 List scores;
2 Iterable belowMedian =Iterables.filter(scores,Range.lessThan(median));
3 ...
4 Range validGrades = Range.closed(112);
5 for(int grade : ContiguousSet.create(validGrades, DiscreteDomain.integers())) {
6     ...
7 }

简介

区间,有时也称为范围,是特定域中的凸性(非正式说法为连续的或不中断的)部分。在形式上,凸性表示对a<=b<=c, range.contains(a)且range.contains(c)意味着range.contains(b)。

区间可以延伸至无限——例如,范围”x>3″包括任意大于3的值——也可以被限制为有限,如” 2<=x<5″。Guava用更紧凑的方法表示范围,有数学背景的程序员对此是耳熟能详的:

  • (a..b) = {x | a < x < b}
  • [a..b] = {x | a <= x <= b}
  • [a..b) = {x | a <= x < b}
  • (a..b] = {x | a < x <= b}
  • (a..+∞) = {x | x > a}
  • [a..+∞) = {x | x >= a}
  • (-∞..b) = {x | x < b}
  • (-∞..b] = {x | x <= b}
  • (-∞..+∞) = 所有值

上面的a、b称为端点 。为了提高一致性,Guava中的Range要求上端点不能小于下端点。上下端点有可能是相等的,但要求区间是闭区间或半开半闭区间(至少有一个端点是包含在区间中的):

  • [a..a]:单元素区间
  • [a..a); (a..a]:空区间,但它们是有效的
  • (a..a):无效区间

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

构建区间

区间实例可以由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()
1 Range.closed("left""right"); //字典序在"left"和"right"之间的字符串,闭区间
2 Range.lessThan(4.0); //严格小于4.0的double值

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

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

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

1 Range.downTo(4, boundType);// (a..+∞)或[a..+∞),取决于boundType
2 Range.range(1, CLOSED, 4, OPEN);// [1..4),等同于Range.closedOpen(1, 4)

区间运算

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

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

查询运算

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

1 Range.closedOpen(44).isEmpty(); // returns true
2 Range.openClosed(44).isEmpty(); // returns true
3 Range.closed(44).isEmpty(); // returns false
4 Range.open(44).isEmpty(); // Range.open throws IllegalArgumentException
5 Range.closed(310).lowerEndpoint(); // returns 3
6 Range.open(310).lowerEndpoint(); // returns 3
7 Range.closed(310).lowerBoundType(); // returns CLOSED
8 Range.open(310).upperBoundType(); // returns OPEN

关系运算

包含[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]可以解决这个问题(见8.5节);
  • [3..6]不 包含(1..1],虽然前者包含了后者的所有值。

包含是一种偏序关系[partial ordering]。基于包含关系的概念,Range还提供了以下运算方法。

相连[isConnected]

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

相连是一种自反的[reflexive]、对称的[symmetric]关系。

1 Range.closed(35).isConnected(Range.open(510)); // returns true
2 Range.closed(09).isConnected(Range.closed(34)); // returns true
3 Range.closed(05).isConnected(Range.closed(39)); // returns true
4 Range.open(35).isConnected(Range.open(510)); // returns false
5 Range.closed(15).isConnected(Range.closed(610)); // returns false

交集[intersection]

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

交集是可互换的[commutative] 、关联的[associative] 运算[operation]。

1 Range.closed(35).intersection(Range.open(510)); // returns (5, 5]
2 Range.closed(09).intersection(Range.closed(34)); // returns [3, 4]
3 Range.closed(05).intersection(Range.closed(39)); // returns [3, 5]
4 Range.open(35).intersection(Range.open(510)); // throws IAE
5 Range.closed(15).intersection(Range.closed(610)); // throws IAE

跨区间[span]

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

span是可互换的[commutative] 、关联的[associative] 、闭合的[closed]运算[operation]。

1 Range.closed(35).span(Range.open(510)); // returns [3, 10)
2 Range.closed(09).span(Range.closed(34)); // returns [0, 9]
3 Range.closed(05).span(Range.closed(39)); // returns [0, 9]
4 Range.open(35).span(Range.open(510)); // returns (3, 10)
5 Range.closed(15).span(Range.closed(610)); // returns [1, 10]

离散域

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

在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))
1 ImmutableSortedSet set = ContigousSet.create(Range.open(15), iscreteDomain.integers());
2 //set包含[2, 3, 4]
3 ContiguousSet.create(Range.greaterThan(0), DiscreteDomain.integers());
4 //set包含[1, 2, ..., Integer.MAX_VALUE]

注意,ContiguousSet.create并没有真的构造了整个集合,而是返回了set形式的区间视图。

你自己的离散域

你可以创建自己的离散域,但必须记住DiscreteDomain契约的几个重要方面。

  • 一个离散域总是代表某种类型值的全集;它不能代表类似”素数”或”长度为5的字符串”这样的局部域。所以举例来说,你无法构造一个DiscreteDomain以表示精确到秒的JODA DateTime日期集合:因为那将无法包含JODA DateTime的所有值。
  • DiscreteDomain可能是无限的——比如BigInteger DiscreteDomain。这种情况下,你应当用minValue()和maxValue()的默认实现,它们会抛出NoSuchElementException。但Guava禁止把无限区间传入ContiguousSet.create——译者注:那明显得不到一个可枚举的集合。

如果我需要一个Comparator呢?

我们想要在Range的可用性与API复杂性之间找到特定的平衡,这部分导致了我们没有提供基于Comparator的接口:我们不需要操心区间是怎样基于不同Comparator互动的;所有API签名都是简单明确的;这样更好。

另一方面,如果你需要任意Comparator,可以按下列其中一项来做:

  • 使用通用的Predicate接口,而不是Range类。(Range实现了Predicate接口,因此可以用Predicates.compose(range, function)获取Predicate实例)
  • 使用包装类以定义期望的排序。

译者注:实际上Range规定元素类型必须是Comparable,这已经满足了大多数需求。如果需要自定义特殊的比较逻辑,可以用Predicates.compose(range, function)组合比较的function。

文章转自 并发编程网-ifeve.com

时间: 2024-08-31 12:08:43

[Google Guava] 8-区间的相关文章

Google Guava vs Apache Commons for Argument Validation

It is an established good practice to validate method arguments at the beginning of the method body. For example you could check that the passed value is not negative before doing some calculation: 1 2 3 4 5 6 public int doSomeCalculation(int value)

Google Guava官方教程(中文版)

原文链接  译文链接 译者: 沈义扬,罗立树,何一昕,武祖  校对:方腾飞 引言 Guava工程包含了若干被Google的 Java项目广泛依赖 的核心库,例如:集合 [collections] .缓存 [caching] .原生类型支持 [primitives support] .并发库 [concurrency libraries] .通用注解 [common annotations] .字符串处理 [string processing] .I/O 等等. 所有这些工具每天都在被Google

[Google Guava] 7-原生类型

原文链接 译文链接 译者:沈义扬,校对:丁一 概述 Java的原生类型就是指基本类型:byte.short.int.long.float.double.char和boolean. 在从Guava查找原生类型方法之前,可以先查查Arrays类,或者对应的基础类型包装类,如Integer. 原生类型不能当作对象或泛型的类型参数使用,这意味着许多通用方法都不能应用于它们.Guava提供了若干通用工具,包括原生类型数组与集合API的交互,原生类型和字节数组的相互转换,以及对某些原生类型的无符号形式的支持

[Google Guava] 4-函数式编程

原文链接 译文链接 译者:沈义扬,校对:丁一 注意事项 截至JDK7,Java中也只能通过笨拙冗长的匿名类来达到近似函数式编程的效果.预计JDK8中会有所改变,但Guava现在就想给JDK5以上用户提供这类支持. 过度使用Guava函数式编程会导致冗长.混乱.可读性差而且低效的代码.这是迄今为止最容易(也是最经常)被滥用的部分,如果你想通过函数式风格达成一行代码,致使这行代码长到荒唐,Guava团队会泪流满面. 比较如下代码: 01 Function<String, Integer> leng

[Google Guava] 2.3-强大的集合工具类:java.util.Collections中未包含的集合工具

原文链接 译文链接 译者:沈义扬,校对:丁一 尚未完成: Queues, Tables工具类 任何对JDK集合框架有经验的程序员都熟悉和喜欢java.util.Collections包含的工具方法.Guava沿着这些路线提供了更多的工具方法:适用于所有集合的静态方法.这是Guava最流行和成熟的部分之一. 我们用相对直观的方式把工具类与特定集合接口的对应关系归纳如下: 集合接口 属于JDK还是Guava 对应的Guava工具类 Collection JDK Collections2:不要和jav

[Google Guava] 1.5-Throwables:简化异常和错误的传播与检查

原文链接 译者: 沈义扬 异常传播 有时候,你会想把捕获到的异常再次抛出.这种情况通常发生在Error或RuntimeException被捕获的时候,你没想捕获它们,但是声明捕获Throwable和Exception的时候,也包括了了Error或RuntimeException.Guava提供了若干方法,来判断异常类型并且重新传播异常.例如: 1 try { 2     someMethodThatCouldThrowAnything(); 3 } catch (IKnowWhatToDoWit

[Google Guava] 2.2-新集合类型

原文链接 译文链接 译者:沈义扬,校对:丁一 Guava引入了很多JDK没有的.但我们发现明显有用的新集合类型.这些新类型是为了和JDK集合框架共存,而没有往JDK集合抽象中硬塞其他概念.作为一般规则,Guava集合非常精准地遵循了JDK接口契约. Multiset 统计一个词在文档中出现了多少次,传统的做法是这样的: Map<String, Integer> counts = new HashMap<String, Integer>(); for (String word : w

[Google Guava] 9-I/O

原文链接 译文链接 译者:沈义扬 字节流和字符流 Guava使用术语"流" 来表示可关闭的,并且在底层资源中有位置状态的I/O数据流.术语"字节流"指的是InputStream或OutputStream,"字符流"指的是Reader 或Writer(虽然他们的接口Readable 和Appendable被更多地用于方法参数).相应的工具方法分别在ByteStreams 和CharStreams中. 大多数Guava流工具一次处理一个完整的流,并且

[Google Guava] 10-散列

原文链接 译文链接 译者:沈义扬 概述 Java内建的散列码[hash code]概念被限制为32位,并且没有分离散列算法和它们所作用的数据,因此很难用备选算法进行替换.此外,使用Java内建方法实现的散列码通常是劣质的,部分是因为它们最终都依赖于JDK类中已有的劣质散列码. Object.hashCode往往很快,但是在预防碰撞上却很弱,也没有对分散性的预期.这使得它们很适合在散列表中运用,因为额外碰撞只会带来轻微的性能损失,同时差劲的分散性也可以容易地通过再散列来纠正(Java中所有合理的散

[Google Guava] 1.1-使用和避免null

原文链接 译文链接 译者: 沈义扬    Doug Lea 说,"Null 真糟糕."   当Sir C. A. R. Hoare 使用了null引用后说,"使用它导致了十亿美金的错误." 轻率地使用null可能会导致很多令人惊愕的问题.通过学习Google底层代码库,我们发现95%的集合类不接受null值作为元素.我们认为, 相比默默地接受null,使用快速失败操作拒绝null值对开发者更有帮助. 此外,Null的含糊语义让人很不舒服.Null很少可以明确地表示某