20210122のJavaに関する記事は10件です。

SonarQube(Java)の、ルールの出典を確認したい

Whats'?

SonarQubeで使えるJavaのルールって、SonarQube自身が提供しているのか、それとも他のOSSなどのルールを使っているのか知りたいなぁと思いまして。

結論

すべて、SonarQube自前のルールでした。

SpotBugs、FindSecBugs、PMD、Checkstyleで検出した問題については、インポートが可能だということです。

Importing Third-Party Issues

SonarQube doesn't run your external analyzers or generate reports. It only imports pre-generated reports.

Importing SpotBugs, FindSecBugs, PMD, Checkstyle Issues Reports(※このページはもうメンテナンスされていません)

今回は、その確認の過程を書いていきます。

環境

今回は、SonarQubeサーバー 8.6 Community Editionを対象とします。

SonarQubeで使えるJavaのルール

SonarQubeサーバーで「Rules」から「Java」を選ぶと、Javaに関するルールが605あることが確認できます。

image.png

なんとなく、ドキュメントを見るとSpotBugs、FindBugs、FindSecBugs、PMD、Checkstyleが関係していそうな気はします。

image.png

External Issues、とは書いていますが…。

Analyzing Source Code / Languages / Java

Java / Related Pages

SonarQube上で見てみても、関係しているのかどうかわかりません。

image.png

個別のルールを見ても、やっぱりわかりません。

image.png

分類として、Code Smell、Bug、Vulnerability、Security Hotspotがあるのはわかるんですけどね。

Rules

Sonar Maven Pluginを見る

こちらのエントリで少し使ってみたので、Sonar Maven Pluginを起点に追ってみましょう。

MavenプロジェクトでSonarQubeを使ってみる

SonarScanner for Maven

現時点のSonar Maven Pluginのpom.xmlなどを見ても、SpotBugsなどが入っている様子はありません。

https://github.com/SonarSource/sonar-scanner-maven/blob/3.8.0.2131/pom.xml

どうやら、クライアント側は関係ないようです。

Sonar Java Pluginを見る

いろいろ調べていると、こちらが実体のように思います。

Code Quality and Security for Java

ここで、SonarQubeサーバーのインストールディレクトリにある、lib/extensionsディレクトリを見てみます。

ちょっと気になります。

$ ll lib/extensions
-rw-r--r--. 1 xxxxx xxxxx  5056472 Dec  9 10:25 sonar-csharp-plugin-8.15.0.24505.jar
-rw-r--r--. 1 xxxxx xxxxx 10627704 Dec  9 10:25 sonar-css-plugin-1.3.1.1642.jar
-rw-r--r--. 1 xxxxx xxxxx  1550044 Dec  9 10:25 sonar-flex-plugin-2.6.0.2294.jar
-rw-r--r--. 1 xxxxx xxxxx  6102054 Dec  9 10:25 sonar-go-plugin-1.8.1.1804.jar
-rw-r--r--. 1 xxxxx xxxxx   823033 Dec  9 10:25 sonar-html-plugin-3.3.0.2534.jar
-rw-r--r--. 1 xxxxx xxxxx    19648 Dec  9 10:25 sonar-jacoco-plugin-1.1.0.898.jar
-rw-r--r--. 1 xxxxx xxxxx 18717478 Dec  9 10:25 sonar-java-plugin-6.9.0.23563.jar
-rw-r--r--. 1 xxxxx xxxxx 19371396 Dec  9 10:25 sonar-javascript-plugin-7.0.1.14561.jar
-rw-r--r--. 1 xxxxx xxxxx  8466805 Dec  9 10:25 sonar-kotlin-plugin-1.8.1.1804.jar
-rw-r--r--. 1 xxxxx xxxxx  5310930 Dec  9 10:25 sonar-php-plugin-3.13.0.6849.jar
-rw-r--r--. 1 xxxxx xxxxx  4527459 Dec  9 10:25 sonar-python-plugin-3.1.0.7619.jar
-rw-r--r--. 1 xxxxx xxxxx 13053290 Dec  9 10:25 sonar-ruby-plugin-1.8.1.1804.jar
-rw-r--r--. 1 xxxxx xxxxx 14687152 Dec  9 10:25 sonar-scala-plugin-1.8.1.1804.jar
-rw-r--r--. 1 xxxxx xxxxx  3760426 Dec  9 10:25 sonar-vbnet-plugin-8.15.0.24505.jar
-rw-r--r--. 1 xxxxx xxxxx  2242738 Dec  9 10:25 sonar-xml-plugin-2.0.1.2020.jar

Community Editionを使っているので、ちょうどサポート対象の15言語分ですね。

SonarQubeサーバーのドキュメントを見ていると、解析はクライアント側でやっているのかな?と思うのですが、

Install the Server

image.png

適用するルールはサーバー側で管理しているので、ちょっとわからなくなりますね。

sonar-java-plugin-6.9.0.23563.jarは、こちらのリポジトリから作られたもののようです。

Code Quality and Security for Java

ちょっと中身を見てみましょう。

$ jar -tvf lib/extensions/sonar-java-plugin-6.9.0.23563.jar | grep -iE 'spotbugs|findbugs|findsecbugs|pmd|checkstyle'
  1626 Mon Oct 05 01:32:58 UTC 2020 org/sonar/java/checks/NoCheckstyleTagPresenceCheck.class
  1587 Mon Oct 05 01:32:58 UTC 2020 org/sonar/java/checks/NoPmdTagPresenceCheck.class
  4548 Mon Oct 05 01:32:58 UTC 2020 org/sonar/java/externalreport/PmdSensor.class
  6057 Mon Oct 05 01:32:58 UTC 2020 org/sonar/java/externalreport/CheckstyleXmlReportReader.class
  9209 Mon Oct 05 01:32:58 UTC 2020 org/sonar/java/externalreport/PmdXmlReportReader.class
  5807 Mon Oct 05 01:32:58 UTC 2020 org/sonar/java/externalreport/SpotBugsSensor.class
   511 Mon Oct 05 01:32:58 UTC 2020 org/sonar/java/externalreport/CheckstyleXmlReportReader$IssueConsumer.class
  9549 Mon Oct 05 01:32:58 UTC 2020 org/sonar/java/externalreport/SpotBugsXmlReportReader.class
     0 Mon Oct 05 01:32:58 UTC 2020 org/sonar/l10n/java/rules/spotbugs/
 24823 Mon Oct 05 01:32:06 UTC 2020 org/sonar/l10n/java/rules/spotbugs/findsecbugs-rules.json
 76105 Mon Oct 05 01:32:06 UTC 2020 org/sonar/l10n/java/rules/spotbugs/fbcontrib-rules.json
     0 Mon Oct 05 01:32:58 UTC 2020 org/sonar/l10n/java/rules/checkstyle/
 34405 Mon Oct 05 01:32:06 UTC 2020 org/sonar/l10n/java/rules/checkstyle/rules.json
  5829 Mon Oct 05 01:32:58 UTC 2020 org/sonar/java/externalreport/CheckstyleSensor.class
     0 Mon Oct 05 01:32:58 UTC 2020 org/sonar/l10n/java/rules/pmd/
 61670 Mon Oct 05 01:32:06 UTC 2020 org/sonar/l10n/java/rules/pmd/rules.json
130757 Mon Oct 05 01:32:06 UTC 2020 org/sonar/l10n/java/rules/spotbugs/spotbugs-rules.json
     0 Mon Oct 05 01:32:58 UTC 2020 META-INF/maven/com.google.code.findbugs/
     0 Mon Oct 05 01:32:58 UTC 2020 META-INF/maven/com.google.code.findbugs/jsr305/
  4286 Fri Mar 31 10:21:36 UTC 2017 META-INF/maven/com.google.code.findbugs/jsr305/pom.xml
   121 Fri Mar 31 10:55:32 UTC 2017 META-INF/maven/com.google.code.findbugs/jsr305/pom.properties

少し関係ありそうですね。

たとえば、こちら。

https://github.com/SonarSource/sonar-java/blob/6.9.0.23563/external-reports/src/main/resources/org/sonar/l10n/java/rules/spotbugs/spotbugs-rules.json

抜粋してみます。

[
  {
    "key": "AM_CREATES_EMPTY_JAR_FILE_ENTRY",
    "name": "Bad practice - Creates an empty jar file entry",
    "type": "CODE_SMELL",
    "severity": "MAJOR",
    "url": "https://spotbugs.readthedocs.io/en/latest/bugDescriptions.html#AM_CREATES_EMPTY_JAR_FILE_ENTRY"
  },
  {
    "key": "AM_CREATES_EMPTY_ZIP_FILE_ENTRY",
    "name": "Bad practice - Creates an empty zip file entry",
    "type": "CODE_SMELL",
    "severity": "MAJOR",
    "url": "https://spotbugs.readthedocs.io/en/latest/bugDescriptions.html#AM_CREATES_EMPTY_ZIP_FILE_ENTRY"
  },
  {
    "key": "AT_OPERATION_SEQUENCE_ON_CONCURRENT_ABSTRACTION",
    "name": "Multi-threading - Sequence of calls to concurrent abstraction may not be atomic",
    "type": "BUG",
    "severity": "MAJOR",
    "url": "https://spotbugs.readthedocs.io/en/latest/bugDescriptions.html#AT_OPERATION_SEQUENCE_ON_CONCURRENT_ABSTRACTION"
  },

...

]

typeとルール上の分類が一致しそうですね。

リポジトリをcloneして中身を見てみます。

$ git clone https://github.com/SonarSource/sonar-java.git
$ cd sonar-java

対象のバージョンにcheckout

$ git checkout 6.9.0.23563

typeで集計してみます。

$ find external-reports/src/main/resources/org/sonar/l10n/java/rules -name '*.json' | xargs grep '"type"' | perl -wp -e 's!.+("type.+)!$1!' | sort | uniq -c
    553 "type": "BUG",
    654 "type": "CODE_SMELL",
    139 "type": "VULNERABILITY",

多い…。

ファイルパスも一部含めてみましょう。

$ find external-reports/src/main/resources/org/sonar/l10n/java/rules -name '*.json' | xargs grep '"type"' | perl -wp -e 's!.+/(.+/.+) *("type.+)!$1 $2!' | sort | uniq -c
    156 checkstyle/rules.json:     "type": "CODE_SMELL",
    288 pmd/rules.json:     "type": "CODE_SMELL",
    308 spotbugs/fbcontrib-rules.json:     "type": "BUG",
    128 spotbugs/findsecbugs-rules.json:     "type": "VULNERABILITY",
    245 spotbugs/spotbugs-rules.json:     "type": "BUG",
    210 spotbugs/spotbugs-rules.json:     "type": "CODE_SMELL",
     11 spotbugs/spotbugs-rules.json:     "type": "VULNERABILITY",

うーん、よくわからないですね…。

実際にSonarQubeサーバーが認識しているルールは、ここにあるようです。

https://github.com/SonarSource/sonar-java/tree/6.9.0.23563/java-checks/src/main/resources/org/sonar/l10n/java/rules/java

Sonar wayプロファイルの定義もありました。

https://github.com/SonarSource/sonar-java/blob/6.9.0.23563/java-checks/src/main/resources/org/sonar/l10n/java/rules/java/Sonar_way_profile.json

なんですけど、全然わからない名前が並んでいます。

