Apache Commons Lang Range

Introduction

The Apache Commons Lang provides utility classes and methods. In the post we will learn about the Apache Commons Lang Range class.

Apache Commons Lang Range class

The Range class represents a range of values from a minimum to a maximum (inclusive).

In this post, I will show the usage of the Apache Commons Lang Range class with integers and a custom class called Point as shown below. It represents a point instance with x and y co-ordinate values.

public class Point {
    private int x;
    private int y;

    public Point(int x, int y) {
        this.x = x;
        this.y = y;
    }

    public int getX() {
        return x;
    }

    public int getY() {
        return y;
    }

    @Override
    public String toString() {
        return "[" + x + ", " + y + "]";
    }
}

The methods in the Commons Lang Range class comes in two flavours:

  • Operating on instances that have a natural ordering i.e., they implement the Comparable interface (like Integer, String etc.,)
  • Operate on values that don’t have a natural ordering. In this case, we have to pass an explicit Comparator.

Let us create a pointsComparator to compare two points as,

Comparator<Point> pointComparator = Comparator.comparingInt(Point::getX)
        .thenComparing(Point::getY);

This uses the Comparator#comparing method to compare two Point instances. It first compares by the x co-ordinate and then compares by the y co-ordinate when the x co-ordinate values are equal. Refer to the Comparator comparing to learn more about the various static utility methods in the Comparator class.

With this we get the below ordering (for some sample Points),

(1, 1) < (1 , 2) < (1, 5) < (2, 0) < (2, 2) < (2, 5) < (3, 1) < (4, 0)

Apache Commons Lang Range creation

There are two static methods using which we can create a Range instance:

  • is
  • between

Range#is

There are two overloaded is methods – one to be used when the object we pass implements the Comparable interface and the one which does not. For the latter, we must pass a comparator.

The is method takes a single value and returns a Range instance. Hence, that value is the only element in the range.

Range<Integer> range = Range.is(1);

In the above code, we get a Apache Commons Lang Range object with just one element in the range. Similarly for the Point class, we can do like,

Range<Point> range = Range.is(new Point(1, 2), pointComparator);

Range#between

The between method accepts two values – the from and the to values (both inclusive) and returns a Range instance which represents a range with the passed from and the to values as the minimum and the maximum values of the range.

Range<Integer> range = Range.between(1, 5);

This represents a range of integers from values 1 to 5 (inclusive of both).

When the object does not have a natural ordering, we have to pass a comparator as,

Range<Point> range = Range.between(
        new Point(1, 2),
        new Point(1, 5),
        pointComparator
);

This represents a range of points from (1, 2) to (1, 5).

Check if a range contains a value or entire range

Range contains

The Range#contains method checks if the specified element is present within a range.

Range<Integer> range = Range.between(1, 5);

System.out.println(range.contains(1)); //true
System.out.println(range.contains(2)); //true
System.out.println(range.contains(5)); //true

System.out.println(range.contains(0)); //false
System.out.println(range.contains(6)); //false

The first line created a Range instance that represents the range of integers from 1 to 5 (inclusive). Then, we call the contains method on it passing various values.

Similarly, shown below is the usage of contains method on the Point range. We create a range of points from (1, 1) to (2,3) and call the contains method with various Points.

Range<Point> points = Range.between(
        new Point(1, 1),
        new Point(2, 3),
        pointComparator
);
System.out.println(points.contains(new Point(1, 2))); //true
System.out.println(points.contains(new Point(2, 1))); //true

System.out.println(points.contains(new Point(1, 0))); //false
System.out.println(points.contains(new Point(2, 4))); //false
System.out.println(points.contains(new Point(5, 0))); //false

Range containsRange

The containsRange method accepts anotherApache Commons Lang Range instance and checks if the range contains all the elements from the passed Range. 

Range<Integer> range2 = Range.between(1, 3);
System.out.println(range.containsRange(range2)); //true

We created a second range consisting of integers 1, 2 and 3. When calling containsRange on the first range passing this, we get a true since the first range has all the values or elements from the second range.

On the other hand, if we had created a range that had at least one element not present in the first range, it would return a false.
range2 = Range.between(1, 6);
System.out.println(range.containsRange(range2)); //false

range2 = Range.between(6, 9);
System.out.println(range.containsRange(range2)); //false

Similarly, for the Points class,

