覚えたら書く

IT関係のデベロッパとして日々覚えたことを書き残したいです。twitter: @yyoshikaw

RxJava - RxJavaでHelloWorld

RxJavaを使いながらリアクティブプログラミングを理解していきたい。

とりあえずは、まず何を置いてもHello World!


準備

pom.xmlに以下の依存関係を追加します。Reactive Streamsにも対応しているバージョン2.Xを使います

<dependency>
    <groupId>io.reactivex.rxjava2</groupId>
    <artifactId>rxjava</artifactId>
    <version>2.0.8</version>
</dependency>


サンプルの実行

■サンプルコード

以下は、ほぼ「RxJavaリアクティブプログラミング (CodeZine BOOKS) 」からの写経です。

Hello, World!-Xの文字列を順に発行して、購読側のonNextで受け取れることを確認しました

import io.reactivex.BackpressureStrategy;
import io.reactivex.Flowable;
import io.reactivex.FlowableEmitter;
import io.reactivex.FlowableOnSubscribe;
import io.reactivex.schedulers.Schedulers;
import org.reactivestreams.Subscriber;
import org.reactivestreams.Subscription;

import java.util.Arrays;
import java.util.List;

public class HelloWorldRx {

    public static void main(String[] args) throws Exception {
        // 発行するデータ
        final List<String> helloList = Arrays.asList("Hello, World!-1", "Hello, World!-2", "Hello, World!-3");

        Flowable<String> flowable = Flowable.create(new FlowableOnSubscribe<String>() {

            @Override
            public void subscribe(FlowableEmitter<String> emitter) throws Exception {
                helloList.forEach(s -> emitter.onNext(s));

                emitter.onComplete();
            }
        }, BackpressureStrategy.BUFFER);


        flowable.observeOn(Schedulers.computation())
            .subscribe(new Subscriber<String>() {

                private Subscription subscription;

                @Override
                public void onSubscribe(Subscription subscription) {
                    this.subscription = subscription;
                    this.subscription.request(1);
                }

                @Override
                public void onNext(String data) {
                    String threadName = Thread.currentThread().getName();
                    System.out.println(threadName + " onNext -> " + data);

                    this.subscription.request(1);
                }

                @Override
                public void onComplete() {
                    String threadName = Thread.currentThread().getName();
                    System.out.println(threadName + " onComplete");
                }

                @Override
                public void onError(Throwable throwable) {
                    throwable.printStackTrace();
                }
         });

        Thread.sleep(500);
    }
}

■実行結果

RxComputationThreadPool-1 onNext -> Hello, World!-1
RxComputationThreadPool-1 onNext -> Hello, World!-2
RxComputationThreadPool-1 onNext -> Hello, World!-3
RxComputationThreadPool-1 onComplete


もっとシンプルに試すだけなら以下のコードとかでもいいかもしれないですね

■サンプルコード

import io.reactivex.Flowable;

public class HelloWorldRx2 {

    public static void main(String[] args) throws Exception {
        Flowable<String> flowable =
                Flowable.just("Hello, World!-1", "Hello, World!-2", "Hello, World!-3");

        flowable.subscribe(data -> System.out.println(data));

        Thread.sleep(500);
    }
}

■実行結果

Hello, World!-1
Hello, World!-2
Hello, World!-3


とりあえず、RxJavaを使ったHello Worldができました



関連書籍

RxJavaリアクティブプログラミング (CodeZine BOOKS)

RxJavaリアクティブプログラミング (CodeZine BOOKS)

Go言語 - HTMLテンプレートの使い方

GolangでのHTMLテンプレート記述方法や値の展開方法等について学ぶために、html/templateを試してみました。


変数をそのまま展開

コード内の変数をhtmlのテンプレートに展開する例です

■htmlテンプレート(template000.html.tpl)

<!DOCTYPE html>
<html>
<body>
    msg: {{.}} 
</body>
</html>

■サンプルコード

package trial

import (
    "html/template"
    "log"
    "net/http"
)

func htmlHandler0(w http.ResponseWriter, r *http.Request) {
    // テンプレートをパース
    t := template.Must(template.ParseFiles("templates/template000.html.tpl"))

    str := "Sample Message"

    // テンプレートを描画
    if err := t.ExecuteTemplate(w, "template000.html.tpl", str); err != nil {
        log.Fatal(err)
    }
}

func main() {
    http.HandleFunc("/page0", htmlHandler0)

    // サーバーを起動
    http.ListenAndServe(":8989", nil)
}


■ブラウザでのアクセス結果

f:id:nini_y:20170702144443p:plain


mapの値を展開

mapをhtmlのテンプレートに展開する例です

