ツリー構造の Java 実装サンプル

以前の続き。

動的にデータを取得する Vue.js のツリーコンポーネント - kntmr-blog

これに関連して、フォルダ階層のようなツリー構造を Java で実装したサンプル。

kntmr/playground/folder-tree-examples - GitHub


子階層の要素を同じ型の List<T> で持つことでツリー構造を表現する。

public class FolderTree {
    private long id;
    private String name;
    private List<FolderTree> children;
    public FolderTree(long id, String name) {
        this.id = id;
        this.name = name;
    }
    public long getId() {
        return id;
    }
    public String getName() {
        return name;
    }
    public List<FolderTree> getChildren() {
        return children;
    }
    public void setChildren(List<FolderTree> children) {
        this.children = children;
    }
}

ツリー全体のデータを取得するときは最上位から順番に子階層の要素を取得する。

public FolderTree folderTree() {
    FolderTree tree = new FolderTree(0, "Root"); // 最上位から
    return getFolderTreeRecursive(tree, tree);
}
FolderTree getFolderTreeRecursive(FolderTree tree, FolderTree currentNode) {
    List<FolderTree> children = getChildren(currentNode.getId()); // 子階層を取得
    currentNode.setChildren(children);
    for (FolderTree nextNode : children) {
        getFolderTreeRecursive(tree, nextNode); // 子階層の要素に対して再帰的に処理する
    }
    return tree;
}

これはグラフデータの探索で言うところの 深さ優先探索 になっている。深さ優先探索の場合、縦方向 (深い階層) の探索を優先する。ちなみに、幅優先探索の場合は横方向 (隣接する要素) の探索を優先する。

@RequestMapping で正規表現を使って @PathVariable を宣言する

普段、こういう使い方をしないので、URI のパスの宣言に正規表現が使えることを初めて知りました...。

URI patterns - Web on Servlet Stack


@GetMapping("/users/{id:[0-9]{5}}") // 数字5桁
public String get(@PathVariable long id) {
    //
}

また、1つのパスの中身は分解して変数に抽出できる。

@GetMapping("/users/{prefix:[a-z]{1}}{id:[0-9]{5}}") // 英小文字1桁 + 数字5桁
public String get(@PathVariable String prefix, @PathVariable long id) {
    //
}

固定の prefix の場合はこういう書き方も可。例えば、内部では数字で扱うけど、外部からは prefix 付きでアクセスされるような要件があるときに便利かもしれない。

@GetMapping("/users/user{id:[0-9]{5}}") // `user` + 数字5桁
public String get(@PathVariable long id) {
    //
}

あと、同階層の URI のパスに応じて Controller のメソッド呼び出しが分けられる。

@GetMapping("/users/user{id:[0-9]{5}}")
public String get1(@PathVariable long id) {
    // /users/user12345 はこっちに入る
}
@GetMapping("/users/admin{id:[0-9]{5}}")
public String get2(@PathVariable long id) {
    // /users/admin12345 はこっちに入る
}

今までは1つのメソッドで受けて、パラメータを判定して処理を分岐したりしてたかもしれないけど、Controller のメソッドで分岐できるならその方がアプリケーションコードはシンプルになりそう。

アルゴリズム体操

Javaアルゴリズムの復習。久しぶりに書くとすっかり忘れてますね...。こういうのを呼吸するように書けるようになりたいものです。

バブルソート

配列の先頭から隣り合う要素を比較して入れ替える。これを配列の最後の要素まで繰り返してソートする。

gist.github.com

クイックソート

ここでは配列の真ん中の要素をピボットとする。ピボットの左右で要素を比較し、ピボットより小さい要素が左に、ピボットより大きい要素が右に来るように入れ替える。ピボット未満の要素の配列とピボット以上の要素の配列で同様のソートを再帰的に繰り返してソートする。ピボットを境界にして再帰するには入れ替え処理で使ったインデックスをそのまま返す。最初のピボットの位置ではない。(入れ替えでピボットの位置が変わるため)

gist.github.com

マージソート

配列を単一要素になるまで分割する。ソートする要素を作業配列に入れておいて、比較しながら元の配列に書き戻すことでソートする。

gist.github.com

二分探索

ソート済みの配列を前提とする。真ん中の要素と対象の要素を比較して左か右の配列を再帰的に探索する。

gist.github.com

(番外編) フィボナッチ数

これはアルゴリズムではないかもしれないが、みんなが大好きなフィボナッチ数。通常バージョンとメモ化バージョン。

gist.github.com

(番外編) 素数判定

