`Interval`

and `GenericInterval`

`uk`.org.bobulous.java.intervals

?A few times whilst writing Java code I found it frustrating that Java offers no core class to represent the concept of a mathematical interval. So I created the `uk.org.bobulous.java.intervals`

package with the aim of providing interfaces and classes which offer support for intervals. The source code itself is full of detailed Javadoc comments, so take a look at that for the full specification. This page will act as an introduction to the package with a few code examples.

In mathematics an interval is a subset of an ordered set, defined by a lower endpoint and an upper endpoint. The interval includes every value of the ordered set which is considered greater than the lower endpoint value **and** less than the upper endpoint value. If the endpoint value itself is included in the interval than the endpoint is said to be closed; if the endpoint value itself is not included then the endpoint is said to be open.

For example, the interval **[1, 5]** in the set of integers has a lower endpoint of one and an upper endpoint of five and both endpoints are closed (indicated by using a square bracket). So the interval includes the integers 1, 2, 3, 4 and 5 and nothing else.

Another example, the interval **(0.0, 10.0]** in the set of real numbers has a lower endpoint of 0.0 and an upper endpoint of 10.0 and the lower endpoint is open (indicated using a round bracket) but the upper endpoint is closed. So this interval includes all real numbers which are greater than zero **and also** less-than-or-equal-to ten. Zero is not included in the interval, even though the lower endpoint is zero, because the lower endpoint mode is open.

It is possible for an interval to include no values whatsoever, so that it represents the empty set. For example, the intervals **(0.0, 0.0)** and **(3.0, 3.0]** and **[8.0, 8.0)** all represent the empty set because in each case there is no value which is permitted by **both** endpoints. It is also possible for a closed interval to contain only a single value, for example the integer interval **[1, 1]**, and this is known as a degenerate interval.

An interval can be bounded (where both endpoints are finite, as in the examples so far), unbounded (where both endpoints are infinite and permit all values from the ordered set), left-bounded (where only the lower endpoint is finite), or right-bounded (where only the upper endpoint is finite). If an endpoint is infinite then its mode (open/closed) is irrelevant as it permits any value.

For example, the integer interval **(-∞, +∞)** has a lower endpoint of negative infinity and an upper endpoint of positive infinity, and so includes every integer. And the real number interval **(0.0, +∞)** includes every real number which is greater than zero. Note that infinity is not actually a number (nor a member of any ordered set) but a statement which says "keep going forever and never stop" and in terms of endpoints it means "include everything in this direction without limit".

`Interval`

The package `uk.org.bobulous.java.intervals`

currently contains the interface `Interval`

, the concrete implementation `GenericInterval`

, and the support class `IntervalComparator`

.

`Interval`

The interface `Interval<T extends Comparable<T>>`

defines a type which represents an interval through the type `T`

. Note that the base type `T`

must be a type which implements `Comparable<T>`

. In other words, the base type `T`

must be a type whose objects can be compared with other objects of the same type, giving the type a natural ordering. For example, a few types which fit this requirement include `Integer`

, `Double`

, `String`

, and `BigDecimal`

.

The interface defines the `public static enum EndpointMode`

type which must be used to specify whether an interval endpoint is `OPEN`

or `CLOSED`

. It also declares the methods:

`getLowerEndpoint()`

which must return the interval's lower endpoint value of type`T`

, or`null`

if the interval is not left-bounded.`getLowerEndpointMode()`

which must return the mode of the lower endpoint, either`Interval`

or.EndpointMode .OPEN `Interval`

..EndpointMode .CLOSED `getUpperEndpoint()`

which must return the interval's upper endpoint value of type`T`

, or`null`

if the interval is not right-bounded.`getUpperEndpointMode()`

which must return the mode of the upper endpoint, either`Interval`

or.EndpointMode .OPEN `Interval`

..EndpointMode .CLOSED `includes(T`

which must return`value`)`true`

if the specified value (which must be of the same base type,`T`

, as this interval) is included by this interval;`false`

if the value is not included by this interval.`includes(Interval<T>`

