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

EclipseでAmaterasUMLを使う

はじめに

Eclipsを久しぶりに使う際に,以前UML図を自動で生成してくれるAmaterasUMLをどうやってインストールするのか忘れてしまい,いろいろし調べたのですが,少しハマったポイントがありましたので,そちらを残して置きたいと思います.

Eclipse側の設定

  • 上部のバーのヘルプ -> 新規ソフトウェアのインストール
  • 作業対象にhttp://download.eclipse.org/releases/lunaを打ち込み,フィルターにGEFを指定
  • グラフィカル編集フレームワーク GEF SDKを選択し,インストール install

以降手順に従ってインストールを完了させて,再起動してください.

AmaterasUMLのインストール

公式ホームページからAmaterasUML_1.3.4.zipをダウンロードし,解凍します.そうすると,以下の3つのjarファイルが入っています.

net.java.amateras.umleditor.java_1.3.4.jar
net.java.amateras.umleditor_1.3.4.jar
net.java.amateras.xstream_1.3.4.jar

これらを
/Applications/Eclipse_{version}.app/Contents/Eclipse/dropins/AmaterasModeler/eclipse/plugins
へコピーします.
ただ,私の場合こちらのディレクトリには以下のファイルが予め含まれていました.

net.java.amateras.db_1.0.9.jar
net.java.amateras.umleditor.java_1.3.5.jar
net.java.amateras.umleditor_1.3.5.jar
net.java.amateras.xstream_1.3.4.jar

umleditorに関するファイルが同じであることと,バージョンが上位であることから,もともとAmaterasUMLは使えるのではと思い,コピーする前にUML図を作成しようと試みてみました.
しかし,cldファイルの作成はできましたがjavaファイルのD&Dでの反映がうまくいきませんでした.

そのため,umleditorに関するファイルのみ上書きしました.(一応バックアップはしたほうがいいのかなと思ってしてます.この違いを知っている方がおられましたら教えていただきたいです)
そうすると,無事にcldファイルの作成をjavaファイルのD&Dで作成することができました.

UML図の作成方法

UML図の作成する場合は,作成したいディレクトリやパッケージなどの中で新規 -> その他 -> AmaterasUML -> クラス図でcldファイルの作成した後,任意のjavaファイルをcldファイルのウィンドウへD&Dするとクラス図が自動で生成されます.
複数のjavaファイルを選択してD&Dすれば委譲関係などもわかるようになっています.便利ですね.

さいごに

いくつかのサイトで書かれていた/Applications/Eclipse_{version}.app/Contents/Eclipse/dropins内へ解凍したjarファイルを入れるという点で少し詰まってしまいました.
いつからAmaterasModelerディレクトリがあったのか確認していなかったため,最新版のEclipseをインストールした方にはあるかもしれません.その場合,AmaterasModelerディレクトリ内にもともとあるjarファイルと置き換えないとUML図が正しく生成できなかったため,注意が必要かもしれません.
久しくUML図を書いていないため,AmaterasUMLを参考に復習したいと思っています.

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

JPAで複数のOneToManyでjoin fetchするとMultipleBagFetchException

JPAで@OneToManyを複数定義して両者をjoin fetchで取得しようとすると、下記のような実行時例外になることがある。

Caused by: org.hibernate.loader.MultipleBagFetchException: cannot simultaneously fetch multiple bags: [jpa.sample2.Tweet.reTweets, jpa.sample2.Tweet.favorites]
    at org.hibernate.loader.BasicLoader.postInstantiate(BasicLoader.java:76) ~[hibernate-core-5.4.20.Final.jar:5.4.20.Final]
    at org.hibernate.loader.hql.QueryLoader.<init>(QueryLoader.java:110) ~[hibernate-core-5.4.20.Final.jar:5.4.20.Final]

状況

説明用のスキーマとして、一つのtweetに複数のReTweetと複数のFavorite、というテーブルを想定する。javaのentityとしては以下のような感じ。

@NoArgsConstructor
@AllArgsConstructor
@Entity
@Data
public class Tweet {
    @Id
    Long id;

    String value;

    @OneToMany
    @JoinColumn(name = "tweetId")
    List<ReTweet> reTweets;

    @OneToMany
    @JoinColumn(name = "tweetId")
    List<Favorite> favorites;
}
@NoArgsConstructor
@AllArgsConstructor
@Entity
@Data
public class ReTweet {
    @Id
    Long id;
    Long tweetId;
    String value;
}
@NoArgsConstructor
@AllArgsConstructor
@Entity
@Data
public class Favorite {
    @Id
    Long id;
    Long tweetId;
    String value;
}

JPQLは以下のようにjoin fetchを2回使用する。

@Repository
public interface TweetRepository extends JpaRepository<Tweet, Long>  {  
    @Query("select distinct t from Tweet t "
            + " left join fetch t.reTweets "
            + " left join fetch t.favorites"
            + " order by t.id")
    List<Tweet> list2();
}

こうすると上記の実行時例外となる。

解決策

このケースの場合はListSetに変えればよい。

    @OneToMany
    @JoinColumn(name = "tweetId")
    Set<ReTweet> reTweets;

    @OneToMany
    @JoinColumn(name = "tweetId")
    Set<Favorite> favorites;

またはMapでも良い。

    @OneToMany
    @JoinColumn(name = "tweetId")
    Map<Long, ReTweet> reTweets;

    @OneToMany
    @JoinColumn(name = "tweetId")
    Map<Long, Favorite> favorites;

もしくは、単一のJPQLを諦めて複数に分割する、というやり方も考えられる。

    @Query("select distinct t from Tweet t "
            + " left join fetch t.reTweets "
            + " order by t.id")
    List<Tweet> list3();

    @Query("select distinct t from Tweet t "
            + " left join fetch t.favorites"
            + " order by t.id")
    List<Tweet> list4();
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

try-catch-finally 例外処理 使い方 java

環境:Windows10, Eclipse, java8

javaにおけるtry catch finallyを使った例外処理について記述します。

例外処理の書き方