■htmlテンプレート(template001.html.tpl)

<!DOCTYPE html>
<html>
<body>
    key1: {{.key1}}, key2: {{.key2}}, key3: {{.key3}} 
</body>
</html>

■サンプルコード

package trial

import (
    "html/template"
    "log"
    "net/http"
)

func htmlHandler1(w http.ResponseWriter, r *http.Request) {
    // テンプレートをパース
    t := template.Must(template.ParseFiles("templates/template001.html.tpl"))

    m := map[string]int{
        "key1": 101,
        "key2": 202,
        "key3": 303,
        "key4": -404,
    }

    // テンプレートを描画
    if err := t.ExecuteTemplate(w, "template001.html.tpl", m); err != nil {
        log.Fatal(err)
    }
}

func main() {
    http.HandleFunc("/page1", htmlHandler1)

    // サーバーを起動
    http.ListenAndServe(":8989", nil)
}


■ブラウザでのアクセス結果

f:id:nini_y:20170702144531p:plain


構造体を展開

構造体をhtmlのテンプレートに展開する例です

■htmlテンプレート(template002.html.tpl)

<!DOCTYPE html>
<html>
<body>
    Name: {{ .Name }}, Age: {{ .Age }} 
</body>
</html>

■サンプルコード

package trial

import (
    "html/template"
    "log"
    "net/http"
)

func htmlHandler2(w http.ResponseWriter, r *http.Request) {

    t := template.Must(template.ParseFiles("templates/template002.html.tpl"))

    type SampleData struct {
        Name string
        Age  int
    }

    data := SampleData{Name: "Taro", Age: 25}

    // テンプレートを描画
    if err := t.ExecuteTemplate(w, "template002.html.tpl", data); err != nil {
        log.Fatal(err)
    }
}

func main() {
    http.HandleFunc("/page2", htmlHandler2)

    // サーバーを起動
    http.ListenAndServe(":8989", nil)
}


■ブラウザでのアクセス結果

f:id:nini_y:20170702144541p:plain


関数を実行

htmlのテンプレート内で関数呼び出しを行う例です

■htmlテンプレート(template003.html.tpl)

<!DOCTYPE html>
<html>
<body>
    Func1 -> {{ samplefunc1 }}
<br/>
    Func2 -> {{.msg1 | samplefunc2}}
<br/>
    Func3 -> {{.msg2 | samplefunc3}}
</body>
</html>

■サンプルコード

package trial

import (
    "fmt"
    "html/template"
    "log"
    "net/http"
    "strings"
    "time"
)

func htmlHandler3(w http.ResponseWriter, r *http.Request) {

    funcMap := template.FuncMap{
        "samplefunc1": func() string { return time.Now().String() },
        "samplefunc2": func(src string) string { return fmt.Sprintf("[$$ %s $$]", src) },
        "samplefunc3": strings.ToUpper,
    }
    // テンプレートをパース
    t := template.Must(template.New("t").Funcs(funcMap).ParseFiles("templates/template003.html.tpl"))

    m := map[string]string{
        "msg1": "golang is programming language.",
        "msg2": "hello template app.",
    }

    // テンプレートを描画
    if err := t.ExecuteTemplate(w, "template003.html.tpl", m); err != nil {
        log.Fatal(err)
    }
}

func main() {
    http.HandleFunc("/page3", htmlHandler3)

    // サーバーを起動
    http.ListenAndServe(":8989", nil)
}


■ブラウザでのアクセス結果

f:id:nini_y:20170702144551p:plain


ループ

htmlのテンプレート内でループを行う例です

■htmlテンプレート(template004.html.tpl)

<!DOCTYPE html>
<html>
<body>
    <div>
        <h4>DataList</h4>
        {{range .}}
        <p>{{.}}</p>
        {{end}}
    </div>
</body>
</html>

■サンプルコード

package trial

import (
    "html/template"
    "log"
    "net/http"
)

func htmlHandler4(w http.ResponseWriter, r *http.Request) {

    t := template.Must(template.ParseFiles("templates/template004.html.tpl"))

    strArray := []string{"aa", "bbb", "ccc", "dddd"}

    // テンプレートを描画
    if err := t.ExecuteTemplate(w, "template004.html.tpl", strArray); err != nil {
        log.Fatal(err)
    }
}

func main() {
    http.HandleFunc("/page4", htmlHandler4)

    // サーバーを起動
    http.ListenAndServe(":8989", nil)
}


■ブラウザでのアクセス結果

f:id:nini_y:20170702144607p:plain


リクエストパラメータをテンプレートにセットして表示

htmlのテンプレート内で受け取ったリクエストパラメータを反映する例です