which must return`interval`)`true`

if the specified interval (which must have the same base type,`T`

, as this interval) is wholly included by this interval;`false`

otherwise. In other words, must return`true`

only if every value included by the specified interval is also included by this interval.

The class `Interval<T extends Comparable<T>>`

is a concrete implementation of the interface `Interval<T extends Comparable<T>>`

. It also permits the use of any base type which implements `Comparable<T>`

, so can be used with any type which has a natural ordering.

The class provides two constructors:

`GenericInterval`

which creates a closed interval using the supplied values of type(T `lowerEndpoint`, T`upperEndpoint`)`T`

.`GenericInterval`

which can be used to create an open, left-open, right-open or closed interval having the supplied endpoint values of type(EndpointMode `lowerEndpointMode`, T`lowerEndpoint`, T`upperEndpoint`, EndpointMode`upperEndpointMode`)`T`

.

After implementing the methods required by the `Interval`

interface, `GenericInterval`

also overrides the `equals`

, `hashCode`

and `toString`

methods, and provides a method called `inMathematicalNotation`

which returns a `String`

which represents the interval using the mathematical square/round bracket notation.

Note that the endpoint values and modes of a `GenericInterval`

cannot be modified once an instance is created. This means that an instance of `GenericInterval`

is immutable if its base type is a truly immutable type such as `Integer`

, `Double`

, `String`

and so on. A `GenericInterval`

of an immutable type can be shared safely. But if the base type is a mutable type, such as `Date`

or `Calendar`

then the instance of `GenericInterval`

cannot be considered immutable because even though the endpoints are permanently fixed to pointing to the original objects, the value of those mutable objects might change, which will alter the interval represented by the `GenericInterval`

instance. If you create a `GenericInterval`

instance on a mutable base type then you must carefully guard that instance, and the objects used as its endpoints, otherwise the interval could be modified when you don't expect it to be.

The class `IntervalComparator<T extends Comparable<T>>`

defines a `Comparator<Interval<T>>`

which offers one method for comparing intervals of the naturally ordered base type `T`

. This does not have any basis in mathematics so far as I'm aware, but it does provide a way of ordering intervals and is used by several methods of the `GenericInterval`

class. See the JavaDoc within the `IntervalComparator`

class for full details of the logic it uses to compare intervals.

Now you've had an overview of the classes which are found within the `uk.org.bobulous.java.intervals`

package, let's take a look at a few examples.

`Interval<Integer> ``oneToFive` = new GenericInterval<>(1, 5);
boolean `included`;
`included` = `oneToFive`.includes(0); // evaluates to false
`included` = `oneToFive`.includes(1); // evaluates to true
`included` = `oneToFive`.includes(3); // evaluates to true
`included` = `oneToFive`.includes(5); // evaluates to true
`included` = `oneToFive`.includes(6); // evaluates to false

This example creates a `GenericInterval<Integer>`

to represent the closed integer interval **[1, 5]** described in an earlier section of this page. Then the `includes`

method is called to determine whether different integer values are included by the interval. Note that, because the interval is closed, the endpoint values of one and five are both included by the interval.

`Interval<Double> ``zeroToTen` = new GenericInterval<>(
EndpointMode.OPEN, 0.0, 10.0, EndpointMode.CLOSED);
boolean `included`;
`included` = `zeroToTen`.includes(0.0); // evaluates to false
`included` = `zeroToTen`.includes(0.1); // evaluates to true
`included` = `zeroToTen`.includes(6.3); // evaluates to true
`included` = `zeroToTen`.includes(10.0); // evaluates to true
`included` = `zeroToTen`.includes(10.1); // evaluates to false

This example creates a `GenericInterval<Double>`

to represent the left-open real number interval **(0, 10]** described in an earlier section of this page. This is done by explicitly stating the endpoint mode of the lower and upper endpoints, setting the lower endpoint mode to `OPEN`

and the upper endpoint mode to `CLOSED`

. Then the `includes`

method is called to determine whether different `double`