try{
  処理
}catch(例外の型 引数){
  例外発生時の処理
}finally{
  最後に実行される処理(例外の有無関係なし)
}

使用例

    public static void main(String[] args) {
        double a = 99999;

        //----------例外処理が発生するケース----------
        System.out.println("-----例外発生-----");
        try {
            a = 30/0;
        }catch(ArithmeticException e) {
            System.out.println("例外処理:0では割れません");
            System.out.println(e);
        }finally {
            System.out.println("finally  "+ "a="+a);
        }

        //----------例外処理が発生しないケース----------
        System.out.println("-----正常-----");
        try {
            a = 30/3;
        }catch(ArithmeticException e) {
            System.out.println("例外処理:0では割れません");
            System.out.println(e);
        }finally {
            System.out.println("finally  "+ "a="+a);
        }
    }
実行結果
-----例外発生-----
例外処理:0では割れません
java.lang.ArithmeticException: / by zero
finally  a=99999.0
-----正常-----
finally  a=10.0

解説

以上のプログラムでは、a=30/0;の行にて、
ゼロで除算をしているため(解なしになってしまう)、例外(ArithmeticException)が発生しています。
そのため、以下の処理が実行されます。

        }catch(ArithmeticException e) {
            System.out.println("例外処理:0では割れません");
        }

そのため、実行結果として、「例外処理:0では割れません」と表示されました。

catch(例外の型 引数)について

catchの中身はどのように書けばよいのか。