■htmlテンプレート(template005.html.tpl)

<!DOCTYPE html>
<html>
<body>
<div>
  Request Param1 -> {{.Param1 |safehtml}}
</div>
<div>
  Request Param2 -> {{.Param2 |safehtml}}
</div>
</body>
</html>

■サンプルコード

package trial

import (
    "html/template"
    "log"
    "net/http"
)

func htmlHandler5(w http.ResponseWriter, r *http.Request) {
    funcMap := template.FuncMap{
        "safehtml": func(text string) template.HTML { return template.HTML(text) },
    }
    t := template.Must(template.New("T").Funcs(funcMap).ParseFiles("templates/template005.html.tpl"))

    st := struct {
        Param1 string
        Param2 string
    }{
        Param1: r.FormValue("param1"),
        Param2: r.FormValue("param2"),
    }

    // テンプレートを描画
    if err := t.ExecuteTemplate(w, "template005.html.tpl", st); err != nil {
        log.Fatal(err)
    }
}

func LoadTemplate() {
    http.HandleFunc("/page5", htmlHandler5)

    // サーバーを起動
    http.ListenAndServe(":8989", nil)
}


■ブラウザでのアクセス結果

f:id:nini_y:20170702144616p:plain



関連エントリ

Lombok - @Builderでデフォルト値を指定する

Lombok@Builderアノテーションは何かと便利なのですが、プロパティへ値をセットするためのBuilderのメソッドを呼ばないと対応するフィールドが初期値(数値なら0, booleanならfalse, オブジェクトならnull)になってしまいます。

このあたりの動きをv1.16.16で増えた@Builder.Defaultで改善できるようになっています。


Builderで値をセットしなかった時の動きの確認

動作確認用にPerson1というクラスを用意して@Builderを付与します。
Person1をBuilderを通じて2回生成します。1回目は全プロパティをセットしますが2回目はnameプロパティに値をセットしません。

import lombok.Builder;
import lombok.Value;

@Builder
@Value
public class Person1 {

    private final long id;

    private final String name;

    private final String description;
}


■呼び出し側のコード

public final class Person1Client {

    public static void main(String[] args) {
        Person1 p1a = Person1.builder()
                .id(10L)
                .name("Taro")
                .description("name set person.")
                .build();

        System.out.println("p1a#toString: " + p1a);

        // nameメソッドを呼ばない
        Person1 p1b = Person1.builder()
                .id(20L)
                .description("name not set person.")
                .build();

        System.out.println("p1b#toString: " + p1b);
    }
}


■実行結果

結果としてPerson1生成の2回目の呼び出しではnameの値がnullになっています。

p1a#toString: Person1(id=10, name=Taro, description=name set person.)
p1b#toString: Person1(id=20, name=null, description=name not set person.)

対象オブジェクトの生成側が全プロパティをセットするのが必須ならいいですが、そうでないケースもあると思います。
そういう場合にはセットしなかったプロパティにはデフォルト値を入れておきたいところです。


デフォルト値をセットしたい時は

デフォルト値を入れておきたいという要望に応えるための方法として若干裏技的に以下リンク先のような方法が存在しています。

@Builderアノテーションによって生み出されるコードを逆手に取ったような手法になっています。


@Builder.Default によるデフォルト値セット

Lombokのv1.16.16から@Builderでのデフォルト値指定のための@Builder.Defaultが追加されました

デフォルト値をセットしたいフィールドに@Builder.Defaultアノテーションを付与して、フィールドにデフォルト値をセットします。

本サンプルではnameフィールドに “<UNKNOWN>"という値をデフォルト値としてセットします

import lombok.Builder;
import lombok.Value;

@Builder
@Value
public class Person2 {

    private final long id;

    @Builder.Default
    private final String name = "<UNKNOWN>";

    private final String description;
}


■呼び出し側のコード

public final class Person2Client {

    public static void main(String[] args) {
        Person2 p2a = Person2.builder()
                .id(10L)
                .name("Taro")
                .description("name set person.")
                .build();

        System.out.println("p1a#toString: " + p2a);


        // nameメソッドを呼ばない
        Person2 p2b = Person2.builder()
                .id(20L)
                .description("name not set person.")
                .build();

        System.out.println("p2b#toString: " + p2b);
    }
}


■実行結果

結果を見ると、Builderのnameメソッドを呼ばない場合はデフォルト値(<UNKNOWN>)がセットされていることが分かります

p1a#toString: Person2(id=10, name=Taro, description=name set person.)
p2b#toString: Person2(id=20, name=<UNKNOWN>, description=name not set person.)


補足