これもアルゴリズムではないかもしれないが、みんなが大好きな素数。判定は平方根以下の値までとする。(対象の値が合成数の場合、平方根より小さい素因数が存在するため)

gist.github.com

PlantUML でシーケンス図の記述を読みやすくしたい

GitBook で UML を書くときは PlantUML を使っています。

f:id:knt_mr:20190828132322p:plain

例えば、こういうシーケンス図を書きたい場合、PlantUML 概要には以下のような記述例が書かれています。

PlantUML 概要 - シーケンス図の構文と機能

@startuml

activate foo

foo -> bar: request
activate bar

bar -> baz: request
activate baz

baz -> baz: request
activate baz
deactivate baz

baz --> bar: response
deactivate baz

bar --> foo: response
deactivate bar

deactivate foo

@enduml

実際に図として出力したときは特に問題ないのですが、Markdown のテキストだけを見た場合、呼び出しの階層がイマイチ分かりづらいです。エディタ上で Side-By-Side でプレビューできるならそれでいいですが、例えば、この UMLGitHub 上でレビューするとなるとプレビューは使えません。あと、空行が多くなると縦に長くなるのが個人的にあまり好みじゃないです。

なので、こういう書き方はどうだろうということで、呼び出しごとにインデントしてみました。コードっぽいし読みやすい。(気がする)

ただ、activatedeactivate の記述位置が悩ましい...。

@startuml
activate foo
  foo -> bar: request
  activate bar
    bar -> baz: request
    activate baz
      baz -> baz: request
      activate baz
      deactivate baz
    baz --> bar: response
    deactivate baz
  bar --> foo: response
  deactivate bar
deactivate foo
@enduml

現場からは以上です。

JJUGナイトセミナー「OpenJDK祭り」に行ってきた #jjug

JJUGナイトセミナー「OpenJDK祭り」 に行ってきました。簡単に所感をまとめます。

jjug.doorkeeper.jp

所感

各 OpenJDK ディストリビューションの話でした。IBMJIT as a Service が気になります。JIT コンパイルするサーバーを複数の JVM が共有する感じなんだろうか。もしくは、JVM 同士がプロファイル情報を共有したりしたら面白そうだけど、それって CDS みたいなもんなんですかね、よく知らないけど。

それから Spring とか Cloud Foundry を商用で使うなら Pivotal Spring Runtime はありだろうなと思われます。

ここまで来たら Amazon Corretto とか BellSoft Liberica JDK の話も聴いてみたい。

あと、令和 Duke Tシャツ欲しかった...。1回戦でじゃんけん負けてもうた。

以下、メモから抜粋。

今、あらためてOracle提供のJDKを語る

www.slideshare.net

  • 2018のレポートでは8割くらいは JDK 8
  • Java 12
    • 新機能8つ
    • 令和対応
  • リリースドキュメントは要チェック
  • Oracle JDK のみ LTS 版あり
    • 各リリースに対する8年間の四半期リリース
  • Java SE 8 の Extended サポートは2025/03まで
  • フィーチャーリリース (6ヶ月ごとのリリース)
  • アップデートリリースは四半期に1度 (セキュリティパッチ)
  • Oracle Java SE Subscription
    • デスクトップ (PC/ユーザー)
    • サーバー (Processor)
    • Oracle IaaS には無償で付いてくる

Red HatのOpenJDK

www.slideshare.net

  • 古い JDKリポジトリRed Hat が引き継いでメンテしている (Backport)
  • REHL/JBoss にバンドル
  • 5年以上のサポート
  • 日本語サポートあり☆
  • OpenJDK 11 は Oracle JDK 11 GA の1ヶ月後に REHL にバンドル
  • IcedTea プロジェクト
    • OpenJDK のビルド/実行を実現
  • Shenandoah GC
  • 開発用途では Windows 版のみ
  • Oracle JDK との差異
  • 利用には要サブスクリプション
    • OSやミドルウェア、OpenShiftには含まれる
    • サーバー向け/デスクトップ向け
  • バックポートのリクエストもサポート☆
  • Oracle脆弱性情報を共有☆
    • 1日〜1ヶ月程度でリリース
    • 重大度が低いと追従に時間がかかるかも
  • コンテナで Java イメージを配布 (要サブスクリプション)
  • サポートは REHL のみ
  • ホットフィックスは最新のマイナーバージョンのみ☆
    • バックポートのリクエストは受け付けている
  • コンテナイメージはサブスクリプション契約なしで使える☆

Azul SystemsのOpenJDK製品技術概要