{
  "name": "Sonar way",
  "ruleKeys": [
    "S100",
    "S101",
    "S106",
    "S107",
    "S108",
    "S110",
    "S112",
    "S114",

...

このディレクトリ内にあるファイルのほとんどは、自動生成されたもののようです。

https://github.com/SonarSource/sonar-java/blob/6.9.0.23563/java-checks/src/main/resources/org/sonar/l10n/java/rules/java/README.md

実体は、同じプロジェクトのこういうコードでしょうか?

@DeprecatedRuleKey(ruleKey = "S00100", repositoryKey = "squid")
@Rule(key = "S100")
public class BadMethodNameCheck extends IssuableSubscriptionVisitor {

https://github.com/SonarSource/sonar-java/blob/6.9.0.23563/java-checks/src/main/java/org/sonar/java/checks/naming/BadMethodNameCheck.java#L34

S[数字]なものを確認してみましょう。

$ grep -rE '"S[0-9]+"' java-checks/src/main/java | perl -wp -e 's!.+(S\d+).+!$1!' | sort -u | wc -l
599

599個ですね。

ここで、SonarQubeのルールを"リポジトリ"という単位別に見れることに気づいたので、見比べてみます。

image.png

「SonarQube」が599個。一致します。

つまり、ここにあるのがSonarQubeのルールの実体です。

https://github.com/SonarSource/sonar-java/tree/6.9.0.23563/java-checks/src/main/java/org/sonar/java/checks

残りの6個はというと、こちらに定義してあります。

https://github.com/SonarSource/sonarqube/blob/8.6.0.39681/server/sonar-webserver-core/src/main/java/org/sonar/server/rule/CommonRuleDefinitionsImpl.java

つまり、605個のルールはすべてSonarQube自前のルールでした。

ここでこのページに気づき、あくまでSpotBugs、FindSecBugs、PMD、Checkstyleは「インポート可能なもの」だと認識しました…。

Importing SpotBugs, FindSecBugs, PMD, Checkstyle Issues Reports

なるほど、それで"External Issues"だったんですね。

こちらのページで、インポートするファイルのパスをシステムプロパティで指定できると書いていますが、それが書いてあるのがここですね。

https://github.com/SonarSource/sonar-java/blob/6.9.0.23563/external-reports/src/main/java/org/sonar/java/externalreport/SpotBugsSensor.java#L50

https://github.com/SonarSource/sonar-java/blob/6.9.0.23563/external-reports/src/main/java/org/sonar/java/externalreport/PmdSensor.java#L38

https://github.com/SonarSource/sonar-java/blob/6.9.0.23563/external-reports/src/main/java/org/sonar/java/externalreport/CheckstyleSensor.java#L43

なるほど、すっきりしました。

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

初心者が1から始めるJava開発-Vol.5- 繰り返し編

前回に引き続き今回はJavaの繰り返しについて書いていこうと思います。

前回のVol.4も重要なので、初心者は要チェックです。


Javaの初心者向けおすすめ本

スッキリわかるJava入門 第3版 (スッキリシリーズ)


繰り返し

繰り返しとは、そのままですが「何度も処理を繰り返す」ことを言います。
プログラムの世界では超頻出です。

例えば

  • 同じ数式で全社員の給与計算をする
  • 動画を再生し終えたら、次の動画を自動再生する
  • 100ページのPDFとして100人分の社員情報を印字する etc

など、他にも山ほどあると思いますが、これらも何らかの処理を複数回実行するために、
この「繰り返し」処理を実行しています。

では実際にコードで書いていきましょう。

前回のあいさつのプログラムを使います。

プログラム実装

Greeting.javaを開きましょう。

まず、greetingIfというメソッドを作り、ifの部分をメソッド化しておきましょう。

ifからgreetingSwitchの直前までをctrlキーを押しながらマウスでスクロールし、
if文の部分を選択したら、右クリックでRefactor > Extract Methodと選択してください。

Method Name:にはgreetingIfと入れてOKを押しましょう。

すると、eclipseが勝手にメソッドを作成してくれたと思います。

これを「リファクタリング」「リファクタ」と言ったりします。
代替の場合は動作(Input/Output)を変えずにコードを書き換えることを言います。
エンジニアやプログラマの現場では頻出単語なので、覚えましょう。

package JavaTutorial;

public class Greeting {
    public static void main(String[] args) {
        String timeZone = "朝";

        greetingIf(timeZone);
        greetingSwitch(timeZone);
    }

    private static void greetingIf(String timeZone) {
        if (timeZone.equals("朝")) {
            System.out.println("Good Morning World!");

        } else if (timeZone.equals("昼")) {
            System.out.println("Hello World!");

} else if (timeZone.equals("夜")) {
            System.out.println("Good Evening World!");

        }
    }

    private static void greetingSwitch(String timeZone) {
        switch (timeZone) {
        case "朝":
            System.out.println("Good Morning World!");
            break;
        case "昼":
            System.out.println("Hello World!");
            break;
        case "夜":
            System.out.println("Good Evening World!");
            break;

        }
    }
}

全体がこんな感じになっていればOKです。

続いて繰り返し処理を書いていきましょう。

繰り返し処理を書くときはforを使います。

mainメソッドのtimeZoneの次の行にforとタイプしてみてください。

for - use index for array

というやつを選びます。すると

        for (int i = 0; i < args.length; i++) {

        }

こんな奴が出てきたと思います。これが一番基本的なfor文です。

日本語訳すると、
i を0からargsの長さまで繰り返し実行する、その時1回実行毎に iは1ずつ増加していく
という感じです。

今回はargsの中には何も持っていないので、args.lengthの部分を3に書き換えましょう。

        for (int i = 0; i < 3; i++) {

        }

こうなります。
で、最後に、forの中にgreetingIfとgreetingswitchを入れて見ましょう。

    public static void main(String[] args) {
        String timeZone = "朝";
        for (int i = 0; i < 3; i++) {
            greetingIf(timeZone);
            greetingSwitch(timeZone);
        }
    }

メインクラスはこのようになります。

では実行してみましょう

Good Morning World!
Good Morning World!
Good Morning World!
Good Morning World!
Good Morning World!
Good Morning World!

のように6回出てくれば成功です。

アレンジ

これでは面白くないので
iが何番のときなのかを把握するためにforの中でiを使ってみましょう。

System.out.println(i + 1 + "周目");

というコードをforのすぐ後ろに書いてみましょう。

    public static void main(String[] args) {
        String timeZone = "朝";
        for (int i = 0; i < 3; i++) {
            System.out.println(i + 1 + "周目");
            greetingIf(timeZone);
            greetingSwitch(timeZone);
        }
    }

こんな感じで実行してみてください。

1周目
Good Morning World!
Good Morning World!
2周目
Good Morning World!
Good Morning World!
3周目
Good Morning World!
Good Morning World!

こうなります。確かにiが1個ずつ増えて3週回ったら終わっていることがわかるかと思います。

さらにアレンジ

今度は1週目は朝、2週目は昼、3週目は夜となるようにしていきます。

String[] timeZones= {"朝","昼","夜"};
String timeZone = "朝";

のように、上にtimeZonesという配列を宣言してください。
そして、for文の3としたところをtimeZones.lengthに書き換えます。

    public static void main(String[] args) {
        String[] timeZones = { "朝", "昼", "夜" };
        String timeZone = "朝";
        for (int i = 0; i < timeZones.length; i++) {
            System.out.println(i + 1 + "周目");
            greetingIf(timeZone);
            greetingSwitch(timeZone);
        }
    }

最後にforのすぐ後ろに

String timeZone = timeZones[i];

としてもともとの

String timeZone = "朝";

は消しましょう。

    public static void main(String[] args) {
        // 配列を宣言
        String[] timeZones = { "朝", "昼", "夜" };

        // timeZones.lengthまで回す
        for (int i = 0; i < timeZones.length; i++) {

            // timeZonesの中身のi番目を取得する(配列は1番目が0、2番目が1、・・・となります)
            String timeZone = timeZones[i];

            // 配列から取得したtimeZoneで挨拶を出力していく
            System.out.println(i + 1 + "周目");
            greetingIf(timeZone);
            greetingSwitch(timeZone);
        }
    }

こんな感じになります。

では実行してみましょう。

1周目
Good Morning World!
Good Morning World!
2周目
Hello World!
Hello World!
3周目
Good Evening World!
Good Evening World!

こんな感じになっていれば完璧です。

最後に

for文には様々な書き方があります。拡張for文、streamなど。
また、forだけでなくwhile文というものもあります。

兎に角書き方が豊富である=必要な場面が多い

という事なので、ぜひ今回のコードが他に書きようないかいろいろ試してみてください。

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

初心者が1から始めるJava開発-Vol.4- 条件分岐編

プログラムには大きく3要素あると思っていて

  • 順次実行(順次)
  • 条件分岐(分岐)
  • 繰り返し(反復)

です。

このうち順次というのは読んで字のごとく、「順番に処理を実行する」という意味で、
Javaでは、上に書いたものから先に実行されるという一般的なルールに従います。

当然、Vol.3で紹介したメソッドやクラスは呼び出された順に実行するので、
クラス内に書かれている順ではなく、呼び出された順です。

今回は2つ目の条件分岐について紹介していきます。


Javaの初心者向けおすすめ本

スッキリわかるJava入門 第3版 (スッキリシリーズ)


条件分岐

読んで字のごとく、何らかの条件に従い処理を分岐させることです。

分かりやすい例をあげましょう(いい例かは別ですw)

子供が生まれて、
1、もし男の子だったら「たかし」という名前を付ける
2、もし女の子だったら「さやか」という名前を付ける

というように性別という「条件」によって、おなじ「名前を付ける」でも、行動が変わります。

というように、あらかじめ条件が存在し、それによって処理される内容も変わるというのが条件分岐で、Javaでも当然そのようなことが実現できます。

ではまず、簡単な例として、あいさつのプログラムを書いていきましょう。

プログラム実装

仕様

「朝」だったら「Good Morning World!」
「昼」だったら「Hallo World!」
「夜」だったら「Good Evening World!」

と出力するプログラム

では実際に、書いていきましょう。

まず、「Greeting.java」というクラスを作ります。パッケージはどこでも構いません。
やり方わすれた方は、Vol.2に戻って確認してください。

package JavaTutorial;

public class Greeting{

}

こんな感じでクラスが作成されましたでしょうか?

packageの部分は作った階層によって異なるので、いまは特に考えなくてよいです(本当は重要です)。

次に出力するメソッドを実装します。Vol.2同様、まずは汎用的な挨拶であるHello World!を表示するように実装しましょう。

package JavaTutorial;

public class Greeting {
    public static void main(String[] args) {
        System.out.println("Hello World!");
    }
}

こんな感じでした。

それではいよいよ、条件分岐の実装をしていきます。

Javaでは条件分岐に使う文法は2つ覚えてください。

  • if文
  • switch文

それぞれ説明していきます。

if文

文字通りifの後ろに定義する条件に合致すれば実行し、合致しなければ実行しないというものです。
実際の実装をしつつ説明していきます。

まず、System.out.println("Hello World!");の前にifとタイプしてみましょう。
すると、

if-if statement

とサジェストされるのでそれを選びましょう。

package JavaTutorial;

public class Greeting {
    public static void main(String[] args) {
        if (condition) {

        }
        System.out.println("Hello World!");
    }
}

こんな感じになりましたでしょうか?

package JavaTutorial;

public class Greeting {
    public static void main(String[] args) {
        if (condition) {
        //conditionの部分の条件に合致したらこの部分の処理を実行する 
        }
        System.out.println("Hello World!");
    }
}

それでは、「朝」だったら「Good Morning World!」と表示する仕様を書いていきましょう

package JavaTutorial;

public class Greeting {
    public static void main(String[] args) {
        String timeZone = "朝";

        if (timeZone.equals("朝")) {
            System.out.println("Good Morning World!");
        }
        System.out.println("Hello World!");
    }
}

timeZoneというString型のローカル変数を定義してこいつの状態によって挨拶を返すようにしてみましょう。

これでRunAsからJavaApplicationで実行してみましょう。。。

2つ出てきましたね。。

そうです。Hello Worldは無条件に実行されます。

なので、実行されないようにしましょう。
以下のようにelseを使えばOKです。

package JavaTutorial;

public class Greeting {
    public static void main(String[] args) {
        String timeZone = "朝";

        if (timeZone.equals("朝")) {
            System.out.println("Good Morning World!");
        } else {
            System.out.println("Hello World!");
        }
    }
}

このようにelseの中に入れてしまえば、

if 朝なら GoodMorning
ではなければ Hello

という意味になります。
これでRunAsからJavaApplicationで実行してみましょう。
確かにGood Morning World!だけが出力されたと思います。

次に
timeZoneを「昼」に書き換えて実行してみましょう。

今度は逆にHello Worldだけが出力されたと思います。

では最後に
「昼」だったら「Hallo World!」
「夜」だったら「Good Evening World!」
の仕様を完成させましょう。

3個の条件判定をする場合にはelse ifを使って2番目以降の条件を書いていくことが可能です。

package JavaTutorial;

public class Greeting {
    public static void main(String[] args) {
        String timeZone = "朝";

        if (timeZone.equals("朝")) {
            System.out.println("Good Morning World!");

        } else if (timeZone.equals("昼")) {
            System.out.println("Hello World!");

        } else if (timeZone.equals("夜")) {
            System.out.println("Good Evening World!");

        }
    }
}

こんな感じで書くことができます。

ちなみにこう書くと「朝」でも「昼」でも「夜」でもない場合は挨拶をしません。
※皆さんはしましょう。

if文が分かったところで、今度はこれをswitch文で書き換えていこうと思います。

switch文

switch文は、if文よりも数が多い条件判定のときに役立ちます。
また決まった番号やIDが振られているときなどに重宝します。

どっちで書いても処理の内容はほとんど変わらないので、覚えましょう。

Vol.3で学んだメソッドに切り出して書いてみましょう。

名前は何でも良いですがgreetingSwitchとでもしましょうか。

private static void greetingSwitch() {

}

private メソッドにして、staticも付けておきましょう。
そして、mainメソッドから呼びます。timeZoneは同じものを使えるように引数に指定します。
こんな感じです。

package JavaTutorial;

public class Greeting {
    public static void main(String[] args) {
        String timeZone = "朝";

        if (timeZone.equals("朝")) {
            System.out.println("Good Morning World!");

        } else if (timeZone.equals("昼")) {
            System.out.println("Hello World!");

        } else if (timeZone.equals("夜")) {
            System.out.println("Good Evening World!");

        }
        greetingSwitch(timeZone);
    }

    private static  void greetingSwitch(String timeZone) {

    }
}

そしたら実際にswitch文を書いていきます。greetingSwitchメソッドの中で「switch」とタイプしてください。

switch-switch case statement

とサジェストされたものがあると思うのでそれを選びます。
すると、

    private static  void greetingSwitch(String timeZone) {
        switch (key) {
        case value:

            break;

        default:
            break;
        }
    }

こんな感じになったと思います。
switch文では
keyvalueだったら直下の処理を実行する」
という意味の記述になります。

なのでifの内容をswitchにすると以下のように書けます。

    private static void greetingSwitch(String timeZone) {
        switch (timeZone) {
        case "朝":
            System.out.println("Good Morning World!");
            break;
        case "昼":
            System.out.println("Hello World!");
            break;
        case "夜":
            System.out.println("Good Evening World!");
            break;

        default:
            break;
        }
    }

こんな感じになります。

  • default 部分は何も処理がない場合は無くてもOKです。
  • breakを書かなかった場合に、場合によっては他の処理も実行されてしまう可能性があるので、breakを書きます。

後は実際に事項してみてください。

Helloのあいさつのやり取りのように2本同じものが出てきたと思います。

このように、入力によって処理を分岐させるのが「条件分岐」であり、javaでは多くの場合湖のように書きます。

最後に

条件分岐はJavaに限らずどんなプログラミング言語でも重要な考え方になりますので、マスターして使いこなしましょう。

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

MavenプロジェクトでSonarQubeを使ってみる

What's?

こちらのエントリでSonarQubeサーバーのインストール方法はわかったので、今後はSonarQubeを動かしてみましょう。

CentOS 8にSonarQubeサーバーをインストールする

Java(Maven)なプロジェクトで、SonarQubeのソースコード解析を動かしてみます。

環境

SonarQubeサーバーのバージョンは8.6.0.39681、SonarQubeサーバーが稼働しているサーバーのIPアドレスは192.168.33.10とします。

JavaやMavenのバージョンはこちら。

$ java --version
openjdk 11.0.9.1 2020-11-04
OpenJDK Runtime Environment (build 11.0.9.1+1-Ubuntu-0ubuntu1.20.04)
OpenJDK 64-Bit Server VM (build 11.0.9.1+1-Ubuntu-0ubuntu1.20.04, mixed mode, sharing)


$ mvn --version
Apache Maven 3.6.3 (cecedd343002696d0abb50b32b541b8a6ba2883f)
Maven home: $HOME/.sdkman/candidates/maven/current
Java version: 11.0.9.1, vendor: Ubuntu, runtime: /usr/lib/jvm/java-11-openjdk-amd64
Default locale: ja_JP, platform encoding: UTF-8
OS name: "linux", version: "5.4.0-64-generic", arch: "amd64", family: "unix"

サンプルプロジェクト

ソースコード解析をしようにも、ソースコードがないとどうにもなりません。

今回は、すでに存在するものを引っ張ってくることにしました。Spring PetClinicです。

Spring PetClinic Sample Application

cloneしてきて

$ git clone https://github.com/spring-projects/spring-petclinic.git
$ cd spring-petclinic

特定のタグにcheckoutしたかったところですが、タグがなかったのでコミットにしておきました。

$ git log -1
commit 02babdd8cb0d494d043fdf35a24dd2127c5d5167 (HEAD -> main, origin/main, origin/HEAD)
Author: Stephane Nicoll <snicoll@vmware.com>
Date:   Fri Dec 25 13:02:48 2020 +0100

    Upgrade to Spring Boot 2.4.1

$ git checkout 02babdd8cb0d494d043fdf35a24dd2127c5d5167

これで、ソースコードを解析するプロジェクトの準備は完了です。

SonarQubeでソースコードを解析する

SonarQubeサーバー側では、ユーザーとプロジェクトを作成しておきます。

プロジェクト作成時に

image.png

トークンを作って

image.png

ビルドシステムや言語を選ぶと、使い方を教えてくれます。

image.png

今回は、プロジェクト名はsonar-spring-petclinicとしました。

※キャプチャとは違う名前ですね…

以下のように、システムプロパティでSonarQubeサーバー上のプロジェクト名、接続先、トークンを指定してsonar:sonarゴールを実行します。

$ mvn compile sonar:sonar \
  -Dsonar.projectKey=sonar-spring-petclinic \
  -Dsonar.host.url=http://192.168.33.10:9000 \
  -Dsonar.login=[SonarQubeトークン]

この時、ソースコードのビルドは完了している必要があります。

Sonar Maven Plugin実行時の様子です。

[INFO] --- sonar-maven-plugin:3.8.0.2131:sonar (default-cli) @ spring-petclinic ---
[INFO] User cache: $HOME/.sonar/cache
[INFO] SonarQube version: 8.6.0
[INFO] Default locale: "ja_JP", source code encoding: "UTF-8"
[INFO] Load global settings
[INFO] Load global settings (done) | time=216ms
[INFO] Server id: 641C58CB-AXcoJzcTxqy5V9PiCfw8
[INFO] User cache: $HOME/.sonar/cache
[INFO] Load/download plugins
[INFO] Load plugins index
[INFO] Load plugins index (done) | time=66ms
[INFO] Load/download plugins (done) | time=105ms
[INFO] Process project properties
[INFO] Process project properties (done) | time=7ms
[INFO] Execute project builders
[INFO] Execute project builders (done) | time=1ms
[INFO] Project key: sonar-spring-petclinic
[INFO] Base dir: /path/to/spring-petclinic
[INFO] Working dir: /path/to/spring-petclinic/target/sonar
[INFO] Load project settings for component key: 'sonar-spring-petclinic'
[INFO] Load project settings for component key: 'sonar-spring-petclinic' (done) | time=16ms
[INFO] Load quality profiles
[INFO] Load quality profiles (done) | time=44ms
[INFO] Load active rules
[INFO] Load active rules (done) | time=798ms
[INFO] Indexing files...
[INFO] Project configuration:
[INFO] 37 files indexed
[INFO] 0 files ignored because of scm ignore settings
[INFO] Quality profile for java: My Sonar way
[INFO] Quality profile for xml: Sonar way
[INFO] ------------- Run sensors on module petclinic
[INFO] Load metrics repository
[INFO] Load metrics repository (done) | time=19ms
[INFO] Sensor JavaSquidSensor [java]
[INFO] Configured Java source version (sonar.java.source): 8
[INFO] JavaClasspath initialization
[INFO] JavaClasspath initialization (done) | time=20ms
[INFO] JavaTestClasspath initialization
[INFO] JavaTestClasspath initialization (done) | time=4ms
[INFO] Java Main Files AST scan
[INFO] 25 source files to be analyzed
[INFO] Load project repositories
[INFO] Load project repositories (done) | time=20ms
[INFO] 25/25 source files have been analyzed
[INFO] Java Main Files AST scan (done) | time=2436ms
[INFO] Java Test Files AST scan
[INFO] 11 source files to be analyzed
[INFO] Java Test Files AST scan (done) | time=710ms
[INFO] 11/11 source files have been analyzed
[INFO] Java Generated Files AST scan
[INFO] 0 source files to be analyzed
[INFO] Java Generated Files AST scan (done) | time=1ms
[INFO] 0/0 source files have been analyzed
[INFO] Sensor JavaSquidSensor [java] (done) | time=3399ms
[INFO] Sensor CSS Rules [cssfamily]
[INFO] No CSS, PHP, HTML or VueJS files are found in the project. CSS analysis is skipped.
[INFO] Sensor CSS Rules [cssfamily] (done) | time=1ms
[INFO] Sensor JaCoCo XML Report Importer [jacoco]
[INFO] 'sonar.coverage.jacoco.xmlReportPaths' is not defined. Using default locations: target/site/jacoco/jacoco.xml,target/site/jacoco-it/jacoco.xml,build/reports/jacoco/test/jacocoTestReport.xml
[INFO] No report imported, no coverage information will be imported by JaCoCo XML Report Importer
[INFO] Sensor JaCoCo XML Report Importer [jacoco] (done) | time=7ms
[INFO] Sensor C# Properties [csharp]
[INFO] Sensor C# Properties [csharp] (done) | time=1ms
[INFO] Sensor SurefireSensor [java]
[INFO] parsing [/path/to/spring-petclinic/target/surefire-reports]
[INFO] Sensor SurefireSensor [java] (done) | time=2ms
[INFO] Sensor JavaXmlSensor [java]
[INFO] 1 source files to be analyzed
[INFO] Sensor JavaXmlSensor [java] (done) | time=219ms
[INFO] 1/1 source files have been analyzed
[INFO] Sensor HTML [web]
[INFO] Sensor HTML [web] (done) | time=3ms
[INFO] Sensor XML Sensor [xml]
[INFO] 1 source files to be analyzed
[INFO] Sensor XML Sensor [xml] (done) | time=123ms
[INFO] 1/1 source files have been analyzed
[INFO] Sensor VB.NET Properties [vbnet]
[INFO] Sensor VB.NET Properties [vbnet] (done) | time=1ms
[INFO] ------------- Run sensors on project
[INFO] Sensor Zero Coverage Sensor
[INFO] Sensor Zero Coverage Sensor (done) | time=11ms
[INFO] Sensor Java CPD Block Indexer
[INFO] Sensor Java CPD Block Indexer (done) | time=56ms
[INFO] SCM Publisher SCM provider for this project is: git
[INFO] SCM Publisher 1 source file to be analyzed
[INFO] SCM Publisher 0/1 source files have been analyzed (done) | time=286ms
[WARNING] Missing blame information for the following files:
[WARNING]   * pom.xml
[WARNING] This may lead to missing/broken features in SonarQube
[INFO] CPD Executor 12 files had no CPD blocks
[INFO] CPD Executor Calculating CPD for 13 files
[INFO] CPD Executor CPD calculation finished (done) | time=11ms
[INFO] Analysis report generated in 71ms, dir size=251 KB
[INFO] Analysis report compressed in 95ms, zip size=112 KB
[INFO] Analysis report uploaded in 82ms
[INFO] ANALYSIS SUCCESSFUL, you can browse http://192.168.33.10:9000/dashboard?id=sonar-spring-petclinic
[INFO] Note that you will be able to access the updated dashboard once the server has processed the submitted analysis report
[INFO] More about the report processing at http://192.168.33.10:9000/api/ce/task?id=AXcqAWMeAa54pbha1y28
[INFO] Analysis total time: 7.170 s
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  15.828 s
[INFO] Finished at: 2021-01-22T21:11:10+09:00
[INFO] ------------------------------------------------------------------------

SonarQubeサーバーへの送信が上手くいったら、プロジェクトのページを見てみます。

こんな感じになっています。

image.png

検出された問題の一覧が見れたり

image.png

指摘箇所が見れたり

image.png

ソースコードも見れたりします。

image.png

ということは、プロジェクトのソースコードはSonarQubeサーバーに送っているんですね。

Sonar Maven Pluginをpom.xmlに組み込む

先ほどはmvn実行時のシステムプロパティですべての設定を行い、またプラグインのバージョン指定などもなかったので、今度はpom.xmlにSonar Maven Pluginを組み込んでみましょう。

こちらですね。

SonarScanner for Maven

SonarQube Scanner for Maven

シンプルに、こんな感じになります。設定項目はほぼありません(スキップの指定があるくらいです)。

  <build>
    <plugins>
      <plugin>
        <groupId>org.sonarsource.scanner.maven</groupId>
        <artifactId>sonar-maven-plugin</artifactId>
        <version>3.8.0.2131</version>
      </plugin>

      ...

    </plugins>
  </build>

設定がないと書きましたが、先ほどのSonarQubeサーバーのURLなどの説明は?というと、こちらにあります。

Analysis Parameters

これらは、pom.xmlにプロパティとして指定しましょう。

  <properties>
    <!-- SonarQube -->
    <sonar.projectKey>sonar-spring-petclinic</sonar.projectKey>
    <sonar.host.url>http://192.168.33.10:9000</sonar.host.url>
    <sonar.login>dummy</sonar.login>

    ...

  </properties>

トークンだけは、システムプロパティで実行時に渡すことにしました。

$ mvn sonar:sonar -Dsonar.login=[トークン]

実際には、ソースコードのビルドとともに動かすことになると思うので、以下のような使い方になるでしょう。

$ mvn compile sonar:sonar -Dsonar.login=[トークン]
$ mvn test sonar:sonar -Dsonar.login=[トークン]
$ mvn package sonar:sonar -Dsonar.login=[トークン]

ちなみに、Spring PetClinicの場合、HTTPでSonarQubeへの接続先を書いていたらCheckStyleでNGになりました…。

[INFO] --- maven-checkstyle-plugin:3.1.1:check (nohttp-checkstyle-validation) @ spring-petclinic ---
[INFO] There is 1 error reported by Checkstyle 8.32 with src/checkstyle/nohttp-checkstyle.xml ruleset.
[ERROR] pom.xml:[36,22] (extension) NoHttp: http:// URLs are not allowed but got 'http://192.168.33.10:9000'. Use https:// instead.

今回は、コメントアウトしておきます…。

src/checkstyle/nohttp-checkstyle.xml
<?xml version="1.0"?>
<!DOCTYPE module PUBLIC
        "-//Puppy Crawl//DTD Check Configuration 1.2//EN"
        "https://checkstyle.org/dtds/configuration_1_2.dtd">
<module name="com.puppycrawl.tools.checkstyle.Checker">
    <!-- <module name="io.spring.nohttp.checkstyle.check.NoHttpCheck"/> -->
</module>

適用しているルールについて

今回はなにも考えずに実行しましたが、デフォルトのルールでソースコードチェックされていることになります。

Project Informationで見た時の、「Quality Profile」というものが適用しているルールをまとめたプロファイルです。

image.png

デフォルトでは「Sonar way」というプロファイルが使われています。

image.png

各言語の全ルールが有効になっているわけではなく、ある程度は選別されているようです。

一覧はこちらです。

image.png

Sonar wayはビルトインプロファイルなので、編集することができません。

Sonar wayをベースにカスタマイズするには、コピーして新しいプロファイルを作って行うのがよいのでしょう。

こちらは、Sonar wayプロファイルをコピーして作成した「My Sonar way」というプロファイルです。Deactivateが押せるようになっています。

image.png

あとは、プロジェクト側のQuality Profiliesで

image.png

作成したプロファイルを選択すると、次回の解析から使用されます。

image.png

これで、SonarQubeでの解析が始められますね。

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

【Android / Java】双方向 DataBinding + Clickイベント サンプルアプリ

はじめに

実務案件で DataBinding を利用している。
なかなか難しい。そこで双方向 DataBinding を用いたサンプルアプリを作成しながら DataBinding について学んだ。

※ 今回は Java ですが、 Kotlin でも同様のものを作成してみようと思っております。その時は改めて投稿します。

サンプルアプリの概要

こんな感じのもの↓↓
無茶苦茶画質悪くてすみません。

実装していることとしては

  • フォーム入力時
    • 入力されたテキストをフォーム下部にリアルタイムで表示
    • ボタンアクティブにする(テキストの有無で切り替え)
  • ボタンクリック時
    • フォーム入力テキストを上部に表示
    • フォームおよび下部のテキストを空欄に戻す(初期化する)

開発環境

実装

DataBinding導入

build.gradleに記述を追加して DataBindingを利用できるようにする。

app/build.gradle
plugins {
    id 'com.android.application'
}

android {
    compileSdkVersion 30
    buildToolsVersion "30.0.3"

    defaultConfig {
        applicationId "com.android.bidirectionaldatabindingsample"
        minSdkVersion 24
        targetSdkVersion 30
        versionCode 1
        versionName "1.0"

        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
    }

    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }
    }

    // ここに記述を追加
    dataBinding {
        enabled = true
    }

    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    }
}