Range<Point> points2 = Range.between(
        new Point(1, 5),
        new Point(2, 1),
        pointComparator
);
System.out.println(points.containsRange(points2)); //true
points2 = Range.between(
        new Point(1, 5),
        new Point(2, 5),
        pointComparator
);
System.out.println(points.containsRange(points2)); //false

The second example had points (2, 4) and (2,5) that are not present in the first range and hence it returned false.

Is Before and Is After

isBefore and isBeforeRange

The isBefore method takes an element as an argument and checks if the range is before the passed element.

Range<Integer> range = Range.between(1, 5);

System.out.println(range.isBefore(6)); //true

System.out.println(range.isBefore(0)); //false
System.out.println(range.isBefore(2)); //false

The first call returns true as the range from 1 to 5 is before 6. In the second call, it returns false as the range is not before 0. In the third call, even though the range starts at 1, the entire range is not present before 2 and hence the call returns false.

For the Points class,

Range<Point> points = Range.between(
        new Point(1, 1),
        new Point(2, 3),
        pointComparator
);
System.out.println(points.isBefore(new Point(2, 4))); //true
System.out.println(points.isBefore(new Point(1, 2))); //false

The isBeforeRange is similar to the isBefore method, but it takes an entire range and checks if the range is completely before the passed range. In other words, it checks if the range is before the minimum element of the passed range.

Range<Integer> range2 = Range.between(6, 10);
System.out.println(range.isBeforeRange(range2)); //true
range2 = Range.between(3, 5);
System.out.println(range.isBeforeRange(range2)); //false
Range<Point> points2 = Range.between(
        new Point(5, 5),
        new Point(5, 9),
        pointComparator
);
System.out.println(points.isBeforeRange(points2)); //true
points2 = Range.between(
        new Point(2, 2),
        new Point(2, 8),
        pointComparator
);
System.out.println(points.isBeforeRange(points2)); //false

isAfter and isAfterRange

The isAfter method takes an element and checks if the range is after the passed element.

The isAfterRange method takes an entire range and checks if the range is completely after the passed range. In other words, it checks if the range is after the maximum element of the passed range.

Range<Integer> range = Range.between(4, 8);
System.out.println(range.isAfter(0)); //true

System.out.println(range.isAfter(6)); //false
System.out.println(range.isAfter(10)); //false


Range<Point> points = Range.between(
    new Point(2, 5),
    new Point(3, 10),
    pointComparator
);
System.out.println(points.isAfter(new Point(1, 0))); //true
System.out.println(points.isAfter(new Point(2, 6))); //false

Shown below are some example for the isAfterRange method.

Range<Integer> range2 = Range.between(1, 2);
System.out.println(range.isAfterRange(range2)); //true
range2 = Range.between(3, 5);
System.out.println(range.isAfterRange(range2)); //false
Range<Point> points2 = Range.between(
        new Point(1, 2),
        new Point(2, 3),
        pointComparator
);
System.out.println(points.isAfterRange(points2)); //true
points2 = Range.between(
        new Point(2, 2),
        new Point(2, 8),
        pointComparator
);
System.out.println(points.isAfterRange(points2)); //false

Range#intersectionWith

We can use the intersectionWith method to get the intersection range of two ranges (this range and the passed range). It throws an IllegalArgumentException if the ranges do not overlap.

Range<Integer> range = Range.between(1, 5);
System.out.println(range.intersectionWith(Range.between(2, 10))); //[2..5]

We created a range of integers from 1 to 5 and called intersectionWith method, passing a range of integers from 2 to 10. It returned the overlapping range [2..5]. It is the toString representation of the Range class – the minimum element followed by two dots and followed by the maximum element.

System.out.println(range.intersectionWith(Range.between(2, 3))); //[2..3]

//Throws an IllegalArgumentException
System.out.println(range.intersectionWith(Range.between(6, 7)));

The last call will throw an java.lang.IllegalArgumentException: Cannot calculate intersection with non-overlapping range [6..7] as there is no overlap between the ranges.

Range<Point> points = Range.between(
        new Point(1, 1),
        new Point(2, 3),
        pointComparator
);
System.out.println(points.intersectionWith(Range.between(
        new Point(1, 5),
        new Point(2, 100),
        pointComparator
)));
System.out.println(points.intersectionWith(Range.between(
        new Point(1, 5),
        new Point(2, 1),
        pointComparator
)));