www.slideshare.net

  • Zulu Enterprise
  • Zulu Embedded
    • 組み込み向け
    • コンパクトプロファイルバイナリ提供
      • ヘッドフル/ヘッドレス
  • Zing
    • Falcon JIT コンパイラ
    • C4 GC
      • 継続的コンパクション
    • Ready Now
      • プロファイル情報を永続化/リロード
      • 立ち上がりの遅さを解消
    • 19.07 からカーネルモジュールなしで動作可能に
  • 開発拠点はロシア
  • Web Start 対応中☆

Open J9 + OpenJDK

www.slideshare.net

  • OpenJ9
    • IBM J9 VM (独自VM)
    • HotSpot VM と同じ Java プログラムが動く
    • -X-XX 系のオプションは一部利用不可
    • GCは独自実装
  • OpenJ9
    • Core 部分を Eclipse OMR として公開
  • HotSpot より高いパフォーマンス (短い時間でピーク性能に)
  • Java Dump (Javacore)
  • Dump エージェント (-Xdump)
    • クラス名やメソッドメソッドでフィルタリング
    • いろいろな条件でスタックトーレス出力したり外部コマンド実行したり
  • JIT as a Service☆
  • クラスライブラリは OpenJDK
    • VM のみ OpenJ9 に入れ替え
  • マルチプラットフォーム / Docker
  • AdoptOpenJDK / Docker Hub からダウンロード
  • パッチは AdoptOpenJDK 経由で提供

Pivotal Spring Runtime

docs.google.com

  • JDK サポート + アプリ実行環境サポート
    • Spring + Pivotal tc Server/Tomcat + OpenJDK/AdoptOpenJDK
  • OpenJDK ビルドを Buildpack として提供
    • cf push すると Java アプリを検知して OpenJDK をアタッチする
  • Cloud Native Buildpack☆
    • Buildpack API v3
    • コンテナイメージを生成
  • Pivotal Spring Runtime☆
    • Pod 課金モデル / コア課金モデル

JVM Language Summit Feedback TOKYO に行ってきた #jvmls_jp

先日、JVM Language Summit Feedback TOKYO に行ってきました。簡単に所感をまとめます。

connpass.com

所感

いつもは JJUG とか 言語関連の内容が中心の勉強会に参加することが多いのですが、今回は JVM 関連の内容が中心でした。おもしろそうだなと思って割と軽い気持ちで参加しちゃいましたが、わからないことが多くてちょっと付いていけないところがありました...。ぐぬぬ

JVMLS や OCW はあまり聞いたことがなかったのですが、普段自分が Java を使えているのもこういうイベントでいろいろと議論されていることが土台になっているのだと思います。

以下、メモから抜粋。ただ、今回はあまりメモが取れてません。特に最後の方は力尽きました...。

JVMLS(&OCW) 2019

speakerdeck.com

  • 言語に関するセッションは減ってきている
    • 2011年に invoke dynamic が入ったあたりから?
  • 最近はほぼ JVM Summit
  • Scala や Kotlin は JVMLS から巣立っていった
  • Project Skara
    • Mercurial から Git へ
    • レビューツールや issue 管理とどう連携するか
  • Java Compiler
  • JIT の欠点
    • コンパイラがリソースを食うので起動時に遅くなる
    • 高品質なデータを集めるのに時間がかかる
  • CDS (Class Data Sharing)
  • JWarmup
    • プロファイルデータを再利用
    • Alibaba が開発
  • JIT Caching
    • JIT が生成したコードを再利用する
  • 同じクラスタでは同じコードを利用する
    • JIT as a Service
  • AOT
  • GraalVM の最初の目的
    • Oracle DB で多言語(Truffle)を使う
  • TornadeVM
    • GPU 対応?
  • 微分可能プログラミング
    • dual number
  • C2 と GraalVM
    • C2 を滅ぼすのは難しい
    • 共存していくではないか

Project Loom & Project Panama Update

  • Loom == 織機
  • スレッドの問題点
    • フットプリント
    • Context Switching Cost
      • スレッドの状態をすべて保存 (保存する情報も多い)
  • JVM が管理するスレッド == Fiber
  • Operand Stack
    • Stack Traces
  • Thread は使い回して Task だけ切り替える
  • park / unpark が使える API
    • File は未対応
  • Fiber = Continuation + Scheduling
  • Continuation は限定継続
  • Panama
    • Java と Native を繋げる
    • JNI に置き換わるもの?
  • jextract
  • Off-Heap Memory Access
    • ByteBuffer / sun.misc.Unsafe

