20200331のJavaに関する記事は11件です。

ガベージコレクションを意識してメモリリークを避ける

メモリリークを避ける為に、ガベージコレクションが機能する時としない時を自分の知る限り列挙してみました。

ガベージコレクションについて

オブジェクトがどの変数からも扱われなくなった場合にJavaの判断によって自動でオブジェクトが破棄される機能のこと。
「finalize()メソッド」がオブジェクトを破棄しているのだが、Javaの場合はこのメソッドを定義してもタイミングを管理することができない。

ガベージコレクションが機能するケース

単純な処理で確かめたので、メモリリークしやすいようにヒープ領域を下げて実行してます。
(2Mでメモリリークするように)
ヒープの初期サイズ -Xms1m
ヒープの最大サイズ -Xmx2m

①オブジェクトを参照している変数がすべてnullになった時

『変数にnullを代入する=変数はどのオブジェクトも表さなくなる』なので、あるオブジェクトを表している変数が全てnullになった場合に機能します。
nullを代入=「これもう使わないです」と言ってるようなものなので、ガベージコレクションさんが不要と判断してくれます。

public class AddList01 {
    public static void main(String[] args) {
        // 処理1
        List<String> list1 = new ArrayList<>();
        addList(list1);
        list1 = null;
        // 処理2
        List<String> list2 = new ArrayList<>();
        addList(list2);
        list2 = null;
    }

    private static void addList(List<String> list) {
        for (int i = 0; i < 100000; i++) {
            list.add("a");
        }
    }
}

②オブジェクトがもう扱われなくなった時

オブジェクトを扱っている変数の記載が以降なくなった時にガベージコレクションが機能します。
「ソース全体見たけど、これ以降扱わなくなってるし中身捨てて良いよね。」って感じでガベージコレクションさんが勝手に判断してくれます。

public class AddList02 {
    public static void main(String[] args) {
        List<String> list1 = new ArrayList<>();
        List<String> list2 = new ArrayList<>();

        // 処理1
        for (int i = 0; i < 100000; i++) {
            list1.add("a");
        }

        // 処理2
        for (int i = 0; i < 100000; i++) {
            list2.add("b");
        }
    }
}

③ループやtry-catch内のローカル変数

ループ内で宣言、インスタンス生成された等、ループ内で完結オブジェクトはループを抜ける時にガベージコレクションが機能してくれる。
ループ内で宣言した変数はループ外で扱うことができないからである。

public class AddList03 {
    public static void main(String[] args) {
        try {
            // 処理1
            for (int i = 0; i < 10; i++) {
                List<String> list1 = new ArrayList<>();
                for (int j = 0; j < 100000; j++) {
                    list1.add("a");
                }
            }

            // 処理2
            List<String> list2 = new ArrayList<>();
            for (int i = 0; i < 10; i++) {
                list2 = new ArrayList<>();
                for (int j = 0; j < 100000; j++) {
                    list2.add("b");
                }
            }

            // 処理3
            int i = 0;
            while (i < 10) {
                List<String> list3 = new ArrayList<>();
                for (int j = 0; j < 100000; j++) {
                    list3.add("c");
                }
                i++;
            }

            // 処理4
            List<String> list4 = new ArrayList<>();
            for (int j = 0; j < 100000; j++) {
                list4.add("d");
            }
        } catch (Exception e) {
            e.printStackTrace();
        }

        // 処理5
        List<String> list5 = new ArrayList<>();
        for (int j = 0; j < 100000; j++) {
            list5.add("e");
        }
    }
}

ちなみに変数「list2」をループ後に扱っていた場合は最後のループのインスタンスのみを残す。

public class AddList031 {
    public static void main(String[] args) {
        List<String> list2 = new ArrayList<>();
        for (int i = 0; i < 10; i++) {
            list2 = new ArrayList<>();
            for (int j = 0; j < 50000; j++) {
                list2.add("b");
            }
        }
        System.out.println(String.join("", list2));
    }
}

④メソッドのローカル変数

③のループと同じ原理です。ただし、引数で参照している値や戻り値などは残る。

public class AddList04 {
    public static void main(String[] args) {
        addList();
        addList();
        addList();
    }

    private static void addList() {
        List<String> list = new ArrayList<>();
        for (int j = 0; j < 100000; j++) {
            list.add("a");
        }
    }
}

ガベージコレクションが機能しないケース

①メインメソッドでインスタンスを保持して別メソッドでオブジェクトを参照する。

機能するケース②では扱われなくなったオブジェクトは破棄されていましたが、別メソッドでオブジェクト参照するとガベージコレクションは機能しなくなる。
これどうしてなんでしょう?

public class AddList05 {
    public static void main(String[] args) {
        // 処理1
        List<String> list1 = new ArrayList<>();
        addList(list1);

        // 処理2
        List<String> list2 = new ArrayList<>();
        addList(list2);
    }

    private static void addList(List<String> list) {
        for (int i = 0; i < 100000; i++) {
            list.add("a");
        }
    }
}

②リストのインスタンスをフィールドで生成している

クラスのいろんな場所で使われるので、引数が多くならないようにフィールドに記載してしまうケース。
リストのスコープが広すぎてガベージコレクションが不要と判断してくれないようです。

public class AddList06 {
    private List<String> list1 = new ArrayList<>();
    private List<String> list2 = new ArrayList<>();

    public static void main(String[] args) {
        AddList06 addList06 = new AddList06();

        // 処理1
        for (int i = 0; i < 100000; i++) {
            addList06.list1.add("a");
        }

        // 処理2
        for (int i = 0; i < 100000; i++) {
            addList06.list2.add("a");
        }
    }
}

③メソッドの戻り値で受け取ってきたもの

mainメソッドでインスタンスを生成していなくても、呼び出したメソッド先でインスタンスを生成し戻り値で受け取ったものは残留してしまう。

public class AddList07 {
    public static void main(String[] args) {
        // 処理1
        List<String> list = addList();

        // 処理2
        list = addList();

        // 処理3
        list = addList();
    }

    private static List<String> addList() {
        List<String> list = new ArrayList<>();
        for (int i = 0; i < 50000; i++) {
            list.add("a");
        }
        return list;
    }
}

④nullを代入するのではなく別の方法で使わないようにしている。

使用しているリストで使わなくなった値をリストのメソッドを使って削除しても、nullにしている訳ではないのでオブジェクトは参照されているままのようです。

public class AddList08 {
    public static void main(String[] args) {
        // 処理1
        List<String> list1 = new ArrayList<>();
        addList(list1);
        list1.clear();

        // 処理2
        List<String> list2 = new ArrayList<>();
        addList(list2);
        for (int i = 0; i < list2.size(); i++) {
            list2.remove(i);
        }

        // 処理3
        List<String> list3 = new ArrayList<>();
        addList(list3);
    }

    private static void addList(List<String> list) {
        for (int i = 0; i < 50000; i++) {
            list.add("a");
        }
    }
}

まとめ

そんな単純じゃないよって思うかもですがとりあえず
・「なるべく処理をメソッドで細かく分けてローカルで持つべし」
・「無駄に変数やインスタンスを作らず使いまわせるところは使いまわすべし」
・「フィールドでの変数宣言は控えろ」
と言うのを感じました。

あとはDBからSQL取得、登録など行う処理がある場合は処理目的ごとに分けることでしょうか。
SQLが複数あると時間がかかるかもですが処理が落ちるよりはましかと思いました。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

プログラマーのための「圏論入門」入門

$\require{AMScd}$

はじめに - 「圏論入門」は難しい!

 タイトルは誤字ではありません。
 この記事は、圏論の門を叩こうとして諦めたという人を、とりあえず圏論の門の内側に入れるだけ入れてあげようという趣旨で書かれたものです。入門記事や入門書を読むための準備のつもりで、軽い気持ちで読んでください。発展的な内容は一切ありません。物足りなく感じられたら、もっときちんとした入門を読むことをお勧めします。
 この記事では、分かりやすい圏の例として型のあるプログラミング言語を使います。タグに困ってJavaをC++を付けましたが、ほとんどそれらの成分はありません。すみません。

