- 投稿日:2022-03-23T23:14:58+09:00
【LINE BotをAPI連携させたいだけなのに #1】エラー祭りへようこそ
LINE Botを思い通りに動かしたーい!! LINE Botでプッシュメッセージとリプライメッセージ(おうむ返しと固定テキスト)返すところまではできた。(やっとのことで。息も絶え絶えで。) そして息切れしながら向かった先は、 脳みそに新たなひらめきをくれるAPI連携Botが作りたい 仕事ではプランナーとして企画制作の1000本ノックを日々行い、大学院ではアート作品を作る私。煮詰まった時に脳みそをリフレッシュさせたい!自分の思考の外から新たな風がほしい!! そんな時のお助けアイテムとして、「デザインに関してのインスピレーションを与える名言」 というステキAPIからお言葉を頂戴しようと思った。 まずは単純なオウム返しBot 'use strict'; const express = require('express'); const line = require('@line/bot-sdk'); const PORT = process.env.PORT || 3000; const config = { channelSecret: 'ひみつ', channelAccessToken: 'ひみつ' }; const client = new line.Client(config); async function handleEvent(event) { if (event.type !== 'message' || event.message.type !== 'text') { return Promise.resolve(null); } return client.replyMessage(event.replyToken, { type: 'text', // テキストメッセージ text: event.message.text // }); } const app = express(); app.get('/', (req, res) => res.send('Hello LINE BOT! (HTTP GET)')); app.post('/webhook', line.middleware(config), (req, res) => { if (req.body.events.length === 0) { res.send('Hello LINE BOT! (HTTP POST)'); console.log('検証イベントを受信しました!'); return; } else { console.log('受信しました:', req.body.events); } Promise.all(req.body.events.map(handleEvent)).then((result) => res.json(result)); }); app.listen(PORT); console.log(`ポート${PORT}番でExpressサーバーを実行中です…`); ここまではできた。 この後特定の単語でのリプライもできた。 今度はAPIを連携させる "axios" を利用できるようにするため、コードを書き換える。 npm i axios をターミナルに入力。 そして、下記のコードを元に、APIを引っ張ってくるコードを入れる。 'use strict'; const express = require('express'); const line = require('@line/bot-sdk'); const axios = require('axios'); const PORT = process.env.PORT || 3000; const config = { channelSecret: 'ひみつ', channelAccessToken: 'ひみつ' }; const client = new line.Client(config); async function handleEvent(event) { // 受信したWebhookが「テキストメッセージ以外」であればnullを返すことで無視します if (event.type !== 'message' || event.message.type !== 'text') { return Promise.resolve(null); } // サンプル関数を実行します return sampleFunction(event); } const app = express(); app.post('/webhook', line.middleware(config), (req, res) => { if (req.body.events.length === 0) { res.send('Hello LINE BOT! (HTTP POST)'); console.log('検証イベントを受信しました!'); return; } else { console.log('受信しました:', req.body.events); } Promise.all(req.body.events.map(handleEvent)).then((result) => res.json(result)); }); app.listen(PORT); console.log(`ポート${PORT}番でExpressサーバーを実行中です…`); ここにひらめき名言をもらうQuotes on DesignとAPI連携できるようコードを完成させたい 入れるコードはこちら。 // axiosライブラリを呼び出す const axios = require('axios'); // 実際にデータを取得する getRequest 関数 async function getRequest() { let response; try { response = await axios.get('https://quotesondesign.com/wp-json/wp/v2/posts/?orderby=rand'); console.log(response.data); } catch (error) { console.error(error); } } // getRequest を呼び出してデータを読み込む getRequest(); しかし、鬼のエラー祭りが私の足を止める。 一歩進むたびに現れる奴ら(=エラー) API連携のため、まず新しいファイルを作った私の前に現れたのは、Webhook設定できない問題。 これは私がMessaging API設定で/Webhookを入力していなかった、というドえらいシンプルなミスをしていたせいでした。 そして次に現れたのが、 ターミナルに文字打てない問題。 こいつが長居するのよ・・・ しかし刻々と時間は過ぎ、メンタルがしおれていくのを感じた私は思った。 「よし、ファイルごと消そう」 しかし私はこの時点で、Control+Cやってエラー出てるファイル消してゼロからやり直す、という作業を、かれこれ20回以上はやっている。 エラーたちがいよいよ盛大に祭りを始める PROBLEMSだけでなく、ターミナルにも直にエラーが表記されているとここら辺で(やっと)気が付いた私。 なるほど!なら検索して直せばいいだけじゃない と思ったら大間違い!初心者は検索そのものがトンチンカンなのでなかなか求める答えドンピシャのページにたどり着かない。 エラーの意味はわかった。 けれど、答えがわからない。 私が出会ったエラーたち(一部) ReferenceError: sampleFunction is not defined at handleEvent これは他の人の記事を読んで、 const sampleFunction = async (event) => { を、コードのどこかに入れる必要がある、というところまではたどり着けた。 でも、ど、どこに・・・・??? コードを最初から一つ一つじー---っと読み込む。 これは何の指示をしているのか、何の設定をするために書いているのか。 コードをじっくり読み返した私が、const sampleFunction = async (event) => {を入れるのはここに違いない!と思ったのは こちらご査収ください。 // LINEサーバーからWebhookがあると「サーバー部分」から以下の "handleEvent" という関数が呼び出されます const sampleFunction = async (event) => { // 受信したWebhookが「テキストメッセージ以外」であればnullを返すことで無視します if (event.type !== 'message' || event.message.type !== 'text') { return Promise.resolve(null); } // サンプル関数を実行します return sampleFunction(event); } そしてターミナルに出てきたのは、 SyntaxError: Identifier 'axios' has already been declared 初めましてのエラー。 意味はわかるよ、わかるの。 構文が違うよ、ってことだよね・・・うん・・・わかった。 ここまでの紆余曲折。 あまりに悔しいので、正しい構文を見つけてやります。 次回、 「#2 ついにLINE BOtとAPIが連携する」 乞うご期待。 おわり!
- 投稿日:2022-03-23T23:00:46+09:00
Java民から見た時のKotlinのギャップまとめ
経緯 社内PJでKotlinを使ってコードを書くことになりました。 メンバーには自分含めJava民が多いです。Kotlinへの移行がちょっとでもスムーズになるように、Java民から見た「Kotlin特有の書き方だなぁ」ってところをまとめました。 Kotlin慣れに良いサイト(たぶん) (公式)イディオム (公式)コレクションタイプ Javaでこう書いてたものがKotlinだとこう、みたいな感じ 公式リファレンス コレも公式。 右上の「TRY ONLINE」からオンライン環境でコーディングできる (公式)SpringBootとH2DB使ってAPIつくる 英語が面倒だけど実処理書こうとするとこんな感じ Kotlinの基本的な言語仕様を学習したまとめ ぶっちゃけ本記事の上位互換。 変数定義周り Null Safety 変数がデフォルトで非NULL型。コンパイルの時点でエラー。 これによりNullPointerExceptionが起きづらい(後述の演算子もあるので、絶対起きないとは言っていない) Some.kt var a: String = "hogehoge" a = null //コンパイルエラー Null can not be a value of a non-null type String var b: Int = "hogehoge" b = null //コンパイルエラー Null can not be a value of a non-null type Int NULL許容型も作れる Some.kt var a: String? = "hogehoge" a = null //コンパイルOK var b: Int? = "hogehoge" b = null //コンパイルOK 定義した変数を呼ぶとき NULL許容で変数定義した場合は、下記の2つのパターンでメソッドを呼ぶ。 後者は非推奨なので実質セーフコールがベター。 セーフコール演算子 値がNULLでない場合のみメソッドが呼ばれる。 Some.kt var a: String? = "hogehoge" println(a?.length) // 8 var b: String? = null println(b?.length) // null lengthメソッドは呼ばれない 非NULL表明演算子(きっと非推奨) NULLだろうが何だろうがメソッドを呼ぶ。 当然nullとなっている変数に対してメソッドが呼ばれるとNullPointerExceptionが出る。 Some.kt var a: String? = "hogehoge" println(a!!.length) // 8 var b: String? = null println(b!!.length) // ぬるぽ 変数定義時にvar(再代入不可能) OR val(再代入可能)を必ず指定する var...再代入可。 val...再代入不可。Javaでいうfinal定義みたいなもん 可能な限りval使えると変数のスコープが小さくなって保守しやすくなりそう Some.kt var a: String = "hogehoge" a = ”piyo” //OK val b: String= "hogehoge" b = ”piyo” //コンパイルエラー。 "Val cannot be reassined"(再代入すんな) 型推論可能 Some.kt val a = "hogehoge" println(a is String) //true あとlateinitとかあった気がするけどなんだっけ。。。 クラス定義周り クラスを作る側の話。 CompanionObject インスタンス生成せずにメソッド/変数を呼びたい時、static定義ではなく、 代わりにCompanionObjectなるものを用いる Some1.kt class Hoge { companion object { } } //クラス外で定義 fun Hoge.Companion.piyo() { println("piyo") } Hoge.fuga() Some2.kt class Hoge { //クラス内で定義 companion object { fun fuga(str : String) { println("$str") } } } Hoge.fuga("Kotlin") 変数定義についても同様っぽい クラス内での変数定義の話 データクラス クラスにlombok(@Data)が外部ライブラリなしで実現できるようになったイメージ エンティティクラスとかによく使われそう equals hashCode copy toString()Object(key1=value1, key2=value2)型のもの componentN Book.kt import java.time.LocalDate data class Book { var id: Long? = null, var title: String? = null, var author: String? = null, var releaseDate: LocalDate? = null ) デフォルトで継承不可(Javaでいうfinalクラスみたいなもん) User.kt class User(val name: String, val age: Int) //継承不可 明治的に継承させたくない場合はsealdをつける User.kt seald class User(val name: String, val age: Int) //継承不可 継承させたい場合はopenをつける User.kt open class User(val name: String, val age: Int) //継承可 getter/setter定義不要 User1.kt class User1 { var name: String = "" } Some.kt val user = User1() user.name = "Tanaka" // user.setName()とかしなくて良い println(user.name) //Tanaka ・プロパティをvalで定義するとgetterのみ生成される(再代入不可なので当然っちゃ当然) getter/setterを拡張することもできる getter拡張 User2.kt class User2 { lateinit var name: String // isValidNameプロパティに対するget()関数の処理を書き換える val isValidName: Boolean get() = name != "" } Some.kt val user = User2() user.name = "Tanaka" println(user.isValidName) //True setter拡張 User3.kt class User3 { var name: String = "" //空文字の場合はデフォルト値として"Kotlin"を返す拡張 set(value) { if (value == "") { field = "Kotlin" } else { field = value } } } Some.kt val user = User3() user.name = "" //空文字を登録しているようで、拡張setterで実質"Kotlin"を代入している println(user.name) //"Kotlin" プライマリコンストラクタ、セカンダリコンストラクタ プライマリコンストラクタ Person.kt class Person constructor( val firstName: String, val lastName: String, var isEmployed: Boolean = true ) コンストラクタに注釈・可視性修飾子がない場合は、constroctorを省略できる Person.kt class Person( val firstName: String, val lastName: String, var isEmployed: Boolean = true ) コンストラクタに注釈・可視性修飾子がある場合は、constructor必須 Person.kt class Person public consutructor ( val firstName: String, val lastName: String, var isEmployed: Boolean = true ) セカンダリコンストラクタ Person.kt class Person (private val name: String, private val age: Int) { // thisはプライマリコンストラクタを指す constructor(name: String) : this(name, 20) } これ調べてる時、kotlinでSingletonパターン書こうとしたらどうなるんだろうな...と思った。 結果、objectで生成する方法が有力らしい インスタンス生成周り クラスを使う側の話。 List,Mapのインスタンス生成(ミュータブル or 読み取り専用OK) Some.kt val mutableList = mutableListOf(1, 2, 3) //可変 set可能 val List = listOf(1, 2, 3) //読み取り専用 getのみ可能...★ val mutableSet = mutableSetOf(1, 2, 3) //可変 val set = setOf(1, 2, 3) //読み取り専用 getのみ可能...★ ★なお、listOf,setOfは"読み取り専用"なだけで、イミュータブルではない。 下記みたいな感じで参照先を書き換えると変更可能。 Some.kt val mutableList = mutableListOf(1, 2, 3) val list: List<Int> = mutableList //読み取り専用なだけ mutableList.add(9) println(mutableList) //1,2,3,9 println(list) //1,2,3,9 スレッドセーフかどうかはまた別途考える必要がある。
- 投稿日:2022-03-23T20:51:34+09:00
Nablarchを使ってみよう
はじめに こんにんちわ、きりと申します。 お仕事でJavaのフレームワークであるNablarchを利用する機会があったので、 その特徴や開発の癖のようなものを整理して皆さんに共有できればと思い記事にしました。 Nablarchの良いところと悪いところでも触れますが、お世辞にもわかりやすいフレームワークではないため、 私のようにNablarchを使った保守・開発を携わる人の助けになればと思います。 整理する中で思いの外、ボリュームが出てきてしまったので、「Nablarchを使ってみよう」シリーズとして 一覧化しましたが、記事の大部分はNablarch開発に関係なく参考にできると思いますので、 気になる記事を閲覧頂ければと思います。 なお、記事の内容や構成は定期的に見直しを行う予定ですので、 情報の誤りや誤記がありましたら優しくコメント頂ければ幸いです。 Nablarch(ナブラーク)とは Nablarchとは、かなりざっくり言うなら、Spring FrameworkのようなJavaアプリケーション開発/実行基盤です。 例えば、会員サイトを作成する際、サイト画面や会員情報の管理、ログインなどの認証処理、 ログ出力など、様々な機能の実装が必要となります。これらを0から実装することは非常に多くの労力が必要となると共に、 メンテナンスの負荷が大きくなります。 Nablarchは上記のようなシステム開発を想定したロバスト(堅牢)な設計思想の元に開発されております。 また、テンプレートプロジェクトやテストシステム、設計書のテンプレートなども用意されており、 未経験者でもすぐに開発を始められるようになっているそうです。←最後の一文は嘘だと思っている。 Nablarchの紹介および関連サイト Nablarchとは Nablarchについて Nablarchポータルサイト Nablarchテクニカルドキュメント Nablarchの良いところと悪いところ まだまだ、Nablarch激弱くんの私なりに、現状の思っているNablarchの良いところと悪いところをまとめてみました。 [O]豊富な利用実績→多数の導入実績 [O]サードパーティ利用を前提としていない開発者的な粋を感じる→もちろんサードパーティライブラリの利用も可 [X]圧倒的、初心者殺しな情報のなさ←リファレンスがあるが使い方がわからない [O]ライブラリを使い分けることで要件に合わせた柔軟な設計(例えばDBやセッション管理など)が可能 [X]各ライブラリ仕様はガチガチ感が強く、最近のリッチな見た目のサイトには向かない(会員サイトなどには向いている) [X]AWSやAzureとの親和性が弱い(対応ライブラリがないため、自作する必要がある) [X]最新の開発トレンドに乗り遅れてる感が否めない(AWS、Azure,コンテナ、通信プロトコルなど) まとめると、人付き合いが苦手で癖が強い昔ながらの職人気質なやつだけど、仲良くなるとめっちゃいい人。 。。。。。だと、いいな。 まだ、仲良くなれてないけどね。 「Nablarchを使ってみよう」シリーズ 記事一覧 環境構築 統合開発環境 EclipseのダウンロードとHello World リリース:2022/03/23 ビルド、クリーン、デバッグ リリース:2022/03/24 JUnitを使ったテストの実施方法 リリース:2022/03/26 CheckStyleの利用方法 リリース:2022/03/23 自動フォーマッタの利用方法 リリース:2022/03/23 SpotBugsの利用方法 リリース:2022/03/24 Gitを使ったソース管理方法 リリース:2022/03/24 エンコードと改行コードの設定 リリース:2022/03/24 Vimキーバインド利用方法 リリース:2022/03/24 [おまけ]テーマの変更 ローカル実行環境 Dockerのダウンロードと基本的な利用方法 DockerにおけるTomcat環境構築 DockerにおけるWildfly環境構築 AWS環境 AWS EC2を使ったWebアプリケーション公開 Java開発 Maven Mavenを使ったシンプルプロジェクトの作成と実行 リリース:2022/03/25 Mavenを使ったJarファイル作成と実行 Mavenを使ったWebアプリケーションの作成と実行 Java開発における小ネタ Javaでスタックトーレスを出力する方法 リリース:2022/03/19 更新:2022/03/23 Nablarch 基本編 Nablarchを使ったWebアプリケーションの作成 Nablarchを使ったバッチアプリケーションの作成 Nablarch開発におけるH2 Databaseの利用方法 リリース:2022/03/19 更新:2022/03/23 応用編 Nablarchにおける認証システムの実装 Nablarchにおけるデータ管理 サイト構築 サンクスポイント 基本構成 ※記事構成は随時見直しを行う 参考URL Nablarchについて Nablarchポータルサイト Nablarchテクニカルドキュメント
- 投稿日:2022-03-23T17:46:29+09:00
JDKリンク集(OpenJDK, Amazon Corretto)
JDK OpenJDK Amazon Corretto
- 投稿日:2022-03-23T16:54:38+09:00
「Unexpected error: java.security.InvalidAlgorithmParameterException: the trustAnchors parameter must be non-empty」の対処方法
まえがき 環境構築時にこの問題にでくわし、4時間ほど時間とられたので記録として残しておく( ´_ゝ`) 参考 原因 ・OpenJDK8ではtrust store($JAVA_HOME/lib/security/cacerts)が空。 対処方法 ・OpenJDK11からとってくる。
- 投稿日:2022-03-23T16:40:29+09:00
Javaのclassを逆コンパイルする
公式 動作環境 MacOS BigSur Java8 Jadのインストール brew install jadx 使い方 jadx hello.class オプション オプション 概要 -d, --output-dir 出力先
- 投稿日:2022-03-23T10:30:18+09:00
コンピュータとオセロ対戦48 ~勝敗予測、改善~
前回 今回の目標 勝敗予測の精度を上げる 使用バージョン Java 17.0.1 MyNet 1.2.4 ここから本編 ディレクトリ構成 48 ├── data // 学習データの保存 └── pic // グラフ保存 // プログラムなどはここに置いてある 01 とりあえず、様々なパターンのネットワークを試してみる まず、前回の記事のRun.javaのみ変更して、様々な条件で学習させてみました。 データ集め部分は前回と全く同じです。 学習の際、誤差の意味が分かりやすくなるよう64で割らず、平均絶対誤差を使用しました。 あてずっぽうで調べたところある程度の精度があった3層で、ノード数や活性化関数を変えながら調べてみます。また、バッチサイズも変更しながら学習し、その学習記録をファイル出力します。エポック数は多めに、30にします。 Run01.java import java.nio.channels.NetworkChannel; import java.util.ArrayList; import java.util.function.BiConsumer; import org.MyNet.matrix.*; import org.MyNet.network.*; import org.MyNet.layer.*; import org.MyNet.nodes.activationFunction.*; import org.MyNet.optimizer.*; import org.MyNet.costFunction.*; public class Run01 { public static void main(String str[]){ ArrayList<BiConsumer<long[], Boolean>> playMethod = new ArrayList<BiConsumer<long[], Boolean>>(); playMethod.add(Osero::random); playMethod.add(Osero::nHand); playMethod.add(Osero::nHandCustom); playMethod.add(Osero::nLeast); playMethod.add(Osero::nMost); OseroData run = new OseroData(); run.setReadGoal(2, 2); run.dataClear(); for (BiConsumer<long[], Boolean> black: playMethod){ for (BiConsumer<long[], Boolean> white: playMethod){ run.setPlayMethod(black, white); run.addData(1); } } // Deep Learning Matrix X = new Matrix(run.history); Matrix T = new Matrix(run.result); // T.div(64.0); Input[] inputLayers = { new Input(150, AF.TANH), new Input(150, AF.SIGMOID), new Input(150, AF.RELU), new Input(100, AF.TANH), new Input(100, AF.SIGMOID), new Input(100, AF.RELU) }; Dense[] denseLayers = { new Dense(100, AF.TANH), new Dense(100, AF.SIGMOID), new Dense(100, AF.RELU), new Dense(50, AF.TANH), new Dense(50, AF.SIGMOID), new Dense(50, AF.RELU) }; int i = 1; for (Input input: inputLayers){ System.out.printf("\r%d/%d", i, inputLayers.length); for (Dense dense: denseLayers){ Network net = new Network( 192, input, dense, new Output(1, AF.RELU) ); Adam opt = new Adam(net, new MeanAbsoluteError()); for (int batch = 10; batch <= 30; batch += 10){ String fileName = "data/01/"; fileName += "node" + Integer.toString(net.layers[0].nodes_num) + " "; fileName += "act" + net.layers[0].nodes[0].aFunc.toString() + " "; fileName += "node" + Integer.toString(net.layers[1].nodes_num) + " "; fileName += "act" + net.layers[1].nodes[0].aFunc.toString() + " "; fileName += "batch" + Integer.toString(batch); opt.fit(X, T, 30, X.row / batch, fileName+".csv"); } } i++; } System.out.println(); } } 結果を以下のプログラムでまとめました。 ここで、学習度という尺度を導入しました。これは、前回の学習で、エポック数が進んでも誤差があまり変わらなかったため、誤差の変化具合を調べる尺度が必要になると考えたためです。 具体的には、各エポックで「(前回の誤差-今回の誤差)/前回の誤差の二乗」を行い、その総和で優劣を比較します。今回全てのエポック数が同じなので平均などの操作はしていません。 なぜ分母で二乗しているかというと、誤差が大きい場合に学習度が大きくなることを防ぐためです。 誤差が大きすぎず、また、エポック数が進むと誤差がだんだん小さくなるものほど、この学習度は大きくなります。 プログラム内では「learning_degree」または「ld」と表しています。 import glob import re import pandas as pd ################################################ # 学習度計算 def cal_learning_degree(loss_history): ld = 0 for i in range(len(loss_history)-1): ld += (loss_history[i] - loss_history[i+1]) / (loss_history[i] * loss_history[i]) return ld # ファイル名から各層のノード数などの情報を抽出 def information(file_name): num = re.findall(r"\d+", file_name) act_ = re.findall(r"(ReLu)|(Sigmoid)|(Tanh)", file_name) act = [] for a in act_: for name in a: if name: act.append(name) return num, act ################################################ full_data = [] for file_name in glob.glob("data/01/*.csv"): data = {} info = information(file_name) data["input_nodes_num"] = info[0][0] data["dense_nodes_num"] = info[0][1] data["batch_size"] = info[0][2] data["input_act"] = info[1][0] data["dense_act"] = info[1][1] with open(file_name, "r") as fp: data["history"] = pd.read_csv(fp)["loss"] data["ld"] = cal_learning_degree(data["history"]) data["min_loss"] = data["history"].min() full_data.append(data) df = pd.DataFrame(full_data) 誤差が小さい順に並べてみます。 df_sorted = df.sort_values(by="min_loss", ascending=True) df_sorted.head(10) 実行結果はこちら。 Ridgeでやった時よりも低い誤差になっています。 続いて、学習度が大きい順に並べます。 学習度と誤差の相関関係を調べたところ、以下のようになっていました。 負の相関になっていますので、一応「学習度が高いほど誤差は小さくなる」といえると思います。ただ、今回は雑に学習度の計算方法を設定したのでどの程度有効であるかは分かりません。分母を二乗していることが効いてこのような結果になっているだけかもしれません。 ここで、学習率がある程度高く、かつ誤差もある程度小さい14番のネットワークについて、グラフを作ってみました。 import matplotlib.pyplot as plt import seaborn as sns import numpy as np df_14 = pd.read_csv("data/01/node100 actReLu node50 actSigmoid batch30.csv") sns.set() ################################################ plt.plot(df_14["loss"]) plt.savefig("pic/df_14.png") plt.clf() plt.close() 一応学習はしてますが・・・。 いいのか悪いのかわからないですね。 02 df_14の、エポック数を増やしてみる 01でグラフ化したもので、エポック数を増やしてどこまで精度が上がるか見てみます。 条件は同じで、一気に1000エポックやります。 Run02.java import java.nio.channels.NetworkChannel; import java.util.ArrayList; import java.util.function.BiConsumer; import org.MyNet.matrix.*; import org.MyNet.network.*; import org.MyNet.layer.*; import org.MyNet.nodes.activationFunction.*; import org.MyNet.optimizer.*; import org.MyNet.costFunction.*; public class Run02 { public static void main(String str[]){ ArrayList<BiConsumer<long[], Boolean>> playMethod = new ArrayList<BiConsumer<long[], Boolean>>(); playMethod.add(Osero::random); playMethod.add(Osero::nHand); playMethod.add(Osero::nHandCustom); playMethod.add(Osero::nLeast); playMethod.add(Osero::nMost); OseroData run = new OseroData(); run.setReadGoal(2, 2); // train data run.dataClear(); for (BiConsumer<long[], Boolean> black: playMethod){ for (BiConsumer<long[], Boolean> white: playMethod){ run.setPlayMethod(black, white); run.addData(1); } } Matrix X = new Matrix(run.history); Matrix T = new Matrix(run.result); // val data run.dataClear(); run.setRandom(100); for (BiConsumer<long[], Boolean> black: playMethod){ for (BiConsumer<long[], Boolean> white: playMethod){ run.setPlayMethod(black, white); run.addData(1); } } Matrix valX = new Matrix(run.history); Matrix valT = new Matrix(run.result); Network net = new Network( 192, new Input(100, AF.RELU), new Dense(50, AF.SIGMOID), new Output(1, AF.RELU) ); Adam opt = new Adam(net, new MeanAbsoluteError()); opt.fit(X, T, 1000, X.row / 30, valX, valT, "data/02/history1000.csv"); } } グラフ化プログラム。 import pandas as pd import matplotlib.pyplot as plt import seaborn as sns sns.set() ################################################ df = pd.read_csv("data/02/history1000.csv") plt.plot(df["loss"], label="train") plt.plot(df["valLoss"], label="val") plt.legend() plt.savefig("pic/df_14_epoch1000.png") plt.clf() plt.close() 全く同じ条件で学習させているはずですが、最初の誤差が非常に大きい・・・。 おそらくvalを追加したことで別のfitメソッドが呼ばれ、それによってランダム値が変わってしまったのだと思いますが、それにしてもここまで変わるものなのでしょうか。 最小値を調べたところ以下のようになりました。 print(df["loss"].min()) print(df["valLoss"].min()) 7.951017 8.41695 まあ、悪くはないですね。 03 誤差関数を変えてみる 02の誤差関数を平均二乗誤差に変えてみます。変更点はそれだけです。 結果はこちら。 最小誤差はこちら。 18140.143332 18160.186681 平均絶対誤差と単位を合わせるため平方根に入れると以下のようになりました。 134.68534935916378 134.75973686899215 平均絶対誤差よりも、誤差も大きいし収束も遅い結果に。 以前作成した足し算機とは逆の結果になりました。 04 02のネットワークをいじる データ量を増やすと誤差も増えますが、02で示されたようにエポック数を増やすことで学習が進むのなら、結果的に低い誤差となるかもしれないと考えました。 データ量を02の10倍にし、エポック数はとりあえず1000で学習させてみました。他の条件は02と全く同じです。 もう少し行けそうですね、1500エポックいきましょう。 9.124575 9.160001 1000エポック学習したモデルを保存しておけばよかったです。 そうすれば、追加で500エポック学習するだけで済んだのに・・・。 それはともかく、結果としては悪くないものになりました。少ないデータ量で学習させたものと比べ、最終的な誤差は大きいですが、おそらく汎用的に対応できるのではないかと思います。 05 02のネットワークのハイパーパラメータをいじる 少し話は変わりますが、教師データの正解値は平均して32ぐらいだと思います(盤面が64あるため)。 ここで02で使ったネットワークは、2層目の活性化関数がSigmoid、1層目ではReLuです。2層目は直接出力層につながる部分なので、0~1の値をとるSigmoidが適しているのかもしれません。ノード数50ですので、出力層へ50個の「0~1をとる実数」が入ってくるわけです。「0~64」の数字を作りやすい環境が整っているように見えます。 また1層目は、2層目でワンクッション置かれて出力層へ行くので、一般的によく使われるReLuが適していたのかもしれません。 そこで、2層目のノード数を64にして実験してみました。 エポック数は、1000も必要なさそうなので800とします。 結果はこちら。 理由は全く分かりませんが、まったく学習しませんでした。 中間層のノード数を64から63に変更し、10エポック学習させたところ以下のようになりました。 Epoch 1/10 loss: 18.1522 - valLoss: 17.9603 Epoch 2/10 loss: 17.9802 - valLoss: 17.7846 Epoch 3/10 loss: 17.8455 - valLoss: 17.6484 Epoch 4/10 loss: 17.7280 - valLoss: 17.5322 Epoch 5/10 loss: 17.6211 - valLoss: 17.4280 Epoch 6/10 loss: 17.5218 - valLoss: 17.3322 Epoch 7/10 loss: 17.4281 - valLoss: 17.2425 Epoch 8/10 loss: 17.3391 - valLoss: 17.1580 Epoch 9/10 loss: 17.2538 - valLoss: 17.0773 Epoch 10/10 loss: 17.1716 - valLoss: 16.9998 次に、65に変更したところ以下のようになりました。 Epoch 1/10 loss: 129.8353 - valLoss: 129.8753 Epoch 2/10 loss: 129.8254 - valLoss: 129.8654 Epoch 3/10 loss: 129.8153 - valLoss: 129.8553 Epoch 4/10 loss: 129.8049 - valLoss: 129.8449 Epoch 5/10 loss: 129.7944 - valLoss: 129.8344 Epoch 6/10 loss: 129.7838 - valLoss: 129.8238 Epoch 7/10 loss: 129.7729 - valLoss: 129.8129 Epoch 8/10 loss: 129.7619 - valLoss: 129.8019 Epoch 9/10 loss: 129.7508 - valLoss: 129.7908 Epoch 10/10 loss: 129.7395 - valLoss: 129.7795 ノード数が2違うだけなのに、ここまでの誤差が生じることも不思議ですが、64で全く学習しないのに63と65では若干学習が進んでいることも不思議です。 06 中間層のノード数 中間層のノード数を、5から100まで5刻みで調べてみます。 Javaのプログラムは学習部分のみ載せます。 Run06.java int i = 1; for (int nodesNum = 5; nodesNum <= 100; nodesNum += 5){ System.out.printf("\r%d", i); Network net = new Network( 192, new Input(100, AF.RELU), new Dense(nodesNum, AF.SIGMOID), new Output(1, AF.RELU) ); Adam opt = new Adam(net, new MeanAbsoluteError()); opt.fit(X, T, 100, X.row / 30, valX, valT, "data/06/history"+Integer.toString(nodesNum)+".csv"); // opt.fit(X, T, 10, X.row / 30, valX, valT); // net.save("df_14_dense64.net"); i++; } System.out.println(); この結果を、以下のプログラムで見てみます。 import glob import re import pandas as pd import matplotlib.pyplot as plt import seaborn as sns import numpy as np sns.set() ################################################ def cal_learn_degree(arr): ld = 0 for i in range(len(arr)-1): ld += (arr[i] - arr[i+1]) / (arr[i] * arr[i]) return ld ################################################ full_data = [] for filename in glob.glob("data/06/history*.csv"): data = {} with open(filename, "r") as fp: df = pd.read_csv(fp) middle_num = re.findall(r"\d+", filename) data["nodes_num"] = int(middle_num[1]) data["loss"] = list(df["loss"]) data["val_loss"] = list(df["valLoss"]) data["loss_min"] = df["loss"].min() data["val_loss_min"] = df["valLoss"].min() data["ld"] = cal_learn_degree(data["val_loss"]) full_data.append(data) full_data = sorted(full_data, key=lambda x: x["nodes_num"]) ################################################ # 訓練と確認それぞれでの最小誤差を、ノード数毎に並べたグラフを作成。 x = np.array(list(map(lambda x: x["nodes_num"], full_data))) y = list(map(lambda y: y["loss_min"], full_data)) val_y = list(map(lambda y: y["val_loss_min"], full_data)) width = 2 plt.bar(x-width/2, y, label="train", width=width) plt.bar(x+width/2, val_y, label="val", width=width) plt.legend() plt.xlabel("Number of middle layer's nodes") plt.ylabel("Mean absolute error") plt.savefig("pic/middle_experiment.png", dpi=150) plt.clf() plt.close() 結果はこちら。 ノード数と誤差の間に、因果関係はあまりなさそうなデータが取れてしまいました。 また、この中では35が最も小さな誤差になっているのが気になったので、35の時の学習推移を見てみました。 loss_35 = [y["loss"] for y in full_data if 35 == y["nodes_num"]][0] val_loss_35 = [y["val_loss"] for y in full_data if 35 == y["nodes_num"]][0] plt.plot(loss_35, label="train") plt.plot(val_loss_35, label="val") plt.legend() plt.xlabel("Number of epoch") plt.ylabel("Mean absolute error") plt.savefig("pic/middle_experiment_35.png", dpi=150) plt.clf() plt.close() ・・・。 学習度が最大になったのはノード数が何の時だったかを調べます。 top_ld = sorted(full_data, key=lambda x: x["ld"], reverse=True)[0] print(top_ld["nodes_num"]) 70 ノード数70のとき、もっとも学習度が高かったようです。 上の棒グラフを見ると、最小誤差はいい感じです。 学習履歴を、ノード数35のグラフを作ったものとほぼ同じプログラムでグラフ化しました。 確かに減ってはいるので学習経過はいい感じですが、、、 もう少し制度上がりそうですね。 07 06のネットワークのエポック数を増やしてみる 上でグラフ化した、もっとも学習度が高かったネットワークのエポック数を増やそうと思います。 プログラムはすべて省略します。 実行結果はこちら。 訓練データと確認データの誤差はそれぞれ以下の通り。 8.076857 9.334738 02のものより若干悪い結果になりました。 まとめ 結局02が最も優秀という結果になりましたので、02のネットワークを保存しました。 エポック数は1000です。 訓練データと確認データの平均絶対誤差をもう一度載せておきます。 7.951017 8.41695 フルバージョン dataフォルダはアップロードしていません。 次回は 今回作成したネットワークを利用して探索を行い、最善手に置くAIを作成します。 次回
- 投稿日:2022-03-23T10:05:39+09:00
Spring bootのプロジェクト作成はSpring Initializrを使おう
昔からある Spring Framework のプロジェクト作成ツールとしては Spring Tool Sweet (STS) が有名ですが、それとは別に Spring 公式ツールとして Spring Initializr があるのでそれを使うと便利です。 Spring Initializrとは Spring Initializr (スプリング イニシャライザ、で読み方合ってる?) は Spring boot プロジェクトのひな形を簡単に作ることが出来るサービスです。 Spring Initializr からアクセスして利用を開始できます。 Spring Initializrの使い方 Spring Initializr にアクセスすると以下のようなページが表示されます。 ページの左側に使用するビルドツール・開発に使用する言語・ Spring boot のバージョンなどプロジェクトの基本となる情報を入力し、ページの右側にはそのプロジェクトで使用するライブラリの選択が出来ます。 最後に下の GENERATE ボタンを押してひな形の Zip ファイルを作成・ダウンロードして、ローカル環境で展開してビルドする、というのが使い方の基本です。 簡単ですね。 参考情報 Pleiades のサイトに Spring Initializr を使ったプロジェクトひな形作成から、そのひな形を使って実際に Hello World! を表示するまでの流れを説明していますので参考にどうぞ。 Spring Initializr クイックスタート - 公式サンプルコード サイトからだとキーボードやマウスでの入力となり再現性が下がるから嫌だ、という場合は curl で CLI 操作も可能です。 プロジェクト作成方法もコード管理したいという場合は便利かと。 curlを通してSpring Initializrを利用する | DevelopersIO
- 投稿日:2022-03-23T08:55:59+09:00
[Set Up] Java + Selenium4 WebDriver + Chrome E2E test automation in windows by Eclipse
Outline Selenium Webdriverをもちいて、WEB (Chrome) のE2E テスト自動化を始めるにあたっての 環境構築のメモである App List APP Version Objective Java Version 8 Update 321 Java Eclipse 2021‑12 Java Programing Editor Selenium Client 4.1.2 (January 30, 2022) Selenium Chrome 99.0.4844.74 Web Browser OS windows10 execution environment Java Javaの実行環境をダウンロードし、インストールする download Chrome WebDriver Seleniumからchromeを起動するときのDriver ほかのブラウザを使いたい場合は、そのブラウザに適したアプリ・プラグインをダウンロードする。 download 使用しているchromeのversionと同一のchromedriver.exeを用意する https://www.selenium.dev/ja/documentation/webdriver/getting_started/install_drivers/ これを後ほど、eclipseの設定時に、chromediverを配置する。 Selenium Client javaのselenium ライブラリ 現時点ではselenium4が安定版であり、このjavaのpackageをダウンロードする download javaのstable版 jarをdownloadする https://www.selenium.dev/downloads/ Eclipse Javaの開発、実行のためのツール download install Eclipse IDE for Java Developersを選択 Create Project chromeを起動するためのサンプルプログラムのプロジェクトを新規作成する。 Project Name 「SeleniumSampleProject」でnew projectを作成する lib seleniumのライブラリを今回作成したプロジェクトに紐づける。 Package Explorerで、SeleniumSampleProjectの配下に 「libs」のフォルダを新規作成する。 そこに、Selenium Clientのjar一式をcopy(drag)する Java Build Path Classpath Build Pathの設定を開き、Librariesの項目を選択する。 ここで、Classpathに、先ほどLibsに追加したライブラリを「Add External JARs」で追加する。 exe downloadしてきた、chromedriverを配置する Run Configurations compile option Run configurationsの argumentsに、以下chromedriverのpathを設定する この設定で、実行時にchromedriverをつかい、chromeを操作することができる。 -Dwebdriver.chrome.driver=exe/chromedriver.exe sample program chromeを起動し、楽天競馬のサイトに飛ばすsimpleなプログラム。 これにより、開発環境がただしく起動することの確認ができる。 import org.openqa.selenium.WebDriver; import org.openqa.selenium.chrome.ChromeDriver; public class SampleScript { public static void main (String[] args) { WebDriver driver = new ChromeDriver(); driver.get("https://keiba.rakuten.co.jp"); } }
- 投稿日:2022-03-23T05:02:39+09:00
Java 18新機能まとめ
Java 18が2022/3/22にリリースされました。 機能的に多くのプログラマに関係ありそうな変更はUTF-8がデフォルトになったくらい。あとは簡単なWebサーバーが入ったことかな。 詳細はこちら JDK 18 Release Notes Java SE 18 Platform JSR 393 OpenJDK JDK 18 GA Release APIドキュメントはこちら Overview (Java SE 18 & JDK 18) 変更点まとめはこちら https://docs.oracle.com/en/java/javase/18/docs/api/new-list.html APIの差分はこちら。 https://cr.openjdk.java.net/~iris/se/18/latestSpec/apidiffs/overview-summary.html MacやLinuxでのインストールにはSDKMAN!をお勧めします Oracle OpenJDK以外に無償で商用利用できるディストリビューションとしては、次のようなものがあります。(まだ用意されていないものもあります。3/23時点ではOracle JDK, Corretto 18だけかな) Oracle JDK ※対外サーバーの運用には有償ライセンスが必要です Adoptium Temurin Azul Zulu Liberica JDK Amazon Corretto 18 Microsoft Build of OpenJDK アップデートは4月に18.0.1が、7月に18.0.2がリリースされることになります。 JEP 大きめの変更はJEPでまとまっています。 https://openjdk.java.net/projects/jdk/18/ 今回は9個のJEPが取り込まれました。すでにプレビューなどで出ていたものが3つあり、新たに取り込まれたものは6です。非推奨が1つあるので、新機能としては5つですね。 JEP 400: UTF-8 by Default JEP 408: Simple Web Server JEP 413: Code Snippets in Java API Documentation JEP 416: Reimplement Core Reflection with Method Handles JEP 417: Vector API (Third Incubator) JEP 418: Internet-Address Resolution SPI JEP 419: Foreign Function & Memory API (Second Incubator) JEP 420: Pattern Matching for switch (Second Preview) JEP 421: Deprecate Finalization for Removal ツール ツール関係の変更としては次の3つのJEPがあります。 JEP 400: UTF-8 by Default JEP 408: Simple Web Server JEP 413: Code Snippets in Java API Documentation JEP 400: UTF-8 by Default APIのデフォルトエンコーディングがUTF-8になりました。これでネットでダウンロードしたファイルを表示するプログラムがWindowsだけ文字化けするみたいなことが起きにくくなります。 環境変数では、file.encodingがUTF-8になります。 C:\Users\naoki>java\jdk\jdk-18\bin\java -XshowSettings:properties -version Property settings: file.encoding = UTF-8 ... line.separator = \r \n native.encoding = MS932 ... sun.jnu.encoding = MS932 ... sun.stderr.encoding = ms932 sun.stdout.encoding = ms932 ... openjdk version "18" 2022-03-22 OpenJDK Runtime Environment (build 18+36-2087) OpenJDK 64-Bit Server VM (build 18+36-2087, mixed mode, sharing) Java 17ではOSごとに違いWindowsではMS932でした。 C:\Users\naoki>java\jdk\jdk-17\bin\java -XshowSettings:properties -version Property settings: file.encoding = MS932 ... line.separator = \r \n native.encoding = MS932 ... sun.jnu.encoding = MS932 ... sun.stderr.encoding = ms932 sun.stdout.encoding = ms932 ... openjdk version "17" 2021-09-14 OpenJDK Runtime Environment (build 17+35-2724) OpenJDK 64-Bit Server VM (build 17+35-2724, mixed mode, sharing) ただ、いままで通りOSごとに違う設定にしたい場合というのもあると思います。その場合は、環境変数file.encodingにCOMPATを指定します。そうすると、実際のfile.encodingの値が環境ごとのエンコーディング設定になります。 C:\Users\naoki>java\jdk\jdk-18\bin\java -Dfile.encoding=COMPAT -XshowSettings:properties -version Property settings: file.encoding = MS932 native.encodingがJava 17で導入されているので、COMPATを選ぶときはfile.encodingにその値が設定されます。 C:\Users\naoki>javac C:\Users\naoki\IdeaProjects\untitled3\src\main\java\projava\WebServer.java C:\Users\naoki\IdeaProjects\untitled3\src\main\java\projava\WebServer.java:29: エラー: この文字(0x83)は、エンコーディン グwindows-31jにマップできません <body><h1>Hello</h1>It works!<br>縺ッ繧阪?シ</body></html> ^ エラー1個 C:\Users\naoki>java\jdk\jdk-18\bin\javac C:\Users\naoki\IdeaProjects\untitled3\src\main\java\projava\WebServer.java Charset.forName("default")はJava 17ではUS-ASCIIを返していました。 C:\Users\naoki>java\jdk\jdk-17\bin\jshell | JShellへようこそ -- バージョン17 | 概要については、次を入力してください: /help intro jshell> java.nio.charset.Charset.forName("default") $1 ==> US-ASCII これがJava 18ではUnsupportedCharsetExceptionを投げるようになります。 C:\Users\naoki>java\jdk\jdk-18\bin\jshell | JShellへようこそ -- バージョン18 | 概要については、次を入力してください: /help intro jshell> java.nio.charset.Charset.forName("default") | 例外java.nio.charset.UnsupportedCharsetException: default | at Charset.forName (Charset.java:527) | at (#1:1) JEP 408: Simple Web Server ファイルを扱う簡単なWebサーバーが用意されました。 jwebserverコマンドで、カレントディレクトリを扱うWebサーバーが起動します。 C:\Users\naoki>echo ^<h1^>hello^</h1^>It^ works > index.html C:\Users\naoki>java\jdk\jdk-18\bin\jwebserver Binding to loopback by default. For all interfaces use "-b 0.0.0.0" or "-b ::". Serving C:\Users\naoki and subdirectories on 127.0.0.1 port 8000 URL http://127.0.0.1:8000/ 127.0.0.1 - - [05/3譛・2022:22:14:03 +0900] "GET / HTTP/1.1" 200 - 127.0.0.1 - - [05/3譛・2022:22:14:03 +0900] "GET /favicon.ico HTTP/1.1" 404 - 8000番ポートが使われます。 起動オプションには次のようなものがあります。 オプション 用途 -h / -? / --help ヘルプメッセージの表示 -b addr / --bind-addess addr アドレスをバインドする。デフォルトはループバック -d dir / --directory dir 提供するディレクトリ。デフォルトはカレントディレクトリ -o level or --output level 出力フォーマット。 none / info / verbose デフォルトはinfo -p port or --port port ポート番号。デフォルトは8000 -version or --version バージョンの出力 API APIから起動することもできます。SimpleFileServer jshell> import com.sun.net.httpserver.SimpleFileServer jshell> import com.sun.net.httpserver.SimpleFileServer.OutputLevel jshell> var server = SimpleFileServer.createFileServer(new InetSocketAddress(8000), Path.of("c:/Users/naoki"), OutputLev el.VERBOSE) server ==> sun.net.httpserver.HttpServerImpl@2f7c7260 jshell> server.start() jshell> 127.0.0.1 - - [05/3譛・2022:23:08:05 +0900] "GET / HTTP/1.1" 200 - Resource requested: c:\Users\naoki > Cookie: _redmine_session=YWNTYi9CVTNqclBQcG9WOFZ4d3o5bHJZTDNIWlUwcnJhZml0c05ac0tCQzZQVVlYelJPY01PaGxrMjdNbDhvazkwL1lEQnhTNSs3SDBSVm5HT3hERGQ4MVc2K2hRV1ptNE4yTjMzbFlKWkpQZmI0aWhVWFJLNkJhMHFtOS8zVURnbUJVZGJyOTdIL0lVd1g5YlJ0OEVTVW90WGs5MTdMRDRkOU1SOUxqYnJ2clJ4bTQzMkt1am4zSlFSa0pFWC9KcUZoSnJqelcrMTJzQ3djUEs4ZXp0dGJSRnBNVTFSYXRwUnFFUGVTMlBEMDRvZGdOZVc2ejVuYXZjY09Fdnp4WjNkUTIydStTMFh4RUtpRnAway9vOHJKbkNzNFFIbTZJc0dOY3ZlWUFTbENRSlRKbjBROU55ZVJDaGtoSW9WNnV2VnpLc1BIYkZySmg3Z2ZwajM1dkM0V3JNemN6b1NaQTBMN0NHRjdUYjcyajlYcm5lOW9RbjN5eVNyUnVOYVRQdUJobjNmY2hZUFlQT3E1WkdROW5vWGc1eDVPZnVhOERwM09PazhibVp0TGYreTVId2tuMDdxUmlKNU5DRFVXOVlMWHV3S08ydCtpaExxdElPdEZDc0hhMzhSUTRKTDBjdDZETjhPT0R2eCtoOGFkR2NqcWtXMHlRVUFPZ1BuQ2szZGVKM1Z3MHp6Ky9KS0NmUkdNNVMxbVdDQUxxbkUybk96L2Y4ZytteGdIQ3BhaXBPeWlWNnBKc2ltMUZJalpsSy93K3R1SjUzRGRCQUJwWnkweSs3UT09LS1IUE1VSmN1dC9ZR1hJTytSSFlrQW5RPT0%3D--672fdb2f2f192abdf0e3b3d62c6eb9001a1110eb > Accept-encoding: gzip, deflate > Sec-fetch-dest: document > Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8 > Sec-fetch-user: ?1 > Connection: keep-alive > Sec-fetch-site: none > Host: localhost:8000 > Sec-fetch-mode: navigate > User-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:97.0) Gecko/20100101 Firefox/97.0 > Accept-language: ja,en-US;q=0.7,en;q=0.3 > Upgrade-insecure-requests: 1 > < Date: Sat, 05 Mar 2022 14:08:05 GMT < Last-modified: Sat, 5 Mar 2022 13:44:43 GMT < Content-type: text/html < Content-length: 25 < createFileServerに与えるPathは絶対パスで指定します。 JEP 413: Code Snippets in Java API Documentation Javadocにコードスニペットを含むことができます。使う機会があるかどうかは別として、わりと面白い。 基本は{@snippet :から}で囲むものです。 /** * The following code shows how to use {@code Optional.isPresent}: * {@snippet : * if (v.isPresent()) { * System.out.println("v: " + v.get()); * } * } */ 制約として、/*~*/のコメントは使えません。また中カッコ{}は対応している必要があります。 スニペットとして外部ファイルを指定することもできます。 /** * The following code shows how to use {@code Optional.isPresent}: * {@snippet file="ShowOptional.java" region="example"} */ // @startから// @endまでがスニペットとして使われる感じですね。 public class ShowOptional { void show(Optional<String> v) { // @start region="example" if (v.isPresent()) { System.out.println("v: " + v.get()); } // @end } } @highlightで強調を指定しることもできます。\bは単語境界を示すので、\barg\bの場合argsは強調されずargだけが強調されます。 /** * {@snippet : * public static void main(String... args) { * for (var arg : args) { // @highlight region regex = "\barg\b" * if (!arg.isBlank()) { * System.out.println(arg); * } * } // @end * } * } */ @replaceで、コンパイルが通らない文字列に置き換えることもできますね。 /** * A simple program. * {@snippet : * class HelloWorld { * public static void main(String... args) { * System.out.println("Hello World!"); // @replace regex='".*"' replacement="..." * } * } * } */ @linkでJavadocへのリンクを設定することもできます。 /** * A simple program. * {@snippet : * class HelloWorld { * public static void main(String... args) { * System.out.println("Hello World!"); // @link substring="System.out" target="System#out" * } * } * } */ 言語機能 言語機能の変更としては、Pattern Matching for switchが2ndプレビューになっています。 JEP 420: Pattern Matching for switch (Second Preview) パターンマッチがswitchで使えるようになります。 2ndプレビューになりました。今回大きな変更がなければJava 19で正式機能になりそうです。 Java 18では、使うときに--enable-previewが必要です。 「といってもパターンマッチってそんなに出番ないのでは?」 と思うかもしれませんが、大きな影響が。 switchでcase nullが書けるようになります。 jshell> String s = null s ==> null jshell> switch (s) { ...> case "test" -> "テスト"; ...> case null -> "ぬるぽ"; ...> default -> "hello"; ...> } $13 ==> "ぬるぽ" case nullがない場合は従来どおりNullPointerExceptionです。 jshell> switch (s) { ...> case "test" -> "テスト"; ...> default -> "hello"; ...> } | 例外java.lang.NullPointerException: Cannot invoke "String.hashCode()" because "<local0>" is null | at (#12:1) ""とnullで同じ処理を行う場合などに併記できるのが便利です。 jshell> switch(s){ ...> case null, "" -> "empty"; ...> default -> s; ...> } $17 ==> "empty" defaultとnullで同じ処理したいときにアロースタイルで書けないんでは?という疑問に応えて、defaultがcaseに書けるようになっています。 jshell> switch (s) { ...> case "one" -> 1; ...> case "two" -> 2; ...> case null, default -> -1; ...> } $21 ==> -1 Java 17の1st previewの時点ではswitchにnullリテラルを渡すとjavacが落ちていました。 >jshell --enable-preview | JShellへようこそ -- バージョン17 | 概要については、次を入力してください: /help intro jshell> switch(null) { ...> default -> "hello"; ...> } コンパイラで例外が発生しました(17)。Bug Database (http://bugs.java.com)で重複が... Java 18で修正されて正しくぬるぽが出るようになっています。 jshell> switch (null) { ...> default -> "hello"; ...> } | 例外java.lang.NullPointerException | at Objects.requireNonNull (Objects.java:208) | at (#33:1) で、パターンマッチですが、型と変数をswtichに書けるようになりました。これをタイプパターンといいます。 jshell> Object o = "abc" o ==> "abc" jshell> switch (o) { ...> case String s -> "--%s--".formatted(s); ...> case Integer i -> "%03d".formatted(i); ...> default -> o.toString(); ...> } $35 ==> "--abc--" タイプパターンのあとに&&でつなげて条件を書くことでガード節とすることができます。 jshell> o = "helo" o ==> "helo" jshell> switch (o) { ...> case String s && s.length() < 5 -> "--%s--".formatted(s); ...> case String s -> s.toUpperCase(); ...> case Integer i -> "%05d".formatted(i); ...> default -> o.toString(); ...> } $42 ==> "--helo--" ただし、3rdプレビューでは&&ではなくwhenを使うようです。 switch (o) { case String s when s.length() < 5 -> "--%s--".formatted(s); case String s -> s.toUpperCase(); case Integer i -> "%05d".formatted(i); default -> o.toString(); } 3rdプレビューが来たということは、Java 19での正式化はないということでもあります。 http://openjdk.java.net/jeps/8282272 定数パターンとタイプパターンをひとつのswitchで使うこともできます。定数パターンに使えるのはこれまでどおり整数、文字列、enumです。 jshell> s = "helo" s ==> "helo" jshell> switch (s) { ...> case "test" -> "テスト"; ...> case String s && s.length() < 5 -> s.toUpperCase(); ...> default -> "too long"; ...> } $29 ==> "HELO" case句のタイプパターンで定義した変数のスコープは、case句の中だけです。 ここではswitchの外にあるのと同じく変数sを定義してcase句で上書きしていますが、switchの外に影響はありません。 jshell> s = "helo" s ==> "helo" jshell> switch (s) { ...> case String s && s.length() < 5 -> (s="Hello").toUpperCase(); ...> default -> "too long"; ...> } $30 ==> "HELLO" jshell> s s ==> "helo" Java 19ではレコードに対するパターンがプレビューとして導入される予定です。 https://openjdk.java.net/jeps/405 return switch(n) { case IntExpr(int i) -> i; case NegExpr(Expr n) -> -eval(n); case AddExpr(Expr left, Expr right) -> eval(left) + eval(right); case MulExpr(Expr left, Expr right) -> eval(left) * eval(right); default -> throw new IllegalArgumentException(n); }; API [JEP 416: Reimplement Core Reflection with Method Handles] JEP 421: Deprecate Finalization for Removal JEP 418: Internet-Address Resolution SPI JEP 417: Vector API (Third Incubator) [JEP 419: Foreign Function & Memory API (Second Incubator)] 小さいもの java.lang.Math / java.lang.StrictMath いろいろ追加されています。 ceilDiv ceilMod divideExact floorDivExact unsignedMultiplyHigh Duration.isPositive Duration.isZeroやDuration.isNegativeはこれまであったのにisPositiveがなかったので追加されました。 HttpRequest.Builder.HEAD() メソッドをHEADとしてビルダーを作ります。 JEP 416: Reimplement Core Reflection with Method Handles java.lang.reflect.Method、java.lang.reflect.Constructor、java.lang.reflect.FieldをMethod Handlesを使って実装しなおします。 APIへの変更はなく、実装の変更です。 JEP 421: Deprecate Finalization for Removal OSリソースの開放漏れを防ぐために導入されたファイナライザですが、オブジェクトがGC対象になってからfinalizeがいつ呼び出されるかわからなかったり、GC対象になったはずのオブジェクトを再び参照させたり、スレッドや呼び出し順をコントロールできなかったり、いろいろな問題がありました。 セキュリティ脆弱性の原因になったり、パフォーマンス悪化につながったりということで、非推奨、そのうち削除ということになりました。 --finalization=disabledをつけて実行することで、ファイナライザが無効になって、GC時にも実行されなくなります。デフォルトではファイナライザは有効ですが、今後のバージョンでデフォルト無効になり、さらにその先のバージョンでファイナライザは取り除かれます。 Object.finalize()やGraphics.finalize()のようなfinalizeメソッドは@Deprecatedになります。 JEP 418: Internet-Address Resolution SPI InetAddress.getByNameのようなAPIはシステムに組み込まれた名前解決を使っていましたが、SPIとして提供できるようになりました。 JEP 417: Vector API (Third Incubator) AVX命令のような、複数のデータに対する計算を同時に行う命令をJavaから利用できるようになります。 Project Panamaのひとつとして開発されていました。 Java 16でインキュベータとして導入されましたが、今回3rdインキュベータになりました。今回、ArmのSVE命令をサポートしています。 使うためには実行時やコンパイル時に--add-modules jdk.incubator.vectorをつける必要があります。 import jdk.incubator.vector.*; static final VectorSpecies<Float> SPECIES = FloatVector.SPECIES_256; void vectorComputation(float[] a, float[] b, float[] c) { for (int i = 0; i < a.length; i += SPECIES.length()) { // SPECIES.length() = 256bit / 32bit -> 8 VectorMask<Float> m = SPECIES.indexInRange(i, a.length); // 端数がマスクされる // a.lengthが11でiが8のとき最初の3つしか要素がないので [TTT.....] // FloatVector va, vb, vc; FloatVector va = FloatVector.fromArray(SPECIES, a, i, m); FloatVector vb = FloatVector.fromArray(SPECIES, b, i, m); FloatVector vc = va.mul(va). add(vb.mul(vb)). neg(); vc.intoArray(c, i, m); } } 利用できるのは次の6つの型です。それぞれに対応するVector型があって、これが基本になります。 型 bit幅 Vector byte 8 ByteVector short 16 ShortVector int 32 IntVector long 64 LongVector float 32 FloatVector double 64 DoubleVector ただ、利用するにはVectorSpeciesが必要です。利用したいVectorにSPECIES_*という定数が用意されているので、それを使います。*は一度に計算するbit数ですね。 jshell> FloatVector.SP SPECIES_128 SPECIES_256 SPECIES_512 SPECIES_64 SPECIES_MAX SPECIES_PREFERRED MAXではそのハードウェアで使える最大、PREFERREDは推奨ビット数だけど、同じになるんじゃないのかな。ここでは256bitが推奨されて、floatが8個同時に計算できるようになっていますね。 jshell> FloatVector.SPECIES_PREFERRED $11 ==> Species[float, 8, S_256_BIT] ハードウェアで使えるbit数は搭載CPUに依存しますが、普通のIntel/AMDであれば256、XEONとか つよつよCPUなら512かな。M1は128でした。ハードウェアでサポートされないbit数を使おうとするとソフトウェア処理になるので遅くなります。 実際のVectorはfrom*というメソッドで取得します。fromArray、fromByteArray、fromByteBufferが用意されています。インキュベータに入る前はfromValuesがあったのですが、なくなってますね。 Vectorを得られたら、用意されたメソッドで計算します。ひととおりの算術命令はあります。 jshell> va. abs() add( addIndex( bitSize() blend( broadcast( byteSize() compare( div( elementSize() elementType() eq( equals( fma( getClass() hashCode() intoArray( intoByteArray( intoByteBuffer( lane( lanewise( length() lt( max( min( mul( neg() notify() notifyAll() pow( rearrange( reduceLanes( reduceLanesToLong( reinterpretAsBytes() reinterpretShape( selectFrom( shape() slice( sqrt() sub( test( toArray() toDoubleArray() toIntArray() toLongArray() toShuffle() toString() unslice( viewAsFloatingLanes() viewAsIntegralLanes() wait( withLane( ところで、こういったメソッド呼び出しの内部でAVX命令などを呼び出すのでは遅くなるんではという気がしますが、実際にはJVM intrinsicsという仕組みでJITコンパイラがこれらのメソッド呼び出しをネイティブ関数呼び出しに置き換えます。 JEP 419: Foreign Function & Memory API (Second Incubator) 2ndインキュベータですが、Foreign Function APIの前身であるForeign Linker APIはJava 16から、Foreign Memory APIはJava 14からインキュベータで、結構ながくインキュベータやってます。 Foreign Memory API ヒープ外のメモリをアクセスする方法としては、ByteBufferを使う方法やUnsafeを使う方法、JNIを使う方法がありますが、それぞれ一長一短があります。 ByteBufferでdirect bufferを使う場合、intで扱える範囲の2GBまでに制限されたり、メモリの解放がGCに依存したりします。 Unsafeの場合は、性能もいいのですが、名前が示すとおり安全ではなく、解放済みのメモリにアクセスすればJVMがクラッシュします。 JNIを使うとCコードを書く必要があり、性能もよくないです。 ということで、ヒープ外のメモリを直接扱うAPIがJava 14でインキュベータモジュールとして導入され、今回5バージョン目のインキュベータです。 次のようなコードになります。 import jdk.incubator.foreign.*; import java.nio.ByteOrder; VarHandle intHandle = MemoryHandles.varHandle(int.class, ByteOrder.nativeOrder()); try (MemorySegment segment = MemorySegment.allocateNative(100)) { for (int i = 0 ; i < 25 ; i++) { intHandle.set(seg, i * 4, i); } } コンパイル、実行では--add-modules jdk.incubator.foreignを付けてモジュールを読み込む必要があります。 パッケージもモジュール名と同様、jdk.incubator.foreignになっています。 MemoryHandlesからwithOffsetとwithStrideが消えましたね。 jshell> import jdk.incubator.foreign.* jshell> MemoryHandles. asAddressVarHandle( asUnsigned( class collectCoordinates( dropCoordinates( filterCoordinates( filterValue( insertCoordinates( permuteCoordinates( varHandle( Java 15ではMemoryHandlesからVarHandleを得るメソッドが拡充してました。 jshell> MemoryHandles. asAddressVarHandle( asUnsigned( class collectCoordinates( dropCoordinates( filterCoordinates( filterValue( insertCoordinates( permuteCoordinates( varHandle( withOffset( withStride( Java 14では次の3つのメソッドしかありませんでした。 jshell> MemoryHandles. class varHandle( withOffset( withStride( Java 16ではMemoryAccessというユーティリティが用意されて、VarHandleを使わずにシンプルにかけるようになっています。 import jdk.incubator.foreign.*; try (MemorySegment segment = MemorySegment.allocateNative(100)) { for (int i = 0 ; i < 25 ; i++) { MemoryAccess.setIntAtOffset(i * 4, i); } } Foreign Function API ネイティブライブラリの呼び出しを行う。 外部メモリのアクセスにはForeign Memory Access APIを使う JNIの代替 Java 16でForeign Linkerとして1stインキュベータになり、今回3バージョン目のインキュベータです。 たとえばこんな感じのC関数があって。 size_t strlen(const char *s); こんな感じでMethodHandleを取り出して。 CLinker linker = CLinker.systemCLinker(); MethodHandle strlen = linker.downcallHandle( linker.lookup("strlen").get(), FunctionDescriptor.of(JAVA_LONG, ADDRESS) ); こんな感じで呼び出すようです。 MemorySegment str = implicitAllocator().allocateUtf8String("Hello"); long len = strlen.invoke(cString); // 5