values are included by the interval. Note that, because the interval's lower endpoint mode is open, the lower endpoint value of zero is **not** included by the interval.

`Interval<String> ``alphaToBravo` = new GenericInterval<>(
EndpointMode.CLOSED, "a", "b", EndpointMode.OPEN);
boolean `included`;
`included` = `alphaToBravo`.includes("A"); // evaluates to false
`included` = `alphaToBravo`.includes("a"); // evaluates to true
`included` = `alphaToBravo`.includes("aardvark"); // evaluates to true
`included` = `alphaToBravo`.includes("ale"); // evaluates to true
`included` = `alphaToBravo`.includes("axe"); // evaluates to true
`included` = `alphaToBravo`.includes("b"); // evaluates to false
`included` = `alphaToBravo`.includes("ball"); // evaluates to false

In this example we consider the right-open interval **["a", "b")** of `String`

values. Any Java `String`

which, according to the natural order of `String`

(as defined by its `compareTo(String)`

method), is equal-to-or-greater-than "a" **and also** less-than "b" is included in this interval. So the `String`

"a" is included in this interval, as are "aardvark", "ale", "axe", etc, but "b" is not included, nor are "ball", "car", "dinosaur", "8-ball", " " (space), "" (empty `String`

) etc. It's also important to note that neither "A" nor "Academy" are included by this interval, because uppercase "A" is not equal to lowercase "a" according to the `compareTo(String)`

method of `String`

.

`Interval<Double> ``allDoubles` = new GenericInterval<>(null, null);
Interval<Double> `nonNegativeDoubles` = new GenericInterval<>(0.0, null);
Interval<Double> `positiveDoubles` = new GenericInterval<>(
EndpointMode.OPEN, 0.0, null, EndpointMode.OPEN);
boolean `included`;
`included` = `allDoubles`.includes(nonNegativeDoubles); // evaluates to true
`included` = `allDoubles`.includes(positiveDoubles); // evaluates to true
`included` = `nonNegativeDoubles`.includes(allDoubles); // evaluates to false
`included` = `positiveDoubles`.includes(allDoubles); // evaluates to false
`included` = `nonNegativeDoubles`.includes(positiveDoubles); // evaluates to true
`included` = `positiveDoubles`.includes(nonNegativeDoubles); // evaluates to false

In this example the `includes`

method is being called to determine whether one interval wholly includes another. That is, whether the first interval includes **every** value permitted by the second interval. The `allDoubles` interval is completely unbounded: its `null`

lower endpoint and `null`

upper endpoint are equivalent to negative and positive infinity respectively, which means that this interval includes every possible `Double`

value. So this interval also includes every possible `Interval<Double>`

, which means that the `includes`

method always returns `true`

when called on `allDoubles`.

Note that the an `Interval`

never actually includes `null`

, even if one or both of its endpoints are `null`

. This is because `null`

represents the lack of any value, which makes sense for an endpoint because the lack of a value is interpreted as that endpoint being unbounded, infinite. But it does not make sense to check whether an interval contains a non-value, so this is not permitted. In fact, if you call either `includes`

method of `GenericInterval`

with a `null`

argument it will throw a `NullPointerException`

.

The only difference between the `nonNegativeDoubles` and `positiveDoubles` intervals is that the interval `nonNegativeDoubles` includes the value 0.0. This means that the interval `nonNegativeDoubles` wholly includes the interval `positiveDoubles`, but the interval `positiveDoubles` does not include the interval `nonNegativeDoubles`. And neither of these intervals includes the all-encompassing interval `allDoubles`.

`uk`.org.bobulous.java.intervals

You can fetch the source code from the JavaIntervals repository on Codeberg. The source code is made available under the Mozilla Public Licence 2.0.

I have harnessed a few dozen unit tests to these classes to hunt for any bugs or errors, but please be sure to write your own unit tests to confirm that the package behaves as you expect before you put it to serious use. (For this reason, the unit tests are not included as part of the package.) If you find any issues, please raise them on the Codeberg issues page.