Vector API

www.slideshare.net

  • Panama 配下で開発されている
  • Immutable Vectors
    • JIT による高速化がしやすい
  • 今後は Valhalla との連携

Project Valhalla Update

Git ブランチモデル改善 (案)

昔は Subversion を使っていましたが、ここ数年は Git をメインで使うようになりました。特に現在参画しているプロジェクトでは基本的に月1でリリースがあり、開発の柔軟さやレビューのしやすさを考えると、やはり Git が適していると感じます。

ブランチモデルとしては、GitLab Flow のようなフローを採用しています。(実際に導入したのは自分ではないですが)

個人的には GitLab Flow は Git Flow より簡単で GitHub Flow より堅牢なブランチモデルという印象です。

about.gitlab.com

ブランチモデルは、代表的なフローをそのまま導入するのではなく、現場の状況や制約に合わせて柔軟にカスタマイズする方がよいです。

現状

現在の運用では、master を開発のメインブランチとして、開発機能ごとに feature ブランチを切っています。また、機能の実装が終わったらマージリクエストを投げて master にマージします。一応、pre-production ブランチがあるのですが、最近は怠慢の極みで master をそのままリリースしています...。本番環境のリリースが終わったら production ブランチにマージします。

で、現実問題として、例えば次のようなケースが発生します。

  • 2019/08 向けの開発と2019/09 向けの開発が並行する
  • ある機能が master にマージされたあとにリリース延期やペンディングになる

こうなると、メインブランチ1本の運用は若干心細い感じに。

現在の運用でもある程度はうまく回っているのですが、どうすればこれらの問題を解消できるか。以降はそのあたりを検討した際のメモです。実際に運用しているわけではなく、あくまでも です。

ちなみに、GitLab と Redmine を使っているので、その環境を前提としています。ただし、バージョンはここでは書けないくらい古いため、お察しください。

課題

  • 複数の開発を同時並行に
  • リリースを柔軟に

要件

最低限どういうブランチが必要か。

  • 本番環境同等のブランチ
  • 開発のベースとなるブランチ (並行運用あり)

検討事項

  • どのブランチをメインブランチとするか
  • マージ漏れをどうやって回避するか
  • 柔軟なリリースをどうやって実現するか

運用案

ブランチ

  • master: 本番環境同等のブランチ
  • release/yyyyMM: リリースブランチ
  • development/yyyyMM: 開発メインブランチ
  • feature/#{チケット番号}: 作業ブランチ
    • feature/#{チケット番号}_#{子チケット番号}: 作業ブランチの子ブランチ
  • hotfix/#{チケット番号}: 緊急のバグ修正ブランチ

毎月のリリースに合わせて開発メインブランチを切る。リリースする機能が fix したタイミングで master からリリースブランチを切る。リリース延期やペンディングなどがなければ開発メインブランチをそのままマージする。リリース延期やペンディングがある場合は、リリースする機能の feature ブランチからリリースブランチにマージリクエストを投げる。cherry-pick は使わず、マージリクエストだけで完結させる想定。

リリースが完了したら release ブランチを master にマージしてタグを作成する。開発が並行している場合は次期リリースの開発メインブランチに master をマージする。

基本的に master 以外は使い捨て。

マイルストーン

マージリクエストにはマイルストーンを設定する。リリース対象から外れた機能はマイルストーンを再設定する。こうすることで、未リリース機能のマージ漏れを防げる想定。GitLab のマージリクエスト画面ではマイルストーンでフィルタリングできるので便利。

マージリクエス

マージリクエストを投げる際のルール。

  • タイトル: 「#{チケット番号} {対応内容 or チケットタイトル}」
  • マイルストーン: release{yyyyMM}

フローイメージ

f:id:knt_mr:20190801182857p:plain

f:id:knt_mr:20190801182920p:plain

f:id:knt_mr:20190801182942p:plain

残課題

  • リリース延期が発生した際、リリース対象のブランチを漏れなくリリースブランチにマージできるか
    • チケット管理と組み合わせてマージ漏れを防ぐ? or GitLab の Issue で管理してみる? (☆要検証)
  • ブランチが増えすぎて運用コストが負担にならないか
    • リリース済みのブランチは削除する?
    • 未リリースの feature ブランチは残す (release ブランチに個別にマージするケースを考慮して)
  • 開発/リリースのブランチを作成するコストが負担にならないか?
    • 月1程度なので頑張る? or CLI ツールを自作する?
  • デプロイするブランチを切り替える作業が負担にならないか
    • 頑張る?