上記での例
}catch(ArithmeticException e){

上記の例での、ArithmeticExceptionというのは、ゼロで除算すると投げられてくる例外です。

他の例外も、以下の形で書けますので、解説していきます。

}catch(例外の型 引数){

例外の型(例:ArithmeticException)

例外が発生した際に投げられてくる型を書く。

try内に書く処理に対応した例外処理を、リファレンスで調べて使う。
java11の場合:https://docs.oracle.com/javase/jp/11/docs/api/index-files/index-1.html

importを伴う場合

また、ファイル入出力操作等で例外処理をしたい場合に、
IOExceptionという例外を使う場合は

import java.io.IOException;

でパッケージをインポートする必要があることもあります。

今回のArithmeticExceptionという例外についても、

import java.lang.ArithmeticException

と記述してimportの必要がありそうです。
しかし、java.langはコンパイラ内で暗黙的にimportされるため、記述の必要はありません。
ちなみにSystem.out.printlnがimportなしで使えるのもこのためです。

引数(例:e)

命名は自由だが。「e」が使われることが多い。

今回の例では
「System.out.println(e);」を記述しているため、
実行結果に「java.lang.ArithmeticException: / by zero」
と表示され、例外を受け取っていることが確認できる。

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

Spring Data JPAの非named queryなのでNo property list found for type

以下のように、named queryではないのにうっかりnameにJPQLを指定しまうと、一見不可解な実行時エラーが発生する。

@Repository
public interface SampleRepository extends JpaRepository<Sample, Long> {
    @Query(name = "select s from Sample s")
    List<Sample> list();
}
Caused by: org.springframework.data.mapping.PropertyReferenceException: No property list found for type Sample!
    at org.springframework.data.mapping.PropertyPath.<init>(PropertyPath.java:94) ~[spring-data-commons-2.3.3.RELEASE.jar:2.3.3.RELEASE]
    at org.springframework.data.mapping.PropertyPath.create(PropertyPath.java:382) ~[spring-data-commons-2.3.3.RELEASE.jar:2.3.3.RELEASE]
    at org.springframework.data.mapping.PropertyPath.create(PropertyPath.java:358) ~[spring-data-commons-2.3.3.RELEASE.jar:2.3.3.RELEASE]

落ち着いてみれば、単にそんなnameのnamed queryは無いですよ、というエラーなのだが、正しくJPQLを指定してるつもりなのに実行時エラーになって焦ってしまった……のでメモを残しておく。

正しくはvalueにJPQLを指定すればよい。

    @Query("select s from Sample s")
    List<Sample> list();
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Java】【Kotlin】Enum の valueOf と values をジェネリックに呼び出す

Enum 派生クラスに暗黙的に宣言されるメソッド/メンバ関数である valueOf および values
ジェネリックに呼び出す方法。

Java

次のような enum 型が定義されているとする。

public enum PrimaryColor {
    RED, GREEN, BLUE
}

具象型が分かっているときの呼び出し

public static void main(String... args) {
    PrimaryColor red = PrimaryColor.valueOf("RED"); // RED
    PrimaryColor[] values = PrimaryColor.values(); // [RED, GREEN, BLUE]
}

ジェネリックな呼び出し

public static <E extends Enum<E>> E valueOf(Class<E> enumType, String name) {
    return Enum.valueOf(enumType, name);
}

public static <E extends Enum<E>> E[] values(Class<E> enumType) {
    return enumType.getEnumConstants();
}

public static void main(String... args) {
    PrimaryColor red = valueOf(PrimaryColor.class, "RED"); // RED
    PrimaryColor[] values = values(PrimaryColor.class); // [RED, GREEN, BLUE]
}

Kotlin

次のような enum 型が定義されているとする。

enum class PrimaryColor {
    RED, GREEN, BLUE
}

具象型が分かっているときの呼び出し

fun main() {
    val red: PrimaryColor = PrimaryColor.valueOf("RED") // RED
    val values: Array<PrimaryColor> = PrimaryColor.values() // [RED, GREEN, BLUE]
}

ジェネリックな呼び出し

inline fun <reified E : Enum<E>> valueOf(name: String): E =
    enumValueOf<E>(name) // `<E>` は今回の場合は省略(型推論)可能

inline fun <reified E : Enum<E>> values(): Array<E> =
    enumValues<E>() // `<E>` は今回の場合は省略(型推論)可能

fun main() {
    val red: PrimaryColor = valueOf("RED")
    val values: Array<PrimaryColor> = values()
}

/以上

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

キャストを含むStreamと拡張for文の処理速度の比較

はじめに

配列やコレクションに含まれる特定の型の要素に対して操作を行うにあたり、Streamと拡張for文のどちらが早いのか、処理速度を検証しました。

事前に用意するコード

要素数1億の配列
Object[] objects = new Object[100_000_000];
for (int i = 0; i < 50_000_000; i++)
    objects[i] = new Object();
for (int i = 50_000_000; i < 100_000_000; i++)
    objects[i] = "";
要素数1億のArrayList
List<Object> objects = new ArrayList<>();
for (int i = 0; i < 50_000_000; i++)
    objects.add(new Object());
for (int i = 0; i < 50_000_000; i++)
    objects.add("");

今回は配列とArrayListを使って検証します。
Object型、String型の要素をそれぞれ5千万個ずつ格納しています。

検証

Stream
long start = System.currentTimeMillis();
objects.stream() // Arrays.stream(objects)
    .filter(String.class::isInstance)
    .map(String.class::cast)
    .forEach(String::toUpperCase);
long finish = System.currentTimeMillis();
System.out.println(finish - start + "ms");
種類 時間
配列 345ms
ArrayList 409ms

配列の方がやや早いようです。

拡張for文
long start = System.currentTimeMillis();
for (Object obj : objects) {
    if (obj instanceof String) {
        ((String) obj).toUpperCase();
    }
}
long finish = System.currentTimeMillis();
System.out.println(finish - start + "ms");
種類 時間
配列 122ms
ArrayList 212ms

今回の比較では、Streamよりも拡張for文の方が早いということがわかりました1
普段百万、千万単位の要素数を扱うコードを組まないのであまり意識せずストリームを使うことが多いのですが、パフォーマンスを重視する上では、ストリームは注意して用いる必要がありそうです。


  1. 計測方法に不備などございましたら、恐れ入りますが教えて頂けますと幸いです。 

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

Kotlinを使いこなす 〜Convert Java File to Kotlin File 卒業への道〜 その3

はじめに

Android等でKotlinを使用している場合、Javaから『Convert Java File to Kotlin File』で自動変換している人は多い。実際にそれでも動くのだが、そこをもう1歩先に進みKotlinをより使いこなす為のメモ。
第3回目
今回はListとfor文の処理を習得する。
Kotlinバージョン:1.4

Kotlin変換前のサンプルコード

あるオブジェクトからListを生成する処理があるとした例。
Listの作成にはいくつか条件がある。

ItemData.java
// Stringが7個あるオブジェクト
public class ItemData {
    private String item1;
    private String item2;
    private String item3;
    private String item4;
    private String item5;
    private String item6;
    private String item7;

    public ItemData2(String item1, String item2, String item3, String item4,
                     String item5, String item6, String item7) {
        this.item1 = item1;
        this.item2 = item2;
        this.item3 = item3;
        this.item4 = item4;
        this.item5 = item5;
        this.item6 = item6;
        this.item7 = item7;
    }

    public String getItem1() {
        return item1;
    }

    public String getItem2() {
        return item2;
    }

    public String getItem3() {
        return item3;
    }

    public String getItem4() {
        return item4;
    }

    public String getItem5() {
        return item5;
    }

    public String getItem6() {
        return item6;
    }

    public String getItem7() {
        return item7;
    }
}
ListUtil.java
// オブジェクトからListを作成するUtil
public final class ListUtil {

    private ListUtil() {
    };

    private static final int MAX_SIZE = 4;

    // オブジェクトのitem1、item2、item3、item4、item5でリストを作る
    // 但し、nullだったらListに追加しない
    public static List<String> createItemList(final ItemData itemData) {
        if (itemData == null) {
            return new ArrayList<>();
        }
        final List<String> list = new ArrayList<>();
        for (int i = 0; i <= MAX_SIZE; i++) {
            switch (i) {
                case 0:
                    if (itemData.item1 != null) {
                        list.add(itemData.item1);
                    }
                    break;
                case 1:
                    if (itemData.item2 != null) {
                        list.add(itemData.item2);
                    }
                    break;
                case 2:
                    if (itemData.item3 != null) {
                        list.add(itemData.item3);
                    }
                    break;
                case 3:
                    if (itemData.item4 != null) {
                        list.add(itemData.item4);
                    }
                    break;
                case 4:
                    if (itemData.item5 != null) {
                        list.add(itemData.item5);
                    }
                    break;
                default:

            }
        }
        return list;
    }
}

Kotlin自動変換後のサンプルコード

これを自動変換したらこんな感じに・・・。
とりあえずこれでも動きはする。

ItemData.kt
// Stringが7個あるオブジェクト
class ItemData(
    val item1: String,
    val item2: String,
    val item3: String,
    val item4: String,
    val item5: String,
    val item6: String,
    val item7: String
)
ListUtil.kt
// オブジェクトからListを作成するUtil
object ListUtil {
    private const val MAX_SIZE = 4
    // オブジェクトのitem1、item2、item3、item4、item5でリストを作る
    // 但し、nullだったらListに追加しない
    fun createItemList(itemData: ItemData?): List<String?> {
        if (itemData == null) {
            return ArrayList()
        }
        val list: MutableList<String?> = ArrayList()
        for (i in 0 until MAX_SIZE) {
            when (i) {
                0 -> if (itemData.item1 != null) {
                    list.add(itemData.item1)
                }
                1 -> if (itemData.item2 != null) {
                    list.add(itemData.item2)
                }
                2 -> if (itemData.item3 != null) {
                    list.add(itemData.item3)
                }
                3 -> if (itemData.item4 != null) {
                    list.add(itemData.item4)
                }
                4 -> if (itemData.item5 != null) {
                    list.add(itemData.item5)
                }
                else -> {
                }
            }
        }
        return list
    }
}

実装方法を考える

せっかくKotlinで実装しているのでもっとシンプルにしたい。
早速、修正してみる

ItemData.kt
// dataを付けてデータクラスと明示する
data class ItemData(
    val item1: String,
    val item2: String,
    val item3: String,
    val item4: String,
    val item5: String,
    val item6: String,
    val item7: String
)
ListUtil.kt
// オブジェクトからListを作成するUtil
object ListUtil {
    private const val MAX_SIZE = 4
    // オブジェクトのitem1、item2、item3、item4、item5でリストを作る
    // 但し、nullだったらListに追加しない

    // 戻り値はList<String?>→List<String>が良い
    fun createItemList(itemData: ItemData?): List<String> {
        return itemData?.let {
            // nullの場合はここに入らない
            val list: MutableList<String?> = ArrayList()
            // for (i in 0..MAX_SIZE) という記述も可能
            for (i in 0 until MAX_SIZE) {
                when (i) {
                    0 -> list.add(it.item1)
                    1 -> list.add(it.item2)
                    2 -> list.add(it.item3)
                    3 -> list.add(it.item4)
                    4 -> list.add(it.item5)
                    else -> {
                    }
                }
            }
            // ここでreturnされる
            list.filterNotNull()
            // これでListのnullが消えStringがNonNullになる
            // add時にnullチェックしなくて済む
        } ?: emptyList()
        // null時は空Listを返す
        // 空ListはArrayList()でなくemptyList()を返す
    }
}

とてもシンプル!

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

vue-wordcloudでツイートを検索して紐づくプロフィールに何が書いてあるかの傾向を調べるWebアプリを作ってみた

1.っていうよく見るアレです。

アレってなんだよっていう方のために結果を先にお見せします。

図1.PNG

ツイートを検索ワードで検索すると、ヒットしたツイートしてる人のプロフィールの単語をWord Cloudのタグクラウドで表示します。

出現回数が多い文字が大きくなります。

作り方は簡単みたいですが、調べるとだいたいPythonです。

今、Vue.jsとJava(Spring Boot)で開発してるので、それで作れないかな~と思ってちょっとWebアプリを作ってみました。

今回はツイートの内容じゃなくて、ツイートの内容からプロフィールの内容をひっぱってきてるのでそこが少しトリッキーかも。

Webアプリ作成日数:2日
Qiita記事作成:2日

Qiita記事作成のほうがしんどい。

何でこんなに長いタイトルになってしまったのか。

Webアプリの動作の流れはこんな感じです。



TwitterのAPIでツイートのプロフィール情報取得

プロフィール情報の形態素解析

可視化



形態素解析とは?

文章を単語に分割する作業って感じですが私はそれ以上のことはわからないのでみなさんの方がきっと詳しくなります。

2.環境

Vue.js:2.6.12
Spring Boot:v2.3.3
Java:AdoptOpenJDK:11.0.6
Kuromoji:0.9.0
vue-wordcloud:1.1.1
axios:0.20.0

性懲りもなくVue.jsをフロント、Spring BootをサーバサイドのAPIとします。

大まかなディレクトリ構造はこんな感じです。

┠ src
┃  ┗ main
┃     ┗ java/app/myapp
┃        ┠ common
┃        ┃  ┗ HttpAccess.java
┃        ┠ controller
┃        ┃  ┗ TwitterAnalysisRestController.java
┃        ┠ entity
┃        ┃  ┗ VueWordCloudEntity.java
┃        ┠ service
┃        ┃  ┗ TwitterAnalysisService.java
┃        色々
┃
┠ web
┃  ┠ 色々
┃  ┠ src
┃  ┃  ┠ 色々
┃  ┃  ┠ router
┃ ┃  ┃  ┗ index.js
┃  ┃  ┠ views
┃  ┃  ┃  ┗ TwitterAnalysis.vue
┃  ┃  ┃
┃  ┃  色々
┃  色々
┃
┠ build.gradle
色々

3.手順

3-1.前提

Spring Boot のプロジェクトが作成済。

3-2.TwitterのAPI利用申請

まず、Twitter側にTwitterのAPI利用申請をする必要があります。

Twitterのアカウントを持ってる前提です。

親切に解説してくれてるサイトがあるので、利用申請はこちらを参考にしてください。
2020年度版 Twitter API利用申請の例文からAPIキーの取得まで詳しく解説

情報は日々変わるのですでに変わってるところがあるかもしれませんが、多分もっと簡単になってるはずです。

やることは、Twitter側からのいくつかの質問に答えるだけです。

どうみても英語で書くことを強いられる申請画面なのですが、実は日本語でもよかったりして…

質問に答えたら承認待ちかな…と思ったらそんなことはなく、すぐにトークン(Bearer Token)の表示画面に行けました。

このトークンとあとキーをコピーしておけばOKです。

ちょっと前は承認待ちに数日かかっていたようです。

3-3.Javaの実装

まずはTwitterのAPIを利用してプロフィール情報を取得します。

Twitter4Jというライブラリを使えば便利なようですが、今回は使ってません。

理由は特にありません。

APIのURLはこんな感じです。

API、最近新しくなったみたいですね。

https://api.twitter.com/2/tweets/search/recent?expansions=author_id&user.fields=description&max_results=100&query=<検索キーワード>
今回使うパラメータ 内容
expansions=author_id ツイートの文章と同時に、そのツイートをした人の情報を取得するために指定
user.fields=description 取得する情報に、ツイートをした人のプロフィールの文章を含めるために指定
max_results=100 最大取得件数
query=<検索ワード> ツイートに対する検索ワード

詳しくは公式サイト

で、APIを利用して情報を取得するのはこんな感じ。

トークンには上記で取得したBearer Tokenを指定します。

TwitterAnalysisService.java
String urlString = "https://api.twitter.com/2/tweets/search/recent?expansions=author_id&user.fields=description&max_results=100";

String method = "GET";
String bearerToken = <トークン>;

HttpAccess httpAccess = new HttpAccess();
String response = httpAccess.requestHttp(urlString, method, bearerToken, <検索ワード>);
HttpAccess.java
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLEncoder;

public class HttpAccess {

    public String requestHttp(String urlString, String method, String bearerToken, String query) {

        String result = null;
        String urlStringFull = null;

        if (query == null || query.isEmpty()) return result;

        try {
            urlStringFull = urlString + "&query=" + URLEncoder.encode(query, "UTF-8");
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }

        if (urlStringFull == null) return result;

        HttpURLConnection  urlConn = null;
        InputStream in = null;
        BufferedReader reader = null;

        try {

            URL url = new URL(urlStringFull);

            urlConn = (HttpURLConnection) url.openConnection();
            urlConn.setRequestMethod(method);
            urlConn.setRequestProperty("Authorization","Bearer " + bearerToken);
            urlConn.connect();

            int status = urlConn.getResponseCode();
            if (status == HttpURLConnection.HTTP_OK) {

                in = urlConn.getInputStream();

                reader = new BufferedReader(new InputStreamReader(in));

                StringBuilder output = new StringBuilder();
                String line;

                while ((line = reader.readLine()) != null) {
                    output.append(line);
                }

                result = output.toString();
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                if (reader != null) {
                    reader.close();
                }
                if (urlConn != null) {
                    urlConn.disconnect();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

        return result;
    }
}

次に形態素解析ですが、Javaでやりたい場合はKuromojiが便利です。

build.gradle ファイルに一行追加するだけです。

build.gradle
dependencies {
~略~
    implementation 'com.atilika.kuromoji:kuromoji-ipadic:0.9.0'
~略~
}

あと、データをどういう形式で返すかというと、ビューの方でvue-wordcloudが「name」と「value」をキーに持つオブジェクトの配列をデータとして扱うので、「name」と「value」をキーに持つオブジェクトの配列をJSONで返します。
※キーの名称は変更可能

「name」には形態素解析した結果の単語、「value」にはその単語が何回出現したかを入れます。

今回、名詞のみを対象にするとして、上記のAPIへのアクセスに加えて形態素解析を行い、「name」と「value」をフィールドに持つオブジェクトを作成したのが以下の通りです。

形態素解析にはTokenizerクラスを使います。

TwitterAnalysisService.java
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;

import com.atilika.kuromoji.ipadic.Token;
import com.atilika.kuromoji.ipadic.Tokenizer;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import app.myapp.common.HttpAccess;
import app.myapp.entity.VueWordCloudEntity;

@Service
public class TwitterAnalysisService {

    public List<VueWordCloudEntity> analysisTwitterProfileByQuery(String query) {

        List<VueWordCloudEntity> result = new ArrayList<>();

        String urlString = "https://api.twitter.com/2/tweets/search/recent?expansions=author_id&user.fields=description&max_results=100";

        String method = "GET";
        String bearerToken = <トークン>;

        HttpAccess httpAccess = new HttpAccess();
        String response = httpAccess.requestHttp(urlString, method, bearerToken, query);

        if (response == null) return result;

        ObjectMapper mapper = new ObjectMapper();
        JsonNode root = null;

        try {
            root = mapper.readTree(response);
        } catch (JsonProcessingException e) {
            e.printStackTrace();
        }

        if (root == null || root.get("meta").get("result_count").asInt() == 0) {
            return result;
        }

        Tokenizer tokenizer = new Tokenizer();
        List<Token> tokens = new ArrayList<>();

        JsonNode users = root.get("includes").get("users");

        for(int i = 0; i < users.size(); i++) {
            if (users.get(i).get("description") != null) {
                tokens.addAll(tokenizer.tokenize(users.get(i).get("description").asText()));
            }
        }

        List<VueWordCloudEntity> vueWordCloudEntityList = new ArrayList<>();
        tokens.stream()
                .filter(x -> x.getPartOfSpeechLevel1().equals("名詞"))
                .map(x -> x.getSurface())
                .collect(Collectors.groupingBy(x -> x, Collectors.counting()))
                .forEach((k, v) -> {
                    VueWordCloudEntity vueWordCloudEntity = new VueWordCloudEntity();
                    vueWordCloudEntity.setName(k);
                    vueWordCloudEntity.setValue(v);
                    vueWordCloudEntityList.add(vueWordCloudEntity);
                });

        result = vueWordCloudEntityList;

        return result;
    }
}
VueWordCloudEntity.java
import lombok.Data;

@Data
public class VueWordCloudEntity {

    private String name;
    private Long value;
}
TwitterAnalysisRestController.java
import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import lombok.RequiredArgsConstructor;
import app.myapp.entity.VueWordCloudEntity;
import app.myapp.service.TwitterAnalysisService;

@RestController
@RequiredArgsConstructor
@RequestMapping("/api")
public class TwitterAnalysisRestController {

    @Autowired
    private TwitterAnalysisService twitterAnalysisService;

    @GetMapping("/twitter/analysis/profile/by/{query}")
    public ResponseEntity<List<VueWordCloudEntity>> analysisTwitterProfileByQuery(
            @PathVariable String query) {

        List<VueWordCloudEntity> result
                = twitterAnalysisService.analysisTwitterProfileByQuery(query);

        // テストアプリのためここでCORS対応
        HttpHeaders headers = new HttpHeaders();
        headers.add("Access-Control-Allow-Credentials", "true");
        headers.add("Access-Control-Allow-Origin", "http://localhost:<Vue.js実行環境ポート番号>");

        return new ResponseEntity<>(result, headers, HttpStatus.OK);
    }
}

きっとJavaでTwitter4Jを使わない場合のもっと簡単なやり方があるはず!

どこかにあるよきっとそんなサンプル。

今回は残念、私のサンプルでした。

※ソースコードは「動作する」優先です、という言い訳をしてみる…

3-4.Vue.jsの実装

こっちはvue-wordcloudをインストールしてvue-wordcloud公式のソースをほぼコピペするだけなので簡単です。

vue-cliのGUIでプロジェクトを作成して上記のディレクト構造の場所へ配置します。

その後、vue-cliのGUIでvue-wordcloudをインストールします。

あ、あとaxiosも。

あとは、公式のサンプルをコピペし、ちょこっと直してSpring BootのAPIからデータを取得します。

TwitterAnalysis.vue
<template>
    <div id="twitter-analysis">
        <input v-model="query" placeholder="キーワードを入力してください" style="width:400px;">
        <button @click="analyzeProfile" style="margin-left:10px;">解析</button>
        <wordcloud
            :data="analyzedWords"
            nameKey="name"
            valueKey="value"
            color="Accent"
            :showTooltip="true"
            :wordClick="wordClickHandler">
        </wordcloud>
    </div>
</template>

<script>
import wordcloud from 'vue-wordcloud'
import axios from 'axios'
axios.defaults.withCredentials = true

export default {
    name: 'TwitterAnalysis',
    components: {
        wordcloud
    },
    data() {
        return {
            query: '',
            analyzedWords: [],
        }
    },
    methods: {
        wordClickHandler(name, value, vm) {
            console.log('wordClickHandler', name, value, vm);
        },
        analyzeProfile: async function () {
            if (this.query == null || this.query === '') return
            await axios.get('http://localhost:<Spring Boot実行環境ポート番号>/api/twitter/analysis/profile/by/'
                    + encodeURIComponent(this.query))
                .then(res => {
                    if (res.data != null) {
                        this.analyzedWords = res.data
                    }
                })
                .catch(err => {
                    alert(err + ' エラーです。')
                })
        },
    },
}
</script>

router/index.js
import Vue from 'vue'
import VueRouter from 'vue-router'

Vue.use(VueRouter)

const routes = [
  {
    path: '/',
    name: 'TwitterAnalysis',
    component: () => import(/* webpackChunkName: "twitteranalysis" */ '../views/TwitterAnalysis.vue')
  },
]

const router = new VueRouter({
  mode: 'history',
  routes
})

export default router

これで
http://localhost:<Vue.js実行環境ポート番号>/
にアクセスします。

図2.PNG

検索ワードを入力して解析ボタンを押すと

図3.PNG

成功!するはず。

見ると、スラッシュとかhttpとかどうでもいい単語があるので、こういうのはきっと除外するんだと思います。

このアプリで何か面白い傾向がわかるかな~と思ったんですが、あんまりわからない!

そもそも、そもそもな感じ。

色々勉強が必要みたいですねぇ…

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

Azure App Service (Windows) の Tomcat の構成変更

この記事について

通常 Java アプリを使用し Tomcat の設定を変更する際「server.xml」を編集しますが、
Azure App Service で設定されているファイル「D:\Program Files (x86)\apache-tomcat-x.x.xx\conf\server.xml」は変更できません。
そこで以下の方法で Tomcat の構成を変更します。

1.前提

  • Apprication settings で Java / Tomcat の設定が完了しているものとします。
  • Kudu コンソールを利用を想定しています。https://<YourAppName>.scm.azurewebsites.net/DebugConsole
  • 以下に内部のイメージを示します。 Azure.png

1.ディレクトリ構成

  • Azure App Service の基本的なディレクトリ構成。
D:.
├─home
│  ├─LogFiles
│  └─site
│      └─wwwroot
│          │  web.config
│          │
│          ├─conf
│          │      server.xml
│          │
│          └─webapps ←アプリ(.war)のデプロイディレクトリ
│                  ROOT
│                  ROOT.war
│
└─Program Files (x86)
    └─apache-tomcat-x.x.xx
        └─conf
                server.xml

2.web.config の設定

  • IIS 構成ファイルであり Tomcat を起動するときに引数で追加構成を設定します。
  • JVM のメモリ設定が可能です。
  • 以下のサンプルでは、変更した Tomcat の設定ファイル「server.xml」を引数として設定しています。
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    <system.webServer>
        <handlers>
            <remove name="httpPlatformHandlerMain" />
            <add name="httpPlatformHandlerMain" path="*" verb="*" modules="httpPlatformHandler" resourceType="Unspecified" />
        </handlers>
        <httpPlatform processPath="D:\Program Files (x86)\apache-tomcat-x.x.xx\bin\startup.bat" requestTimeout="00:04:00" arguments="-config D:\home\site\wwwroot\conf\server.xml start" startupTimeLimit="60" startupRetryCount="3" stdoutLogEnabled="true">
            <environmentVariables>
                <environmentVariable name="CATALINA_OPTS" value="-Xms2048 -Xmx2048m" />
            </environmentVariables>
        </httpPlatform>
    </system.webServer>
</configuration>

3.server.xml の設定

  • 上記2.で設定したパスに「server.xml」ファイルを作成します。(D:\home\site\wwwroot\conf\server.xml)
    ※server.xmlは「D:\Program Files (x86)\apache-tomcat-x.x.xx\conf\server.xml」からコピーし、必要に応じて変更してください。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Kotlin】KFunctionからJavaのConstructor/Methodを取得し呼び出す

Kotlinのリフレクションでのメソッド/コンストラクタであるKFunctionは様々な部分が抽象化されており、アプリケーションからの利用が容易となっています。
一方、JavaConstructor/Methodを直接呼び出した場合と比べ、KFunctionは抽象化によるオーバーヘッドから呼び出しが遅いという問題が有ります。

この記事では、このオーバーヘッドを回避するため、KFunctionの定義別にJavaConstructor/Methodを取得し直接呼び出すパターンをまとめます。
バージョンはそれぞれKotlin 1.4.10/Java8です。

前置き

本題に入る前に、kotlin.reflect.KFunctionjava.lang.reflect.Constructor/java.lang.reflect.Methodについて書きます。

パッケージ名の通り、前者はKotlinのリフレクションでの関数です。
Kotlinでは、メソッドとコンストラクタは同じKFunctionとして同様に扱えます。
KFunctionは、callBy呼び出しによってデフォルト引数を用いたり、特定条件でJavaMethod.invokeにおけるインスタンス引数を無視することができます。

後者はJavaのリフレクションでの関数です。
ConstructorMethodは型として分かれているため同様には取り扱えず、またMethodはインスタンスパラメータ(static methodの場合null、それ以外はインスタンス)を要求します。

冒頭で書いた「JavaConstructor/Methodを直接呼び出した場合と比べ、KFunctionは呼び出しが遅い」というのは、デフォルト引数を用いず(= 全パラメータを揃えて)KFunction.callを呼び出した場合とConstructor.newInstance/Method.invokeを呼び出した場合との比較です。

本文

関数の定義方法は大まかに2種類、細かく見て4種類としてそれぞれについてまとめます。

  • コンストラクタ
  • コンストラクタ以外
    • インスタンス
    • コンパニオンオブジェクト
    • コンパニオンオブジェクト(@JvmStatic有り)

検証には以下のコードを用いました。

検証に用いたプログラムの全体
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.DisplayName
import org.junit.jupiter.api.Nested
import org.junit.jupiter.api.Test
import java.lang.reflect.Constructor
import java.lang.reflect.Method
import kotlin.reflect.KFunction
import kotlin.reflect.full.companionObject
import kotlin.reflect.full.companionObjectInstance
import kotlin.reflect.full.functions
import kotlin.reflect.jvm.javaConstructor
import kotlin.reflect.jvm.javaMethod

@Suppress("UNCHECKED_CAST")
class CallJavaReflectionTest {
    data class ConstructorSample(val foo: Int, val bar: String)

    fun instanceMethodSample(foo: Int, bar: String): ConstructorSample = ConstructorSample(foo, bar)

    companion object {
        fun companionObjectFunctionSample(foo: Int, bar: String): ConstructorSample = ConstructorSample(foo, bar)

        @JvmStatic
        fun staticMethodSample(foo: Int, bar: String): ConstructorSample = ConstructorSample(foo, bar)
    }

    val expected = ConstructorSample(1, "2")

    @Test
    @DisplayName("コンストラクタの場合")
    fun constructorTest() {
        val function: KFunction<ConstructorSample> = ::ConstructorSample
        assertEquals(expected, function.call(1, "2"))

        val javaConstructor: Constructor<ConstructorSample> = function.javaConstructor!!
        assertEquals(expected, javaConstructor.newInstance(1, "2"))
    }

    @Test
    @DisplayName("インスタンス関数の場合")
    fun instanceMethodTest() {
        val function: KFunction<ConstructorSample> = this::instanceMethodSample
        assertEquals(expected, function.call(1, "2"))

        val javaMethod: Method = function.javaMethod!!
        assertEquals(expected, javaMethod.invoke(this, 1, "2"))
    }

    @Nested
    @DisplayName("コンパニオンオブジェクトに定義したメソッドの場合")
    inner class CompanionObjectFunctionTest {
        @Test
        @DisplayName("メソッドリファレンスで取得した場合")
        fun byMethodReferenceTest() {
            val function: KFunction<ConstructorSample> = (CallJavaReflectionTest)::companionObjectFunctionSample
            // メソッドリファレンスで取得した場合インスタンスパラメータが不要
            assertEquals(expected, function.call(1, "2"))

            val javaMethod: Method = function.javaMethod!!
            assertEquals(expected, javaMethod.invoke(CallJavaReflectionTest::class.companionObjectInstance!!, 1, "2"))
        }

        @Test
        @DisplayName("リフレクションで取得した場合")
        fun byReflectionTest() {
            val function: KFunction<ConstructorSample> = CallJavaReflectionTest::class.companionObject!!
                .functions.first { it.name == "companionObjectFunctionSample" }
                .let { it as KFunction<ConstructorSample> }
            // リフレクションで取得した場合インスタンスパラメータが必要
            assertEquals(expected, function.call(CallJavaReflectionTest::class.companionObjectInstance, 1, "2"))

            val javaMethod: Method = function.javaMethod!!
            assertEquals(expected, javaMethod.invoke(CallJavaReflectionTest::class.companionObjectInstance!!, 1, "2"))
        }
    }

    @Nested
    @DisplayName("コンパニオンオブジェクトに定義したメソッド(JvmStatic指定有り)の場合")
    inner class StaticMethodTest {
        @Test
        @DisplayName("メソッドリファレンスで取得した場合")
        fun byMethodReferenceTest() {
            val function: KFunction<ConstructorSample> = (CallJavaReflectionTest)::staticMethodSample
            assertEquals(expected, function.call(1, "2"))

            val javaMethod: Method = function.javaMethod!!
            assertEquals(expected, javaMethod.invoke(CallJavaReflectionTest::class.companionObjectInstance!!, 1, "2"))
        }

        @Test
        @DisplayName("コンパニオンオブジェクトからリフレクションで取得した場合")
        fun byReflectionTest() {
            val function: KFunction<ConstructorSample> = CallJavaReflectionTest::class.companionObject!!
                .functions.first { it.name == "staticMethodSample" }
                .let { it as KFunction<ConstructorSample> }
            // リフレクションで取得した場合インスタンスパラメータが必要
            assertEquals(expected, function.call(CallJavaReflectionTest::class.companionObjectInstance, 1, "2"))

            val javaMethod: Method = function.javaMethod!!
            assertEquals(expected, javaMethod.invoke(CallJavaReflectionTest::class.companionObjectInstance!!, 1, "2"))
        }
    }
}

コンストラクタの場合

KFunctionの生成元がコンストラクタの場合、KFunction.javaConstructorConstructorを取得することができます。
コンストラクタはインスタンスパラメータなどを要求しないため、KFunction.callと同じようにConstructor.newInstanceを呼び出すことができます。

@Suppress("UNCHECKED_CAST")
class CallJavaReflectionTest {
    data class ConstructorSample(val foo: Int, val bar: String)

    val expected = ConstructorSample(1, "2")

    @Test
    @DisplayName("コンストラクタの場合")
    fun constructorTest() {
        val function: KFunction<ConstructorSample> = ::ConstructorSample
        assertEquals(expected, function.call(1, "2"))

        val javaConstructor: Constructor<ConstructorSample> = function.javaConstructor!!
        assertEquals(expected, javaConstructor.newInstance(1, "2"))
    }
}

コンストラクタ以外の場合

KFunctionの生成元がコンストラクタ以外の場合、KFunction.javaMethodMethodを取得することができます。
前述の通り、Methodを呼び出すには、インスタンスパラメータ(static methodの場合null、それ以外はインスタンス)が必要になります。

ここで、コンストラクタ以外の場合、生成方法に関わらずKFunctionからインスタンスを取得する方法が公開されていないため、KFunction.callでインスタンスパラメータが省略されている場合でも、KFunction単体からMethod.invokeを呼び出すことはできません1
JvmStaticを付けた場合でも、KFunctionとして取得する方法で取得できるのはcompanion objectへの定義となるため、インスタンスパラメータをnullとしてMethod.invokeを呼び出すことはできません。

従って、コンストラクタ以外から取得したKFunctionからMethodを呼び出すには、別口でインスタンスを用意する必要が有ります。

インスタンス関数の場合

@Suppress("UNCHECKED_CAST")
class CallJavaReflectionTest {
    data class ConstructorSample(val foo: Int, val bar: String)

    fun instanceMethodSample(foo: Int, bar: String): ConstructorSample = ConstructorSample(foo, bar)

    val expected = ConstructorSample(1, "2")

    @Test
    @DisplayName("インスタンス関数の場合")
    fun instanceMethodTest() {
        val function: KFunction<ConstructorSample> = this::instanceMethodSample
        assertEquals(expected, function.call(1, "2"))

        val javaMethod: Method = function.javaMethod!!
        assertEquals(expected, javaMethod.invoke(this, 1, "2"))
    }
}

コンパニオンオブジェクトに定義した関数の場合

@Suppress("UNCHECKED_CAST")
class CallJavaReflectionTest {
    data class ConstructorSample(val foo: Int, val bar: String)

    companion object {
        fun companionObjectFunctionSample(foo: Int, bar: String): ConstructorSample = ConstructorSample(foo, bar)
    }

    val expected = ConstructorSample(1, "2")

    @Nested
    @DisplayName("コンパニオンオブジェクトに定義したメソッドの場合")
    inner class CompanionObjectFunctionTest {
        @Test
        @DisplayName("メソッドリファレンスで取得した場合")
        fun byMethodReferenceTest() {
            val function: KFunction<ConstructorSample> = (CallJavaReflectionTest)::companionObjectFunctionSample
            // メソッドリファレンスで取得した場合インスタンスパラメータが不要
            assertEquals(expected, function.call(1, "2"))

            val javaMethod: Method = function.javaMethod!!
            assertEquals(expected, javaMethod.invoke(CallJavaReflectionTest::class.companionObjectInstance!!, 1, "2"))
        }

        @Test
        @DisplayName("リフレクションで取得した場合")
        fun byReflectionTest() {
            val function: KFunction<ConstructorSample> = CallJavaReflectionTest::class.companionObject!!
                .functions.first { it.name == "companionObjectFunctionSample" }
                .let { it as KFunction<ConstructorSample> }
            // リフレクションで取得した場合インスタンスパラメータが必要
            assertEquals(expected, function.call(CallJavaReflectionTest::class.companionObjectInstance, 1, "2"))

            val javaMethod: Method = function.javaMethod!!
            assertEquals(expected, javaMethod.invoke(CallJavaReflectionTest::class.companionObjectInstance!!, 1, "2"))
        }
    }
}

コンパニオンオブジェクト(@JvmStatic有り)の場合2

@Suppress("UNCHECKED_CAST")
class CallJavaReflectionTest {
    data class ConstructorSample(val foo: Int, val bar: String)

    companion object {
        @JvmStatic
        fun staticMethodSample(foo: Int, bar: String): ConstructorSample = ConstructorSample(foo, bar)
    }

    val expected = ConstructorSample(1, "2")

    @Nested
    @DisplayName("コンパニオンオブジェクトに定義したメソッド(JvmStatic指定有り)の場合")
    inner class StaticMethodTest {
        @Test
        @DisplayName("メソッドリファレンスで取得した場合")
        fun byMethodReferenceTest() {
            val function: KFunction<ConstructorSample> = (CallJavaReflectionTest)::staticMethodSample
            assertEquals(expected, function.call(1, "2"))

            val javaMethod: Method = function.javaMethod!!
            assertEquals(expected, javaMethod.invoke(CallJavaReflectionTest::class.companionObjectInstance!!, 1, "2"))
        }

        @Test
        @DisplayName("コンパニオンオブジェクトからリフレクションで取得した場合")
        fun byReflectionTest() {
            val function: KFunction<ConstructorSample> = CallJavaReflectionTest::class.companionObject!!
                .functions.first { it.name == "staticMethodSample" }
                .let { it as KFunction<ConstructorSample> }
            // リフレクションで取得した場合インスタンスパラメータが必要
            assertEquals(expected, function.call(CallJavaReflectionTest::class.companionObjectInstance, 1, "2"))

            val javaMethod: Method = function.javaMethod!!
            assertEquals(expected, javaMethod.invoke(CallJavaReflectionTest::class.companionObjectInstance!!, 1, "2"))
        }
    }
}

まとめ

この記事ではKFunctionの呼び出しオーバーヘッドを回避するため、KFunctionの定義別にJavaConstructor/Methodを取得し直接呼び出すパターンをまとめました。
検証の結果、コンストラクタの場合はKFunction単体からConstructor.newInstanceを呼び出すことができ、それ以外の場合は別口でインスタンスを取得できる場合に限りMethod.invokeを呼び出せるという結論を得ました。

単純に動くツールを作るだけであればKFunction.callByを使うのが最も容易だと思われますが、実行速度にも拘ってツールを作る場合はConstructor/Method直接呼び出せるよう工夫してみても良いかもしれません。

この記事が何かのお役に立てば幸いです。


  1. KFunction.callでインスタンスパラメータが省略できる以上、内部的には何らかの方法でインスタンスの情報を保持していると考えられますが、KFunctionのインターフェースではそれが公開されておらず、KFunctionの実装クラスであるKFunctionImplinternalクラスであるため、これ以上追いかけると本当の黒魔術になると判断し、ここでは「できない」を結論とします。 

  2. こちらの記事にまとめた通り、Kotlin上で定義した関数はstaticFunctionsで取得できないため、この例では省略しています。 

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