この冬こそ関数型プログラミングにデビューしようと思い立ち、RxJava なるものを動かしてみました。
Gradle で Hello World
まず、gradle で Java プログラムを動かす方法を確認します。
適当なディレクトリ (gradle-hello とか) に Hello.java があるとします。
public class Hello {
public static void main(String[] args) {
System.out.println("Hello, World!");
}
}
ふつーはこうしますよね。
$ javac Hello.java
$ java Hello
Hello, World!
$
簡単なプログラムならこれでもいいけど、ライブラリの指定とか面倒。
ってわけで、gradle の出番です。
Gradle で Java プログラムを動かす
これを gradle で動かすには、まず build.gradle が必要です。
apply plugin: 'java'
apply plugin: 'application'
mainClassName = 'Hello'
次に、src/main/java ディレクトリを作って、その中にソースを置きます。
$ mkdir -p src/main/java
$ mv Hello.java src/main/java
$ tree
.
├── build.gradle
└── src
└── main
└── java
└── Hello.java
3 directories, 2 files
$
あとは gradle におまかせ。
$ gradle run
:compileJava
:processResources UP-TO-DATE
:classes
:run
Hello, World!
BUILD SUCCESSFUL
Total time: 5.164 secs
This build could be faster, please consider using the Gradle Daemon: https://docs.gradle.org/2.10/userguide/gradle_daemon.html
$
-q を付けるとおとなしくなります。
$ gradle -q run
Hello, World!
$
コンパイルしたものは build ディレクトリに生成されてます。
$ tree
.
├── build
│ ├── classes
│ │ └── main
│ │ └── Hello.class
│ ├── dependency-cache
│ └── tmp
│ └── compileJava
├── build.gradle
└── src
└── main
└── java
└── Hello.java
9 directories, 3 files
$
ちなみに、build.gradle で指定した java というプラグインは Java のソースをコンパイルして jar などを作ってくれるものですが、実行するには application というプラグインも必要になります。
これだから Java ってやつは…。
Gradle でコマンドライン引数
プログラムが動いたら、コマンドライン引数も扱いたいですよね。
ついでに、後でテストを書くことも考慮して、Hello.java を次のように書き換えます。
public class Hello {
public String message(String name) {
return "Hello, " + name + "!";
}
public static void main(String[] args) {
Hello hello = new Hello();
if (args.length == 0) {
System.out.println(hello.message("World"));
} else {
for (String name : args) {
System.out.println(hello.message(name));
}
}
}
}
引数を付けて実行してみましょう。
$ gradle -q run Earth Moon Sun
FAILURE: Build failed with an exception.
* What went wrong:
Task 'Earth' not found in root project 'gradle-hello'.
* Try:
Run gradle tasks to get a list of available tasks. Run with --stacktrace option to get the stack trace. Run with --info or --debug option to get more log output.
$
残念ながら、コマンドライン引数は build.gradle で指定する必要があります。
apply plugin: 'java'
apply plugin: 'application'
mainClassName = 'Hello'
run.args = ['Earth', 'Moon', 'Sun']
$ gradle -q run
Hello, Earth!
Hello, Moon!
Hello, Sun!
$
または、-P オプションで指定する方法もあるけど、複数指定できないし、省略したら怒られるので、面倒。
apply plugin: 'java'
apply plugin: 'application'
mainClassName = 'Hello'
run.args = [project.args]
$ gradle -q run -Pargs="Earth Moon Sun"
Hello, Earth Moon Sun!
$ gradle -q run
FAILURE: Build failed with an exception.
* Where:
Build file '/Users/hoshi/gradle-hello/build.gradle' line: 5
* What went wrong:
A problem occurred evaluating root project 'gradle-hello'.
> Could not find property 'args' on root project 'gradle-hello'.
* Try:
Run with --stacktrace option to get the stack trace. Run with --info or --debug option to get more log output.
$
main なんか実行してないで、ちゃんとテストを書けってことなんでしょうね。
これだから Java ってやつは…。
Gradle で JUnit
テストコードを書く場合は、まず build.gradle を次のように書き換えます。
apply plugin: 'java'
repositories {
mavenCentral()
}
dependencies {
testCompile 'junit:junit:4.12'
}
次に、src/test/java ディレクトリを作って、テストクラスを置きます。
$ mkdir -p src/test/java
$ vi src/test/java/HelloTest.java
...
$ tree src
src
├── main
│ └── java
│ └── Hello.java
└── test
└── java
└── HelloTest.java
4 directories, 2 files
$
import org.junit.Before;
import org.junit.Test;
import static org.junit.Assert.assertEquals;
public class HelloTest {
Hello hello;
@Before
public void setup() {
hello = new Hello();
}
@Test
public void testMessage() {
assertEquals("Hello, World!", hello.message("World"));
}
}
あとは gradle におまかせ。
$ gradle test
:compileJava UP-TO-DATE
:processResources UP-TO-DATE
:classes UP-TO-DATE
:compileTestJava
:processTestResources UP-TO-DATE
:testClasses
:test
BUILD SUCCESSFUL
Total time: 5.918 secs
This build could be faster, please consider using the Gradle Daemon: https://docs.gradle.org/2.10/userguide/gradle_daemon.html
$
build/reports/tests/index.html をブラウザで開くと、テスト結果のレポートが見れます。
Gradle で RxJava
それでは RxJava を試してみましょう。
ObserverパターンとHelloWorldからはじめるRxJava を参考にしました。
import rx.Observable;
public class HelloRx1 {
public static void main(String[] args) {
String[] names = { "Ben", "George" };
Observable.from(names).subscribe(name -> System.out.println("Hello, " + name + "!"));
}
}
build.gradle もちょっと書き換えて、コマンドラインから実行するクラスを指定できるようにします。
apply plugin: 'java'
apply plugin: 'application'
mainClassName = project.main
repositories {
mavenCentral()
}
dependencies {
compile 'io.reactivex:rxjava:1.1.0'
}
$ gradle -q run -Pmain=HelloRx1
Hello, Ben!
Hello, George!
$
もひとつ。
import rx.Observable;
import rx.Observer;
public class HelloRx2 {
public static void main(String[] args) {
String[] names = { "Ben", "", "George" };
Observable.from(names).subscribe(new Observer<String>() {
@Override
public void onNext(String name) {
if (name.isEmpty()) {
throw new RuntimeException("Empty name");
}
System.out.println("Hello, " + name + "!");
}
@Override
public void onCompleted() {
System.out.println("Completed.");
}
@Override
public void onError(Throwable e) {
System.out.println("Error: " + e);
}
});
}
}
$ gradle -q run -Pmain=HelloRx2
Hello, Ben!
Error: java.lang.RuntimeException: Empty name
$
さらに高度な例。(Learning Reactive Programming with Java 8 より拝借。)
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import rx.Observable;
import rx.Subscriber;
import rx.observables.ConnectableObservable;
public class SumRx {
public static void main(String[] args) {
System.out.println("start");
ConnectableObservable<String> input = from(System.in).publish();
Observable<Double> a = varStream("a", input);
a.subscribe(v -> System.out.println("a = " + v));
Observable<Double> b = varStream("b", input);
b.subscribe(v -> System.out.println("b = " + v));
Observable.combineLatest(a, b, (x, y) -> x + y).subscribe(sum -> System.out.println("sum = " + sum));
input.connect();
}
public static Observable<String> from(InputStream stream) {
return from(new BufferedReader(new InputStreamReader(stream)));
}
public static Observable<String> from(BufferedReader reader) {
return Observable.create(new Observable.OnSubscribe<String>() {
@Override
public void call(Subscriber<? super String> subscriber) {
if (subscriber.isUnsubscribed()) { return; }
try {
while (!subscriber.isUnsubscribed()) {
String line = reader.readLine();
if (line == null || line.equals("exit")) { break; }
subscriber.onNext(line);
}
} catch (IOException e) {
subscriber.onError(e);
}
if (!subscriber.isUnsubscribed()) {
subscriber.onCompleted();
}
}
});
}
public static Observable<Double> varStream(String varName, Observable<String> input) {
return input
.map(str -> str.split(":"))
.filter(array -> array.length == 2 && array[0].equals(varName))
.map(array -> Double.parseDouble(array[1]));
}
}
gradle で動かしたプログラムから標準入力を読み込むために、以下を追加。
run {
standardInput = System.in
}
$ gradle -q run -Pmain=ch01.Sum
start
a:1
a = 1.0
a:2
a = 2.0
b:3
b = 3.0
sum = 5.0
b:4
b = 4.0
sum = 6.0
c:5
a:6
a = 6.0
sum = 10.0
b:7
b = 7.0
sum = 13.0
exit
$
あとは各自いろいろ試せばいいと思います。