【Java】Java17以降で整数の完全な16進数文字列を得たいならHexFormatがおすすめ

Long.toStringは上位ビットが0だった場合に削れた文字列を返してしまいます。
完全な16進数表記を得るにはString.format("%016x", number)を利用する例がよく引っかかりますが、内部処理を見ると結構重そうなことをしています。

一方、Java 17からは、このような場合に利用可能なHexFormatというクラスが追加されました。

docs.oracle.com

toHexDigitsの内部実装を読んだ所、以下のような形で、ほぼビット演算のみで実装されていました。
ベンチマークは取っていませんが、これなら確実にString.formatよりも高速でしょう。

Java内から引用
    public String toHexDigits(long value) {
        byte[] rep = new byte[16];
        rep[0] = (byte)toHighHexDigit((byte)(value >>> 56));
        rep[1] = (byte)toLowHexDigit((byte)(value >>> 56));
        rep[2] = (byte)toHighHexDigit((byte)(value >>> 48));
        rep[3] = (byte)toLowHexDigit((byte)(value >>> 48));
        rep[4] = (byte)toHighHexDigit((byte)(value >>> 40));
        rep[5] = (byte)toLowHexDigit((byte)(value >>> 40));
        rep[6] = (byte)toHighHexDigit((byte)(value >>> 32));
        rep[7] = (byte)toLowHexDigit((byte)(value >>> 32));
        rep[8] = (byte)toHighHexDigit((byte)(value >>> 24));
        rep[9] = (byte)toLowHexDigit((byte)(value >>> 24));
        rep[10] = (byte)toHighHexDigit((byte)(value >>> 16));
        rep[11] = (byte)toLowHexDigit((byte)(value >>> 16));
        rep[12] = (byte)toHighHexDigit((byte)(value >>> 8));
        rep[13] = (byte)toLowHexDigit((byte)(value >>> 8));
        rep[14] = (byte)toHighHexDigit((byte)value);
        rep[15] = (byte)toLowHexDigit((byte)value);

        try {
            return jla.newStringNoRepl(rep, StandardCharsets.ISO_8859_1);
        } catch (CharacterCodingException cce) {
            throw new AssertionError(cce);
        }
    }

また、このクラスには16進数文字列 -> intのようなデコード関数も実装されており、相互変換に利用できます。

docs.oracle.com

【jOOQ】UUIDを大量指定する際の省メモリ化【PostgreSQL】

状況

大量のUUIDを指定したin句を発行する際に、メモリ消費量が問題になりました。
見たところ、jOOQではUUID1件当たりcast('b81b8735-6ac8-4b0b-a969-658e70425616' as uuid)みたいな形のクエリになってしまうことが原因のようでした。

対処

応急処置として、以下2点を満たす省メモリ化されたin句を発行するためのカスタム実装を行いました。

  • cast文無し
  • UUIDのハイフン無し

サンプルコード

Kotlinかつ一部余計な処理が入ってますが、要するに${取得対象カラム} in (b81b87356ac84b0ba969658e70425616, ...)みたいな条件を生成しています。

private val hexFormat = HexFormat.of()

fun UUID.hyphenLessStr(): String = hexFormat.toHexDigits(this.mostSignificantBits) + hexFormat.toHexDigits(this.leastSignificantBits)

// jOOQの生成結果が基本nullableなため、Fieldの型パラメータはnullableにしている
fun <T> Field<UUID?>.reducedIn(args: Collection<T>, unbox: (T) -> UUID): Condition {
    val condStr = args.joinToString(prefix = "${this.qualifiedName} in (", separator = ",", postfix = ")") {
        // アプリ上メモリ消費量低減のため、普通のUUIDからハイフンを抜いた形式にしている
        // 参照: https://www.postgresql.org/docs/14/datatype-uuid.html
        "'${unbox(it).hyphenLessStr()}'"
    }

    return DSL.condition(condStr)
}

PostgreSQLのクエリにおけるUUID指定について

ハイフン無しでOKということは文章化されています。

www.postgresql.org

【Java】AWS SDK2で、S3からファイルへgetObjectする際、デフォルトでは失敗時にDL先ファイルが削除される

AsyncClientの方で調査していますが、通常の方も同じ内容が有るはずです。

特にコンフィグ無しでAsyncResponseTransformer.toFileを呼び出した場合、FileTransformerConfigurationdefaultCreateNewで設定されます。
aws-sdk-java-v2/core/sdk-core/src/main/java/software/amazon/awssdk/core/internal/async/FileAsyncResponseTransformer.java at bb6c1a489e2bfd03dc08ccf2bb94278342f810b9 · aws/aws-sdk-java-v2 · GitHub

この中ではFailureBehavior.DELETEが指定されています。
aws-sdk-java-v2/core/sdk-core/src/main/java/software/amazon/awssdk/core/FileTransformerConfiguration.java at bb6c1a489e2bfd03dc08ccf2bb94278342f810b9 · aws/aws-sdk-java-v2 · GitHub

(よく読んだらJavadocにもそう書いてありました)

sdk.amazonaws.com

【Kotlin】kotlin-reflectを弄るためのメモ

github.com

greadle.propertiesを弄る