dependencies {

    implementation 'androidx.appcompat:appcompat:1.2.0'
    implementation 'com.google.android.material:material:1.2.1'
    implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
    testImplementation 'junit:junit:4.+'
    androidTestImplementation 'androidx.test.ext:junit:1.1.2'
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
}

レイアウト要素をセット

まずは各要素には DataBinding の記述無しの状態で xml にのレイアウトを作成。

<layout></layout>だけは先に記述。
このようにレイアウトのルート要素に <layout>でくくったレイアウトファイルがあると、自動的にxmlファイル名に応じたBindingクラスが作られる。
今回: activity_main.xml => ActivityMainBinding

activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    >

    <androidx.constraintlayout.widget.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        >

        <TextView
            android:id="@+id/click_text"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textSize="20dp"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintVertical_bias="0.15"
            />

        <EditText
            android:id="@+id/input_form"
            android:layout_width="184dp"
            android:layout_height="wrap_content"
            android:hint="テキストを入力"
            android:inputType="text"
            android:textSize="20dp"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintEnd_toStartOf="@id/button"
            app:layout_constraintTop_toTopOf="parent"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintVertical_bias="0.3"
            />

        <Button
            android:id="@+id/button"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="ボタン"
            app:layout_constraintStart_toEndOf="@id/input_form"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintBottom_toBottomOf="@id/input_form"
            />

        <TextView
            android:id="@+id/realTimeText"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textSize="20dp"
            app:layout_constraintStart_toStartOf="@id/input_form"
            app:layout_constraintTop_toBottomOf="@id/input_form"
            android:layout_marginTop="16dp"
            />

    </androidx.constraintlayout.widget.ConstraintLayout>