圏の定義

 あまり真剣に読まなくても大丈夫です。型は「対象」、関数は「射」です。

対象 (Object)

  • この記事では$A$, $B$ のように大文字で書きますが、小文字で書かれる場合もあります。
  • ある圏$C$ の対象全ての集合を $Obj(C)$ や$|C|$ などと書きます。
  • プログラミング言語でいう「型」です。対象は構造を持たない、いわばプリミティブな型です。継承関係や暗黙変換という概念もありません。

射 (Morphism)

  • この記事では$f$, $g$ のように小文字で書きますが、大文字で書かれる場合もあります。
  • ある圏$C$ の射全ての対象を $Mor(C)$ や$C$ などと書きます。
  • プログラミング言語でいう「関数」です。
  • この関数は必ず引数一つと戻り値を持ちます。引数の型にあたる対象を「始域(domain)」、戻り値の型にあたる対象を「終域(codomain)」と呼びます。
    • ある対象$A$からある対象$B$への射、すなわち「ある型$A$を受け取ってある型$B$を返す関数 $f$」を次のように表します。 $f : A \rightarrow B$
      • 矢印の形で区別はなく、対象と対象を結ぶ矢印は全て射です。e.g. $f : A \longmapsto B$
      • また、矢印の向きを逆にして書かれる場合もあります。e.g. $f : B \leftarrow A$
    • また、$f$の始域(引数の型)を$dom(f)$、終域(戻り値の型)を$cod(f)$のようにして表すことがあります。

 射$f : A \rightarrow B$は、Javaで書くと以下のような型を持つ関数です。

class C {
    B f(A a) {
        ..
    }
}

 $A \rightarrow B$という射は必ずしも一つではないことに注意してください。$f : A \rightarrow B$, $g : A \rightarrow B$, ほかにもたくさんあるかもしれません。同じ型を持つ関数でも、複数の実装があり得るということです。

int f(int n) {
    return n + 1;
}

int g(int n) {
    return n - 1;
}

射の合成 (Composition)

  射、すなわち関数の合成です。プログラミングに慣れていても、関数の合成という言葉には馴染みがないかもしれません。次の二つの関数f, gと、それらを合成した関数hを見てください。

B f(A a) {
    ..
}

C g(B b) {
    ..
}

C h(A a) {
    return g(f(a));
}

 この関数hは、fした後にgをすることで、fgを合成しています。
 ところでこの関数の合成は、副作用を考えない場合、実装が一意に存在します。すなわち、任意の関数合成を一般的に書くことができます。Javaでは、この関数は java.util.Function#composeとして提供されています。C++で書くと次のようになります。

template<class F, class G, class A, class C>
C compose(F f, G g) {
    return [=] (A a) { return g(f(a)); };
}

  圏論では、このような射の合成を $g \circ f$ と書きます。

テキスト順記法と図式順記法

 $g \circ f$ の順序はg(f(a))から取られています。これはテキスト順記法と呼ばれています。しかし、「g after f」よりは「fしてg」と読んだ方が直感的で便利な場合があります。そのため$f ; g$という順序を反転した記法があり、こちらは図式順記法と呼ばれています。図式というのは、次のような対象と対象を射が繋ぐようなものです。

\begin{CD}
A @>f>> B @>g >> C
\end{CD}

記法の整理

  • $f : A \rightarrow B$, $g : B \rightarrow C$という二つの射があった時に、
    1. テキスト順記法は、この合成を$g \circ f$ のように書くものです。
      • 混乱の恐れがない場合は、$gf$ のように省略して書かれることがあります。
    2. 図式順記法は、この合成を$f ; g$ のように書くものです。
      • $f \ggg g$ のように書かれることもあります。

 どちらとも合成の結果は $A \rightarrow C$ です。あくまで書き方が違うだけで、全く同じ意味です。
 本の筆者は好きな記法を使いますので、記法の違いで混乱しないようにしてください。時には、自分が読みやすい方で書き直して考えることも重要です。
 この記事では基本的に図式順記法を使っていきます。(一般的にはテキスト順記法の方がポピュラーだと思います)

恒等律

 全ての対象には、恒等射という射 $id_A : A \rightarrow A$ が一意に存在します。これはJavaで書くと次のような関数です。

static <A> A id(A a) {
    return a;
}

 圏論にはジェネリクスやテンプレートといったものはないため、$id_A$のように対象を明示します。
 恒等射には、射の合成をしたときに元の射に何もしないという性質があります。すなわち、$f : A \rightarrow B$ があったときに、$id_A ; f$ と、$f ; id_B$ と、$f$ は同じものを表しています。

射の合成の制限

 射の合成$f ; g$が定義されるのは、$cod(f) = dom(g)$を満たすときに限ります。逆に、これを満たすときには常に射の合成を定義できます。言い換えると、射の合成は、終域と始域がうまく噛み合う射どうしでないとできないということです。
 これは関数の合成を考えれば当たり前のことです。戻り値の型と引数の型が噛み合っていない関数どうしを合成することはできません。

B f(A a) {
    ..
}

Y h(X x) {
    ..
}

Y fh(A a) {
   return h(f(a))  // コンパイルエラー, BとXの型が合わない
}

結合律

 射の合成は結合律を満たします。つまり、$f ; (g ; h)$ と$(f ; g) ; h$ は全く同じものを表します。
 Javaで書くと次のような感じです。ghghを予め合成したもので、fgfgを予め合成したものだと思ってください。fgh1fgh2はどう見ても同じことをしています。同じことをしてくれないと困るということです。

D fgh1(A a) {
    return gh(f(a));
}

D fgh2(A a) {
    return h(fg(a));
}

 図で書くと次のような感じです。この図は可換図式と呼ばれており、どの矢印(射)をなぞっても出発地点の対象(始域、引数の型)と到着地点の対象(終域、戻り値の型)さえ同じならば結果も等しいことを示す図です。(どちらかといえば、この性質を担保するために結合律が要求されています)

\begin{CD}
A @>f>> B @>g;h>> D \\
@Vid_AVV @VgVV @Vid_DVV \\
A @>f;g>> C @>h>> D\\
\end{CD}

 試しにAから好きなルートを通ってDまで行ってみてください。どのルートを使うように関数合成をしても、全く同じ関数$A \rightarrow D$ が得られるはずです。

圏まとめ

 型は「対象」関数は「射」という圏を例にしながら圏が成り立つ条件についてまとめました。プログラミング言語の圏を例にすると、合成の条件や単位律、結合律が自然なものに思えるはずです。
 実際には、これらの条件を満たすものは全て圏とみなすことができ、その無数に考えられる圏の中にはプログラミング言語の圏からは連想できないようなものもたくさん存在します。射は必ずしも関数のような何かではありませんし、対象も必ずしも型っぽい何かではありません。こちらでは、しりとりの圏や行列の圏、あみだくじの圏などが紹介されています。

可換図式

 圏の定義の次は、圏論の勉強をしているときにしばしば説明なく出てくるあの図です。上でもちらっと出しましたが、ここで改めて紹介します。
 これは「出発点(始域)と到達点(終域)が同じならどのルート(射)を通っても同じ結果になる」ということを表した図であり、射の合成を視覚的に説明するのに使われます。
 下の可換図式は、4つの射$f$, $g$, $h$, $k$ が存在して、$f ; g = h ; k$ であることを表しています。逆に「4つの射が下の図式を可換にする」というような言い方で射の合成を説明することもあります。

\begin{CD}
A @>f>> B \\
@VhVV @VgVV \\
C @>k>> D \\
\end{CD}

同型

 圏論の勉強をしているときにしばしば説明なく出てくる単語で、「同型」というものがあります。
 同型とは、$f ; g = id_A$, $g ; f = id_B$を満たすような射$f : A \rightarrow B$と射$g : B \rightarrow A$が存在する対象$A$と$B$の関係をいい、$f$, $g$をそれぞれ同型射と呼びます。すなわち、以下の図式を可換にするような射$f$, $g$が存在するとき、対象$A$, $B$は同型であるといいます。(圏論チックな物言いですが、理解できたでしょうか?)
 また、同型である2つの対象を$A \simeq B$のように表します。