実は私がこのエントリ書いてる段階では、IntelliJのIDE上では、@Builder.Defaultを付与したフィールドへBuilderで値をセットするメソッドがコンパイルエラーになってしまいました。
ただし、ビルドや実行はできるので、Lombokのpluginが対応できていないだけなのかもしれません。

EclipseであればIDE上も何もエラー出ませんでした。



関連エントリ

Go言語 - XMLを読んで特定の要素を削って出力

Golangではencoding/xmlパッケージでXMLを簡単に扱えそうなので、Hello World的に試してみました。

今回のエントリでは、XMLファイルを読んで特定のエレメントを削ったXMLを再出力するということをやってみました。
(といっても、Unmarshal(XML⇒構造体への変換)する構造体に対象要素に紐づくメンバを用意しないだけで実現できます)


前提

今回は以下のようなXMLが書かれたファイルがあるとして、その中からdescriptionというエレメントを除外したいとします

<?xml version="1.0" encoding="UTF-8"?>
<servers>
  <server>
    <id>s0001</id>
    <name>サーバA-001</name>
    <port>443</port>
    <description>試験用のサーバです</description>
    <subscriber_id>sub000002</subscriber_id>
    <contract_period>
      <start_date>2017-10-01</start_date>
      <end_date>2021-09-30</end_date>
    </contract_period>
  </server>
  <server>
    <id>s0002</id>
    <name>サーバA-002</name>
    <port>8080</port>
    <description>ECサイトで利用するための</description>
    <subscriber_id>sub000001</subscriber_id>
    <contract_period>
      <start_date>2012-04-01</start_date>
      <end_date>2024-03-31</end_date>
    </contract_period>
  </server>
  <server>
    <id>s0003</id>
    <name>サーバA-002</name>
    <port>8080</port>
    <description>利用用途未定</description>
    <subscriber_id>sub000003</subscriber_id>
    <contract_period>
      <start_date>2015-04-01</start_date>
      <end_date>2019-03-31</end_date>
    </contract_period>
  </server>
</servers>


Golang側のコードでは、上記XMLをUnmarshalして紐づける構造体を以下のように用意しました。
(server構造体に、descriptionのエレメントを紐づける変数がありません)

除外する要素以外は構造体のタグで紐づけを行っています

type ServerList struct {
    XMLName xml.Name `xml:"servers"`
    Svs     []server `xml:"server"`
}

type server struct {
    XMLName                   xml.Name `xml:"server"`
    ID                        string   `xml:"id"`
    ServerName                string   `xml:"name"`
    Port                      string   `xml:"port"`
    SubscriberID              string   `xml:"subscriber_id"`
    ContractStartDate         string   `xml:"contract_period>start_date"`
    ContractEndDate           string   `xml:"contract_period>end_date"`
}


実行サンプル

以下XMLファイルを読んで、エレメント削って出力するGoのプログラムです。
読み込むXMLファイルをコマンドラインで -f パラメータに与えて、エレメントを除外した結果を標準出力に出力するようにしています。
(そのため、結果をリダイレクトれば結果をファイルに出力できます)

package main

import (
    "encoding/xml"
    "flag"
    "fmt"
    "io/ioutil"
    "os"
)

type ServerList struct {
    XMLName xml.Name `xml:"servers"`
    Svs     []server `xml:"server"`
}

type server struct {
    XMLName                   xml.Name `xml:"server"`
    ID                        string   `xml:"id"`
    ServerName                string   `xml:"name"`
    Port                      string   `xml:"port"`
    SubscriberID              string   `xml:"subscriber_id"`
    ContractStartDate         string   `xml:"contract_period>start_date"`
    ContractEndDate           string   `xml:"contract_period>end_date"`
}

func main() {
    var (
        srcXML string
    )

    flag.StringVar(&srcXML, "f", "servers.xml.", "src xml path.")

    flag.Parse()

    file, err := os.Open(srcXML)
    if err != nil {
        fmt.Printf("error: %v", err)
        return
    }
    defer file.Close()
    data, err := ioutil.ReadAll(file)
    if err != nil {
        fmt.Printf("error: %v", err)
        return
    }
    serverList := ServerList{}
    err = xml.Unmarshal(data, &serverList)
    if err != nil {
        fmt.Printf("error: %v", err)
        return
    }

    output, err := xml.MarshalIndent(serverList, "  ", "    ")
    if err != nil {
        fmt.Printf("error: %v\n", err)
    }
    os.Stdout.Write([]byte(xml.Header))
    os.Stdout.Write(output)
}


ビルド(go build)後に以下のようにコマンドを実行(original.xmlが元のXMLファイル)

