glf4k
glf4k

Reputation: 449

How to map and collect primitive return type using Java 8 Stream

I am new in Java 8 streams and I am wondering if there is way to perform forEach/map call on method returning a byte and accepting an int as parameter.

Example:

public class Test {
   private byte[] byteArray; // example of byte array

   public byte getByte(int index) {
      return this.byteArray[index];
   }

   public byte[] getBytes(int... indexes) {
      return Stream.of(indexes)
             .map(this::getByte) // should return byte
             .collect(byte[]::new); // should return byte[]
   }
}

As you may guess, the getBytes method not working. "int[] cannot be converted to int"Probably somewhere is missing foreach, but personally cant figure it out.

However, this is working, old-fashioned approach which I would like to rewrite as Stream.

byte[] byteArray = new byte[indexes.length];
for ( int i = 0; i < byteArray.length; i++ ) {
   byteArray[i] = this.getByte( indexes[i] );
}
return byteArray;

Upvotes: 6

Views: 3746

Answers (3)

Flown
Flown

Reputation: 11740

You can write your own Collector and build your byte[] with an ByteArrayOutputStream:

final class MyCollectors {

  private MyCollectors() {}

  public static Collector<Byte, ?, byte[]> toByteArray() {
    return Collector.of(ByteArrayOutputStream::new, ByteArrayOutputStream::write, (baos1, baos2) -> {
      try {
        baos2.writeTo(baos1);
        return baos1;
      } catch (IOException e) {
        throw new UncheckedIOException(e);
      }
    }, ByteArrayOutputStream::toByteArray);
  }
}

And use it:

public byte[] getBytes(int... indexes) {
  return IntStream.of(indexes).mapToObj(this::getByte).collect(MyCollectors.toByteArray());
}

Upvotes: 8

Donald Raab
Donald Raab

Reputation: 6686

If you are open to using a third-party library, Eclipse Collections has collections support for all eight Java primitive types. The following should work:

public byte[] getBytes(int... indexes) {
    return IntLists.mutable.with(indexes)
            .asLazy()
            .collectByte(this::getByte)
            .toArray();
}

Updated: I changed the original code to be lazy.

Note: I am a committer for Eclipse Collections

Upvotes: 5

Sean Van Gorder
Sean Van Gorder

Reputation: 3453

There's no good way to do this with streams. Any implementation using collect is going to rely on appending elements, which gets really ugly for arrays. This is as close as you're going to get:

int[] ints = IntStream.of(indexes)
        .map(this::getByte) // upcast to int, still IntStream
        .toArray(); // specialized collect

The IntStream.toArray method has a ton of overhead involving internal "node" objects and array concatenation, so this is also much less efficient. I recommend sticking with the old for loop.

Upvotes: 4

Related Questions