</layout>

DataBinding のロジック定義をするViewModelを作成する

ViewModel.java
// BaseObservableクラスを継承
public class ViewModel extends BaseObservable {
// ここにロジックを書いていく
}

MainActivityでViewModelをバインドする(紐づける)

MainActivity.java
public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        setTitle("双方向データバインディングサンプル");

        // Bindingインスタンスを生成
        ActivityMainBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_main);
        // ViewModelオブジェクトとバインドする(紐付ける)
        ViewModel viewModel = new ViewModel();
        binding.setViewModel(viewModel);
    }
}

レイアウトファイルでViewModelオブジェクトを利用できるようにする

レイアウトファイルに記述を追加

activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    >

    <data>

    <!--        viewModelという名前でViewModelオブジェクトを登録  -->
        <variable
            name="viewModel"
            type="com.android.bidirectionaldatabindingsample.ViewModel"
            />

    </data>

<!--   ・・・ 省略 ・・・   -->

準備が整ったのでこれから機能を実装していく。

フォーム入力でボタンアクティブにする

ViewModel

以下を記述

  • formText
  • getFormText()
  • setFormtext()
  • isButtonEnable()
ViewModel.java
public class ViewModel extends BaseObservable {
    // 代入せずにnullでも良いが今回は "" を初期値とする。
    private String formText = "";

   // フォーム(EditText)の入力値の getter
    @Bindable public String getFormText() {
        return formText;
    }

    // フォーム(EditText)の入力値の setter
    public void setFormText(String formText) {
        this.formText = formText;
        // この記述でisButtonEnable()が呼ばれる
        notifyPropertyChanged(BR.buttonEnable);
    }

    // フォーム(EditText)へのテキスト入力有無で、ボタン活性・非活性を制御するフラグの getter
    @Bindable public boolean isButtonEnable() {
     // 入力あり:true  入力なし:false
        return !TextUtils.isEmpty(formText);
    }
}

レイアウト

以下を記述

  • フォーム要素にandroid:text="@={viewModel.formText}"
  • ボタン要素にandroid:enabled="@{viewModel.buttonEnable}"
activity_main.xml
<!--  ・・・ 省略 ・・・ -->

        <!--  「@={}」で双方向のバインディング -->
        <!--  ViewModelの getFormText() と setFormText() に対応 -->
        <EditText
            android:id="@+id/input_form"
            android:layout_width="184dp"
            android:layout_height="wrap_content"
            android:hint="テキストを入力"
            android:inputType="text"
            android:text="@={viewModel.formText}" ←ここを追加
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintEnd_toStartOf="@id/button"
            app:layout_constraintTop_toTopOf="parent"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintVertical_bias="0.3"
            />

        <!-- ViewModelの isButtonEnable に対応 -->
        <Button
            android:id="@+id/button"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="ボタン"
            android:enabled="@{viewModel.buttonEnable}" ←ここを追加
            app:layout_constraintStart_toEndOf="@id/input_form"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintBottom_toBottomOf="@id/input_form"
            />

