Introduction
A PrimitiveIterator in Java is an interface for the primitive specializations of an Iterator. In this post, we will learn about the PrimitiveIterator, its subclasses and its uses.
First, let us start with a brief overview of a Java Iterator. Second, we will look at the problems of using an Iterator when working with primitive values (int, long or double). Next, we will see how a PrimitiveIterator solves the problem.
Java Iterator - A brief look
We use an Iterator to iterate over a collection of values. The Iterator interface has type parameter E which denotes the type of the elements being iterated on. It has the below contract.
public interface Iterator<E> {
boolean hasNext();
E next();
default void remove() {
throw new UnsupportedOperationException("remove");
}
default void forEachRemaining(Consumer<? super E> action) {
Objects.requireNonNull(action);
while (hasNext())
action.accept(next());
}
}
It has two methods (abstract) and two default methods.
hasNext and next
Calling hasNext() returns a true if there are more items left to be iterated on and a false otherwise. The next() method returns the next item in the iteration. A simple iterator using these methods is shown below.
List<String> strings = List.of("Java", "Language", "List", "Iterator");
Iterator<String> iterator = strings.iterator();
while (iterator.hasNext()) {
System.out.println(iterator.next());
}
Prints,
Java
Language
List
Iterator
Note: I created the list by using the List.of static factory method add in Java 9 (See Convenience Factory Methods for Collections to learn more).
remove and forEachRemaining
The default implementation for the remove() method throws an UnsupportedOperationException. The forEachRemaining method takes a Consumer<? super E> as a parameter i.e., we can pass a consumer of a super class type. Example: When we have an iterator of Integers, we can pass a Consumer<Number> as Integer is a Number. It uses the hasNext() and the next() methods and passes the elements one-by-one to the consumer.
Iterator<String> iterator = strings.iterator();
iterator.forEachRemaining(System.out::println);
In the above example, we pass a Consumer (using method references) which accepts each element and prints it.
The problem with Iterators and primitives
Since an Iterator has a generic type parameter, we cannot (the Java language does not allow) put a primitive there (an int/long/double). It has to be one of the corresponding primitive wrapper classes viz., Integer, Long and Double. If the underlying data set is a primitive (say an array of integers, int[]), then when iterating over them using an Iterator will result in boxing i.e., the primitive value converted (boxed) to the wrapper type.
This can affect the performance when dealing with a large data set or when this is done very often, as this involves creating new objects.
public class MyArrayIterator implements Iterator<Integer> {
private int[] arr = new int[]{1, 2, 3, 4, 5};
private int i = 0;
public MyArrayIterator() {
i = 0;
}
@Override
public boolean hasNext() {
return i < arr.length;
}
@Override
public Integer next() {
return arr[i++];
}
}
The MyArrayIterator implements Iterator<Integer>. But the underlying data it stored as an int[]. Hence, when calling the next() method, the primitive value is boxed to the wrapper class type.
Prints,
1
2
3
4
5
Note: To measure how much the performance is affected when working with a lot of boxing operations, use JMH tool to benchmark the code.
PrimitiveIterator in Java
A PrimitiveIterator extends the Iterator interface. It has two type parameters T and T_CONS.
- T - It is the type of elements returned by the PrimitiveIterator. It is the wrapper type for the primitives (Integer/Long/Double).
- T_CONS - It is the type of primitive consumer. It is a primitive specialization of the consumer for T. It will be,
- IntConsumer for an Integer
- LongConsumer for a Long
- DoubleConsumer for a Double
The PrimitiveIterator interface has an overloaded forEachRemaining (non-default) (in addition to the one from the Iterator base interface). Its signature is,
void forEachRemaining(T_CONS action);
You might think what benefit does a PrimitiveIterator provide as it just has one additional method and it inherits the key methods hasNext() and next() from the base interface. The answer is in the three subclasses of the PrimitiveIterator.
Subclasses of PrimitiveIterator
A PrimitiveIterator has three public static nested interfaces - one for each primitive specialization. They are OfInt, OfLong and OfDouble. Let us look at OfInt interface first and the other two are very similar.
PrimitiveIterator.OfInt
The PrimitiveIterator.OfInt interface extends the type PrimitiveIterator<Integer, IntConsumer> i.e., it is a primitive iterator for an Integer where IntConsumer is the primitive consumer type (T_CONS) we saw before.
It has a method called nextInt() which returns a primitive int value. This is the key part.
public static interface OfInt extends PrimitiveIterator<Integer, IntConsumer> {
int nextInt();
}
Hence, when we iterate we shouldn’t call the next() method on the Iterator. Rather, we should call nextInt() and it returns the primitive integer without doing any boxing.
It also provides a default implementation of the forEachRemaining method on the PrimitiveIterator.
default void forEachRemaining(IntConsumer action) {
Objects.requireNonNull(action);
while (hasNext())
action.accept(nextInt());
}
It calls hasNext() and, if true, calls nextInt() and passes the element to the consumer.
PrimitiveIterator.OfInt example
Let us create a class which extends PrimitiveIterator.OfInt. We implement the hasNext() and the nextInt() .
public class MyIntPrimitiveIterator implements PrimitiveIterator.OfInt {
private final int[] arr = new int[]{1, 2, 3, 4, 5};
private int i = 0;
public MyIntPrimitiveIterator() {
i = 0;
}
@Override
public int nextInt() {
return arr[i++];
}
@Override
public boolean hasNext() {
return i < arr.length;
}
}
MyIntPrimitiveIterator myIntPrimitiveIterator = new MyIntPrimitiveIterator();
while (myIntPrimitiveIterator.hasNext()) {
System.out.println(myIntPrimitiveIterator.nextInt());
}
Prints,
1
2
3
4
5
Since the nextInt() returns a primitive int, no boxing is involved during the iteration.
Using forEachRemaining
MyIntPrimitiveIterator myIntPrimitiveIterator = new MyIntPrimitiveIterator();
IntConsumer intConsumer = System.out::println; //i -> System.out.println(i);
myIntPrimitiveIterator.forEachRemaining(intConsumer);
In the above code, we call the forEachRemaining method in the OfInt interface passing an IntConsumer.
Default implementation for next and forEachRemaining
Remember, the next() method on an Iterator had no default implementation. But we did not provide an implementation for it as well.
The OfInt interface provides a default implementation for the Iterator#next and Iterator#forEachRemaining method which takes a Consumer<? super E>.
The default implementation involves boxing. Hence, using it will offset the advantages of using a PrimitiveIterator. Thus, we should use nextInt() and forEachRemaining(IntConsumer) over next() and forEachRemaining(Consumer).
Tripwire
What if by mistake we call next() instead of nextInt(). There is a way to get warnings in the logs if we do that. Tripwire to the rescue!!
We can set a boolean system property called org.openjdk.java.util.stream.tripwire to true. If we set that, then it will print diagnostic warnings if primitive values are boxed when operating on primitive subtype specializations.
System.setProperty("org.openjdk.java.util.stream.tripwire", "true");
MyIntPrimitiveIterator myIntPrimitiveIterator = new MyIntPrimitiveIterator();
while (myIntPrimitiveIterator.hasNext()) {
System.out.println(myIntPrimitiveIterator.next());
}
We set the above mentioned system property to true. Then when iterating, we call next() method and this will involve boxing as it returns an Integer object. This prints the below warning for each element we iterate upon.
WARNING: com.javadevcentral.java8.primitiveiterator.MyIntPrimitiveIterator calling PrimitiveIterator.OfInt.nextInt()
where com.javadevcentral.java8.primitiveiterator is the Java package namespace and MyIntPrimitiveIterator is the class name.
The same warning gets trigger when calling the forEachRemaining method which takes a Consumer object as well.
MyIntPrimitiveIterator myIntPrimitiveIterator = new MyIntPrimitiveIterator();
Consumer<Integer> consumer = System.out::println;
myIntPrimitiveIterator.forEachRemaining(consumer);
WARNING: com.javadevcentral.java8.primitiveiterator.MyIntPrimitiveIterator calling PrimitiveIterator.OfInt.forEachRemainingInt(action::accept)
PrimitiveIterator.OfLong
Let us look into the PrimitiveIterator.OfLong interface. It is very similar to the OfInt, but for long values. Its contract is,
public static interface OfLong extends PrimitiveIterator<Long, LongConsumer> {
long nextLong();
default void forEachRemaining(LongConsumer action) {
Objects.requireNonNull(action);
while (hasNext())
action.accept(nextLong());
}
// has default implementation for next() and forEachRemaining(Consumer<? super Long>)
}
It extends the PrimitiveIterator interface and has the nextLong and forEachRemaining methods. The forEachRemaining takes a LongConsumer.
Let us create a class implementing PrimitiveIterator.OfLong.
public class MyLongPrimitiveIterator implements PrimitiveIterator.OfLong {
private final long[] arr = new long[]{1, 2, 3};
private int i;
public MyLongPrimitiveIterator() {
i = 0;
}
@Override
public long nextLong() {
return arr[i++];
}
@Override
public boolean hasNext() {
return i < arr.length;
}
}
Calling nextLong() and forEachRemaining(LongConsumer) methods are shown below. The tripwire system property setup will work for this as well.
PrimitiveIterator.OfDouble
Shown below is the contract of PrimitiveIterator.OfDouble. I’ll skip the rest of the explanation as it is very similar to what we’ve seen for OfInt and OfLong.
public static interface OfDouble extends PrimitiveIterator<Double, DoubleConsumer> {
double nextDouble();
default void forEachRemaining(DoubleConsumer action) {
Objects.requireNonNull(action);
while (hasNext())
action.accept(nextDouble());
}
//has default implementation for next() and forEachRemaining(Consumer<? super Double>)
}
MyLongPrimitiveIterator myLongPrimitiveIterator = new MyLongPrimitiveIterator();
while (myLongPrimitiveIterator.hasNext()) {
System.out.println(myLongPrimitiveIterator.nextLong());
}
System.out.println();
// using forEachRemaining
myLongPrimitiveIterator = new MyLongPrimitiveIterator();
LongConsumer longConsumer = System.out::println; //l -> System.out.println(l);
myLongPrimitiveIterator.forEachRemaining(longConsumer);
Prints,
1
2
3
1
2
3
Conclusion
In this post, we learnt about the PrimitiveIterator interface in Java and learnt what problem it solves. Hope it was useful.