Prints,

[[1,5]..[2,3]]
[[1,5]..[2,1]]

For the first call, the overlapping range is from (1, 5) to (2, 3) and the second overlapping range is from (1, 5) to (2, 1).

Range#isOverlappedBy

The isOverlappedBy method checks if the passed range and the range overlaps or not. The two ranges overlap if they have at least one element in common. 

Comparing to the intersectionWith method, we can use this method to check if there is an overlap or not. The intersectionWith method returns the actual overlapping range itself. Note that it throws an exception when there is no overlap. Hence we can use isOverlappedBy method before calling intersectionWith.

Range<Integer> range = Range.between(1, 5);
System.out.println(range.isOverlappedBy(Range.between(2, 10))); //true
System.out.println(range.isOverlappedBy(Range.between(2, 3))); //true

System.out.println(range.isOverlappedBy(Range.between(6, 7))); //false

Range<Point> points = Range.between(
        new Point(1, 1),
        new Point(2, 3),
        pointComparator
);
//true
System.out.println(points.isOverlappedBy(Range.between(
        new Point(1, 5),
        new Point(2, 100),
        pointComparator
)));
//true
System.out.println(points.isOverlappedBy(Range.between(
        new Point(1, 5),
        new Point(2, 1),
        pointComparator
)));
//false
System.out.println(points.isOverlappedBy(Range.between(
        new Point(2, 4),
        new Point(2, 7),
        pointComparator
)));

Range isStartedBy and isEndedBy

The isStartedBy method takes an element and checks if the range starts with that element. TheisEndedBy method takes an element and checks if the range ends with that element.

Range<Integer> range = Range.between(1, 5);
System.out.println(range.isStartedBy(1)); //true

System.out.println(range.isStartedBy(2)); //false
System.out.println(range.isStartedBy(5)); //false
Range<Point> points = Range.between(
        new Point(1, 1),
        new Point(2, 3),
        pointComparator
);
System.out.println(points.isStartedBy(new Point(1, 1))); //true
System.out.println(points.isStartedBy(new Point(2, 2))); //false
Range<Integer> range = Range.between(1, 5);
System.out.println(range.isEndedBy(5)); //true
System.out.println(range.isEndedBy(1)); //false
System.out.println(range.isEndedBy(2)); //false


System.out.println();
//2
Range<Point> points = Range.between(
        new Point(1, 1),
        new Point(2, 3),
        pointComparator
);
System.out.println(points.isEndedBy(new Point(2, 3))); //true
System.out.println(points.isEndedBy(new Point(1, 1))); //false

Range get minimum and get maximum

We can get the minimum and maximum elements of a range by calling the getMinimum and getMaximum methods, respectively.

Range<Integer> range = Range.between(1, 5);
System.out.println(range.getMinimum()); //1
System.out.println(range.getMaximum()); //5

System.out.println();
//2
Range<Point> points = Range.between(
        new Point(1, 1),
        new Point(2, 3),
        pointComparator
);
System.out.println(points.getMinimum()); //[1, 1]
System.out.println(points.getMaximum()); //[2, 3]

Range fit

The Range#fit method takes an element and returns an element by fitting it into the range. Three scenarios are possible:

  • If the element is within the range, it returns it as it is,
  • If the element is less than the minimum element of the range, it returns the minimum element of the range.
  • Else it returns the maximum element of the range (as the passed element is greater than the maximum of the range).
Range<Integer> range = Range.between(1, 5);
System.out.println(range.fit(3)); //3
System.out.println(range.fit(0)); //1
System.out.println(range.fit(7)); //5
  • Since 3 is within the range, it returns it
  • 0 is outside the range and is less than the minimum of the range and hence it returns the minimum of the range, 1.
  • 7 is outside the range and is greater than the maximum of the range and hence it returns the maximum of the range, 5.

Examples with the Points class,

Range<Point> points = Range.between(
        new Point(1, 1),
        new Point(2, 3),
        pointComparator
);
System.out.println(points.fit(new Point(1, 0))); //[1, 1]
System.out.println(points.fit(new Point(1, 4))); //[1, 4]
System.out.println(points.fit(new Point(7, 1))); //[2, 3]

Conclusion

We learnt about the Range class and its methods in the Apache Commons Lang. I recommend checking out the other posts in the Apache Commons library.

Leave a Reply