Now that we have the nice new foreach
loop in Java, the old-style loop looks ugly be comparison.
I like the way Python has a range()
generator that allows the foreach construct to iterate over a range of integers.
I have written a Range
class which allows this. Please provide comments.
package highland.mark;
import java.util.Iterator;
/**
* A class to enable java's 'foreach' loop to accept a range.
* <p>
* This allows replacing the C style:
*
* <pre>
* <code>
* for (int i = 0; i < 10; i++) {...}
* </code>
* </pre>
*
* with:
*
* <pre>
* <code>
* for (int i : range(10)) {...}
* </code>
* </pre>
*
* Three versions of range() are provided allowing combinations of start, end,
* and step.
*
* @author Mark Thomas
* @version 1.0
*/
public final class Range implements Iterator<Integer>, Iterable<Integer> {
/**
* The next integer to be returned by the iterator.
*
*/
private int next;
/**
* The last integer to be returned will be (next - 1).
*/
private final int to;
/**
* The increment added to the value of next after each iteration.
*/
private final int step;
/**
* A Method to be used with the java 'foreach' loop.
* <p>
* Usage:
*
* <pre>
* <code>
* import static highland.mark.Range.range;
* ...
* for (int i : range(from, to, step)) {...}
* </code>
* </pre>
*
* @param from
* : int, first value returned.
* @param to
* : int, one more than last value returned.
* @param step
* : int, increment for each iteration (may be negative so long
* as <code>(to < step)</code>.
* @return An Iterable<Integer> which supplies an Iterator<Integer> which,
* on each iteration, returns integers from <code>from</code> to
* <code>(to - 1)</code> incrementing by <code>step</code> each
* time.
* @throws IllegalArgumentException
* if <code>step == 0</code> or <code>step</code> is the wrong
* sign.
*/
public static Iterable<Integer> range(int from, int to, int step)
throws IllegalArgumentException {
return new Range(from, to, step);
}
/**
* A Method to be used with the java 'foreach' loop.
* <p>
* Usage:
*
* <pre>
* <code>
* import static highland.mark.Range.range;
* ...
* for (int i : range(from, to)) {...}
* </code>
* </pre>
*
* @param from
* : int, first value returned.
* @param to
* : int, one more than last value returned.
* @return An Iterable<Integer> which supplies an Iterator<Integer> which,
* on each iteration, returns integers from <code>from</code> to
* <code>(to - 1)</code> incrementing by 1 each time.
* @throws IllegalArgumentException
* if <code>(to < from)<code>.
*/
public static Iterable<Integer> range(int from, int to)
throws IllegalArgumentException {
return Range.range(from, to, 1);
}
/**
* A Method to be used with the java 'foreach' loop.
* <p>
* Usage:
*
* <pre>
* <code>
* import static highland.mark.Range.range;
* ...
* for (int i : range(to)) {...}
* </code>
* </pre>
*
* @param to
* : int, one more than last value returned.
* @return An Iterable<Integer> which supplies an Iterator<Integer> which,
* on each iteration, returns integers from 0 to
* <code>(to - 1)</code> incrementing by 1 each time.
*/
public static Iterable<Integer> range(int to) {
return Range.range(0, to, 1);
}
/*
* (non-Javadoc) private constructor only used by the static range()
* methods.
*
* @param from : int, first value returned.
*
* @param to : int, one more than last value returned.
*
* @param step : int, increment for each iteration (may be negative so long
* as (to < step).
*
* @throws IllegalArgumentException if step == 0 or step is the wrong sign.
*/
private Range(int from, int to, int step) throws IllegalArgumentException {
if (step == 0) {
throw new IllegalArgumentException(
"The step argument cannot be zero");
}
if ((to - from) / step < 0) {
throw new IllegalArgumentException(
"The step argument has the wrong sign");
}
this.next = from;
this.to = to;
this.step = step;
}
/*
* (non-Javadoc)
*
* @see java.lang.Iterable#iterator()
*/
@Override
public Iterator<Integer> iterator() {
return this;
}
/*
* (non-Javadoc)
*
* @see java.util.Iterator#hasNext()
*/
@Override
public boolean hasNext() {
return this.step < 0 ? this.to < this.next : this.next < this.to;
}
/*
* (non-Javadoc)
*
* @see java.util.Iterator#next()
*/
@Override
public Integer next() {
int value = this.next;
this.next += this.step;
return value;
}
/*
* (non-Javadoc)
*
* @see java.util.Iterator#remove()
*/
@Override
public void remove() throws UnsupportedOperationException {
throw new UnsupportedOperationException(
"The iterator returned from range() does not support remove()");
}
}
Test code (testng):
package highland.mark;
import static highland.mark.Range.range;
import static org.testng.Assert.assertEquals;
import org.testng.annotations.Test;
public class RangeTest {
@Test
public void testRangeFullySpecified() {
String result = "";
for (int i : range(2, 19, 3)) {
result += i + " ";
}
assertEquals(result, "2 5 8 11 14 17 ");
}
@Test
public void testRangeBackWards() {
String result = "";
for (int i : range(19, 2, -3)) {
result += i + " ";
}
assertEquals(result, "19 16 13 10 7 4 ");
}
@Test
public void testRangeDefaultStep() {
String result = "";
for (int i : range(2, 9)) {
result += i + " ";
}
assertEquals(result, "2 3 4 5 6 7 8 ");
}
@Test
public void testRangeDefaultStepAndStart() {
String result = "";
for (int i : range(7)) {
result += i + " ";
}
assertEquals(result, "0 1 2 3 4 5 6 ");
}
@Test(expectedExceptions=IllegalArgumentException.class)
public void testwrongWay1() {
for (@SuppressWarnings("unused") int i : range(2, 19, -3)) {
// No-op;
}
}
@Test(expectedExceptions=IllegalArgumentException.class)
public void testwrongWay2() {
for (@SuppressWarnings("unused") int i : range(2, -19, 3)) {
// No-op;
}
}
@Test(expectedExceptions=IllegalArgumentException.class)
public void testZeroStep1() {
for (@SuppressWarnings("unused") int i : range(2, 19, 0)) {
// No-op;
}
}
@Test(expectedExceptions=IllegalArgumentException.class)
public void testZeroStep2() {
for (@SuppressWarnings("unused") int i : range(2, -19, 0)) {
// No-op;
}
}
}
testFoo
when you use the@Test
annotation. Also, given that all tests are for theRange
class, I'd remove it from their names. \$\endgroup\$