This is a talk from Devoxx 2011.
Java developers wear a lot of hats these days: they manage builds, develop applications, write command-line scripts, and must master all tiers. If only there were a way to make these tasks simple and fun.
Enter JRuby.
Build engineers can write or enhance builds with Ruby, never losing a thing they depend on from Ant or Maven. Ruby offers several elegant testing options that work great with JRuby. Web developers can create Rails applications in minutes, effortlessly incorporating the latest Web technologies while taking advantage of the existing Java libraries. JRuby supports binding native libraries with FFI (foreign function interface). Command-line scripts? They're easy with JRuby's system-level features.
Come to this session to learn how JRuby makes you a happy developer.
10. Quick Tour of
Ruby
A brief overview of the language
Wednesday, November 16, 11
11. Classes
public class Circle extends Shape { class Circle < Shape
private final int radius; def initialize(radius)
@radius = radius
public Circle(int radius) { end
this.radius = radius;
} attr_reader :radius
public int getRadius() {
return radius; def area
} Math::PI*(@radius **
public double getArea() { 2)
return Math.PI * Math.pow(radius,2); end
} end
public static void main(String[] a) {
double area=new Circle(2).getArea(); puts Circle.new(2).area
System.out.println(area);
}
}
Wednesday, November 16, 11
12. Single Inheritance
Same Thing
public class Circle extends Shape { class Circle < Shape
private final int radius; def initialize(radius)
@radius = radius
public Circle(int radius) { end
this.radius = radius;
} attr_reader :radius
public int getRadius() {
return radius; def area
} Math::PI*(@radius **
public double getArea() { 2)
return Math.PI * Math.pow(radius,2); end
} end
public static void main(String[] a) {
double area=new Circle(2).getArea(); puts Circle.new(2).area
System.out.println(area);
}
}
Wednesday, November 16, 11
13. Constructor
public class Circle extends Shape { class Circle < Shape
private final int radius; def initialize(radius)
@radius = radius
public Circle(int radius) { end
this.radius = radius;
} attr_reader :radius
public int getRadius() {
return radius; def area
} Math::PI*(@radius **
public double getArea() { 2)
return Math.PI * Math.pow(radius,2); end
} end
public static void main(String[] a) {
double area=new Circle(2).getArea(); puts Circle.new(2).area
System.out.println(area);
}
}
Wednesday, November 16, 11
14. No Type Declarations
public class Circle extends Shape { class Circle < Shape
private final int radius; def initialize(radius)
@radius = radius
public Circle(int radius) { end
this.radius = radius;
} attr_reader :radius
public int getRadius() {
return radius; def area
} Math::PI*(@radius **
public double getArea() { 2)
return Math.PI * Math.pow(radius,2); end
} end
public static void main(String[] a) {
double area=new Circle(2).getArea(); puts Circle.new(2).area
System.out.println(area);
}
}
Wednesday, November 16, 11
15. Instance Variables
‘@’ == ‘this.’ and is mandatory
public class Circle extends Shape { class Circle < Shape
private final int radius; def initialize(radius)
@radius = radius
public Circle(int radius) { end
this.radius = radius;
} attr_reader :radius
public int getRadius() {
return radius; def area
} Math::PI*(@radius **
public double getArea() { 2)
return Math.PI * Math.pow(radius,2); end
} end
public static void main(String[] a) {
double area=new Circle(2).getArea(); puts Circle.new(2).area
System.out.println(area);
}
}
Wednesday, November 16, 11
16. Working with Instance Variables
Common tasks are easy
public class Circle extends Shape { class Circle < Shape
private final int radius; def initialize(radius)
@radius = radius
public Circle(int radius) { end
this.radius = radius;
} attr_reader :radius
public int getRadius() {
return radius; def area
} Math::PI*(@radius **
public double getArea() { 2)
return Math.PI * Math.pow(radius,2); end
} end
public static void main(String[] a) {
double area=new Circle(2).getArea(); puts Circle.new(2).area
System.out.println(area);
}
}
Wednesday, November 16, 11
17. Symbols
:identifier
• Prefixed with a colon
• Uniquely defined in a runtime
Wednesday, November 16, 11
18. Point of No Return
Last Expression is always return value
public class Circle extends Shape { class Circle < Shape
private final int radius; def initialize(radius)
@radius = radius
public Circle(int radius) { end
this.radius = radius;
} attr_reader :radius
public int getRadius() {
return radius; def area
} Math::PI*(@radius **
public double getArea() { 2)
return Math.PI * Math.pow(radius,2); end
} end
public static void main(String[] a) {
double area=new Circle(2).getArea(); puts Circle.new(2).area
System.out.println(area);
}
}
Wednesday, November 16, 11
19. .new is just a class method
public class Circle extends Shape { class Circle < Shape
private final int radius; def initialize(radius)
@radius = radius
public Circle(int radius) { end
this.radius = radius;
} attr_reader :radius
public int getRadius() {
return radius; def area
} Math::PI*(@radius **
public double getArea() { 2)
return Math.PI * Math.pow(radius,2); end
} end
public static void main(String[] a) {
double area=new Circle(2).getArea(); puts Circle.new(2).area
System.out.println(area);
}
}
Wednesday, November 16, 11
20. Blocks/Closures
• Pass an anonymous function to a method call
# Look mom...no boilerplate!
my_open(file) do |io| letters.group_by {|b| b.zip_code }
first_line = io.gets
# ...
end
def my_open(file, mode='r')
io = File.open(file)
yield io button.action_performed do
ensure exit
io.close end
end
Wednesday, November 16, 11
21. Modules
module Enumerable class MyTree
def find(if_none = nil) include Enumerable
each { |o| return o if yield(o) }
if_none # tree impl not shown :)
end
def each
# Many other methods not shown # yield each element
# in tree
def collect end
ary = [] end
each { |o| ary << yield(o) }
ary
end
end
dude = people.find { |person| person.id == 123 }
floats = [1,2].collect { |int| int.to_f } # => [1.0, 2.0]
Wednesday, November 16, 11
22. Everything is an Expression
class Color
COLORS = {:red => 0xff0000, :green => 0x00ff00, :blue => 0x0000ff}
COLORS.each do |name, value|
define_method(name) do
value
end
end
end
Wednesday, November 16, 11
23. Classes/Modules are open
class MyTree
def each
#...
end
end
#... later in source
#... maybe even different file
class MyTree
def debug
#...
end
end
Wednesday, November 16, 11
24. Pure OO Language
• Everything is an object
MyTree.foo #=> method call
12 + 8 #=> here, '+' is a method on Fixnum, 12.
12.to_s(8) #=> "14"
puts 11.odd? #=> true
Wednesday, November 16, 11
25. JRuby on Rails
Ruby's Killer app
Wednesday, November 16, 11
29. Generate Rails app
$ jruby -S rails new awesomeapp
create
⋮
create vendor/plugins
create vendor/plugins/.gitkeep
run bundle install
Fetching source index for http://rubygems.org/
Using rake (0.9.2.2)
⋮
Using json (1.6.1)
Using rdoc (3.11)
Using thor (0.14.6)
Using railties (3.1.1)
Installing coffee-rails (3.1.1)
Installing jquery-rails (1.0.16)
Installing jruby-openssl (0.7.4)
Using rails (3.1.1)
Installing sass (3.1.10)
Installing sass-rails (3.1.4)
Installing uglifier (1.0.4)
Your bundle is complete! Use `bundle show [gemname]` to see
where a bundled gem is installed.
Wednesday, November 16, 11
32. Start Server
$ jruby -S rails server
=> Booting WEBrick
=> Rails 3.1.1 application starting in development on http://
0.0.0.0:3000
=> Call with -d to detach
=> Ctrl-C to shutdown server
[2011-11-07 00:26:30] INFO WEBrick 1.3.1
[2011-11-07 00:26:30] INFO ruby 1.8.7 (2011-11-05) [java]
[2011-11-07 00:26:30] INFO WEBrick::HTTPServer#start: pid=40454
port=3000
Wednesday, November 16, 11
34. app/controllers/people_controller.rb
class PeopleController < ApplicationController
# GET /people
# GET /people.json
def index
@people = Person.all
respond_to do |format|
format.html # index.html.erb
format.json { render :json => @people }
end
end
⋮
end
Wednesday, November 16, 11
35. JSON out of the box
Wednesday, November 16, 11
36. app/models/person.rb
class Person < ActiveRecord::Base
end
Wednesday, November 16, 11
47. DeepThought.java
class DeepThought {
public static int answerToEverything(Object obj) {
return 42;
}
public static String getName() {
return "DeepThought";
}
}
Wednesday, November 16, 11
48. RSpec
require 'rspec'
require 'java'
java_import 'DeepThought'
describe 'DeepThought' do
describe '.answerToEverything' do
it "should return 42, given any input" do
DeepThought.answerToEverything(nil).should == 42
DeepThought.answerToEverything(true).should == 42
end
end
describe '.getName' do
it "should return 'DeepThought'" do
DeepThought.name.should == "DeepThought"
end
end
end
Wednesday, November 16, 11
50. Cucumber
Feature: AnswerToEverything
Ask DeepThought the answer to everything
Scenario: Ask a question
Given nil as input
Then the result should be 42
Feature: getName
Ask DeepThought what its name is
Scenario: get name
When asked the name
Then it answers the name as "DeepThought"
Wednesday, November 16, 11
51. Cucumber
$ jruby -S cucumber
Feature: AnswerToEverything
Ask DeepThought the answer to everything
Scenario: Ask a question # features/answer.feature:4
Given nil as input # features/step_definitions…
Then the result should be 42 # features/step_definitions…
Feature: getName
Ask DeepThought what its name is
Scenario: get name # features/…
When asked the name # features/…
Then it answers the name as "DeepThought" # features/…
2 scenarios (2 passed)
4 steps (4 passed)
0m0.065s
Wednesday, November 16, 11
52. JTestr
• Bridge for Java to make use of Ruby testing
• Integrates with both Ant and Maven
• Allows mocking of non-final Java class/method
• Written by Ola Bini
Wednesday, November 16, 11
53. JTestr Example
my_project/
build.xml!
test/
test_hashmap.rb
hashmap_spec.rb
lib/
jtestr.jar
src/
<your source ;) >
Wednesday, November 16, 11
54. JTestr in Ant
<?xml version="1.0" encoding="UTF-8"?>
<project basedir="." default="jar" name="JRuby">
<target name="test" description="Runs all tests">
<taskdef name="jtestr"
classname="org.jtestr.ant.JtestRAntRunner"
classpath="lib/jtestr.jar"/>
<jtestr/>
</target>
</project>
Wednesday, November 16, 11
55. Ruby’s test/unit
test/test_hashmap.rb
class HashMapTests < Test::Unit::TestCase
def setup
@map = java.util.HashMap.new
end
def test_that_an_entry_can_be_added
@map.put "foo", "bar"
assert_equal "bar", @map.get("foo")
end
def test_empty_key_set_iterator_throws_exception
assert_raises(java.util.NoSuchElementException) do
@map.key_set.iterator.next
end
end
end
Wednesday, November 16, 11
56. RSpec revisited
test/hashmap_spec.rb
require 'java'
java_import java.util.HashMap
describe "An empty", HashMap do
before :each do
@hash_map = java.util.HashMap.new
end
it "should be able to add an entry to it" do
@hash_map.put "foo", "bar"
@hash_map.get("foo").should == "bar"
end
it "should not be empty after an entry has been added to it" do
@hash_map.put "foo", "bar"
@hash_map.should_not be_empty
end
it "should be empty" do
@hash_map.should be_empty
end
end
Wednesday, November 16, 11
57. Results
$ ant test
Buildfile: /Users/asari/Development/projects/jtestr-example/
build.xml
test:
[jtestr] Other TestUnit: 2 tests, 0 failures, 0 errors
[jtestr]
[jtestr] Other Spec: 3 examples, 0 failures, 0 errors
[jtestr]
[jtestr] Total: 5 tests, 0 failures, 0 errors, 0 pending
[jtestr]
BUILD SUCCESSFUL
Total time: 7 seconds
Wednesday, November 16, 11
58. Ant-Rake
Integration
Wednesday, November 16, 11
59. Problem…
NUL
Windows
dev.null
/dev/null dev_null
Un*x everything else
Wednesday, November 16, 11
81. JRuby via Maven
• Group ID: org.jruby
• Artifact IDs: jruby, jruby-complete
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>3.8.1</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.jruby</groupId>
<artifactId>jruby</artifactId>
<version>1.6.5</version>
</dependency>
</dependencies>
Wednesday, November 16, 11
82. Maven artifacts as gems
jruby -S gem install mvn:org.clojure:clojure
maven
Wednesday, November 16, 11
83. Maven artifacts as gems
jruby -S gem install mvn:org.clojure:clojure
group ID
Wednesday, November 16, 11
84. Maven artifacts as gems
jruby -S gem install mvn:org.clojure:clojure
artifact ID
Wednesday, November 16, 11
85. Maven artifacts as gems
jruby -S gem install mvn:org.clojure:clojure
Successfully installed mvn:org.clojure:clojure-1.4.0.a.2-java
1 gem installed
Some restrictions apply
Wednesday, November 16, 11
86. Maven artifacts as gems
$ jruby -S irb
irb(main):001:0> require 'mvn:org.clojure:clojure'
=> true
irb(main):002:0> java_import Java::clojure.lang.PersistentHashMap
=> Java::ClojureLang::PersistentHashMap
irb(main):003:0> phm = PersistentHashMap.create({:a => 100, :b =>
200, :c => 300})
=> {:c=>300, :a=>100, :b=>200}
irb(main):004:0> phm.delete_if {|k,v| k == :b}
Java::JavaLang::UnsupportedOperationException:
from clojure.lang.APersistentMap.remove(APersistentMap.java:273)
from org.jruby.java.proxies.MapJavaProxy
$RubyHashMap.internalDelete(MapJavaProxy.java:154)
from org.jruby.RubyHash.delete(RubyHash.java:1407)
from org.jruby.RubyHash$22.visit(RubyHash.java:1464)
from org.jruby.java.proxies.MapJavaProxy
$RubyHashMap.visitAll(MapJavaProxy.java:182)
⋮
Wednesday, November 16, 11
87. Foreign Function
Interface
Calling native functions
Wednesday, November 16, 11
88. Gamma function
∞
Γ(z) = ∫ t
0
z-1e-1 dt
Wednesday, November 16, 11
89. Apache Commons?
org.apache.commons.math.special
Class Gamma
java.lang.Object
org.apache.commons.math.special.Gamma
public class Gamma
extends java.lang.Object
This is a utility class that provides computation methods related
to the Gamma family of functions.
Version:
$Id: Gamma.java 1131229 2011-06-03 20:49:25Z luc $
Wednesday, November 16, 11
90. Apache Commons?
org.apache.commons.math.special
Class Gamma
ize
?
java.lang.Object
e
org.apache.commons.math.special.Gamma
s
Rs
public class Gamma
en
extends java.lang.Object
Lic
This is a utility class that provides computation methods related
JA
to the Gamma family of functions.
Version:
$Id: Gamma.java 1131229 2011-06-03 20:49:25Z luc $
Wednesday, November 16, 11
91. for the coe cients in the series and estimate the error. After the
Read academic paper
our new formula with some classical results. The numerical comp
equal number of ’correction’ terms the new formula is the most acc
recommended for computing the Gamma function for large real arg
Theorem 1. Let x 1, unction every n
then for 1, the following express
( x) f
or the !!x r
sion f n 1
X Gk 2⇡
expan x
ptotic (x) = + Rn (x)
1
Neme
s ,
New asym G ˝
ergo e x2k x
8
er 7, 200 05
k=0
ecemb t h08.0
D sl2ma
7/
0.324
wherex.don.o = O
d
R i (x) rg/1 1
x2n
and the Gk coe cients are given by
-
// ap
http: otic series ter
ract
re as ympt with bet -
Abst
, St
-De Moiv a new one se of Stir
irling erted into X wit h tho nd turns Y 1 ✓
k
B2r
◆mr
ation n is conv pared than 0.5 a rior to
sform G = io k com
s tran ma funct formula is ts greater ically sup ed up
e ,
U
serie m n
sing a to the Ga The new al argume rms, num on has tu maxi-
er rn m ! 2r (2r 1)r
atio n ertie s. for re n’ te m1 ,m2 ,...,msk( 0 al
ima ti ing’
r=1
p roxim nce prop amanujan ’correctio 2m p+4mer+...+2kmu =2k p rox rd Stirl r or eq
e f rm a 1 e
co nverg lace and R number o closed-fo d as 3rd o nts great to which 2 k
Lap ual t, a goo ume e [7]
ling, be, for eq side benefi about as al arg n older on
or re
o ut to em. As a ich is an 1e-10 f ersion th of a
where B denotes the r
th si
all of the analy r rmall
g o
s wh er th
s ne xtend
ed v Bernoulli number [4]. Moreover, if x n
durin elative err article is a mainder.
r s e
mum Note: thi te of the r !
). a
to 10 the estim
8e1/5 the ⇣ n ⌘2
adds n for a
it
|Rn (x)| mptot+expxpeci+ormu ed
(n ic 1) li t f ansio l
2n
n w as y an e mnr(2n
pa 1) (2⇡) x
uctio nt a n
e e give of, we co for
Introd se
pre
ct, we ual than
one.
W ro
the p n shows
tha t
hly
1 esult
nr a
abstr r or eq . After pariso is hig
Wednesday, November 16, 11 1 i
e ma in the ate error com and i
t
92. …and implement
if (Double.isNaN(x)) {
value = Double.NaN;
return;
}
double int_part = (int) x;
sign = (int_part % 2 == 0 && (x - int_part) != 0.0 && (x < 0)) ? -1 : 1
!
if ((x - int_part) == 0.0 && 0 < int_part && int_part <= FACTORIAL.leng
value = Math.log(FACTORIAL[(int) int_part - 1]);
d
}
r
else if (x < 10) {
double rising_factorial = 1;
a
for (int i = 0; i < (int) Math.abs(x) - int_part + 10; i++) {
rising_factorial *= (x + i);
H
}
NemesLogGamma l = new NemesLogGamma(x + (int) Math.abs(x) - int_par
value = l.value - Math.log(Math.abs(rising_factorial));
} else {
double temp = 0.0;
for (int i = 0; i < NEMES_GAMMA_COEFF.length; i++) {
temp += NEMES_GAMMA_COEFF[i] * 1.0 / Math.pow(x, i);
}
value = x * (Math.log(x) - 1 + Math.log(temp)) +
(Math.log(2) + Math.log(Math.PI) - Math.log(x)) / 2.0;
}
Wednesday, November 16, 11
94. Not reinventing the wheel
TGAMMA(3) BSD Library Functions Manual TGAMMA(3)
NAME
tgamma, lgamma, gamma -- gamma and log of gamma
SYNOPSIS
#include <math.h>
double
tgamma(double x);
⋮
DESCRIPTION
tgamma() calculates the gamma function of x. lgamma()
calculates the natural logorithm of the absolute value of the
gamma function of x. gamma() is the same function as tgamma.
Its use is deprecated.
⋮
Wednesday, November 16, 11
95. JNI Workflow
javah
Example.java Example.h
cc
gamma.c libgamma.dylib
javac
Example.class
$ java Example
Wednesday, November 16, 11
96. Example.java
import java.util.*;
class Example {
native static double gamma(double x);
static {
System.loadLibrary("gamma");
}
public static void main(String args[]) {
System.out.println(gamma(0.5));
}
}
Wednesday, November 16, 11
97. Example.h
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class Example */
#ifndef _Included_Example
#define _Included_Example
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: Example
* Method: gamma
* Signature: (D)D
*/
JNIEXPORT jdouble JNICALL Java_Example_gamma
(JNIEnv *, jclass, jdouble);
#ifdef __cplusplus
}
#endif
#endif
Wednesday, November 16, 11
103. But…
d y?
I n
ut
a b o
ha t
W
h0p://www.flickr.com/photos/evablue/5665409533/
Wednesday, November 16, 11
104. Invokedynamic!
• Only mainstream
alternative JVM language
• Worked closely with
Hotspot team
• JRuby master/1.7
• Waiting for Java 1.7.0u2
release
• What does it mean for
you?
Wednesday, November 16, 11
105. Benchmark
Times faster than Ruby 1.9.3
16
14.2x
12
8 7.4x
5.5x
4.4x
4 2.7x
3.6x 3.5x 3.2x
1.7x 1.8x 1.6x 1.9x
0
fib +calls +consts +both richardsredblack
JRuby on Java 6
JRuby on invokedynamic
Wednesday, November 16, 11
106. Benchmark
0.300
0.225
0.150
0.075
0
currentTimeMillis Thread#name
No indy With indy
Wednesday, November 16, 11
107. Indy TODOs
• Remaining invocation paths
–varargs, super, all Java paths
• Per-type specialization
–Faster block-receiving calls
–Faster module methods
• More code in Ruby!
Wednesday, November 16, 11