ビルド・テストで全コア使われると他アプリの動作が重くなるので、kotlin.test.junit5.maxParallelForks全コア数 - 最低限確保したいコア数にしていいかも。
以前はJDK7以前を無視するオプションも有ったがいつの間にか消えていた。

テスト周り

変更

kotlin-reflect関連はcompiler/testData/codegen/box/reflectionに有る。
特にテスト追加のような変更後はgenerateTestsIDEに保存されている内だとGenerators -> GenerateTests)を実行することで、各環境向けテスト生成を行う(この結果もコミットが必要)。

実行

codegenTarget8Jvm8Testを実行すれば最低限の確認は出来る。

成果物の生成先

kotlin-reflectjarlibraries/reflect/build/libs/kotlin-reflect-${バージョン}-SNAPSHOT-sources.jarに出力される。 ベンチマーク等を取る時はこの辺りを弄る。

OpenAPI Generatorで、何でも入れてOKのプロパティの値に型付け(Map<String, String>とかに)する方法

以下のように、additionalPropertiestypeを指定すると、値の型を変えられます。
例ではstringを指定しているので、Kotlinで生成した場合Map<String, String>になります。

type: object
additionalProperties: 
  type: string

type: integerにすればMap<String, Int>になりますし、type: stringのままformat: uuidにすればMap<String, UUID>になります。

1年のアウトプットを振り返る

この振り返りも5年目となりましたがやっていきます。

wrongwrong163377.hatenablog.com

OSS関連

jackson-module-kotlinのメンテナになった

今年最大の変化はjackson-module-kotlinのメンテナになったことです。

jackson-module-kotlinのメンテナは、2022年中ほぼ不在でした。
また、以前のメンテナも新機能の追加やバグの修正、性能改善などには積極的でなく、実質数年開発が止まっている状況でした。
そんな中、以前からの貢献もあってJackson全体のメンテナをされている方からお誘いを頂いたため、思い切って手を挙げることにしました。

jackson-module-kotlinへの貢献

2023年の年始からメンテナンス作業を始め、年内で既に30程度の変更をマージしています(今現在も作業中です)。
この他にも、テストの修正やイシューの整理(合計150件位closeしました)、databind側の数件の修正やイシュー報告といった点で貢献できました。

github.com

正直な所、最初はイシュー対応など英語でのやり取りは難しいだろうと考えていましたが、DeepLさえあれば割となんとかなりました。
ただ、英訳含め対応には非常に多くの時間がかかってしまう上、直接jackson-module-kotlinに不備があるようなケースはかなり少なかったため、正直今でもイシュー対応はやりたくない作業になっています。

jackson-module-kogeraの開発

jackson-module-kotlinに関する実験的な実装を行うための場として、jackson-module-kogeraというプロジェクトも始めました。

github.com

kotlinx-metadata-jvmへの置換やデシリアライズの大幅高速化、value class対応と、様々な難しい要素を実現できたため、このプロジェクトは割と自慢できる仕上がりになっています。
客観的にも、人生初の100スターを集めたプロジェクトとなりました。
以前やっていたプロジェクトではここまで到達できなかったため、非常に嬉しかったです。

ベンチマークプロジェクトの方も色々工夫でき、充実した開発ライフを過ごせました。

github.com

その他OSSへの貢献

今年はjackson-module-kotlin関連ばっかりだったので、それ以外の貢献はあまり有りませんでした。
kotlin-reflectの軽微なバグ修正と、mockkの軽微なパフォーマンス改善位です。

ブログ・外部登壇

ブログ

がっつりOSS活動に振り切った1年でしたが、一応30本は書いていたようです。
正直、業務的に何か新しい発見をすることは減ってしまいましたし、OSSならブログ書いてるよりPR出す方に行ってしまいがちなので、中々書けない感が有ります。

外部登壇

今年は前半3回ほど登壇していました。
来年も1回位はどこかで登壇したいですね(といってもまずはOSSの開発優先になりそうですが)。

終わりに

今年はアウトプット面でとても充実した1年だったと思います。

特にjackson-module-kotlin関係では様々な改善を行うことができました。
自分の入れた改善を全世界の人間が参照しているというのはとても面白い感覚があります。
間違いなく、自分のアウトプットが発揮した価値は過去最高を更新しました。

jackson-module-kotlinへの関わり方に関しては、来年リリース予定の2.17でいよいよ数年越しのvalue class対応を入れられそうなこともあって、まだまだモチベーションは有ります。
ただ、余暇の大半がjackson-module-kotlinに消えてしまったのはちょっと考えものでした。
そのため、2.18辺りを目処にjackson-module-kotlinへの関与は減らすつもりです。

OSSに関わりたい欲の解消やジム通いの再開など、来年も充実させていければと思います。

【Spring WebFlux】Bean初期化処理でblockすると、lazy-initが有効な場合に実行時エラーになる

以下の追記部で紹介した話です。

qiita.com

本文

lazy-initが無効な場合、Bean初期化処理中であればblockしても特に問題は起きません。
このため、Bean初期化時のwarmup目的でblockすることもできます。

一方、lazy-initが有効な場合、アクセス時のコンテキストでblockが呼び出されてしまうため、それによるエラーが発生します。

そもそもlazy-initが有効であれば、リクエストを待たせてまでwarmupする意味は無いため、プロパティを見て処理をスキップすることをおすすめします。