<!--  ・・・ 省略 ・・・ -->

処理の流れ

  1. View側でフォームの値を変更(入力または削除)したときにViewModelsetFormText()が呼ばれる。
  2. このタイミング(formTextが変更されたタイミング)でnotifyPropertyChanged(BR.buttonEnable)によりisButtonEnable()を呼ぶ。
  3. isButtonEnable()formTextの値があるかどうかによりbooleanを返す
  4. View側android:enabled="@{viewModel.buttonEnable}"booleanが入ってくることによりボタンの活性非活性が反映される

ポイント

@={viewModel.formText}により双方向データバインディングが実現される。つまりView側でフォームの値を変更(入力または削除)したときにViewModelsetFormText()が呼ばれるようになる。
@=ではなく@だと一方向のデータバインディングとなり、フォーム入力内容の変更が検知されずsetFormText()が呼ばれないため注意。

フォーム入力内容をリアルタイム表示させる

ViewModel

以下を追加

  • setFormText()内にnotifyPropertyChanged(BR.realTimeText);
  • getRealTimeText()
ViewModel.java
public class ViewModel extends BaseObservable {
    // 代入せずにnullでも良いが今回は "" を初期値とする。
    private String formText = "";

   // フォーム(EditText)の入力値の getter
    @Bindable public String getFormText() {
        return formText;
    }

    // フォーム(EditText)の入力値の setter
    public void setFormText(String formText) {
        this.formText = formText;
        // この記述でgetRealTimeText()が呼ばれる
        notifyPropertyChanged(BR.realTimeText);
        // この記述でisButtonEnable()が呼ばれる
        notifyPropertyChanged(BR.buttonEnable);
    }

    // フォーム(EditText)下にリアルタイムで表示するテキスト(TextView)の getter
    @Bindable public String getRealTimeText() { 
        // フォーム入力されているテキストを代入して返す
        String realTimeText = formText;
        return realTimeText;
    }

    // フォーム(EditText)へのテキスト入力有無で、ボタン活性・非活性を制御するフラグの getter
    @Bindable public boolean isButtonEnable() {
     // 入力あり:true  入力なし:false
        return !TextUtils.isEmpty(formText);
    }
}

レイアウト

フォーム入力の内容をリアルタイムで表示したいTextViewにandroid:text="@{viewModel.realTimeText}"を記述

activity_main.xml
        <!-- 一部抜粋して記述 -->

        <!-- ViewModelのgetRealTimeText()に対応 -->
        <TextView
            android:id="@+id/realTimeText"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@{viewModel.realTimeText}"  ←ここを追加
            android:textSize="20dp"
            app:layout_constraintStart_toStartOf="@id/input_form"
            app:layout_constraintTop_toBottomOf="@id/input_form"
            android:layout_marginTop="16dp"
            />

処理の流れ

  1. フォーム入力内容の変更でsetFormText()内のnotifyPropertyChanged(BR.realTimeText);によりgetRealTimeText()が呼ばれる。
  2. getRealTimeText()の返り値がレイアウトのandroid:text="@{viewModel.realTimeText}"`に取得され表示される。

ポイント

notifyPropertyChanged(BR.~)によりプロパティの変更を通知できる。
getter@Bindableを付与する。そうすることでモジュールパッケージ内のBRクラスにデータバインディングで使用するリソースIDを持つ監視変数が作成されるため、このような記述が可能になる。

詳しくはこちらを参照

BRクラスへ監視変数作成例

以下二つを記述

  • @Bindable public String getFormText()
  • @Bindable public boolean isButtonEnable()
BR.java
public class BR {

  public static final int formText = 1;

  public static final int buttonEnable = 2;
}

ボタンクリックで テキスト表示+フォーム初期化 をさせる

ViewModel

以下を追加

  • 変数clickText
  • getClickText()
  • onButtonClick()
ViewModel.java
public class ViewModel extends BaseObservable {
    // 代入せずにnullでも良いが今回は "" を初期値とする。
    private String formText = "";
    // 初期値をセット
    private String clickText = "ボタンクリックでここに表示";

   // フォーム(EditText)の入力値の getter
    @Bindable public String getFormText() {
        return formText;
    }

    // ボタンクリック時に表示するテキスト(TextView)の getter
    @Bindable public String getClickText() {
        return clickText;
    }

    // フォーム(EditText)の入力値の setter
    public void setFormText(String formText) {
        this.formText = formText;
        // この記述でgetRealTimeText()が呼ばれる
        notifyPropertyChanged(BR.realTimeText);
        // この記述でisButtonEnable()が呼ばれる
        notifyPropertyChanged(BR.buttonEnable);
    }

    // フォーム(EditText)下にリアルタイムで表示するテキスト(TextView)の getter
    @Bindable public String getRealTimeText() { 
        // フォーム入力されているテキストを代入して返す
        String realTimeText = formText;
        return realTimeText;
    }

    // フォーム(EditText)へのテキスト入力有無で、ボタン活性・非活性を制御するフラグの getter
    @Bindable public boolean isButtonEnable() {
     // 入力あり:true  入力なし:false
        return !TextUtils.isEmpty(formText);
    }

    // ボタンクリックイベント
    public void onButtonClick() {
        // clickTextにフォーム入力テキストにセット
        clickText = formText;
        // formTextは初期化
        formText = "";
        // 変更を通知する
        // この記述でgetClickText()が呼ばれる
        notifyPropertyChanged(BR.clickText);
        // この記述でgetFormText()が呼ばれる
        notifyPropertyChanged(BR.formText);
    }
}

レイアウト

以下を追加

  • ボタンクリック時表示したいテキストにandroid:text="@{viewModel.clickText}"
  • ボタンにandroid:onClick="@{() -> viewModel.onButtonClick()}"
activity_main.xml
        <!-- クリック時表示テキストを抜粋 -->

        <!-- ViewModelのgetClickText()に対応 -->
        <TextView
            android:id="@+id/click_text"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textSize="20dp"
            android:text="@{viewModel.clickText}"  ←ここを追加
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintVertical_bias="0.15"
            />

        <!-- ボタン部分を抜粋 -->

        <!-- ViewModelのonButtonClick()に対応 -->
        <Button
            android:id="@+id/button"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="ボタン"
            android:enabled="@{viewModel.buttonEnable}"
            android:onClick="@{() -> viewModel.onButtonClick()}" ←ここを追加
            app:layout_constraintStart_toEndOf="@id/input_form"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintBottom_toBottomOf="@id/input_form"
            />       

ポイント

ボタンクリックしたときの処理の流れ

  1. onButtonClick()が呼ばれる
  2. clickTextにそのときのフォーム入力内容を代入
  3. formText""を代入して初期化
  4. notifyPropertyChanged(BR.clickText/formText)でそれぞれの変数の変更をView側に通知
  5. getClickText()getRealTimeText()が呼ばれ、 View側に変数の値が反映される

補足

Viewが描画される?(アクティビティとバインディングされる?)タイミングでも全てのgetterが呼ばれていた。それによりView側に変数の値が反映される。

参考

https://developer.android.com/topic/libraries/data-binding/observability?hl=ja

最後に

わかりやすく書こうと頑張ってかきました。しかし、かえって冗長でわかりにくくなってしまっていたらすみません。
ご指摘やこうした方が良いなどなどありましたら気軽にコメントいただければ嬉しいです!

DataBinding やっと慣れてきたかも...

冒頭にも記載しましたが、Kotlin でも同様のサンプルを作成しようと思っております。

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

【Android / Java】双方向 DataBinding + Clickイベント サンプル

はじめに

実務案件で DataBinding を利用している。
なかなか難しい。そこで双方向 DataBinding を用いたサンプルアプリを作成しながら DataBinding について学んだ。

※ 今回は Java ですが、 Kotlin でも同様のものを作成してみようと思っております。その時は改めて投稿します。

サンプルアプリの概要

こんな感じのもの↓↓
無茶苦茶画質悪くてすみません。

実装していることとしては

  • フォーム入力時
    • 入力されたテキストをフォーム下部にリアルタイムで表示
    • ボタンアクティブにする(テキストの有無で切り替え)
  • ボタンクリック時
    • フォーム入力テキストを上部に表示
    • フォームおよび下部のテキストを空欄に戻す(初期化する)

開発環境

実装

DataBinding導入

build.gradleに記述を追加して DataBindingを利用できるようにする。

app/build.gradle
plugins {
    id 'com.android.application'
}

android {
    compileSdkVersion 30
    buildToolsVersion "30.0.3"

    defaultConfig {
        applicationId "com.android.bidirectionaldatabindingsample"
        minSdkVersion 24
        targetSdkVersion 30
        versionCode 1
        versionName "1.0"

        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
    }

    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }
    }

    // ここに記述を追加
    dataBinding {
        enabled = true
    }

    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    }
}

dependencies {

    implementation 'androidx.appcompat:appcompat:1.2.0'
    implementation 'com.google.android.material:material:1.2.1'
    implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
    testImplementation 'junit:junit:4.+'
    androidTestImplementation 'androidx.test.ext:junit:1.1.2'
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
}

レイアウト要素をセット

まずは各要素には DataBinding の記述無しの状態で xml にのレイアウトを作成。

<layout></layout>だけは先に記述。
このようにレイアウトのルート要素に <layout>でくくったレイアウトファイルがあると、自動的にxmlファイル名に応じたBindingクラスが作られる。
今回: activity_main.xml => ActivityMainBinding

activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    >

    <androidx.constraintlayout.widget.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        >

        <TextView
            android:id="@+id/click_text"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textSize="20dp"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintVertical_bias="0.15"
            />

        <EditText
            android:id="@+id/input_form"
            android:layout_width="184dp"
            android:layout_height="wrap_content"
            android:hint="テキストを入力"
            android:inputType="text"
            android:textSize="20dp"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintEnd_toStartOf="@id/button"
            app:layout_constraintTop_toTopOf="parent"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintVertical_bias="0.3"
            />

        <Button
            android:id="@+id/button"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="ボタン"
            app:layout_constraintStart_toEndOf="@id/input_form"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintBottom_toBottomOf="@id/input_form"
            />

        <TextView
            android:id="@+id/realTimeText"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textSize="20dp"
            app:layout_constraintStart_toStartOf="@id/input_form"
            app:layout_constraintTop_toBottomOf="@id/input_form"
            android:layout_marginTop="16dp"
            />

    </androidx.constraintlayout.widget.ConstraintLayout>
</layout>

DataBinding のロジック定義をするViewModelを作成する

ViewModel.java
// BaseObservableクラスを継承
public class ViewModel extends BaseObservable {
// ここにロジックを書いていく
}

MainActivityでViewModelをバインドする(紐づける)

MainActivity.java
public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        setTitle("双方向データバインディングサンプル");

        // Bindingインスタンスを生成
        ActivityMainBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_main);
        // ViewModelオブジェクトとバインドする(紐付ける)
        ViewModel viewModel = new ViewModel();
        binding.setViewModel(viewModel);
    }
}

レイアウトファイルでViewModelオブジェクトを利用できるようにする

レイアウトファイルに記述を追加

activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    >

    <data>

    <!--        viewModelという名前でViewModelオブジェクトを登録  -->
        <variable
            name="viewModel"
            type="com.android.bidirectionaldatabindingsample.ViewModel"
            />

    </data>

<!--   ・・・ 省略 ・・・   -->

準備が整ったのでこれから機能を実装していく。

フォーム入力でボタンアクティブにする

ViewModel

以下を記述

  • formText
  • getFormText()
  • setFormtext()
  • isButtonEnable()
ViewModel.java
public class ViewModel extends BaseObservable {
    // 代入せずにnullでも良いが今回は "" を初期値とする。
    private String formText = "";

   // フォーム(EditText)の入力値の getter
    @Bindable public String getFormText() {
        return formText;
    }

    // フォーム(EditText)の入力値の setter
    public void setFormText(String formText) {
        this.formText = formText;
        // この記述でisButtonEnable()が呼ばれる
        notifyPropertyChanged(BR.buttonEnable);
    }

    // フォーム(EditText)へのテキスト入力有無で、ボタン活性・非活性を制御するフラグの getter
    @Bindable public boolean isButtonEnable() {
     // 入力あり:true  入力なし:false
        return !TextUtils.isEmpty(formText);
    }
}

レイアウト

以下を記述

  • フォーム要素にandroid:text="@={viewModel.formText}"
  • ボタン要素にandroid:enabled="@{viewModel.buttonEnable}"
activity_main.xml
<!--  ・・・ 省略 ・・・ -->

        <!--  「@={}」で双方向のバインディング -->
        <!--  ViewModelの getFormText() と setFormText() に対応 -->
        <EditText
            android:id="@+id/input_form"
            android:layout_width="184dp"
            android:layout_height="wrap_content"
            android:hint="テキストを入力"
            android:inputType="text"
            android:text="@={viewModel.formText}" ←ここを追加
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintEnd_toStartOf="@id/button"
            app:layout_constraintTop_toTopOf="parent"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintVertical_bias="0.3"
            />

        <!-- ViewModelの isButtonEnable に対応 -->
        <Button
            android:id="@+id/button"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="ボタン"
            android:enabled="@{viewModel.buttonEnable}" ←ここを追加
            app:layout_constraintStart_toEndOf="@id/input_form"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintBottom_toBottomOf="@id/input_form"
            />

<!--  ・・・ 省略 ・・・ -->

処理の流れ

  1. View側でフォームの値を変更(入力または削除)したときにViewModelsetFormText()が呼ばれる。
  2. このタイミング(formTextが変更されたタイミング)でnotifyPropertyChanged(BR.buttonEnable)によりisButtonEnable()を呼ぶ。
  3. isButtonEnable()formTextの値があるかどうかによりbooleanを返す
  4. View側android:enabled="@{viewModel.buttonEnable}"booleanが入ってくることによりボタンの活性非活性が反映される

ポイント

@={viewModel.formText}により双方向データバインディングが実現される。つまりView側でフォームの値を変更(入力または削除)したときにViewModelsetFormText()が呼ばれるようになる。
@=ではなく@だと一方向のデータバインディングとなり、フォーム入力内容の変更が検知されずsetFormText()が呼ばれないため注意。

フォーム入力内容をリアルタイム表示させる

ViewModel

以下を追加

  • setFormText()内にnotifyPropertyChanged(BR.realTimeText);
  • getRealTimeText()
ViewModel.java
public class ViewModel extends BaseObservable {
    // 代入せずにnullでも良いが今回は "" を初期値とする。
    private String formText = "";

   // フォーム(EditText)の入力値の getter
    @Bindable public String getFormText() {
        return formText;
    }

    // フォーム(EditText)の入力値の setter
    public void setFormText(String formText) {
        this.formText = formText;
        // この記述でgetRealTimeText()が呼ばれる
        notifyPropertyChanged(BR.realTimeText);
        // この記述でisButtonEnable()が呼ばれる
        notifyPropertyChanged(BR.buttonEnable);
    }

    // フォーム(EditText)下にリアルタイムで表示するテキスト(TextView)の getter
    @Bindable public String getRealTimeText() { 
        // フォーム入力されているテキストを代入して返す
        String realTimeText = formText;
        return realTimeText;
    }

    // フォーム(EditText)へのテキスト入力有無で、ボタン活性・非活性を制御するフラグの getter
    @Bindable public boolean isButtonEnable() {
     // 入力あり:true  入力なし:false
        return !TextUtils.isEmpty(formText);
    }
}

レイアウト

フォーム入力の内容をリアルタイムで表示したいTextViewにandroid:text="@{viewModel.realTimeText}"を記述

activity_main.xml
        <!-- 一部抜粋して記述 -->

        <!-- ViewModelのgetRealTimeText()に対応 -->
        <TextView
            android:id="@+id/realTimeText"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@{viewModel.realTimeText}"  ←ここを追加
            android:textSize="20dp"
            app:layout_constraintStart_toStartOf="@id/input_form"
            app:layout_constraintTop_toBottomOf="@id/input_form"
            android:layout_marginTop="16dp"
            />

処理の流れ

  1. フォーム入力内容の変更でsetFormText()内のnotifyPropertyChanged(BR.realTimeText);によりgetRealTimeText()が呼ばれる。
  2. getRealTimeText()の返り値がレイアウトのandroid:text="@{viewModel.realTimeText}"`に取得され表示される。