xmlconv.exe -f original.xml > dest.xml


結果、dest.xmlには以下のような内容が出力されています(descriptionというエレメントが除外されています)

<?xml version="1.0" encoding="UTF-8"?>
<servers>
    <server>
        <id>s0001</id>
        <name>サーバA-001</name>
        <port>443</port>
        <subscriber_id>sub000002</subscriber_id>
        <contract_period>
            <start_date>2017-10-01</start_date>
            <end_date>2021-09-30</end_date>
        </contract_period>
    </server>
    <server>
        <id>s0002</id>
        <name>サーバA-002</name>
        <port>8080</port>
        <subscriber_id>sub000001</subscriber_id>
        <contract_period>
            <start_date>2012-04-01</start_date>
            <end_date>2024-03-31</end_date>
        </contract_period>
    </server>
    <server>
        <id>s0003</id>
        <name>サーバA-002</name>
        <port>8080</port>
        <subscriber_id>sub000003</subscriber_id>
        <contract_period>
            <start_date>2015-04-01</start_date>
            <end_date>2019-03-31</end_date>
        </contract_period>
    </server>
</servers>


というわけで簡単にXMLファイルを扱うことができました

普通にコンストラクタ呼び出すのとリフレクション使うのとでどれだけ速度差あるの?

Javaでインスタンス生成する場合は、コンストラクタを実行するのが普通ですが、リフレクションでもやれます。

ただ、一般的にリフレクション使うと遅いというのが懸念点の一つとして上がってきます。

というわけで、普通にコンストラクタ実行するとのリフレクション使うのでどんな差があるのかをJMH使って測定してみます


測定条件

測定するインスタンス生成方法は以下の通りです

  • 普通のコンストラクタ実行
  • Class#newInstance(リフレクションを利用)
  • Constructor#newInstance(リフレクションを利用)


インスタンス化するクラスは以下です(デフォルトコンストラクタのみ持ちます)

final class Person {
    private long id;
    private String name;

    public long getId() {
        return id;
    }

    public void setId(long id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}


実行環境のJavaは、Java7です


JMHでの測定用のコードは以下の通りとなります

import org.openjdk.jmh.annotations.Benchmark;
import org.openjdk.jmh.annotations.Mode;
import org.openjdk.jmh.runner.Runner;
import org.openjdk.jmh.runner.RunnerException;
import org.openjdk.jmh.runner.options.Options;
import org.openjdk.jmh.runner.options.OptionsBuilder;


public class NewInstanceBenchmark {

    @Benchmark
    public void normal() {
        Person p = new Person();
    }

    @Benchmark
    public void clazzNewInstance() throws Exception {
        Person p = Person.class.newInstance();
    }

    @Benchmark
    public void constructorNewInstance() throws Exception {
        Person p = Person.class.getDeclaredConstructor().newInstance();
    }

    static final class Person {
        private long id;
        private String name;

        public long getId() {
            return id;
        }

        public void setId(long id) {
            this.id = id;
        }

        public String getName() {
            return name;
        }

        public void setName(String name) {
            this.name = name;
        }
    }

    public static void main(String[] args) throws RunnerException {
        Options opt = new OptionsBuilder()
                .include(NewInstanceBenchmark.class.getSimpleName())
                .warmupIterations(20)
                .forks(1)
                .mode(Mode.Throughput)
                .build();
        new Runner(opt).run();
    }
}


測定結果

JMHの実行結果は以下の通りです

# Run complete. Total time: 00:02:02

Benchmark                                     Mode  Cnt           Score          Error  Units
NewInstanceBenchmark.clazzNewInstance        thrpt   20     7833794.167 ±    96565.569  ops/s
NewInstanceBenchmark.constructorNewInstance  thrpt   20     2182452.929 ±    70285.111  ops/s
NewInstanceBenchmark.normal                  thrpt   20  3600508098.995 ± 18204543.613  ops/s

上記結果は以下のスループットを表しています

  • NewInstanceBenchmark.clazzNewInstance ⇒ Class#newInstance で インスタンス生成
  • NewInstanceBenchmark.constructorNewInstance ⇒ Constructor#newInstance で インスタンス生成
  • NewInstanceBenchmark.normal ⇒ 普通のコンストラクタ(デフォルトコンストラクタ)実行 で インスタンス生成


Java7環境で測定したというのも関係しているとは思いますが、普通にコンストラクタ実行するのとリフレクション使うのでは雲泥の差です。
本測定においては、普通のコンストラクタ呼び出しとClass#newInstanceでは450倍以上の違いが出ています。
やはり、リフレクションは必要最低限の場面で使うべきかなと感じました。



関連エントリ