\begin{CD}
A @>f>> B \\
@AgAA @VgVV \\
B @<f<< A \\
\end{CD}

 プログラミング言語に例えれば、同型な型とは全く情報を落とすことなく相互変換可能な型といったところです。具体的にはどんな型があるでしょうか。
 例えば、String型とint型はどうでしょう。

String f(int n) {
    return String.valueOf(n);
}

int g(String s) {
    return Integer.parseInt(s);
}       

 一見よさそうに見えますが、parseIntは例外を発生させる可能性があります。例外は副作用なので、これではダメですね。int型の情報を落とさずにString型に変換することはできそうですが、String型の情報を落とさずにint型に変換することはできなそうです。例えば"Hello"int型で表すことはできません。
 次の例はどうでしょうか。

enum Unit { UNIT };

List<Unit> f(int n) {
    return Collections.nCopies(n, Unit.UNIT);
}

int g(List<Unit> list) {
    return list.size();
}

 値が一つしかない型(Unit)を要素に持つリストと、整数型のintです。
 コンピュータの限界としてintの扱える範囲や使えるメモリに限りがあることに目を瞑れば、これらは全く情報を落とさずに互いの型を行き来することができます。というか要素が単一では、そのリストには長さ以上の情報がありません。

 Pair<Integer, String>Pair<String, Integer>はどうでしょうか。これは相互変換というか、そもそも同じとみなしてもいいような何かですね。

 ざっくり言えば同型とは、圏論の上では同じとみなしてもいいような関係のことをいいます。ただし同値ではないことに注意してください。

Pair<Integer, String> p = new Pair<String, Integer>(); // 同型でもコンパイルエラーになる

 定義に戻ると、$f ; g = id_A$, $g ; f = id_B$を満たすとき$A$と$B$をざっくり同じだとみなせるということです。

まとめ

 この記事では、ある程度複雑かつ慣れ親しんだ圏としてプログラミング言語の圏を使いながら圏の定義を確認しました。また、十分一般的な知識として前提にされる「可換図式」と「同型」についてもプログラミング言語の圏を使って説明しました。
 ここまで読んで頂けたのであれば、他のもう少しまともな入門記事を読んで、より一般的で厳密な圏の定義を学ぶことをお勧めします。そのうえで、話が一般的すぎて何を言っているのか分からない、となった時に、再び「プログラミング言語の圏」を思い出してもらえればと思います。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

屋内環境を手軽にモニタリングする~⑪BH1750FVI(代用)から照度をJavaで取得する(I2C / Pi4J)~

以前、こちらの記事で紹介した、環境情報をモニタリングする簡易ツールは、照度の情報をテキサス・インスツルメンツ社のSensorTag CC2650から取得します。ただ、BLE環境センサータグとして、永らく世界的に知られたCC2650は生産中止のため、今後、入手が困難になると思われます。そこで、別のセンサーにも対応することにしました。

今回、新たな照度センサーとして、ROHMのBH1750FVIを選択しました。手頃な価格であり、数百円で入手できます。

BH1750FVIセンサーとは

このセンサーは、照度を測定することができます。インターフェースはI2Cで、I2Cアドレスには、0x23or0x5cのいずれかを指定することができます。また、Raspberry Pi 3B or 4BのI2Cバスは、01の二つありますので、Raspberry Pi 3B or 4Bでは、BH1750FVIを最大4個、同時に使用することができます。
rainy_bh1750fvi_floor.png

作成したBH1750FVIのJavaライブラリ(bh1750fvi-driver)はGithubで公開しています。OSとセンサーの設定手順も書いておきました。また、このライブラリを組み込んだ環境情報モニタリングの簡易ツールは、こちらのGithubで公開しています。

Javaライブラリの基本的な使い方

bh1750fvi-driverの簡単な使い方を掲載します。以下のサンプルコードでは、I2Cバス=1、アドレス=0x23を指定しています。

import com.pi4j.io.i2c.I2CBus;

import io.github.s5uishida.iot.device.bh1750fvi.driver.BH1750FVIDriver;

public class MyBH1750FVI {
    private static final Logger LOG = LoggerFactory.getLogger(MyBH1750FVI.class);

    public static void main(String[] args) {
        BH1750FVIDriver bh1750fvi = null;
        try {
            bh1750fvi = BH1750FVIDriver.getInstance(I2CBus.BUS_1, BH1750FVIDriver.I2C_ADDRESS_23);
            bh1750fvi.open();

            while (true) {
                float value = bh1750fvi.getOptical();
                LOG.info("optical:" + value);

                Thread.sleep(10000);
            }
        } catch (InterruptedException e) {
            LOG.warn("caught - {}", e.toString());
        } catch (IOException e) {
            LOG.warn("caught - {}", e.toString());
        } finally {
            if (bh1750fvi != null) {
                bh1750fvi.close();
            }
        }
    }
}

このように使い方はシンプルです。なお、Pi4JというRaspberry PiのGPIOを使用するためのJavaライブラリを使用して作成しました。

最後に

このJavaライブラリを組み込んだ簡易ツールには、センサー値をダッシュボードでモニタリングする機能とJSON形式でMQTTブローカに送信する機能があります。

最後に、簡易ツールはこちらのGithubで公開しています。

一連の記事

このシリーズは、以下の記事から構成されます。
1. 動機とコンセプト
2. Bluetooth LEアドバタイズ信号をJavaでキャッチする(Bluetooth LE / bluez-dbus)
 関連するGithubはこちら
3. TI SensorTag CC2650から温度/湿度/照度などをJavaで取得する(Bluetooth LE / bluez-dbus)
 関連するGithubはこちら
4. MH-Z19BからCO2濃度をJavaで取得する(シリアル通信 / jSerialComm)
 関連するGithubはこちら
5. PPD42NSからPM2.5濃度をJavaで取得する(GPIO / Pi4J)
 関連するGithubはこちら
6. 産業オートメーション機器の稼動情報をJavaで取得する(OPC-UA / Eclipse Milo)
 関連するGithubはこちら
7. 簡易ツールにまとめる
 関連するGithubはこちら
8. 後記
9. モーション検知(HC-SR501 / RCWL-0516)をJavaで取得する(GPIO / Pi4J)
 関連するGithubはこちら(HC-SR501)こちら(RCWL-0516)
10. BME280(代用)から温度/湿度/気圧をJavaで取得する(I2C / Pi4J)
 関連するGithubはこちら
11. BH1750FVI(代用)から照度をJavaで取得する(I2C / Pi4J)(今回)
 関連するGithubはこちら

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

屋内環境を手軽にモニタリングする~⑩BME280(代用)から温度/湿度/気圧をJavaで取得する(I2C / Pi4J)~

以前、こちらの記事で紹介した、環境情報をモニタリングする簡易ツールは、温度、湿度、気圧の各情報をテキサス・インスツルメンツ社のSensorTag CC2650から取得します。ただ、BLE環境センサータグとして、永らく世界的に知られたCC2650は生産中止のため、今後、入手が困難になると思われます。そこで、別のセンサーにも対応することにしました。

世間には、温湿度センサーは数多く存在し、どれを選べばよいか正直悩みます。ちょうどその時、参考になる情報を見付け、結果、BoschのBME280を選択しました。手頃な価格であり、数百円で入手できます。

BME280センサーとは

このセンサーは、温度、湿度、気圧を測定することができます。なお、温度は気圧の測定値を補正する用途がメインのようですが、折角なので値そのものも利用します。インターフェースはI2CとSPIに対応していますが、ここではI2Cを使います。I2Cアドレスには、0x76or0x77のいずれかを指定することができます。また、Raspberry Pi 3B or 4BのI2Cバスは、01の二つありますので、Raspberry Pi 3B or 4Bでは、BME280を最大4個、同時に使用することができます。
rainy_bme280_floor.png

作成したBME280のJavaライブラリ(bme280-driver)はGithubで公開しています。OSとセンサーの設定手順も書いておきました。また、このライブラリを組み込んだ環境情報モニタリングの簡易ツールは、こちらのGithubで公開しています。

Javaライブラリの基本的な使い方