ポイント

notifyPropertyChanged(BR.~)によりプロパティの変更を通知できる。
getter@Bindableを付与する。そうすることでモジュールパッケージ内のBRクラスにデータバインディングで使用するリソースIDを持つ監視変数が作成されるため、このような記述が可能になる。

詳しくはこちらを参照

BRクラスへ監視変数作成例

以下二つを記述

  • @Bindable public String getFormText()
  • @Bindable public boolean isButtonEnable()
BR.java
public class BR {

  public static final int formText = 1;

  public static final int buttonEnable = 2;
}

ボタンクリックで テキスト表示+フォーム初期化 をさせる

ViewModel

以下を追加

  • 変数clickText
  • getClickText()
  • onButtonClick()
ViewModel.java
public class ViewModel extends BaseObservable {
    // 代入せずにnullでも良いが今回は "" を初期値とする。
    private String formText = "";
    // 初期値をセット
    private String clickText = "ボタンクリックでここに表示";

   // フォーム(EditText)の入力値の getter
    @Bindable public String getFormText() {
        return formText;
    }

    // ボタンクリック時に表示するテキスト(TextView)の getter
    @Bindable public String getClickText() {
        return clickText;
    }

    // フォーム(EditText)の入力値の setter
    public void setFormText(String formText) {
        this.formText = formText;
        // この記述でgetRealTimeText()が呼ばれる
        notifyPropertyChanged(BR.realTimeText);
        // この記述でisButtonEnable()が呼ばれる
        notifyPropertyChanged(BR.buttonEnable);
    }

    // フォーム(EditText)下にリアルタイムで表示するテキスト(TextView)の getter
    @Bindable public String getRealTimeText() { 
        // フォーム入力されているテキストを代入して返す
        String realTimeText = formText;
        return realTimeText;
    }

    // フォーム(EditText)へのテキスト入力有無で、ボタン活性・非活性を制御するフラグの getter
    @Bindable public boolean isButtonEnable() {
     // 入力あり:true  入力なし:false
        return !TextUtils.isEmpty(formText);
    }

    // ボタンクリックイベント
    public void onButtonClick() {
        // clickTextにフォーム入力テキストにセット
        clickText = formText;
        // formTextは初期化
        formText = "";
        // 変更を通知する
        // この記述でgetClickText()が呼ばれる
        notifyPropertyChanged(BR.clickText);
        // この記述でgetFormText()が呼ばれる
        notifyPropertyChanged(BR.formText);
    }
}

レイアウト

以下を追加

  • ボタンクリック時表示したいテキストにandroid:text="@{viewModel.clickText}"
  • ボタンにandroid:onClick="@{() -> viewModel.onButtonClick()}"
activity_main.xml
        <!-- クリック時表示テキストを抜粋 -->

        <!-- ViewModelのgetClickText()に対応 -->
        <TextView
            android:id="@+id/click_text"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textSize="20dp"
            android:text="@{viewModel.clickText}"  ←ここを追加
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintVertical_bias="0.15"
            />

        <!-- ボタン部分を抜粋 -->

        <!-- ViewModelのonButtonClick()に対応 -->
        <Button
            android:id="@+id/button"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="ボタン"
            android:enabled="@{viewModel.buttonEnable}"
            android:onClick="@{() -> viewModel.onButtonClick()}" ←ここを追加
            app:layout_constraintStart_toEndOf="@id/input_form"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintBottom_toBottomOf="@id/input_form"
            />       

ポイント

ボタンクリックしたときの処理の流れ

  1. onButtonClick()が呼ばれる
  2. clickTextにそのときのフォーム入力内容を代入
  3. formText""を代入して初期化
  4. notifyPropertyChanged(BR.clickText/formText)でそれぞれの変数の変更をView側に通知
  5. getClickText()getRealTimeText()が呼ばれ、 View側に変数の値が反映される

補足

Viewが描画される?(アクティビティとバインディングされる?)タイミングでも全てのgetterが呼ばれていた。それによりView側に変数の値が反映される。

参考

https://developer.android.com/topic/libraries/data-binding/observability?hl=ja

最後に

わかりやすく書こうと頑張ってかきました。しかし、かえって冗長でわかりにくくなってしまっていたらすみません。
ご指摘やこうした方が良いなどなどありましたら気軽にコメントいただければ嬉しいです!

DataBinding やっと慣れてきたかも...

冒頭にも記載しましたが、Kotlin でも同様のサンプルを作成しようと思っております。

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

【Android / Java】双方向 DataBinding + Clickイベント / サンプルアプリ実装

はじめに

実務案件で DataBinding を利用している。
なかなか難しい。そこで双方向 DataBinding を用いたサンプルアプリを作成しながら DataBinding について学んだ。

※ 今回は Java ですが、 Kotlin でも同様のものを作成してみようと思っております。その時は改めて投稿します
Kotlin バージョンも作成して記事にしましたのでご覧いただければ幸いです!!↓↓
【Android / Kotlin】双方向 DataBinding + Clickイベント / サンプルアプリ実装

双方向データバインディングとは(一方向との違い)

  • 一方行データバインディング
    • ViewModel などの中でデータを変更 → View に反映
  • 双方向データバインディング
    • ViewModel などの中でデータを変更 → View に反映
    • ユーザが View でデータを変更 → ViewModel にデータの変更が反映(通知)

一方向が ViewModel などのロジックファイルから View 側への通知だけであるのに対し、
双方向ではユーザがアプリのフォームなどに値を入力したときなどに、ロジックファイルの方にも値の入力(変更)が通知される。と言うものである。

なお一方向データバインディングについてはこちらで記事にしております。

※ ここで説明している ViewModel とはデータバインディングのロジックを定義してあるファイルのことを指しています。

サンプルアプリの概要

こんな感じのもの↓↓
無茶苦茶画質悪くてすみません。

実装していることとしては

  • フォーム入力時
    • 入力されたテキストをフォーム下部にリアルタイムで表示
    • ボタンアクティブにする(テキストの有無で切り替え)
  • ボタンクリック時
    • フォーム入力テキストを上部に表示
    • フォームおよび下部のテキストを空欄に戻す(初期化する)

開発環境

  • Android Studio: 4.1.1
  • Build #AI-201.8743.12.41.6953283, built on November 5, 2020
  • Runtime version: 1.8.0_242-release-1644-b3-6915495 x86_64
  • VM: OpenJDK 64-Bit Server VM by JetBrains s.r.o
  • macOS: 10.15.7

実装

DataBinding導入

build.gradleに記述を追加して DataBindingを利用できるようにする。

app/build.gradle
plugins {
    id 'com.android.application'
}

android {
    compileSdkVersion 30
    buildToolsVersion "30.0.3"

    defaultConfig {
        applicationId "com.android.bidirectionaldatabindingsample"
        minSdkVersion 24
        targetSdkVersion 30
        versionCode 1
        versionName "1.0"

        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
    }

    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }
    }

    // ここに記述を追加
    dataBinding {
        enabled = true
    }

    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    }
}

