Adding Range Type in Java
1. Introduction
Modern and more recent programming languages have the concept of a range type. Programming languages such as Python, Ada, Ruby, and Rust, each have the concept of a range-a bounded beginning and end of a numerical sequence or series of integers.
The Java programming language has a reputation for being a verbose, wordy programming language [Pras 2012] by nature. But yet, there is no range type in the Java language. This is a deficiency in the Java language, but using existing Java language features the range type is easily added into the programming language.
1.1 Python Programming Language
A range example in the Python programming language [PYna 2020] is:
#!/usr/bin/env python
#
range0to9 = range(0, 10)
for n in range0to9:
print(n)
for n in range(0,10):
print(n)
1.2 Ada Programming Language
A range example in the Ada programming language [Wiki 2017] is:
with Ada.Text_IO; use Ada.Text_IO;
procedure Main is
type Index0to9 is range 0..9;
package Index_IO is new Integer_IO (Index0to9);
begin
for I in 0..9 loop
Put(Integer'Image (I));
New_Line;
end loop;
for I in Index0to9 loop
Index_IO.Put(I);
New_Line;
end loop;
end Main;
1.3 Rust Programming Language
One of the newer, much discussed programming languages, Rust has a range type. Consider the adapted Rust source code [Mozi 2019] of:
fn main() {
for number in 1..10 {
println!("{}", number);
}
}
1.4 Ruby Programming Language
An example in the Ruby [Ruby 2020] programming language using a range is:
#
#!/usr/bin/ruby
#
range0to9 = 0..9
for i in range0to9 do
puts i
end
puts "\n"
for i in 0..9 do
puts i
end
puts "\n"
val = 7
if (0..9) === val
puts "In the range"
else
puts "Out of range"
end
puts "\n"
if range0to9.include?(val)
puts "In the range"
else
puts "Out of range"
end
1.5 Advantages of the Range Type
All four example do the same thing, and illustrate the syntax for a range type in each programming language. The advantage of a range in a programming language is a more compact, and concise for expressiveness in comparison to alternatives that are an archaic syntax form. Without a range type, a programming language can be more verbose in writing functionality, but not have the clarity of expression from the range type feature.
2. Range Type for Java
Adding a range type for Java is possible in two ways:
- Extend the Java language with a non-standard syntax and keywords.
- Use the Java language with a class and features to add the feature.
2.1 Extending the Java Language
One approach to add a range type is to add it directly into the programming language. The problem is that this way adds non-standard syntax, and keywords. This would require a new compiler or new transcompiler in order to use the range type. Also as stated, this approach is non-standard, so as Java continues to develop and evolve, the new range type could become incompatible, or obsolete.
This approach, while feasible, is not an effective method to add a range type. Basically it is “embrace and extend” but the extension might become incompatible, and requires a proprietary compiler.
2.2 Use the Java Language
A more effective and less proprietary approach is to use the Java programming language to add a feature to the Java language. This adds a range type, but also does not require a new compiler to use, and risk future incompatible syntax and keywords.
Using the Java programming language, there are two approaches in terms of syntax:
- Use static methods accessed through the class.
- Use static methods along with the static import feature of Java.
The static import feature was added to Java 5 in 2004 [Orac 2004] so using that feature requires Java version 5 or later.
2.2.1 Implementation Approach
For the feature the Iterator interface of Java can be used for creation a virtual data structure—a Range type. However this Range type is not an actual data structure with a list of integers, but a virtual data structure. An example of the syntax used with a for-each loop similar to the Python and Ada examples is:
for(Integer n : Range.of(0,10)){
System.out.print(n);
System.out.println();
}//end for
As the syntax demonstrates, the Range class with the “of()” that performs like an iterator in Java, and implements a Java iterator.
2.2.2 Using an Iterator
An iterator is a classic design pattern from the “Gang of Four” [Gamm 1994] design patterns book. Java [ZetC 2020] introduced a language standard iterator in Java 1.2 in December 1998 used with the Java Collections. As of Java 5, objects implementing the Iterable interface, which returns an Iterator instances from its only method, can be traversed using the Java for-each loop syntax.
The Iterator brings the Iterator design pattern, which is a common behavioral pattern used to access the elements of a collection object in sequential manner without any need to know its underlying representation.
The Java iterator is an interface with four methods, but only two methods: “hasNext()” and “next()” are used for the Range type. For the Range type the method “iterator()” is of significance as it creates the iterator over the range of values from a lower and upper boundary.
3. Implementation of Range
The implementation of a Range type in Java requires two classes:
- Range – the class for creating a range and getting an iterator through the range type.
- RangeIterator – the class that implements the Iterator interface and the methods required.
The Range class is the virtual data structure, and the RangeIterator class is to traverse the Range data structure.
3.1 Range Class
The Range class is the “virtual” data structure of a list of integers from the start until the close of a range. The Range class creates a range and gets the iterator for a given range. Static factory methods create an iterator that is used to traverse through the “virtual” data structure list of integers.
The Range class is organized around three areas:
- Class attributes and constructors to initialize those attributes.
- Class iterator method and factory methods to create a range type.
- Class accessor methods to get class attributes.
3.1.1. Range Class Attributes and Constructors
The Range class attributes and constructors are:
public final class Range implements Iterable<Integer> {
private final int begin;
private final int close;
private final int index;
public Range(final int begin, final int close) {
this(begin, close, 1);
}//end constructor
public Range(final int begin, final int close, final int index) {
this.begin = begin;
this.close = close;
this.index = index;
}//end constructor
//...
}//end class Range
3.1.2 Range Class Iterator Method and Factory Methods
The Range class iterator method and factor methods are:
public final class Range implements Iterable<Integer> {
//...
public static Range of(final int begin, final int close) {
return new Range(begin, close);
}//end of
public static Range of(final int begin, final int close, final int index) {
return new Range(begin, close, index);
}//end of
public static Range to(final int begin, final int close) {
return new Range(begin, close+1);
}//end to
public static Range to(final int begin, final int close, final int index) {
return new Range(begin, close+1, index);
}//end to
public boolean in(final int value) {
//...
}//end of
public Iterator<Integer> iterator() {
return new RangeIterator<>(this);
}//end iterator
}//end class Range
3.1.3 Range Class Accessor Methods
The Range class accessor methods for the beginning and ending of the range, and the index are:
public final class Range implements Iterable<Integer> {
//...
public int getBegin() {
return this.begin;
}//end getBegin
public int getClose() {
return this.close;
}//end getClose
public int getIndex() {
return this.index;
}//end getIndex
}//end class Range
3.2 Range Methods
The three core methods of a range type in the Range class are:
- of – is to create the range from an inclusive lower bound to an exclusive upper bound.
- to – is to create the range from an inclusive lower bound to an inclusive upper bound.
- in – is to verify if inside the range from the lower bound to the upper bound inclusively.
3.2.1 Range of() method
The Range.of() method creates a range that begins at the lower bound, and closes at the upper bound, but while the iterator value is lesser than…thus the upper bound is exclusive. The Range type source code, and the more basic for-loop equivalent is:
for(Integer idx : Range.of(0,10)){
//...
}//end for
for(int idx=0; idx<10; idx++){
//...
}//end for
3.2.2 Range to() Method
The Range.to() method creates a range that begins at the lower bound, and closes at the upper bound, but while the iterator value is equal than…thus the upper bound is inclusive. The Range type source code, and the more basic for-loop equivalent is:
for(Integer idx : Range.to(0,10)){
//...
}//end for
for(int idx=0; idx <= 10; idx++){
//...
}//end for
3.2.3 Range in() Method
The Range “in()” method verifies a value is within the range boundaries for a given range type.
3.2.3.1 Implementation of Range “in()” Method
The “in()” method is implemented in two steps:
- Check the value is within the lower and upper bounds of the range.
- If within the range boundaries, check the modulus is zero from the index—so that the value is a multiple within the range.
The source code to implement the “in()” method is more explicit:
public final class Range implements Iterable<Integer> {
//...
public boolean in(final int val) {
if(val < this.begin || val > this.close) return false; //val within range return (((val - this.begin) % this.index) == 0); //val reachable by index
}//end in
//...
}//end class Range
3.2.3.2 Using the Range “in()” Method
The Range type source code, and the more basic if-then equivalent is for the range “of()” method:
if(Range.of(0,10).in(x)){
//...
}//end if
if(x <=0 && x < 10){
//...
}//end if
The Range type source code, and the more basic if-then equivalent is for the range “to()” method:
if(Range.to(0,10).in(x)){
//...
}//end if
if(x <=0 && x <= 10){
//...
}//end if
3.3 Range Iterator Class
The RangeIterator is the class that implements the iterator and returns integers at each step in the process of iterating through the “virtual” data structure of a Range. The RangeIterator class in Java is defined as:
public final class RangeIterator<T> implements Iterator<T> {
private int idx = -1; //increment index
private int pos = 0; //position in range
private int begin = -1; //begin value of range
private int close = -1;//close value of range
public RangeIterator(final Range range){
this.begin = range.getBegin();
this.close = range.getClose();
this.idx. = range.getIndex();
}//end constructor
public boolean hasNext(){
return (this.begin + this.pos) < this.close;
}//end hasNext
public T next(){
if(!this.hasNext()) {
throw new NoSuchElementException("No further values in range.");
}//end if
@SuppressWarnings("unchecked")
T val = (T) Integer.valueOf(this.begin + this.pos);
this.pos = this.pos + this.idx;
return val;
}//end next
public void remove(){
throw new UnsupportedOperationException("Remove is not supported.");
}//end remove
}//end class RangeIterator
The RangeIterator is simple, implementing the necessary functions to iterate over the range. The function “remove()” is not implemented, as there is no actual datum to remove.
3.4 Range using Static Import
In Java, the static import concept is introduced in 1.5 version. Using static import, we can access the static members of a class directly without class name or any object. This can allow more compact and concise access using a static method of the Range class for the range, rather than the class name and static method.
3.4.1 Static Import
Static import allows static elements of a class to be included and used without a static reference to the class. This allows for more concise expression of static attributes and methods used. This conciseness can be used to include a range type using the method names rather than the entire class and method in the syntax.
3.4.2 Methods Added to Range Class
By means of static import, the static methods “of()” and “to()” can be used, but they are very short and possibly cryptic methods for a range. Thus more expressive names are used that are equivalent to the methods “of()” and “to()” in the Range class.
3.4.2.1 Method range()
The method “range()” is equivalent to the method “of()” in that it creates an iterator from the beginning value and iterates by the index value to the closing value. But the closing value is excluded from the range. The closing value is the upper boundary of the range but is not included in the range.
3.4.2.2 Method series()
The method “series()” is equivalent to the method “to()” in that it creates an iterator from the beginning value and iterates by the index value to the closing value. But the closing value is included within the range. The closing value is the upper boundary of the range and is included in the range.
3.4.2.3 Class Source Code
The source code for both the “range()” method and “series()” method are simply to wrap the “of()” and “to()” methods, respectively.
public final class Range implements Iterable<Integer> {
//...
public static Range range(final int begin, final int close){
return Range.of(begin, close);
}//end range
public static Range range(final int begin, final int close, final int index){
return Range.of(begin, close, index);
}//end range
public static Range series(final int begin, final int close) {
return Range.to(begin, close+1);
}//end series
public static Range to(final int begin, final int close, final int index) {
return Range.to(begin, close+1, index);
}//end series
public boolean in(final int value) {
//...
}//end in
public Iterator<Integer> iterator() {
return new RangeIterator<>(this);
}//end iterator
}//end class Range
3.4.3 Examples of using “range()” and “series()”
The “range()” and “series()” static functions used with static import are used both in a for-loop, and in an if statement.
import static com.javacodegeeks.rangeiterator.Range.*;
//...
int[] myArray = new int[100];
//initialize array with integer index
for(Integer i : range(0,myArray.length)){
myArray[i] = i;
}//end for
int intVal = 3;
//check if even number from 0 to 100
if(series(0,100,2).in(intVal)){
System.out.printf(“Number: %d is even from 0 to 100!%n”,intVal);
}//end if
Both examples illustrate that using the range type in Java source code simplifies and makes more explicit the functionality. A traditional implementation of the if statement with the “series()” function is more cryptic and verbose.
//check if even number from 0 to 100 using series function
if(series(0,100,2).in(intVal)){
System.out.printf(“Number: %d is even from 0 to 100!%n”,intVal);
}//end if
//check if even number in traditional if statement syntax
if(intVal >= 0 && intVal <= 100 && ((intVal %2 ) == 0)){
System.out.printf(“Number: %d is even from 0 to 100!%n”,intVal); }//end if
A software developer must determine first what the relational sub-expressions signify, and then division by two for a remainder of zero. With the “series()” function of a range type the intention is much more clearly explicit.
4. Application
The range type added to Java via a class and iterator is best illustrated by example-a simple application traditionally implemented one way in Java, and now implemented using the new range type. From both a comparison and contrast can be made in the source code before and after objectively.
4.1 Primality Check
The simple application is a Boolean function in Java to test for primality. This is a common function [Wiki 2020] often implemented by students and given in computer science textbooks, and source code is online in Java related web pages and web sites.
The function “isPrime” determines if an integer number is prime, returning a Boolean true or false. The function checks if the number is divisible by two, and is less than two—if so, the number is not prime. If the number is two it is prime. Afterwards all odd number from three to the integer square root are checked by division to see if the remainder is zero. If so, the number is not prime. Otherwise, the number is prime.
The first function is implemented using a traditional for loop, and then using a for-each loop using the Range type both with static import and without.
4.1.1 Function “isPrime()” using For Loop
The function “isPrime()” uses the traditional, C-style for-loop with three sub-expressions.
public static boolean isPrime(final int number){
if (num <= 3) {
return (num > 1);
} else if ((num % 2) == 0 || (num % 3) == 0) {
return false;
}//end if
final int SQRT = (int) Math.sqrt(number) + 1;
for(int i=3 ; i < SQRT ; i++){
if(number % i == 0){
return false;
}//end if
}//end for
return true;
}//end isPrime
4.1.2 Function “isPrime()” using Range Type
The function “isPrime()” uses the explicit static method “of()” in the range class for the range of the for-loop.
public static boolean isPrime(final int number){
if (num <= 3) {
return (num > 1);
} else if ((num % 2) == 0 || (num % 3) == 0) {
return false;
}//end if
final int SQRT = (int) Math.sqrt(number) + 1;
for(Integer i : Range.of(3, SQRT, 2)) {
if(number % i == 0) {
return false;
}//end if
}//end for
return true;
}//end isPrime
4.1.3 Function “isPrime()” using Static Import
The function “isPrime()” uses the static import to use the “range()” method for the range of the for-loop.
public static boolean isPrime(final int number){
if (num <= 3) {
return (num > 1);
} else if ((num % 2) == 0 || (num % 3) == 0) {
return false;
}//end if
final int SQRT = (int) Math.sqrt(number) + 1;
for(Integer i : range(3, SQRT, 2)) {
if(number % i == 0) {
return false;
}//end if
}//end for
return true;
}//end isPrime
4.2 Function Implementation Comparisons
All three versions of the same “isPrime()” function essentially have the same number of lines of code, and code structure. Thus the range type does not change length or structure.
The primary difference is that the range type is more explicit and expressive in the functionality. At a casual glance, a software development engineer can see either a “Range.of” or a “range” is being used in determining if a number is prime.
Consider the for statement with each kind of syntax in proximity to each other:
for(int i=3 ; i < SQRT ; i++){ //traditional for loop
for(Integer i : Range.of(3, SQRT, 2)) { //Range “of()” for-each loop
for(Integer i : range(3, SQRT, 2)) { //static import range for-each loop
The for-each loop using a range type is not lesser in syntax size, but it is more explicit in what the parameters of the for-each loop are specifically.
The other difference is in concise, succinct syntax. The traditional for loop has three sub-expressions, and can potentially have an error, such as the infamous “off by one” error. With a range, the boundaries are explicit, and there are no sub-expressions that must be taken together to determine the for loop function.
Using the range type simplifies the for-loop used, is more concise, and simple. This makes the “isPrime()” function using the range time more expressive, simpler, and less error prone with parameters of the for-loop.
5. Conclusion
A range type adds power and expressivity to the Java language which is available in other programming languages like Python, Ada, and Rust. As illustrated, the range type in Java adds more compact, expressive source code. The feature of range type also allows Java to have a feature that language critics can decry as lacking.
A range type is a feature found in other non-Java languages such as Rust, Ada, and Python. The range type provides a feature that is useful in creating explicit but well-defined functionality in source code. There is a feature gap that the Java programming language lacks the feature of a range type.
Java is a popular programming language, but it also has the existing features and power to extend the language to add new features. This capability is demonstrated by adding a range type to the language without requiring a non-canonical extension or specialized pre-processor or transcompiler. The added feature is the sum of other features of the Java programming language.
The range type adds the power and expressivity to the Java programming language, allowing for iteration over the range, and also to test for inclusion or exclusion of the range. Thus Java is enriched overall as a programming language.
In the future, the Java programming language might add a range type into the Java language in the specification, but for now the feature is available through the implementation utilizing features of Java.
6. References
[Gamm 1994] Gamma, Erich, Helm, Richard, Johnson, Ralph, and Vlissides, John. “Design Patterns: Elements of Reusable Object-Oriented Software,” Addison Wesley, Reading, Massachusetts, 1994, pp. 257.
[Mozi 2019] Mozilla Corporation. Klabnik, Steve and Nichols, Carol, authors. The Rust Programming Language, No Starch Press, San Francisco, California, 2019, p. 57.
[Orac 2004] Oracle Corporation. “New Features and Enhancements J2SE 5.0,” 2004. https://docs.oracle.com/javase/1.5.0/docs/relnotes/features.html, Accessed July 14, 2020.
[Pras 2012] Prasanna, Dhanji R. “Languages, Verbosity, and Java,” informIT, January 5, 2012. http://www.informit.com/articles/article.aspx?p=1824790, Accessed February 6, 2020.
[PYna 2020] PYnative, “Python range() Function Explained with Examples,” January 1, 2020, https://pynative.com/python-range-function/, Accessed February 2, 2020.
[Ruby 2020] Ruby Documentation Project, “Range,” https://ruby-doc.org/core-2.5.1/Range.html Accessed February 23, 2020.
[Wiki 2017] Wikibooks, “Ada Programming,” July 31, 2017, https://en.wikibooks.org/wiki/Ada_Programming/Types/range, Accessed February 2, 2020.
[Wiki 2020] Wikipedia, “Primality Test,” June 20, 2020, https://en.wikipedia.org/wiki/Primality_test, Accessed July 13, 2020.
[ZetC 2020] ZetCode, “The History of Element Iteration in Java,” July 13, 2020. http://zetcode.com/articles/javaiterationhistory/ Accessed July 13, 2020.
7. Acknowledgments
The author gratefully and thankfully acknowledges the advice, help, comments, and suggestions by his colleague, friend Andrew Gauger with the Ruby and Rust examples in the article.
8. Download the Source Code
You can download the full source code of this article here: Adding Range Type in Java
This is a good helper/utility class which can be distributed in a utility library.
Apache commons-lang3 has
org.apache.commons.lang3.Range<T>
class which uses generics, supports Comparable, but does not support iteration.Using Java 8 lambdas and function references this implementation may be merged with Apache commons’s implementation to support both generics, comparators, an incrementor function, and iteration.
Thanks for this informative information, it really helps me to understand the range type in Java. Java is my second language, Python was first, and comparing both it really help me to grab the concept.