beirtipol
beirtipol

Reputation: 863

Java Streams toArray with primitives

Speaking to people about this around the office made me realise that without the context of how I got here, their response is always "You're doing it wrong", so here's the context:

I've got to deal with crappy objects created by another team using ICE (https://doc.zeroc.com/display/Ice37/Ice+Overview) and wanted to write a utility for the boiler plate code I've been stuck writing.

One of the objects I've got to deal with is a container for floats and strings. I just want the float values.

public class FloatWrapper {
    public String   StringValue;
    public float    FloatValue;

    public FloatWrapper(float f) {
        this.FloatValue = f;
    }

    public FloatWrapper(String s) {
        this.StringValue = s;
    }

    public FloatWrapper(float f, String s) {
        this.FloatValue = f;
        this.StringValue = s;
    }
}

This container is delivered to me in a 2D array so I want a float[][]. Let's start with 1 dimension

FloatWrapper[] wrappers = new FloatWrapper[] { 
    new FloatWrapper(1.0f), 
    new FloatWrapper(2.0f) 
};

Float[] asFloats = Arrays.stream(wrappers)
    .map(f -> f.FloatValue)
    .toArray(Float[]::new);

That was easy. Let's try a 2D array.

// Make a lovely method for what I have above
public Float[] convert1Darray(FloatWrapper[] wrappers) {
    return Arrays.stream(wrappers)
            .map(f -> f.FloatValue)
            .toArray(Float[]::new);
}

public static Float[][] convert2Darray(FloatWrapper[][] wrappers) {
    return Arrays.stream(wrappers)
            .map(f -> convert1Darray(f))
            .toArray(Float[][]::new);
}

...

// Create a 2D array
FloatWrapper[][] wrappers2d = new FloatWrapper[][] { 
    { new FloatWrapper(1.0f), new FloatWrapper(2.0f) }, 
    { new FloatWrapper(3.0f), new FloatWrapper(4.0f) }
};

Float[][] floats2d = convert2Darray(wrappers2d);

Boom!

It'd be nice to have primitive floats (it just would, ok!) The wizened among you can see where I'm going...

Turns out you can't use primitive floats very easily in the 'toArray' call. Apache commons will sort that

public static float[] convert1DprimitiveArray(FloatWrapper[] wrappers) {
    return ArrayUtils.toPrimitive(
            Arrays.stream(wrappers)
                    .map(f -> f.FloatValue)
                    .toArray(Float[]::new));
}

public static float[][] convert2DprimitiveArray(FloatWrapper[][] wrappers) {
    return Arrays.stream(wrappers)
            .map(f -> convert1DprimitiveArray(f))
            .toArray(float[][]::new);
}

.... wait a minute. Why can I use 'toArray(float[][]::new)' but not 'toArray(float[]::new)' ?

The argument floating around the office right now is that the 'float[]::new' call is illegal because you can't call 'new' on a primitive.

But that's not what it's doing. That's just shorthand for the IntFunction

Let's break it down

This:

Arrays.stream(wrappers1d).map(f -> f.FloatValue).toArray(Float[]::new);

is the equivalent of this:

Arrays.stream(wrappers1d).map(f -> f.FloatValue).toArray(new IntFunction<Float[]>() {
    @Override
    public Float[] apply(int size) {
        return new Float[size];
    }
});

So, is it that you can't create something of type IntFuntion ??

Nope. Totally allowed:

IntFunction<float[]> happyToCompile = new IntFunction<float[]>() {
    @Override
    public float[] apply(int size) {
        return new float[size];
    }
};

Executing that function leaves you with the equivalent of

new float[]{ 0.0f, 0.0f };

So, my half-educated guess is that there's some type inference going on from the IntFunction which works when you have a 2d array but not a 1d array. But, 'could' it work?

Upvotes: 11

Views: 5497

Answers (3)

Lino
Lino

Reputation: 19910

That error occurs, because there exists no wrapping stream for float. So you can't work with FloatStream or something similar.

So when you analyse what Stream.toArray() really does you get something like this:

Return an array T[] which is holding every object of type T in this stream

The problem comes with the T because you have a stream containing floats, stored in their WrapperClass Float (because primitives cannot be used with generics).

Now when you try to convert a Float to a float, java does it for you with auto-unboxing. The bad thing is, it does not the same for arrays.

In java an array of a primitives can not directly be converted to it's specific Wrapper type.

So Float[] floats = new float[10]; gives a compile error.

Now you ask yourself: Why does float[][] work then?. Well, if you look at it then you see that float[] is an Object and a stream of float-arrays is valid, because they can be referenced as an Object. Thats why float[][]::new works. It matches the description from earlier: Return an array T[] which is holding every object of type T in this stream because float[]actually is of type T and an T[] is then valid too.

Upvotes: 2

Alexis C.
Alexis C.

Reputation: 93902

Why can I use 'toArray(float[][]::new)' but not 'toArray(float[]::new)' ?

As you said, Arrays.stream(wrappers).map(f -> f.FloatValue) is a Stream<Float> so toArray(float[]::new) does not work as you are required to provide an IntFunction<Float[]>, not an IntFunction<float[]>.

On the other hand, Arrays.stream(wrappers).map(f -> convert1DprimitiveArray(f)) is a Stream<float[]> so toArray(IntFunction<A[]>) requires an IntFunction<float[][]> which is what float[][]::new is for.

.toArray(float[]::new) would work if you had a Stream<float>, which is not possible since you can't have a primitive type as a generic parameter.

Upvotes: 7

daniu
daniu

Reputation: 15028

Why can I use 'toArray(float[][]::new)' but not 'toArray(float[]::new)

Because after the map call you have a stream of arrays, and to create an array of the stream, you need to create an array of arrays, not of floats.

To get the primitive type, you'd have to transform into a DoubleStream first.

Arrays.stream(wrappers).mapToDouble(FloatWrapper::getFloat)

which gives you a stream of primitives; those you should be able to make float-arrays of with some additional magic.

Upvotes: 2

Related Questions