dependencies {

    implementation 'androidx.appcompat:appcompat:1.2.0'
    implementation 'com.google.android.material:material:1.2.1'
    implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
    testImplementation 'junit:junit:4.+'
    androidTestImplementation 'androidx.test.ext:junit:1.1.2'
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
}

レイアウト要素をセット

まずは各要素には DataBinding の記述無しの状態で xml にのレイアウトを作成。

<layout></layout>だけは先に記述。
このようにレイアウトのルート要素に <layout>でくくったレイアウトファイルがあると、自動的にxmlファイル名に応じたBindingクラスが作られる。
今回: activity_main.xml => ActivityMainBinding

activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    >

    <androidx.constraintlayout.widget.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        >

        <TextView
            android:id="@+id/click_text"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textSize="20dp"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintVertical_bias="0.15"
            />

        <EditText
            android:id="@+id/input_form"
            android:layout_width="184dp"
            android:layout_height="wrap_content"
            android:hint="テキストを入力"
            android:inputType="text"
            android:textSize="20dp"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintEnd_toStartOf="@id/button"
            app:layout_constraintTop_toTopOf="parent"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintVertical_bias="0.3"
            />

        <Button
            android:id="@+id/button"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="ボタン"
            app:layout_constraintStart_toEndOf="@id/input_form"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintBottom_toBottomOf="@id/input_form"
            />

        <TextView
            android:id="@+id/realTimeText"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textSize="20dp"
            app:layout_constraintStart_toStartOf="@id/input_form"
            app:layout_constraintTop_toBottomOf="@id/input_form"
            android:layout_marginTop="16dp"
            />

    </androidx.constraintlayout.widget.ConstraintLayout>
</layout>

DataBinding のロジック定義をするViewModelを作成する

ViewModel.java
// BaseObservableクラスを継承
public class ViewModel extends BaseObservable {
// ここにロジックを書いていく
}

MainActivityでViewModelをバインドする(紐づける)

MainActivity.java
public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        setTitle("双方向データバインディングサンプル");

        // Bindingインスタンスを生成
        ActivityMainBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_main);
        // ViewModelオブジェクトとバインドする(紐付ける)
        ViewModel viewModel = new ViewModel();
        binding.setViewModel(viewModel);
    }
}

レイアウトファイルでViewModelオブジェクトを利用できるようにする

レイアウトファイルに記述を追加

activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    >

    <data>

    <!--        viewModelという名前でViewModelオブジェクトを登録  -->
        <variable
            name="viewModel"
            type="com.android.bidirectionaldatabindingsample.ViewModel"
            />

    </data>

<!--   ・・・ 省略 ・・・   -->

準備が整ったのでこれから機能を実装していく。

フォーム入力でボタンアクティブにする

ViewModel

以下を記述

  • formText
  • getFormText()
  • setFormtext()
  • isButtonEnable()
ViewModel.java
public class ViewModel extends BaseObservable {
    // 代入せずにnullでも良いが今回は "" を初期値とする。
    private String formText = "";

   // フォーム(EditText)の入力値の getter
    @Bindable public String getFormText() {
        return formText;
    }

    // フォーム(EditText)の入力値の setter
    public void setFormText(String formText) {
        this.formText = formText;
        // この記述でisButtonEnable()が呼ばれる
        notifyPropertyChanged(BR.buttonEnable);
    }

    // フォーム(EditText)へのテキスト入力有無で、ボタン活性・非活性を制御するフラグの getter
    @Bindable public boolean isButtonEnable() {
     // 入力あり:true  入力なし:false
        return !TextUtils.isEmpty(formText);
    }
}

レイアウト

以下を記述

  • フォーム要素にandroid:text="@={viewModel.formText}"
  • ボタン要素にandroid:enabled="@{viewModel.buttonEnable}"
activity_main.xml
<!--  ・・・ 省略 ・・・ -->

        <!--  「@={}」で双方向のバインディング -->
        <!--  ViewModelの getFormText() と setFormText() に対応 -->
        <EditText
            android:id="@+id/input_form"
            android:layout_width="184dp"
            android:layout_height="wrap_content"
            android:hint="テキストを入力"
            android:inputType="text"
            android:text="@={viewModel.formText}" ←ここを追加
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintEnd_toStartOf="@id/button"
            app:layout_constraintTop_toTopOf="parent"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintVertical_bias="0.3"
            />

        <!-- ViewModelの isButtonEnable に対応 -->
        <Button
            android:id="@+id/button"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="ボタン"
            android:enabled="@{viewModel.buttonEnable}" ←ここを追加
            app:layout_constraintStart_toEndOf="@id/input_form"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintBottom_toBottomOf="@id/input_form"
            />

<!--  ・・・ 省略 ・・・ -->

処理の流れ

  1. View側でフォームの値を変更(入力または削除)したときにViewModelsetFormText()が呼ばれる。
  2. このタイミング(formTextが変更されたタイミング)でnotifyPropertyChanged(BR.buttonEnable)によりisButtonEnable()を呼ぶ。
  3. isButtonEnable()formTextの値があるかどうかによりbooleanを返す
  4. View側android:enabled="@{viewModel.buttonEnable}"booleanが入ってくることによりボタンの活性非活性が反映される

ポイント

@={viewModel.formText}により双方向データバインディングが実現される。つまりView側でフォームの値を変更(入力または削除)したときにViewModelsetFormText()が呼ばれるようになる。
@=ではなく@だと一方向のデータバインディングとなり、フォーム入力内容の変更が検知されずsetFormText()が呼ばれないため注意。

フォーム入力内容をリアルタイム表示させる

ViewModel

以下を追加

  • setFormText()内にnotifyPropertyChanged(BR.realTimeText);
  • getRealTimeText()
ViewModel.java
public class ViewModel extends BaseObservable {
    // 代入せずにnullでも良いが今回は "" を初期値とする。
    private String formText = "";

   // フォーム(EditText)の入力値の getter
    @Bindable public String getFormText() {
        return formText;
    }

    // フォーム(EditText)の入力値の setter
    public void setFormText(String formText) {
        this.formText = formText;
        // この記述でgetRealTimeText()が呼ばれる
        notifyPropertyChanged(BR.realTimeText);
        // この記述でisButtonEnable()が呼ばれる
        notifyPropertyChanged(BR.buttonEnable);
    }

    // フォーム(EditText)下にリアルタイムで表示するテキスト(TextView)の getter
    @Bindable public String getRealTimeText() { 
        // フォーム入力されているテキストを代入して返す
        String realTimeText = formText;
        return realTimeText;
    }

    // フォーム(EditText)へのテキスト入力有無で、ボタン活性・非活性を制御するフラグの getter
    @Bindable public boolean isButtonEnable() {
     // 入力あり:true  入力なし:false
        return !TextUtils.isEmpty(formText);
    }
}

レイアウト

フォーム入力の内容をリアルタイムで表示したいTextViewにandroid:text="@{viewModel.realTimeText}"を記述

activity_main.xml
        <!-- 一部抜粋して記述 -->

        <!-- ViewModelのgetRealTimeText()に対応 -->
        <TextView
            android:id="@+id/realTimeText"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@{viewModel.realTimeText}"  ←ここを追加
            android:textSize="20dp"
            app:layout_constraintStart_toStartOf="@id/input_form"
            app:layout_constraintTop_toBottomOf="@id/input_form"
            android:layout_marginTop="16dp"
            />

