Working with java 8 streams in JRuby


#1

The following java code:

public static void main (String[] args) {
List numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8);
List twoEvenSquares =
numbers.stream()
.filter(n -> {
System.out.println("filtering " + n);
return n % 2 == 0;
})
.map(n -> {
System.out.println("mapping " + n);
return n * n;
})
.limit(2)
.collect(() -> new ArrayList<>(),
(c, e) -> c.add(e),
(c1, c2) -> c1.addAll(c2));
System.out.println("Output: " + twoEvenSquares);
}

yields:

filtering 1
filtering 2
mapping 2
filtering 3
filtering 4
mapping 4
Output: [4, 16]

The ostensibly equivalent JRuby code:

numbers = java.util.Arrays.asList(1,2,3,4,5,6,7,8)

filter_lamb = ->(n) {
puts “filtering #{n}”
n % 2 == 0
}
map_lamb = ->(n) {
puts “mapping #{n}”
n*n
}
l1 = -> { java.util.ArrayList.new }
l2 = -> (c,e) { c.add(e) }
l3 = -> (c1,c2) { c1.addAll(c2)}

two_even_squares =
numbers.stream().filter(filter_lamb).map(map_lamb).limit(2).collect(l1,l2,l3)

bombs out with:

ArgumentError: wrong number of arguments (1 for 2)

in the collect call.

What am I doing wrong?

Thanks,

Cris


#2

I can not really recreate the issue, because I am using Java 1.7 (you
are doing it with Java 1.8, aren’t you?), and it seams that 1.7 doesn’t
have the method ‘stream’ yet, because I get the error message

NoMethodError: undefined method `stream’ for
#Java::JavaUtil::Arrays::ArrayList:0x3534ef8c

However, I see a few other suspicious points in your code:

  • You are applying the method ‘limit’ to the result of ‘map’. I am not
    aware of the existence of this method, and could not find it in the core
    docs. Could it be that you mean ‘take’ instead?

  • The error message says

    “wrong number of arguments (1 for 2)”

which means “you supplied to 1 argument, but two where expected”. Your
call to collect looks like

collect(l1,l2,l3)

which passes 3 arguments. The Java version of ‘collect’ expects 3
arguments, but from the error message, we conclude not only, that a
different ‘collect’ is executed from what you expect, but it is also
weird that a different number of arguments is reported. So I ask my
self: Can you verify, that this is really THIS collect call, which is in
error, and not a different one in some other part of your program? Can
you reproduce this error with the code given, in jirb?

If yes, maybe you could print out the class name of the result of your
limits() call, to see whether this is really the class you are
expecting.


#3

Correction to my previous post - there are two remarks in my post, which
are silly, so please ignore them.

(1) Of course you are working with Java 1.8. The title of your post says
it.

(2) You are not using the Ruby method Array.limit, so my remark about
limit doesn’t make sense.

But I think, my remark about the number of arguments still applies.


#4

Ronald,

These new stream apis are only found in java 8.

Consider the following irb:

irb(main):001:0> JRUBY_VERSION
=> “9.0.4.0”
irb(main):002:0> numbers = java.util.Arrays.asList(1,2,3,4,5,6,7,8)
=> #Java::JavaUtil::Arrays::ArrayList:0x2d3379b4
irb(main):003:0>
irb(main):004:0* filter_lamb = ->(n) {
irb(main):005:1* puts “filtering #{n}”
irb(main):006:1> n % 2 == 0
irb(main):007:1> }
=> #<Proc:0x5e0e82ae@(irb):4 (lambda)>
irb(main):008:0> map_lamb = ->(n) {
irb(main):009:1* puts “mapping #{n}”
irb(main):010:1> n*n
irb(main):011:1> }
=> #<Proc:0x51399530@(irb):8 (lambda)>
irb(main):012:0> l1 = -> { java.util.ArrayList.new }
=> #<Proc:0x6b2ea799@(irb):12 (lambda)>
irb(main):013:0> l2 = -> (c,e) { c.add(e) }
=> #<Proc:0x411f53a0@(irb):13 (lambda)>
irb(main):014:0> l3 = -> (c1,c2) { c1.addAll(c2)}
=> #<Proc:0x2b71e916@(irb):14 (lambda)>
irb(main):015:0> two_even_squares
=numbers.stream().filter(filter_lamb).map(map_lamb).limit(2)
=> #Java::JavaUtilStream::SliceOps::1:0x702657cc
irb(main):016:0>
irb(main):017:0>
two_even_squares.java_class.java_instance_methods.map(&:to_s).reject do
|e| e !~ /.collect./ end
=> [“public final java.lang.Object
java.util.stream.ReferencePipeline.collect(java.util.stream.Collector)”,
“public final java.lang.Object
java.util.stream.ReferencePipeline.collect(java.util.function.Supplier,java.util.function.BiConsumer,java.util.function.Bi
Consumer)”]
irb(main):018:0> two_even_squares.java_class
=> class java.util.stream.SliceOps$1
irb(main):024:0> two_even_squares.java_send :collect,
[java.util.function.Supplier,java.util.function.BiConsumer,java.util.function.BiConsumer],
l1, l2, l3
ArgumentError: wrong number of arguments (1 for 2)
from org/jruby/java/proxies/JavaProxy.java:352:in java_send' from (irb):24:in
from org/jruby/RubyKernel.java:978:in eval' from org/jruby/RubyKernel.java:1291:inloop’
from org/jruby/RubyKernel.java:1098:in catch' from org/jruby/RubyKernel.java:1098:incatch’
from C:/languages/jruby/jruby-9.0.4.0/bin/jirb:13:in `’

Cris


#5

Cris S. wrote in post #1179416:

irb(main):018:0> two_even_squares.java_class
=> class java.util.stream.SliceOps$1
irb(main):024:0> two_even_squares.java_send :collect,

[java.util.function.Supplier,java.util.function.BiConsumer,java.util.function.BiConsumer],

l1, l2, l3
ArgumentError: wrong number of arguments (1 for 2)
from org/jruby/java/proxies/JavaProxy.java:352:in java_send' from (irb):24:in
from org/jruby/RubyKernel.java:978:in eval' from org/jruby/RubyKernel.java:1291:inloop’
from org/jruby/RubyKernel.java:1098:in catch' from org/jruby/RubyKernel.java:1098:incatch’
from C:/languages/jruby/jruby-9.0.4.0/bin/jirb:13:in `’

OK, so the problem is not the invocation of ‘collect’, but something
which happens inside ‘collect’, which means that, if the error is in
your code, it must be in one of your lambda definitions. Now this is an
area I don’t know much about, and I don’t know enought about the Java
code, but the error message says that two arguments are passed to a
method, where only one is expected.

In your code, I can’t see any function where you explicitly pass two
arguments, so just a wild guess:

‘collect’ is defined on the Java side, expecting Java-Lambdas, and you
are passing it Ruby-Lambdas. Could it be, that this conversion doesn’t
work right yet in JRuby?

What happens, if you directly execute

number.stream.collect(…)

? If this brings up the same error message, I suspect that this is a bug
in JRuby and you could open a bug report for it.


#6

https://github.com/jruby/jruby/issues/3475