如果想要建立一個包括 1 到 5 的數列,你會怎麼做?手寫一個
Arrays.asList(1, 2, 3, 4, 5)?如果是 1 到 20 呢?手寫一個看來就有點笨了,至少用個迴圈 ...
      List<Integer> list = new ArrayList<>(20);
for(int i = 1; i <= 20; i++) {
    list.add(i);
}List<Character> list = new ArrayList<>(26);
for(char c = 'a'; c <= 'z'; c++) {
    list.add(c);
}Range,則只需要撰寫 Range.atLeast(1),就代表著 1 到 +∞ 的自然數範圍,如果要建立 1 到 20 的範圍,則可以撰寫為 Range.closed(1, 20),如果要建立包括字元 'a' 到 'z' 的範圍,則可以撰寫為 Range.closed('a', 'z')。Range 的一些 static 方法與範圍的對照為:
      | (a..b) | {x | a < x < b} | open | 
| [a..b] | {x | a <= x <= b} | closed | 
| (a..b] | {x | a < x <= b} | openClosed | 
| [a..b) | {x | a <= x < b} | closedOpen | 
| (a..+∞) | {x | x > a} | greaterThan | 
| [a..+∞) | {x | x >= a} | atLeast | 
| (-∞..b) | {x | x < b} | lessThan | 
| (-∞..b] | {x | x <= b} | atMost | 
| (-∞..+∞) | {x} | all | 
Range.closed("abc", "abz")。實際上,任何物件只要與生俱有順序,也就是任何物件只要實作了 Comparable,都可以使用 Range 來建立範圍。一旦有了 Range,你就可以作一些基本的查詢動作,像是使用 contains 或 containAll 來察看某元素或某些元素是否包括在建立的範圍內。
        Range 與 Range 之間也可以進行範圍的相關測試或操作,像是使用 enclose 方法測試某範圍是否包括另一範圍,使用 isConnected 測試某範圍是否連接至另一範圍,使用 intersection 取得兩個範圍的交集,使用 span 來建立橫跨兩個範圍的範圍,你可以在 RangeExplained 的 Operations 察看相關範例程式碼。實際上,範圍不是數列,也就是像
Range.closed(1, 20) 並沒有實際產生 1、2 ... 20 的整數數列,它就僅僅只是個…呃…範圍!如果想要取得的是範圍中的數字,那麼可以透過 ContiguousSet 類別 static 的 create 方法,呼叫時必須指定 Range 物件及一個 DiscreteDomain 物件,DiscreteDomain 物件定義了指定的範圍中,不連續元素間的關係以及 DiscreteDomain 的邊界。
        由於經常打算取得的是整數,因此 DiscreteDomain 提供了 integers 、 longs 以及支援大數的 bigIntegers 三個 static 方法。Range 與 DiscreteDomain 來迭代 1 到 20 的數字,可以如下撰寫:
      for(int i : create(Range.closed(1, 20), integers())) {
    // 做些事 ...            
}create 方法不會傳回整個數列,它傳回的是 Set,因此實際上 for 迴圈是運用其 iterator 方法傳回的 Iterator 物件進行迭代,也就是這讓你有實現惰性(Laziness)的可能性。例如,如果你要找的某 int 數是在自然數列中,但不確定其範圍,那麼就可以如下撰寫:
      for(int i : create(Range.atLeast(1), integers())) {
    // 做些運算
    if(某些條件) { break; }
}Integer.MIN_VALUE 至 Integer.MAX_VALUE 的 List,以上方法顯然經濟多了。你可以建立自己的 DiscreteDomain,例如,建立小寫字母的 LowerCaseDomain 話,可以如下定義:
      class LowerCaseDomain extends DiscreteDomain<Character> {
    private static LowerCaseDomain domain = new LowerCaseDomain();
    public static DiscreteDomain letters() {
        return domain;
    }
    @Override
    public Character next(Character c) {
        return (char) (c + 1);
    }
    @Override
    public Character previous(Character c) {
        return (char) (c - 1);
    }
    @Override
    public long distance(Character start, Character end) {
        return end - start;
    }
    @Override
    public Character maxValue() {
        return 'z';
    }
    @Override
    public Character minValue() {
        return 'a';
    }
}DiscreteDomain 後,一定要實作的三個方法是 next、previous 與 distance,當你建立的範圍是有界時,若要取得下一個不連續元素,會呼叫 next 方法,若要取得前一個不連續元素,則會呼叫 previous,distance 則指出,從範圍的 start 至 end 間,必須呼叫幾次 next 才能達到。如果你指定的範圍是無界的,像是指定
Range.atLeast('a') 時,則必須定義 DiscreteDomain 的 maxValue 與 minValue,這兩個方法指出在 DiscreteDomain 中最大值與最小值為何,這很重要,範圍可以是無界,但 DiscreteDomain 會是有界的。例如 DiscreteDomain 的 integers 方法傳回的是 IntegerDomain 其邊界是 Integer.MIN_VALUE 與 Integer.MAX_VALUE,這是受限於 int 的位元組長度,因而其是有界的:
        private static final class IntegerDomain extends DiscreteDomain<Integer>
      implements Serializable {
    ...
    @Override public Integer minValue() {
      return Integer.MIN_VALUE;
    }
    @Override public Integer maxValue() {
      return Integer.MAX_VALUE;
    }
    ...
  }DiscreteDomain 的 longs 方法傳回的是 LongDomain 其邊界是 Long.MIN_VALUE 與 Long.MAX_VALUE,這是受限於 long 的位元組長度:
        private static final class LongDomain extends DiscreteDomain<Long>
      implements Serializable {
    ...
    @Override public Long minValue() {
      return Long.MIN_VALUE;
    }
    @Override public Long maxValue() {
      return Long.MAX_VALUE;
    }
    ...
  }LowerCaseDomain 是有界的,也就是 'a' 到 'z',你可以這麼使用:
      for(char i : create(Range.closed('a', 'z'), LowerCaseDomain.letters())) {
    // 做些事 ...
}
for(char i : create(Range.atLeast('m'), LowerCaseDomain.letters())) {
    // 做些事 ...
}Range 就足夠了,如果真的需要逐一取得範圍中的不連續元素,搭配 DiscreteDomain 就可以達到目的,而且不用一開始就建立所有的元素,只需在必要的時候取用即可。