bme280-driverの簡単な使い方を掲載します。以下のサンプルコードでは、I2Cバス=1、アドレス=0x76を指定しています。

import com.pi4j.io.i2c.I2CBus;

import io.github.s5uishida.iot.device.bme280.driver.BME280Driver;

public class MyBME280 {
    private static final Logger LOG = LoggerFactory.getLogger(MyBME280.class);

    public static void main(String[] args) {
        BME280Driver bme280 = null;
        try {
            bme280 = BME280Driver.getInstance(I2CBus.BUS_1, BME280Driver.I2C_ADDRESS_76);
            bme280.open();

            while (true) {
                float[] values = bme280.getSensorValues();
                LOG.info("temperature:" + values[0]);
                LOG.info("humidity:" + values[1]);
                LOG.info("pressure:" + values[2]);

                Thread.sleep(10000);
            }
        } catch (InterruptedException e) {
            LOG.warn("caught - {}", e.toString());
        } catch (IOException e) {
            LOG.warn("caught - {}", e.toString());
        } finally {
            if (bme280 != null) {
                bme280.close();
            }
        }
    }
}

このように使い方はシンプルです。なお、Pi4JというRaspberry PiのGPIOを使用するためのJavaライブラリを使用して作成しました。

最後に

このJavaライブラリを組み込んだ簡易ツールには、各センサー値をダッシュボードでモニタリングする機能とJSON形式でMQTTブローカに送信する機能があります。

最後に、簡易ツールはこちらのGithubで公開しています。

一連の記事

このシリーズは、以下の記事から構成されます。
1. 動機とコンセプト
2. Bluetooth LEアドバタイズ信号をJavaでキャッチする(Bluetooth LE / bluez-dbus)
 関連するGithubはこちら
3. TI SensorTag CC2650から温度/湿度/照度などをJavaで取得する(Bluetooth LE / bluez-dbus)
 関連するGithubはこちら
4. MH-Z19BからCO2濃度をJavaで取得する(シリアル通信 / jSerialComm)
 関連するGithubはこちら
5. PPD42NSからPM2.5濃度をJavaで取得する(GPIO / Pi4J)
 関連するGithubはこちら
6. 産業オートメーション機器の稼動情報をJavaで取得する(OPC-UA / Eclipse Milo)
 関連するGithubはこちら
7. 簡易ツールにまとめる
 関連するGithubはこちら
8. 後記
9. モーション検知(HC-SR501 / RCWL-0516)をJavaで取得する(GPIO / Pi4J)
 関連するGithubはこちら(HC-SR501)こちら(RCWL-0516)
10. BME280(代用)から温度/湿度/気圧をJavaで取得する(I2C / Pi4J)(今回)
 関連するGithubはこちら
11. BH1750FVI(代用)から照度をJavaで取得する(I2C / Pi4J)
 関連するGithubはこちら

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

新しいJavadocのタグ @apiNote @implSpec @impleNote

初めに

javadocを眺めていたらふと見たことが無い@apiNoteというタグを見つけました。
少し調べてみると @apiNote @implSpec @impleNote というタグがJava 8から追加されているようです。

公式ドキュメントなどを探しても記載がないため少し調べてみました。

概要

これらのタグはLambda式の仕様策定の副産物で生まれたそうです。
2013年4月にJDK Javadoc maker が更新され使えるようになったとのこと。

ただし、これらのタグはJDKドキュメントで使用する以外に普及させる計画が無いそうです。
そのため公式ドキュメントにも記載がされていないということのようです。

各タグの詳細

それぞれのタグで記載されている内容を見ていきます。

@apiNote

APIに関する解説、根拠、または例を記載します。
通常javadocに記載するAPI仕様の補足的な記載をします。

Object#isNull
APIの注が@apiNoteで記載されている箇所です。

@implSpec

デフォルトメソッドの仕様を記載します。
もしくはオーバーライドして実装するべきメソッドの詳細を記載します。

ConcurrentMap#replaceAll
「実装要件」が@implSpecで記載されている箇所です。

Clock
クラス説明の中の「実装要件」が@implSpecで記載されている箇所です。

@implNote

提供される実装の有益な情報や補足、注意するべき内容を記載します。

ConcurrentMap#forEach
Clock
「実装上の注意」が@implNoteで記載されている箇所です

使用上の注意

これらのタグはデフォルトではjavadocコメントとして生成されません。
コマンドラインで出力する場合には以下の引数を与える必要があるようです。

-tag "apiNote:a:API Note:"
-tag "implSpec:a:Implementation Requirements:"
-tag "implNote:a:Implementation Note:"

参考記事

https://blog.codefx.org/java/new-javadoc-tags/

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Java Silver SE8 (Oracle Certified Java Programmer, Silver SE 8)に合格した話

はじめに

4月から入社3年目になるエンジニアです。
Javaに関する知識は業務でJavaのWebアプリをServletで作成したくらいです。ほぼ初心者です。
今回この試験の学習を通してオブジェクト指向言語またはJava特有の言語仕様の知識を深めることができたと感じています。
自分のように会社から何かベンダー資格を取れと言われている方や、エンジニアとしてのスキルアップとして何を取ろうか迷っているという方におすすめできる資格だと思います。

Oracle Certified Java Programmer, Silver SE 8とは?

公式からの抜粋です。

Javaアプリケーション開発に必要とされる基本的なプログラミング知識を有し、上級者の指導のもとで開発作業を行うことができる開発初心者向け資格です。日常的なプログラミング・スキルだけでなく、さまざまなプロジェクトで発生する状況への対応能力も評価することを目的としています。

学習時間

試験1か月前から平日は1日あたり30分、休日は2~3時間くらいしました。

学習方法

徹底攻略 Java SE 8 Silver 問題集[1Z0-808]対応(通称黒本)
1冊のみです。これだけで十分だと思います。
実際、巻末の模擬試験問題から(ほとんど)同じ問題が何問も出題されました。

1~9章までの問題を解いて完璧にしてから、巻末の2回分の模擬試験を解きました。
模擬試験で間違った問題の解説を読み、それでも不安なら1~9章までの該当箇所の問題と解説を読むという流れで当日までに本書を3周しました。

受験までの流れ

この方の記事を参考にしました。他のベンダー資格は受けたことないので比較できませんが、かなりめんどくさかったです。

試験当日

自分は13:30開始にしました。それぞれベストなパフォーマンスが発揮できそうな時間に試験予約してください。少し早めに試験会場に入り、受付手続きを済ませてから試験開始です。
テストセンターのPCで77問解き、見直しをしてもなお1時間(試験時間は2時間半)くらい余しましたが、終了して会場を出ました。
会場から駅に向かって歩いている途中にOracleから合否メールが届きました。早すぎ。
結果は正答率78%で合格でした(合格ラインは65%)。

感想

満点を取る気で臨んだのですが、黒本とは毛色が違う問題や、拾いきれなかった問題、テストセンターの空気に飲まれてかなり落としてしまいました。