処理の流れ

  1. フォーム入力内容の変更でsetFormText()内のnotifyPropertyChanged(BR.realTimeText);によりgetRealTimeText()が呼ばれる。
  2. getRealTimeText()の返り値がレイアウトのandroid:text="@{viewModel.realTimeText}"`に取得され表示される。

ポイント

notifyPropertyChanged(BR.~)によりプロパティの変更を通知できる。
getter@Bindableを付与する。そうすることでモジュールパッケージ内のBRクラスにデータバインディングで使用するリソースIDを持つ監視変数が作成されるため、このような記述が可能になる。

詳しくはこちらを参照

BRクラスへ監視変数作成例

以下二つを記述

  • @Bindable public String getFormText()
  • @Bindable public boolean isButtonEnable()
BR.java
public class BR {

  public static final int formText = 1;

  public static final int buttonEnable = 2;
}

ボタンクリックで テキスト表示+フォーム初期化 をさせる

ViewModel

以下を追加

  • 変数clickText
  • getClickText()
  • onButtonClick()
ViewModel.java
public class ViewModel extends BaseObservable {
    // 代入せずにnullでも良いが今回は "" を初期値とする。
    private String formText = "";
    // 初期値をセット
    private String clickText = "ボタンクリックでここに表示";

   // フォーム(EditText)の入力値の getter
    @Bindable public String getFormText() {
        return formText;
    }

    // ボタンクリック時に表示するテキスト(TextView)の getter
    @Bindable public String getClickText() {
        return clickText;
    }

    // フォーム(EditText)の入力値の setter
    public void setFormText(String formText) {
        this.formText = formText;
        // この記述でgetRealTimeText()が呼ばれる
        notifyPropertyChanged(BR.realTimeText);
        // この記述でisButtonEnable()が呼ばれる
        notifyPropertyChanged(BR.buttonEnable);
    }

    // フォーム(EditText)下にリアルタイムで表示するテキスト(TextView)の getter
    @Bindable public String getRealTimeText() { 
        // フォーム入力されているテキストを代入して返す
        String realTimeText = formText;
        return realTimeText;
    }

    // フォーム(EditText)へのテキスト入力有無で、ボタン活性・非活性を制御するフラグの getter
    @Bindable public boolean isButtonEnable() {
     // 入力あり:true  入力なし:false
        return !TextUtils.isEmpty(formText);
    }

    // ボタンクリックイベント
    public void onButtonClick() {
        // clickTextにフォーム入力テキストにセット
        clickText = formText;
        // formTextは初期化
        formText = "";
        // 変更を通知する
        // この記述でgetClickText()が呼ばれる
        notifyPropertyChanged(BR.clickText);
        // この記述でgetFormText()が呼ばれる
        notifyPropertyChanged(BR.formText);
    }
}

レイアウト

以下を追加

  • ボタンクリック時表示したいテキストにandroid:text="@{viewModel.clickText}"
  • ボタンにandroid:onClick="@{() -> viewModel.onButtonClick()}"
activity_main.xml
        <!-- クリック時表示テキストを抜粋 -->

        <!-- ViewModelのgetClickText()に対応 -->
        <TextView
            android:id="@+id/click_text"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textSize="20dp"
            android:text="@{viewModel.clickText}"  ←ここを追加
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintVertical_bias="0.15"
            />

        <!-- ボタン部分を抜粋 -->

        <!-- ViewModelのonButtonClick()に対応 -->
        <Button
            android:id="@+id/button"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="ボタン"
            android:enabled="@{viewModel.buttonEnable}"
            android:onClick="@{() -> viewModel.onButtonClick()}" ←ここを追加
            app:layout_constraintStart_toEndOf="@id/input_form"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintBottom_toBottomOf="@id/input_form"
            />       

ポイント

ボタンクリックしたときの処理の流れ

  1. onButtonClick()が呼ばれる
  2. clickTextにそのときのフォーム入力内容を代入
  3. formText""を代入して初期化
  4. notifyPropertyChanged(BR.clickText/formText)でそれぞれの変数の変更をView側に通知
  5. getClickText()getRealTimeText()が呼ばれ、 View側に変数の値が反映される

補足

Viewが描画される?(アクティビティとバインディングされる?)タイミングでも全てのgetterが呼ばれていた。それによりView側に変数の値が反映される。

レイアウトファイル全体

activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    >

    <!--        viewModelという名前でViewModelオブジェクトを登録  -->
    <data>
        <variable
            name="viewModel"
            type="com.android.bidirectionaldatabindingkotlin.ViewModel" />
    </data>

    <androidx.constraintlayout.widget.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        >

        <TextView
            android:id="@+id/click_text"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textSize="20dp"
            android:text="@{viewModel.clickText}"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintVertical_bias="0.15"
            />

        <EditText
            android:id="@+id/input_form"
            android:layout_width="184dp"
            android:layout_height="wrap_content"
            android:hint="テキストを入力"
            android:inputType="text"
            android:textSize="20dp"
            android:text="@={viewModel.formText}"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintEnd_toStartOf="@id/button"
            app:layout_constraintTop_toTopOf="parent"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintVertical_bias="0.3"
            />

        <Button
            android:id="@+id/button"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="ボタン"
            android:enabled="@{viewModel.buttonEnable}"
            android:onClick="@{() -> viewModel.onButtonClick()}"
            app:layout_constraintStart_toEndOf="@id/input_form"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintBottom_toBottomOf="@id/input_form"
            />

        <TextView
            android:id="@+id/realTimeText"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textSize="20dp"
            android:text="@{viewModel.realTimeText}"
            app:layout_constraintStart_toStartOf="@id/input_form"
            app:layout_constraintTop_toBottomOf="@id/input_form"
            android:layout_marginTop="16dp"
            />

    </androidx.constraintlayout.widget.ConstraintLayout>
</layout>

参考

https://developer.android.com/topic/libraries/data-binding/observability?hl=ja

最後に

わかりやすく書こうと頑張ってかきました。しかし、かえって冗長でわかりにくくなってしまっていたらすみません。
ご指摘やこうした方が良いなどなどありましたら気軽にコメントいただければ嬉しいです!

DataBinding やっと慣れてきたかも...

冒頭にも記載しましたが、Kotlin でも同様のサンプルを作成しようと思っております。
Kotlin バージョンも作成して記事にしました!!よろしければ↓↓
【Android / Kotlin】双方向 DataBinding + Clickイベント / サンプルアプリ実装

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

Java dependency シリアル化性能比較

Java シリアルツール性能比較

本ページでは主にjavaのシリアルツールの性能比較を記載します。
コードと結果の部分があります。

 使用されたのdependency

  • Java原生方法
  • XStream方式
  • FastJson方式
  • Hessian方式

シリアルされたデータ

@Data
public class User implements Serializable {
    private static final long serialVersionUID = -2564324657866843L;
    private String name;
    private int age;

    private void writeObject(java.io.ObjectOutputStream s) throws IOException {
        s.defaultWriteObject();
        s.writeObject(name);
    }

    private void readObject(java.io.ObjectInputStream s) throws IOException, ClassNotFoundException {
        s.defaultReadObject();
        name=(String)s.readObject();
    }

    @Override
    public String toString() {
        return "User{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}

Userのデータをシリアル化されたデータのレングスに基づいて、ツール間の効率を検証します。

効率検証

1・ まずJava原生登場

   ByteArrayOutputStream byteArrayOutputStream=
           new ByteArrayOutputStream();
   try {
       ObjectOutputStream outputStream=
               new ObjectOutputStream(byteArrayOutputStream);

       outputStream.writeObject(obj);

       return  byteArrayOutputStream.toByteArray();
   } catch (IOException e) {
       e.printStackTrace();
   }
   return new byte[0];

データのレングスはどのぐらいですか?

byte.length:92

2・ 次はXStreamですXMLにベースのツールです

   XStream xStream=new XStream(new DomDriver());

   @Override
   public <T> byte[] serialize(T obj) {

       return xStream.toXML(obj).getBytes();
   }

データのレングスはどのぐらいですか?

byte.length:221 やはりXMLですね~

3・ そしてFastJson登場、中国企業Alibaba 開発されたのツールです。

   return JSON.toJSONString(obj).getBytes();

書き方は安いですか?

でデータのレングスはどのぐらいですか?

byte.length:23 すごいのシリアルですね

4・ 最後にはHessianです

   ByteArrayOutputStream outputStream=new ByteArrayOutputStream();
   HessianOutput hessianOutput=new HessianOutput(outputStream);
   try {
       hessianOutput.writeObject(obj);
       return outputStream.toByteArray();
   } catch (IOException e) {
       e.printStackTrace();
   }

結果は以下です↓

byte.length:50

まとめ

比較してみると,fastjsonは最も効率的なツールです。でも他のツールが悪いわけではない。fastjsonは複数言語に対応できないので。皆さんは項目の内容に応じてシリアルツール使うしよう!

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

Java シリアルツール性能比較

Java シリアルツール性能比較

本ページでは主にjavaのシリアルツールの性能比較を記載します。
コードと結果の部分があります。

使用されたのdependency

  • Java原生方法
  • XStream方式
  • FastJson方式
  • Hessian方式

シリアルされたデータ

@Data
public class User implements Serializable {
    private static final long serialVersionUID = -2564324657866843L;
    private String name;
    private int age;

    private void writeObject(java.io.ObjectOutputStream s) throws IOException {
        s.defaultWriteObject();
        s.writeObject(name);
    }

    private void readObject(java.io.ObjectInputStream s) throws IOException, ClassNotFoundException {
        s.defaultReadObject();
        name=(String)s.readObject();
    }

    @Override
    public String toString() {
        return "User{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}

Userのデータをシリアル化されたデータのレングスに基づいて、ツール間の効率を検証します。

効率検証

1・ まずJava原生登場

   ByteArrayOutputStream byteArrayOutputStream=
           new ByteArrayOutputStream();
   try {
       ObjectOutputStream outputStream=
               new ObjectOutputStream(byteArrayOutputStream);

       outputStream.writeObject(obj);

       return  byteArrayOutputStream.toByteArray();
   } catch (IOException e) {
       e.printStackTrace();
   }
   return new byte[0];

データのレングスはどのぐらいですか?

byte.length:92

2・ 次はXStreamですXMLにベースのツールです

   XStream xStream=new XStream(new DomDriver());

   @Override
   public <T> byte[] serialize(T obj) {

       return xStream.toXML(obj).getBytes();
   }

データのレングスはどのぐらいですか?

byte.length:221 やはりXMLですね~

3・ そしてFastJson登場、中国企業Alibaba 開発されたのツールです。

   return JSON.toJSONString(obj).getBytes();

書き方は安いですか?

でデータのレングスはどのぐらいですか?

byte.length:23 すごいのシリアルですね

4・ 最後にはHessianです

   ByteArrayOutputStream outputStream=new ByteArrayOutputStream();
   HessianOutput hessianOutput=new HessianOutput(outputStream);
   try {
       hessianOutput.writeObject(obj);
       return outputStream.toByteArray();
   } catch (IOException e) {
       e.printStackTrace();
   }

結果は以下です↓

byte.length:50

まとめ

比較してみると,fastjsonは最も効率的なツールです。でも他のツールが悪いわけではない。fastjsonは複数言語に対応できないので。皆さんは項目の内容に応じてシリアルツール使うしよう!

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

Java リフレクションでメソッド実行

Javaでリフレクションを実行した際の備忘録
最後尾にサンプルソースを記載している

◆文字列をクラスオブジェクトに変換する

  // 文字列をクラスオブジェクトに変換する
    String strClass = "imamu.reflect.Called";
    Class<?> c = Class.forName(strClass);

◆クラス名とメソッドの取得方法

  // クラス名の取得
    System.out.println("Class name: " + c.getName());
    // メソッドの取得
    for (Method m : c.getMethods()) {
          boolean isFirstParam = true;
          System.out.print(" " + m.getName() + "(");
          for (Class<?> p : m.getParameterTypes()) {
            if (!isFirstParam) {
              System.out.print(",");
            }
            System.out.print(p.getName());
            isFirstParam = false;
          }
          System.out.println(")");
        }

◆クラスの実行(staticメソッド、引数が配列)
※ static void main(string[] args)を実行する

  // 実行するメソッドと引数を定義する
    String strMethod = "main";
    Method m = c.getMethod(strMethod,String[].class);
    //引数をオブジェクト化する必要がある
    String[] strParam = {};
    Object[] objParam = new Object[] {strParam};
    // 実行
    m.invoke(c,objParam);

◆クラスのインスタンス化と実行
Class.newInstance()は非推奨なので要注意
Class.getDeclaredConstructor().newInstance()とすること

  //インスタンス化
    Object obj = c.getDeclaredConstructor().newInstance();
    // メソッドの定義と実行
    strMethod = "execTest";
    Method m2 = c.getMethod(strMethod);
    m2.invoke(obj);

◆プライベートなstaticメソッドの実行、戻り値あり

  // メソッドの定義と実行
    strMethod = "privateTest";
    Method m3 = c.getMethod(strMethod,String.class,Integer.class);
    // private 
    m3.setAccessible(true);
    Object rtn = m3.invoke(c,"test",500);
    System.out.println(rtn.toString());

★呼び出される側のクラス

/**
 * 呼ばれる側のクラス
 */
public class Called {

public static void main(String[] args) {
    // TODO 自動生成されたメソッド・スタブ
    System.out.println("mainメソッドが実行されました");
}

public void execTest() {
    System.out.println("要インスタンス化ーexecTestが実行されました");
}

public static Integer privateTest(String str,Integer i) {
    System.out.println("要インスタンス化ーprivateTestが実行されました");
    return 200;
}

}

★呼び出す側のクラス

import java.lang.reflect.Method;

/**
 * 呼ぶ側のクラス
 */
public class Call {

/**
 *
 * @param args
 * @throws Exception
 */
public static void main(String[] args) throws Exception{

    // 文字列をクラスオブジェクトに変換する
    String strClass = "imamu.reflect.Called";
    Class<?> c = Class.forName(strClass);

    // クラス名とメソッドの取得
    getClassNameAndMethod(c);

    /*クラスの実行(staticメソッド、引数が配列)
    ※ static void main(string[] args)を実行する
    */
    // 実行するメソッドと引数を定義する
    String strMethod = "main";
    Method m1 = c.getMethod(strMethod,String[].class);
    //引数をオブジェクト化する必要がある
    String[] strParam = {};
    Object[] objParam = new Object[] {strParam};
    // 実行
    m1.invoke(c,objParam);


    /* クラスのインスタンス化と実行 */
    //インスタンス化
    Object obj = c.getDeclaredConstructor().newInstance();
    // メソッドの定義と実行
    strMethod = "execTest";
    Method m2 = c.getMethod(strMethod);
    m2.invoke(obj);

    /* プライベートなstaticメソッドの実行、戻り値あり */
    // メソッドの定義と実行
    strMethod = "privateTest";
    Method m3 = c.getMethod(strMethod,String.class,Integer.class);
    // private 
    m3.setAccessible(true);
    Object rtn = m3.invoke(c,"test",500);
    System.out.println(rtn.toString());
    System.out.println("おしまい");

}

/**
 *
 * @param c
 */
private static void getClassNameAndMethod(Class<?> c ) {

    // クラス名の取得
    System.out.println("Class name: " + c.getName());

    // メソッドの取得
    for (Method m : c.getMethods()) {
          boolean isFirstParam = true;
          System.out.print(" " + m.getName() + "(");
          for (Class<?> p : m.getParameterTypes()) {
            if (!isFirstParam) {
              System.out.print(",");
            }
            System.out.print(p.getName());
            isFirstParam = false;
          }
          System.out.println(")");
        }

}

}

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