個人的に試験勉強していて大切だと思った箇所

  • Javaでboolは使用できない(C#等と混同しない)。

  • インターフェース内のメソッド式は暗黙的にコンパイラが書き換える。そのため、サブクラスでの対象メソッドの実装のアクセス修飾子はpublicのみ許容。

void test();
// ↑コンパイル前 ↓コンパイル後
public abstract void test();
  • コンストラクタ内の処理は明示しなければ、暗黙的に先頭でスーパークラスのコンストラクタを引数なしで呼び出す。
public class Test extends Sample{
    public Test(){
        System.out.println("test");
    }
}
/* ↑コンパイル前 ↓コンパイル後 */
public class Test extends Sample{
    public Test(){
        // Sampleクラスのデフォルトコンストラクタを呼び出す
        super();
        System.out.println("test");
    }
}
  • コンストラクタで明示的にスーパークラスのコンストラクタを呼び出す場合、必ず先頭で呼び出さなければならない。
public Test(){
    System.out.println("test");
    super("A"); // コンパイルエラー
}
  • アクセス修飾子は厳しい順に private < なし < protected < public
アクセス修飾子 アクセス権限
private 同じクラス内のみ許容
なし 同じパッケージ内のみ許容
protected 同じパッケージ内+サブクラスからのみ許容
public 全て許容
  • オーバーライドする際は、シグニチャを変更せずにスーパークラスのメソッド宣言と同じか緩いアクセス修飾子で宣言する必要がある。
public class Sample {
    protected void test(String name) {
        System.out.println(name);
    }
}
public class Test extends Sample {
    // protected,publicは許容。修飾子なし,priveteはコンパイルエラー
    public void test(String name) {
        super.test(name);
        System.out.println(name);
    }
}
  • スーパークラスのコンストラクタ呼び出し後に、オーバーロードしたコンストラクタ呼び出しはできない。

さいごに

ここまで読んでいただきありがとうございました。
何か間違っている箇所等ございましたら、容赦なく指摘していただけると幸いです。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

初めてのSpring案件を終えたメモ ~データベース編~

はじめに

ピュアJavaとDBの基本知識のみスタートからのSpring案件が波乱の末ひと段落したので、所感や覚書などを。:speech_balloon:
記事の内容としてはチュートリアル以上、実践未満のイメージ。
掲載コードは動作確認できていないのであくまで参考で。:pray:

開発環境

RedHat(開発環境はCent)7系
Java8
SpringBoot2.1.6
MySQL8系
STS

SpringDataJPAについて

関連用語が難しい。
JPA(仕様)を実装したもの(Hibernate)を利用したフレームワークがSpringDataJPA。自信ない。
動的クエリ作成で使うCriteriaAPIはJPAの機能だから参考にしていい。
サンプルで見かけるEclipseLinkはHibernateとは違う実装なので参考にしないほうがよさそう。
でもHibernateの機能でなくSpringの機能を推奨してたりするから、油断ならない。

エンティティのライフサイクルを知っておくと、親子関係のエンティティの追加や削除、明示的トランザクションとかで不審な動きをするとき解決の助けになるかも。
https://qiita.com/Maruo110/items/4dc4a49aedd6323ebfdb

H2DB

インメモリデータベース。

Springに含まれてるから環境にRDBMSを用意しなくてもデータベースが使える。
アプリを実行するたびに初期化されるる。カジュアルに使えるのでプロトタイプやテストに使える。

SQLの文法はH2DB独自のものだけどmodeオプションで他のRDBMSっぽい文法が使えるけど完全ではない。
http://www.h2database.com/html/features.html#compatibility
mysqlモードでcreate文のインデックスの指定の仕方はこれで吸収できたけど、
ストアドとかトリガー、tinyint型のマッピングなどは吸収できなかった。
そもそも、ここで困るようだとDBに依存しすぎってことなんだろうね。:kissing:

Entityについて

シンプルにつくるとテーブルの情報だけのクラスになるけど、結構いろいろできる。
:blush:データの加工はやってもよさそう。
:blush:データの更新(永続化)前のタイミングなどで呼び出せるコールバック系メソッドも便利。
:confused:AuditingEntityListenerを使えばDI対象クラスも使えるけど、テストが難しくなる。
:confused:継承もできるけど、lombokと相性がよくない。
:angry::angry::angry:「とりあえず動かしてみた」系サンプルでたまに見られるformとの兼用なんかをしようものなら瞬く間に複雑になるので、ちゃんとformとEntityは分けること。

Employee.java
@Entity
@Data
class Employee {
    @Id
    private long id;

    private String lastName;

    private String firstName;

    //データ加工
    public String getFullName(){
      return this.lastName + this.firstName;
    }

    private Date updateDate;

    //コールバック系メソッド
    @PreUpdate
    private void preUpdate() {
      setUpdateDate(new Date());
    }
}

関連付けは鬼門。古今東西の情報が入り乱れている。
OneだかManyだか、相互参照、遅延フェッチ、削除時の動作など正しく設定できているか早いうちに確認しないと、後からの修正は影響範囲が泣ける。:joy:

Repositoryについて

命名規則通りのメソッドを宣言すると、自動でクエリを実装してくれる。
countやexsist、Entityに関連付けを定義していれば関連テーブルのカラムも指定できる
https://spring.pleiades.io/spring-data/jpa/docs/current/reference/html/#jpa.query-methods.query-creation

名前が長くなりがちだから、適切な名前でラップした方が良さそう。

戻り値にはOptionalが使えるので積極的に使うといい。

findAllは検索条件やページング情報、ソート情報を引数に渡せる。
でもページングかつソートのときは、ページング情報にソート条件を入れる。
ページングするときは内部でselect * ~select count ~が発行されているため注意。

動的クエリについて

Specificationを使った。これも鬼門。

基本の使い方はすんなり導入できるけど、少し変わったことしようとすると理解しないといけないことが多い。
具体的には CriteriaAPI とか ラムダ式 とか。
メタモデルも便利そうだけど今回は知るのが遅くて使わなかった。

・distinct

hogeSpecification.java
public Specification<Hoge> distinct() {
    @Override
    public Predicate toPredicate(Root<Users> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
        cb.distinct(true);
        return cb.equal(root.get("id"),root.get("id")); //必ずtrue。苦肉の策。
    }
}

・fetch
結合はroot.joinで出来るけど、これ指定しないと

select *
   from a 
    innner join b1 on a.id = b1.id
    innner join b2 on a.id = b2.id
  where b1.id = {1}
    and b2.name = {2};

みたいに何度も同じテーブルと結合するSQLが発行されてしまった。
原因も解決方法も、理解できていない。:poop:

また、フェッチしたクエリでページングしたいとき以下の記述を入れる必要がある。
if (query.getResultType() != Long.class && query.getResultType() != long.class)
なぜならページングのときはselect * ~select count ~が発行されるけどcountのときフェッチできないから回避する必要がある。。。えぇ?:poop:
https://coderanch.com/t/656073/frameworks/Spring-Data-fetch-join-Specification

それを踏まえてこんなコードになった。

hogeSpecification.java
public Specification<Hoge> fetch() {
    @Override
    public Predicate toPredicate(Root<Users> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
        if (query.getResultType() != Long.class && query.getResultType() != long.class){
            root.fetch("childTable", JoinType.LEFT); //結合するテーブルはEntityで定義しておく
        }
        return cb.equal(root.get("id"),root.get("id")) //必ずtrue。苦肉の策。
    };
}

2つ以上フェッチできないらしく、親子孫関係に使えなかったのでクエリ結果を加工してごまかす羽目になった。むずかしい。。。:poop::poop:

・制御(条件分岐とかループとか)
サンプルだとメソッドチェーンで全て繋げるけど、もちろん途中で切ってもいい。
SQLでいうところのIN句の複数カラム指定( where (name,id) in ((hoge,1),(fuga,2)) みたいなこと)はこれで実装した。

hogeService.java
public List<Hoge> serch(HogeForm form) {
    Specification<hoge> 検索条件 = 
        Specification
            .where(検索条件1)
            .and(検索条件2);

    //条件分岐とか
    if(分岐条件) 検索条件 = 検索条件.and(追加検索条件);

    //ループとか
    Specification<hoge> ループ用検索条件 = null;
    for(ループする){
        ループ用検索条件 = ループ用検索条件.or(繰返検索条件);
    }
    検索条件 = 検索条件.and(ループ用検索条件);

    return hogeRepository.findAll(検索条件);
}

他のメモ

初めてのSpring案件を終えたメモ ~Springとは編~
初めてのSpring案件を終えたメモ ~MVC編~

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

初めてのSpring案件を終えたメモ ~MVC編~

はじめに

ピュアJavaとDBの基本知識のみスタートからのSpring案件が波乱の末ひと段落したので、所感や覚書などを。
記事の内容としてはチュートリアル以上、実践未満のイメージ。:speech_balloon:
掲載コードは動作確認できていないのであくまで参考で。:pray:
英語は目が滑るので日本語多様してる、許して。:pray::sob::pray:

開発環境

RedHat(開発環境はCent)7系
Java8
SpringBoot2.1.6
MySQL8系
STS

MVCについて

ロジック(Model)と、画面(View)とそれを繋ぐコントローラ(Controller)に分けて設計しましょう、という理解。
でもSpringではViewとControllerでやりとりするデータのことをModelと呼んでいる。
自分がそもそもMVCを理解できていたか不安になったけど、言葉の定義はフレームワークごとに異なるので深く考えるのはやめた。:innocent:
https://qiita.com/yo1000/items/a6acbf5f454a7f53aef9
https://qiita.com/takasek/items/70ab5a61756ee620aee6

コントローラー

・マッピング
メソッドには@GetMapping("test")とかをつける。@RequestMapping("/test")は古いみたい。

カッコの中に記述するのは属性と呼ぶらしい。上記は@GetMapping(path="test")の省略。
1つのformタグに複数ボタンを配置してPOSTしたいとき、htmlでnameを指定すると、コントローラーのparams属性とマッピングできる。

view.html
<form method="POST" action="path1">
    <input type="submit" name="param1" /> ←こちらを押す
    <input type="submit" name="param2" />
</form>
controller.java
@PostMapping(path="path1",params="param1")

・バリデーション
Springの機能として提供されてる。
BindingResultの操作ができると、フレームワークに縛られないバリデーションが実装できる。
でもアノテーションやカスタムバリデーションを駆使すればほとんどのことはできそう。
https://qiita.com/yakumo3390/items/4e47930ba643b45b7430

メッセージは messages.properties で埋め込み文字や多言語対応も提供してるけど、メッセージをDB管理する要件だったので使えなかった。

グループ属性をつけると、バリデーション対象を指定したり外したりできる。
これのために空のクラスを作ったけど、もっとスマートな方法があるんじゃないかという気持ち。
https://tech.asoview.co.jp/entry/2019/12/11/104928

・return
基本はViewのPathをあらわす文字列。
redirectやforwardもある。
redirectのときはRedirectAttributesに値を詰め込めば遷移先に届く。
http://www.ne.jp/asahi/hishidama/home/tech/java/spring/boot/web/Controller.html#h_forward_attribute

オブジェクトを渡すときはtoString()が実装されていればそのまま渡せる、そうでないときはひと手間いるみたい。
https://grandbig.github.io/blog/2016/05/28/redirect-parameter-spring-boot/

ビューについて

thymeleaf(タイムリーフ)を使用。読みもスペルも難しい。:kissing:
いわゆるテンプレート。画面だけでなく、生成されるhtmlを確認しておかないとカッコワルイ記述になってたりするので注意。

th:fieldは便利だけどnameやidを指定したいJavaScriptとかと組み合わせるとき使えないこともある。
何をしているか理解しておくといいかも。
https://qiita.com/beeeyan/items/5bd820e55cb53f176b3e

JavaScript内で変数を使うときは慎重に。
例外が発見しにくいのと、オブジェクト丸ごと使うとhtml上に全て展開されてセキュリティ的に良くないし、見たときびっくりする。
公式ドキュメント 12.2 スクリプトのインライン処理 (JavaScript と Dart)

サービスについて

ビジネスロジックを書くところ。

今回は原則1テーブルに対して、1つのサービスを作った。あと汎用クラスをいくつか。
DBへアクセスできる層を絞るのが目的だったけど、そうすると1つのクラスが膨れ上がってしまった。:frowning2:
1画面に対して1つのサービスを作って、DBから取得したデータの加工までRepositoryで行うのがいいのかも。
もしくは、1つの画面に対するロジックを記述する層を追加するか。。。?:confused:
Untitled Diagram.png

他のメモ

初めてのSpring案件を終えたメモ ~Springとは編~
初めてのSpring案件を終えたメモ ~データベース編~

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

初めてのSpring案件を終えたメモ ~Springとは編~

はじめに

ピュアJavaとDBの基本知識のみスタートからのSpring案件が波乱の末ひと段落したので、所感や覚書などを。:speech_balloon:
記事の内容としてはチュートリアル以上、実践未満のイメージ。

開発環境

RedHat(開発環境はCent)7系
Java8
SpringBoot2.1.6
MySQL8系
STS

Spring Framework について

10年もののフレームワークで、大体のことができる、つまり、ごちゃごちゃしてる。
情報を入手するときは書籍にせよネットにせよ、鮮度を確認する。
特に設定に関して、XML地獄からはすでに解放され、アノテーションやapplication.propertiesに記述できることが多い。
https://docs.spring.io/spring-boot/docs/current/reference/html/appendix-application-properties.html

application.propertiesについて

後から知った application.yml のほうが見やすそう。
設定できるものは↓。暇なときにざっと眺めておくといい、ざっと。
https://docs.spring.io/spring-boot/docs/current/reference/html/appendix-application-properties.html

application.propertiesは環境によって切り替えることができる。
コマンドラインから実行するときは --spring.profiles.active
https://qiita.com/NagaokaKenichi/items/fd9b5e698776fe9b9cc4
STSから実行するときは
propatiese.PNG
奥深いので、ひねった使い方するとハマりそう。シンプルにいこう。:unamused:
https://blog.tagbangers.co.jp/ja/2019/07/12/how-spring-deals-with-properties

Springで作成するWEBシステムの構成

MVCでDIでAOPな設計を提供している。
それぞれ頭の中で構成図をイメージできるようにしておくと設計やバグ探しのとき役立つ。
リスナーやカスタムバリデーションの実装とか、テストコードの環境とかで、なるほどなぁってなる。
https://qiita.com/kazuki43zoo/items/757b557c05f548c6c5db

DIについて

結局「コントローラーとDB関連クラスはDI対象にする」くらいの理解。
@Autowiredしていたけど良くないみたい。今はコンストラクタインジェクションを使用するのが主流らしい。
https://qiita.com/jackazu/items/aead50c699fefe56c120
意識しないといけないのは、@Controller とかつけるとSpringがDI対象と判断して勝手にインスタンス化するのでnew HogeController() とかしない。nullになるので。

DI対象のインスタンスはシングルトン。
設定によって変えられるらしいけど、変えなければいけないと思ったら設計のほうを疑うべし、らしい。
コントローラーのメンバ変数にユーザの情報を保持したら別のユーザにどんどん書き換えられちゃった:laughing:

他のメモ

初めてのSpring案件を終えたメモ ~データベース編~
初めてのSpring案件を終えたメモ ~MVC編~

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【GradleでJakartaEE 8 アプリケーション開発】2. プロジェクト作成

はじめに

本記事は 【GradleでJakartaEE 8 アプリケーション開発】1. 環境構築 の続きです。
前回の記事では開発環境の構築を行いました。本記事ではGradleプロジェクトを作成します。

EclipseのGradle設定

EclipseでGradleプロジェクトを使う際、前回の環境設定でインストールしたGradleを使用するために下記設定を行います。
Eclipseを起動し、ウィンドウ > 設定 を開き、Gradle を選択し、下記の変更を行います。

項目 内容
ローカル・インストール・ディレクトリー Gradleのインストールフォルダ
Java ホーム Jakarta EE 8

image.png

変更後、適用して閉じる をクリックします。

Gradleプロジェクトの作成

Gradleプロジェクトの作成方法ですが、Eclipseを使う方法とコマンドライン上で作成する方法の二つがあります。
それぞれ紹介していきます。

EclipseでGradleプロジェクト作成

Eclipseにて、ファイル > 新規 > Graldeプロジェクト をクリックします。
image.png

プロジェクト名を入力し、次へ をクリックします。
image.png

設定はそのまま、次へ をクリックします。
image.png

しばらくすると下記画面が表示されるので、完了 をクリックします。
image.png

Gradleプロジェクトが作成され、パッケージ・エクスプローラー上に表示されます。
image.png

コマンドラインでGradleプロジェクト作成

コマンドラインを開き、プロジェクトを作成するフォルダへ移動します。
プロジェクト名となるフォルダを作成します。

c:\projects>mkdir jakartaeesample2

c:\projects>dir
 ドライブ C のボリューム ラベルは Windows です
 ボリューム シリアル番号は F06A-A652 です

 c:\projects のディレクトリ

2020/03/29  12:11    <DIR>          .
2020/03/29  12:11    <DIR>          ..
2020/03/29  12:11    <DIR>          jakartaeesample2
               0 個のファイル                   0 バイト
               3 個のディレクトリ  134,858,534,912 バイトの空き領域

c:\projects>

作成したフォルダへ移動し、 gradle init --type java-library を実行します。
build script DSL の選択肢では 1 を、test frameworkの選択肢では 1 を入力します。
Project name、Source package はdefaultのままとしますので、空欄のままEnterを入力して下さい。

c:\projects>cd jakartaeesample2

c:\projects\jakartaeesample2>gradle init --type java-library
Starting a Gradle Daemon (subsequent builds will be faster)

Select build script DSL:
  1: Groovy
  2: Kotlin
Enter selection (default: Groovy) [1..2] 1

Select test framework:
  1: JUnit 4
  2: TestNG
  3: Spock
  4: JUnit Jupiter
Enter selection (default: JUnit 4) [1..4] 1

Project name (default: jakartaeesample2):
Source package (default: jakartaeesample2):

> Task :init
Get more help with your project: https://docs.gradle.org/6.2.2/userguide/java_library_plugin.html

BUILD SUCCESSFUL in 3m 57s
2 actionable tasks: 2 executed
c:\projects\jakartaeesample2>

BUILD SUCCESSFUL が表示されたらプロジェクト作成は完了です。
作成したプロジェクトをEclipseをからプロジェクトをインポートします。
Eclipseを起動し、ファイル > インポートをクリックします。
image.png

Gradle > 既存の Gradle プロジェクト を選択し、次へ をクリックします。

image.png

プロジェクト・ルート・ディレクトリーに先ほど作成したプロジェクトのフォルダを選択し、次へ をクリックします。
image.png

デフォルト設定のまま、次へ をクリックします。
image.png

しばらくすると下記画面が表示されるので、完了 をクリックします。
image.png

パッケージ・エクスプローラーにインポートしたGradleプロジェクトが表示されます。
image.png

gradle.properties の追加

Gradleプロジェクトを作成したら、build.gradle に使用するためのプロパティファイル gradle.properties を追加します。
パッケージ・エクスプローラーにてプロジェクト名を右クリックし、新規 > ファイル を選択します。

ファイル名に gradle.properties を入力し、完了 をクリックします。
image.png

gradle.properties ファイルが追加されエディタ表示されるので、下記内容を入力してください。

gradle.properties
#########################
 # gradle.properties for jakartaeesample2
 # author:         sunnycloudy764
 # since:          2020/03/27
 # Gradle version: 6.2.2
#########################

#########################
# base setting
#########################
groupName=jakartaeesample2
artifactid=jakartaeesample2
buildVersion=0.0.1
appName=jakartaeesample2
encoding=UTF-8
jdkVersion=11

#####################
# plugin version
#####################
spotbugsVersion = 4.0.5
flywayVersion = 6.3.2

#####################
# plugin tool version
#####################
checkstyleToolVersion = 8.31
spotbugsToolVersion = 4.0.1

#####################
# library version
#####################
JakartaEEAPIVersion = 8.0
PostgreSQLVersion=42.2.11

slf4jVersion = 1.7.30
logbackVersion = 1.2.3

commonsCollections4Version = 4.4
commonsLang3Version = 3.10
commonsIoVersion = 2.6

lombokVersion=1.18.12

junitVersion = 4.13
hamcrestVersion = 1.3
assertjVersion = 3.15.0
assertjdbVersion = 1.3.0
mockitoVersion = 3.3.3
jacocoCoreVersion=0.8.5

h2Version = 1.4.200
dbSetupVersion = 2.1.0

arquillianJunitContainerVersion=1.6.0.Final
arquillianJacocoVersion=1.1.0
arquillianPayaraEmbeddedVersion=2.2
payaraEmbeddedAllVersion=5.201

#####################
# flyway settings
#####################
flywayuser=postgresql
flywaypassword=xxxxxxx
flywaydriver = org.postgresql.Driver
flywayurl = jdbc:postgresql://localhost:5432/testdb
flywaytarget = 0.0.1
outOfOrder = false
validateOnMigrate = true

#####################
# project facet
#####################
javaFacet = 11
webFacet = 4.0
jsfFacet = 2.3
glassfishFacet = 5.0

#####################
# glassfish settings
#####################
glassfishHome = C:/payara/payara-5.201/payara5/glassfish
glassfishDomain = domain1
isRemote = false
remoteIP = 127.0.0.1

build.gradle の変更

次に、Jakarta EE アプリケーションを開発するためにbuild.gradleを変更します。
下記の通り変更してください。

build.gradle
/*
 ******************************
 * build.gradle for jakartaeesample2
 * author:         sunnycloudy764
 * since:          2020/03/27
 * Gradle version: 6.2.2
 ******************************
 */
plugins {
  id "com.github.spotbugs" version "${spotbugsVersion}"
  id "org.flywaydb.flyway" version "${flywayVersion}"
}

apply plugin: 'project-report'
apply plugin: 'java'
apply plugin: 'war'
apply plugin: 'eclipse'
apply plugin: 'eclipse-wtp'
apply plugin: 'checkstyle'
apply plugin: 'jacoco'

sourceCompatibility = "${jdkVersion}"
targetCompatibility = "${jdkVersion}"

tasks.withType(AbstractCompile)*.options*.encoding = "${encoding}"
tasks.withType(GroovyCompile)*.groovyOptions*.encoding = "${encoding}"
[compileJava, compileTestJava, javadoc].each { it.options.encoding = "${encoding}" }

version = "${buildVersion}"
group = "${groupName}"

configurations {
    all*.exclude module: 'slf4j-jdk12'
    all*.exclude module: 'slf4j-jdk14'
    all*.exclude module: 'servlet-api'
    assertj
}

repositories {
   mavenCentral()
   maven { url 'http://repository.jboss.org/nexus/content/groups/public' }
}

dependencies {
    //Jakarta EE API
    compileOnly group: 'jakarta.platform', name: 'jakarta.jakartaee-api', version: "${JakartaEEAPIVersion}"
    testCompileOnly group: 'jakarta.platform', name: 'jakarta.jakartaee-api', version: "${JakartaEEAPIVersion}"

    //JDBC driver (PostgreSQL)
    implementation group: 'org.postgresql', name: 'postgresql', version: "${PostgreSQLVersion}"

    //SLF4j + logback
    implementation group:'org.slf4j', name: 'slf4j-api', version: "${slf4jVersion}"
    implementation group:'ch.qos.logback', name: 'logback-classic', version: "${logbackVersion}"
    implementation group:'ch.qos.logback', name: 'logback-core', version: "${logbackVersion}"

    //Apache Commons
    implementation group:'org.apache.commons', name: 'commons-collections4', version: "${commonsCollections4Version}"
    implementation group:'org.apache.commons',name:'commons-lang3',version:"${commonsLang3Version}"
    implementation group:'commons-io', name:'commons-io',version:"${commonsIoVersion}"

    //Lombok
    annotationProcessor group: 'org.projectlombok', name: 'lombok', version: "${lombokVersion}"
    compileOnly group: 'org.projectlombok', name: 'lombok', version: "${lombokVersion}"
    testAnnotationProcessor  group: 'org.projectlombok', name: 'lombok', version: "${lombokVersion}"
    testCompileOnly group: 'org.projectlombok', name: 'lombok', version: "${lombokVersion}"

    //Test libraries
    //JUnit
    testImplementation (group: 'junit', name: 'junit', version: "${junitVersion}"){
        exclude module: 'hamcrest-core'
    }
    testImplementation group:'org.hamcrest', name: 'hamcrest-all', version: "${hamcrestVersion}"
    testImplementation group: 'org.assertj', name: 'assertj-core', version: "${assertjVersion}"
    testImplementation group: 'org.assertj', name: 'assertj-db', version: "${assertjdbVersion}"

    //Mock libraries
    testImplementation group:'org.mockito', name:'mockito-inline', version: "${mockitoVersion}"

    //jacoco
    testImplementation group: 'org.jacoco', name: 'org.jacoco.core', version: "${jacocoCoreVersion}"

    //for DB Test
    testImplementation group: 'com.h2database', name: 'h2', version: "${h2Version}"
    testImplementation group: 'com.ninja-squad', name: 'DbSetup', version: "${dbSetupVersion}"

    //CDI Test(Arquillian)
    testImplementation group: 'org.jboss.arquillian.junit', name: 'arquillian-junit-container', version: "${arquillianJunitContainerVersion}"
    testImplementation group:'org.jboss.arquillian.extension', name: 'arquillian-jacoco', version: "${arquillianJacocoVersion}"
    testImplementation group:'fish.payara.arquillian', name: 'arquillian-payara-server-embedded', version: "${arquillianPayaraEmbeddedVersion}"
    testImplementation group: 'fish.payara.extras', name: 'payara-embedded-all', version: "${payaraEmbeddedAllVersion}"

    //assertj assertion generator
    assertj 'org.assertj:assertj-assertions-generator:2.2.0'
    assertj project
}

// flyway settings
flyway {
    driver = "${flywaydriver}"
    url = "${flywayurl}"
    user = "${flywayuser}"
    password = "${flywaypassword}"
    target = "${flywaytarget}"
    outOfOrder = false
    validateOnMigrate = true
    cleanOnValidationError = false
}

//assertj assertion generator
def assertjOutput = file('src-gen/test/java')
task assertjClean(type: Delete) {
    delete assertjOutput
}

task assertjGen(dependsOn: assertjClean, type: JavaExec) {
    doFirst {
        if (!assertjOutput.exists()) {
            if (!assertjOutput.mkdirs()) {
                throw new InvalidUserDataException("Unable to create `$assertjOutput` directory")
            }
        }
    }
    main 'org.assertj.assertions.generator.cli.AssertionGeneratorLauncher'
    classpath = files(configurations.assertj)
    workingDir = assertjOutput
    args = [    ]
}
compileTestJava.dependsOn(assertjGen)

// assertj generator生成のコードをsourceSetsに追加
sourceSets {
    test {
        java {
            srcDir 'src/test/java'
            srcDir 'src-gen/test/java'
        }
    }
}

war {
    webAppDirName = "WebContent"
    baseName = "${artifactid}"
}

test {
    systemProperties 'property': 'value'
    ignoreFailures = true
    reports {
        html.enabled = true
        junitXml.enabled = true
    }
}

jacoco {
    toolVersion = "${jacocoCoreVersion}"
}
jacocoTestReport {
    reports {
        html.enabled = true
        xml.enabled = true
        csv.enabled = false
    }
}

checkstyle {
    toolVersion = "${checkstyleToolVersion}"
    ignoreFailures = true
    sourceSets = subprojects.sourceSets.main
    configFile file("$rootDir/config/checkstyle.xml")
}

spotbugs {
    toolVersion =  "${spotbugsToolVersion}"
    ignoreFailures = true
    effort = "max"
    reportLevel = 'low'
}

//Eclipse IDE用の設定
eclipse {
  wtp {
    facet {
        facet name: 'jst.java', version: "${javaFacet}"
        facet name: 'jst.web', version: "${webFacet}"
        facet name: 'jst.jsf', version: "${jsfFacet}"
        facet name: 'glassfish.web', version: "${glassfishFacet}"
    }
    component {
      contextPath = 'WebContent'
    }
  }
}

//OSチェックを行います。
def isWindows() {
    return System.properties['os.name'].toLowerCase().contains('windows')
}

//Payara Server設定
ext {
    asadmin = "${glassfishHome}" + (isWindows() ? '/bin/asadmin.bat' : '/bin/asadmin')
    domain = "${glassfishDomain}"
}

//Payara Server を起動します。
task startServer(type: Exec) {
    description 'アプリケーションサーバを起動します。'
    commandLine asadmin, 'start-domain', '--debug=true', domain
}

//Payara Server を停止します。
task stopServer(type: Exec) {
    description 'アプリケーションサーバを停止します。'
    commandLine asadmin, 'stop-domain', domain
}

//Payara Server にWARファイルをデプロイします。
task deploy(type: Exec, dependsOn: ':war') {
    appName = "${appName}"
    description 'WARファイルをデプロイします。'
    if("${isRemote}" == 'true'){
        commandLine asadmin, '--host', "${remoteIP}", '--user', 'admin', '--passwordfile', '..\\glassfishpassword.txt', '--port', '4848' , 'deploy', '--force=true', '--contextroot', appName, '--name', appName, war.archivePath
    }else{
        commandLine asadmin, 'deploy', '--force=true', "--contextroot", appName, "--name", appName, war.archivePath
    }
}

Jakarta EEの開発をするためには dependency に Jakarta EE API を追加すればOKですが、ここでは普段の開発で使用するためのライブラリを色々と追加しています。

  • JDBCドライバ:今回はPostgreSQL
  • ロガー:slf4j + logback
  • Apache Commons:Commons Collection, Commons lang, Commons IO
  • 冗長コード削減:Lombok
  • 単体テスト:JUnit, mockito, assertj
  • コードカバレッジ:jacoco
  • スルーテスト:Arquillian

また、DB作成のためflywayDB pluginを追加し、静的解析としてcheckstyleとspotbugsを追加しています。
そして、Gradleタスクでデプロイが可能となるようにdeployタスクを作成しています。

checkstyle 定義ファイル作成

今回のプロジェクトでは静的解析ツールの一つとしてcheckstyleを使用しているため、checkstyleの定義ファイルをプロジェクト内に配置する必要があります。
まず、定義ファイル配置先となるconfigフォルダを作成します。
パッケージ・エクスプローラーにてプロジェクト名を右クリックし、新規 > フォルダー を選択します。
フォルダー名に config と入力し、完了 をクリックします。

image.png

config フォルダが作成されたら、config フォルダを右クリックし、 新規 > ファイル を選択します。
ファイル名に checkstyle.xml と入力し、完了 をクリックします。
checkstyle.xmlについては、公式の google_check.xml を参考に作成するとよいと思います。

checkstyle.xmlを作成し、Eclipse上でエラーが発生しない場合はプロジェクト作成の完了となります。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

VisualStudioCodeにおけるデバッグコンソールへの出力設定

vscodeにてJavaのデバッグ環境を作ろうとしたところ、実行結果をデバッグコンソールに表示させられず、かなりつまづいたので書いておきます。

デバッグ環境を構築&デバッグ

まずは適当なサイトを参考にしながらJDKや拡張機能をインストール。そして、テストのためにJavaのファイルを作り、デバッグコンソールに文字列を表示するだけの簡単なコードを記述してみました。
alt
参考にしていたサイトでは、この段階でデバッグを開始したら実行結果がデバッグコンソールに表示されていましたがここで問題発生。なぜかデバッグコンソールに「hello world」の文字列が表示されないのです。
JDKをインストールし直しても、拡張機能を入れ直しても、ファイルを作り直しても、一向に表示されない…
alt

解決方法

どんなワードで検索しても解決策が見つからず、萎えてしまい一ヶ月が経過したとき、ふと「そういえばデバッグコンソールへの出力設定とかって無いのか?」と思いつきました。
早速、設定の検索窓に「DebugConsole」と入力してみると
alt
それらしき雰囲気の設定項目が見つかった。
英語でよくわからないのでGoogle先生に聞くと

Javaプログラムを起動するための指定されたコンソール。特定のデバッグセッション用にコンソールをカスタマイズする場合は、launch.jsonのコンソール設定を変更してください。

とのこと、いまいち訳がわかりにくいが恐らく「プログラムの実行結果を表示する場所を変えたいのなら、この設定を変更してください」という感じの意味合いだと思われる。
ということで、デフォルトとして選択されていた integratedTerminal から internalConsole へと設定を変更。そして試しにデバッグをしてみると…
alt
しっかりデバッグコンソールに実行結果が表示されました。どうやらデフォルトの設定で、実行結果の出力先がターミナルになっていたようです。

終わりに

皆さんもvscodeなどで何かつまづいたら、まずはソフト自体の設定にそれらしき項目がないか探してみましょう。今回のように意外と簡単な設定ミスかもしれません。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む