技術と日常。

日々の気が付いたこと・気になったことを残しておきます。

[ふるさと納税]大阪府泉佐野市 しらすの釜揚げを頂きました

実は大阪湾はしらす天国

皆さんは、大阪湾で水揚げされるシラスの漁獲量について、どれくらいの規模かご存知ですか?
実は、2019年度実績で年間3,700トンという数字で、全国シェアは6.2%、日本で3番目となる漁獲高なんです。
結構多いんや!と思われた方々もきっと多いはず!?そうなんです、実は大阪湾はしらす天国なんです。

出典: さのちょく 泉佐野市ふるさと納税寄付サイト

泉佐野市はふるさと納税では有名な市町村です。
個人的なイメージでは、良くも悪くも「あまり大阪に関係なくない?」というものも含めて、バラエティに富んだものを、リーズナブルな寄付金で返礼いただけるイメージした。

しかし、しらすに関しては、大阪府は日本で3番目の漁獲量とのこと。れっきとした名産品です。
特に泉佐野市は、しらすの街として、味にこだわったブランディングを進めているようでした。

注文~開封

私は5,000円の寄付金額で、1kg(500g x 2パック)の返礼品を頂きました。
寄付のサイトでは、透明なパックに入って届くとは記載されていたのですが、いまいち大きさがわからず届くまで少々不安でしたので、ここで共有いたします。

縦横1辺は16cmでした。

高さは7.5cmでした。

個人的には、ある程度大きいだろうなーと思ってはいたものの、想像と同じくらいか、一回り大きいものが届いた印象でした。
二つ重ねると、16cm x 16cm x 15cmくらいのサイズ感になりますので、冷凍庫のスペースにはご注意ください。

賞味期限は、冷凍保存にて180日、解凍後は解凍日含めて3日とのことでした。
なかなか500gは3日で食べるのは苦しいので、他の方のレビューを参考にしつつ、以下のような保存方法を考えました。

  • 届いてから冷蔵庫で2時間ほど、軽く解凍する
  • 開封し、塊のまま、まな板に出し十字に切れ込みを入れ、4等分する
  • それぞれをラップで包み、もう一度入っていた容器に戻し、ふたをして冷凍する

個人的に、ふるさと納税で大きなパックで来たものは、いかに届いたその日に小分けの処理をして、使いやすい状態にするかがポイントと感じています。
4等分すれば、1つ125g前後となり、3日あれば食べきれると思います。ぜひトライしてみてください。

実食

解凍ついでにそのまま頂いてみました。
適度な塩味と、何よりも口当たりがフワっとした食感で、美味しかったです。
港隣接の加工場で迅速に釜揚げをしているからこそ、フワフワとした食感が楽しめるそうです。
また、チャーハンにしてみても、美味しく頂けました。しらすの塩味を考量した味付けをすると、丁度よく仕上がるかと思います。
泉佐野市様、ありがとうございました。

寄付はこちらから

1kg(記事で紹介)


2kg


[Java]Comparator#reversed()のエラー解消から学ぶJavaコンパイラの弱点

きっかけ

Comparator#reversed() という、Comparatorの逆順を返してくれる便利なメソッドを知り、使おうとしたところ、以下のエラーが出てしまいました。

var list = new ArrayList<>(List.of(2, 3, 1));

// NG:
// no instance(s) of type variable(s) U exist so that Object conforms to Comparable<? super U>
list.sort(Comparator.comparing(s -> s).reversed());

// OK: reversedをつけなければ
list.sort(Comparator.comparing(s -> s));

調査をしたところ、以下のページにて興味深い話が載っていましたので、共有をしたいと思います。
stackoverflow.com

原因

原因は、どうやらJavaコンパイラの、 ジェネリクスに対する遡っての解決 が、うまくできないことにあるようです。
例えば、普段よく使うStreamは、ジェネリクスは以下のように先頭から解決が行われています。

list.stream() // Collection<Integer> -> Stream<Integer>
.map(i -> i + 1) // Stream<Integer> -> Stream<Integer>
.toList(); // Stream<Integer> -> List<Integer>

まずこのケースについて考えます。

list.sort(Comparator.comparing(s -> s));

list#sort()は、List<Integer>が、Comparator<? super Integer>を必要としています。
また、Comparator#comparing()は、Function<? super T, ? extends U> keyExtractorを引数としてComparator<T>を返します。
そのため、List<Integer> -> Comparator<Integer> -> Function<Integer, U> -> Function<Integer, Integer>という風な解決が行われています。

次に、エラーとなったケースについて考えます。

list.sort(Comparator.comparing(s -> s).reversed());

list#sort()は、List<Integer>が、Comparator<? super Integer>を必要としています(ここまでは一緒)。
Comparator#reversed()は、Comparator<T>からComparator<T>を返すメソッドのため、上の情報を使用して、両方をComparator<Integer>と解決します。
あとは後ろからそれを使ってComparator#comparing()に伝えれば……、というところなのですが、どうやらJavaコンパイラは、これが解決できないようです。
これが冒頭にあげた「遡っての解決」にあたります。

再現

凄くシンプルな例で再現をしてみると以下のようになります

// こんなクラスを作って
public static class Test<T> {
    public Test<T> method() {
        return this;
    }
}

// NG:
// Required type: Test<String>
// Provided: Test<Object>
//
// 変数の型のTest<String>から、method -> new<>と解決してほしいのだが、できない。
Test<String> t = new Test<>().method();

以下のように、型を明示的に宣言をするか、遡り解決をしなければ、怒られません。

// OK: 型の明示的な宣言
Test<String> t = new Test<String>().method();

// OK: 遡り解決をしない
Test<String> t = new Test<>();

それでも逆順のComparatorがほしい

冒頭にあげたstack overflowにて、Brian Goetzさんという方が、以下のようにコメントしていました。
Brian Goetzさんは、OracleにてJava Language Architectをされている方のようです。

Lambdas are divided into implicitly-typed (no manifest types for parameters) and explicitly-typed; method references are divided into exact (no overloads) and inexact. When a generic method call in a receiver position has lambda arguments, and the type parameters cannot be fully inferred from the other arguments, you need to provide either an explicit lambda, an exact method ref, a target type cast, or explicit type witnesses for the generic method call to provide the additional type information needed to proceed.

翻訳・整理すると、

ラムダは暗黙的型付け(パラメータに明示的型付けがない)と明示的型付けに分けられ、メソッド参照は厳密(オーバーロードなし)と非厳密とに分けられる。

受信位置のジェネリックメソッド呼び出しにラムダ引数があり、型パラメータが他の引数から完全に推測できない場合

  1. 明示的ラムダ
  2. 正確メソッド参照
  3. ターゲット型キャスト
  4. ジェネリクスメソッドの呼び出しに対する明示的型付け

を提供して、進行に必要な追加の型情報を提供する必要があります。

折角なのでそれぞれ試してみました

// 1. 明示的ラムダ
// この書き方は知らなかった……。
list.sort(Comparator.comparing((Integer s) -> s).reversed());

// 2. 正確メソッド参照
// 以下のようなメソッドを用意してあげて、メソッド参照する
// public Integer integerFunction(Integer i) {
//     return i;
// }
list.sort(Comparator.comparing(this::integerFunction).reversed());

// 3. ターゲット型キャスト
list.sort(Comparator.comparing((Function<Integer, Integer>) s -> s).reversed());

// 4. ジェネリクスに対する明示的型付け
list.sort(Comparator.<Integer, Integer>comparing(s -> s).reversed());

あとは、Collections#reverseOrder()を使用することで、遡り解決を避けられるようです。

list.sort(Collections.reverseOrder(Comparator.comparing(s -> s)));

最後に

コンパイラへの突っ込んだ話となりましたので、もしかしたら間違っているところもあるかもしれませんが、何かの参考になれば幸いです。
最後までお読みいただきありがとうございました。

当記事では以下のバージョンを使用しています。

>java --version
openjdk 17.0.4 2022-07-19 LTS
OpenJDK Runtime Environment Corretto-17.0.4.8.1 (build 17.0.4+8-LTS)
OpenJDK 64-Bit Server VM Corretto-17.0.4.8.1 (build 17.0.4+8-LTS, mixed mode, sharing)

[AWS] SAA合格の勉強法と心得(ソリューションアーキテクト-アソシエイト)

概要

2022/08に、AWS SAA(ソリューションアーキテクト-アソシエイト) SAA-C02に合格しましたので、使用教材や勉強法について共有させて頂ければと思います。
なお、現行のSAA-C03試験ではありまぜんので、ご注意ください。
受験する方それぞれの経歴によって、最適な勉強法は違うと思います。参考程度にお読みいただければ幸いです。

プロフィール

投稿者は以下のような経歴です。

  • 普段はサーバーサイドアプリケーションの設計・実装が中心
  • インフラ歴としては
    • Google Cloud Platformにて、AppEngine, CloudSQLを経験
    • オンプレで、頼まれればLinux等を入れてAP/DB等を構築するくらいの温度感
  • 今年(2022年)に入ってからAWSを仕事で触るように
    • EC2はなんかコンピューターで、S3がストレージ?という程度の知識
    • 当初はS3をブロックストレージ(EBS)的なものだと思っていた
  • 他の所持資格は応用情報技術者
    • AWS SAAは人生初のベンダー資格への挑戦

勉強法

まずは座学

【SAA-C03版】これだけでOK! AWS 認定ソリューションアーキテクト – アソシエイト試験突破講座 | Udemy

SAA合格体験記にもよく登場する有名教材だと思います。
とりあえず、EC2/S3の章までを、ハンズオンとして実施しました。
EC2にアタッチするのはEBSで、S3はなんか便利なストレージなんだな、というくらいの認識ができてきました。
そのあとの章は、まずは講義部分を全部見て、興味がある部分を少し手を動かして、章末の小テストを解いて、という感じで、最後まで進めていきました。

最後まで一旦見終わったところで、付属の模擬試験①を5問程度やってみたところ、全く太刀打ちできずショックを受けたので、情報を収集することにしました。

練習問題を解く

【AWS資格】無料WEB問題集&徹底解説 | ソリューションアーキテクト(SAA)

まずこちらのサイトの練習問題を解いてみることにしました。
難易度は比較的簡単だと思います。とっかかりとしては丁度よかったです。しかし、基礎的内容のツボの部分は押さえられていると思います。
同じ問題で勉強している方のコメントが載っていて、ちょっと難しい問題の「引っかかったー!」等の反応を読みながら、1日2~30問ずつくらい、楽しみながら解かせててもらいました。

AWS SAA/SAP資格 予想問題集 Web版 | 日本語完全無料

次はこちらのサイトの練習問題にお世話になりました。
ひとつ前のサイトに比べれば、サービスの細かい内容まで聞かれたりと、全体的にワンランク難しい内容となっているかと思いますが、その分非常にタメになる内容で、本番対策として十分成立する内容だったと思います。
わからないところ、初見のところを、メモにまとめつつ、最後まで解きました。最終的には3週しました。

確かこの辺りで、SAA試験が改定になる情報を得て、慌てだした記憶があります。

Udemy模擬試験1周目

いったんUdemyに戻り、3つある模擬試験を解いてみました。以下のような成績でした

模擬試験① 模擬試験② 模擬試験③
1周目 83%(54/65) 69%(45/65) 77%(50/65)

※70%以上が合格点

模擬試験②があと1問足らず、惜しかったなー!という感想でした。

また練習問題へ

CloudTech | AWS初学者を導く体系的な動画学習

Twitterで情報収集をしていたら見つけたので、無料会員登録の範囲の200問を解きました。
問題レベルは難しいです。Udemyの模擬試験では問われていないサービスも出たり、切り口としても少々捻られていたりで「こんなの本当に出るの!?」と思いながら解いてました(ちなみに出ました。助かりました。)
このサイトは、なんといっても。問題を解いた後に出るまとめ資料が秀逸でした。 S3/EBS/ElastiCacheで、ユースケースに対してどれをえらべばいいのかの比較表のおかげで、今までなんとなくでしか選べなかった、例えばS3-Standard IAとGlacierの違いなどが、自信をもって選べるようになりました。
このサイトも、最終的には3週しました。

Udemy模擬試験2周目

試験も近くなってきたので、もう一度Udemyを解きました。

模擬試験① 模擬試験② 模擬試験③
1周目 83%(54/65) 69%(45/65) 77%(50/65)
2周目 84%(55/65) 87%(57/65) 80%(52/65)

※70%以上が合格点

なんだかんだで、全て合格点以上になりました。
しかし、2周目の模擬試験①では、ケアレスミスや、2個選択すべきところを1個のみ選択で飛ばしてしまったりと、よくない点の落とし方をしていたため、注意しようと思いました。
あと、模擬試験③においては、前わからなかった問題が、そのまま今回もわからなかった感じがあったので、改めて全体を通して復習をしました。

AWS公式の模擬試験

AWS認定の無料模擬試験がさらに便利になりました! | DevelopersIO

模擬試験があることをなんとなく知っていたので、試験前日に、上記記事を頼りにユーザー登録をし、試験を受けました。
結果16/20(80%)で、合格ラインではありました。
しかしまず、今まで勉強してきたどの練習問題より問題文が長いのに面くらいました。日本語の言い回しも少々独特でした。
また、結構頑張って勉強したつもりではありましたが、それでも聞いたことがない/考えたことがないようなことも、何問か聞かれました。
合格ラインはとったものの、これで本当に大丈夫なのかと不安を抱えながら、本番を迎えることとなりました。

試験当日

当日はピアソンVUEにて受験しました。朝一の時間に行ったのですが、私と同様?に、AWS試験を受けに駆け込みに来られている方も、何名かいるようでした。
CBTのテストは初めてで、大きめのワイドディスプレイに画面いっぱいに文字が出て「読みにくいなぁ……」と思いながら試験を受けました。
140分の試験のうち、80分かけ、残り60分のところで1周目が終わり、もう30分かけ見直しの2週目をして、30分残して試験を終えました。
手ごたえはそこまでなかったですが、最後にアンケートに答え「合格」の文字が出て、ほっとしました。
終わってみれば、なかなか得点できていたようです。

勉強/試験の心得

教材/練習問題が古いことがある

AWSは常に改善がされているので、教材/練習問題の内容にて、差異があることがあります。
「なんか前聞いたのと違う気がするな……?」と思ったら、公式のドキュメントにて、最新の情報を入手しに行きましょう。

試験自体が結構難しい

インターネットだと「未経験からxx日で受かりました!!」等の派手なタイトルを見かけますが、普通に難しい試験だと思います。
「ソリューションアーキテクト」というだけあり、クライアントの要望に対して「その要望に対して満たし方はいろいろあるけれど、最適なのはどれ?」ということを聞かれます。
「それでも満たせるけど、最適なのはこちらだよ。」という、ひっかけ的なものもあります。
個人的には、オンプレ等でインフラ構築の経験があるか、もしくはアプリ側で設計の経験があるか、どちらかでもあると、回答(提案)力に差がつくかと思います。

持ち物に気を付ける

ピアソンVUEにて試験申し込みが完了すると、英語のメールが来ますが、ちゃんと(Google翻訳等してでも)読みましょう。身分証が2つ必要なことを見落としそうでした。
あと、ポケットの中身や時計は持ち込めずロッカーに預けさせられるので、例えば高い時計なんかはしていかないことをお勧めします。

試験問題が長い

模擬試験のところでもでてきましたが、とにかく1問1問が長いです。
紙とペン(消せない)を渡されますので、頭で考えすぎずに構成図を描いたりだとか、途中で適度に休憩をはさむとかして、脳を休めつつ、最後まで集中できるようにしましょう。

1割くらいは全く聞いたことない問題が出る

体感1割(6問)くらいは「これ聞いたことないなーわかんないなー-」というものが出ました(勉強不足かもしれませんが)。
気を落とさず、AWSの気持ちになって「AWSならどうしてくれるかな」なんてことを考えながら、解けると良いと思います。

日本語に疑問を持ったら英語を

日本語が読みにくいです。イントネーションが微妙で、何を最適化するのか問われていることが微妙な時は、英語も見つつ解いていました。
それだけならまだ良いのですが、私の時は「なんかこの選択肢変だな……?」と思って、英語に切り替えて見てみたら、日本語と英語でサービス名が違っている箇所がありました……。日本人に厳しい。

終わりに

AWSは進化も早く、まだまだわからないことだらけですが、今までより少し自信をもって仕事に取り組めるようになりました。
資格の有効期限は3年なので、いつかSAPにもチャレンジ出来たらいいなと思います。

[Java/JJUG] Streamが閉じれるって知ってた?にて登壇しました

2022/10/17 本発表を記事にしました。 beppy.hatenablog.com

2022/08/26の、JJUGナイトセミナー「おうちで!ビール片手にLT大会!」にて、スピーカーを務めました。

動画

スライド

感想

他のスピーカーの方々は、LocalStack(AWS)でのモダンな開発や、Javaプレビュー機能など、かなり進んだ/コアな内容を発表されていて、「こんなJavaAPIの基礎の話は場違いだったかな……?」と一瞬不安になりました。

しかし、視聴してくださった方から「知らなかった」「これはやらかしそう」というコメントを頂き、発表してよかったな、と思いました。
「Streamは閉じないといけないケースがある」ということが、頭の片隅に記憶としてあるだけでも、今後リソースリークを起こす確率が減ると思います。

改めて、関係者様、スピーカーの皆様、視聴者様、ありがとうございました。

[ふるさと納税]千葉県勝浦市 カツオのたたき(わけあり)を頂きました

勝浦市は関東一のかつお水揚げ量

カツオといえば、私の中では高知県や、静岡県の焼津のイメージでしたが、関東では、勝浦漁港が一番の水揚げ量だそうです。
小型船がひき縄漁で1尾ずつカツオを釣り上げる「勝浦のひき縄カツオ」として、千葉ブランド水産物にも認定されています。
漁獲したその日に水揚げされることから「日戻りカツオ」とも呼ばれ、鮮度の良さが特徴とのことでした。

注文~開封

注文してから1週間ほどで到着しました。 段ボールを開けるとカツオがぎっしりでした。

テーブルの上に並べてみました

実食

訳アリということで、確かにサイズに多少のばらつきは感じましたが、気になるほどではなく、なにより傷や痛み等は全く感じませんでした。
たたきにして頂いたところ、外側の焼かれた部分の香ばしさ・歯ざわりと、内側の生の部分の食感のバランスが良く、臭みもなく、大変美味しく頂けました。
ありがとうございました。

寄付はこちらから


参考

かつおの水揚げ関東一を誇る | 勝浦漁業協同組合

勝浦産ひき縄カツオ(千葉ブランド水産物)/千葉県

[Gradle]サブプロジェクトの依存性を上位でも使用する方法

発生した問題

Gradleのプロジェクト間にmain <- sub の依存関係があるときに、以下のように書き、librarymainでも使用しようとしましたが、読み込めない問題が発生しました

dependencies {
    implementation project(':sub')
}

main/build.gradle

dependencies {
    implementation 'library'
}

sub/build.gradle

解決法

調べたところ、implementationではなくapiを使用すればよいことがわかりました

dependencies {
    api 'library'
}

sub/build.gradle

まとめ

config クラスパス追加 ビルド出力 上位モジュールへ公開 補足
implementation × 依存が他のモジュールに伝搬しない
ビルド時間が大幅に短縮される
基本はこれを使用することを推奨
api 最強だが依存性が増える
implementationよりビルド時間も増える
慎重に用いる必要がある
compileOnly × × コンパイルに必要だが実行時に必要ない場合
PaaSにライブラリが用意されいる場合など
上位への伝搬が必要ならcompileOnlyApi
runtimeOnly × × コンパイルには必要ないが実行時に必要な場合

参考

Gradleのimplementationとapiの違いメモ - Qiita

Add build dependencies  |  Android Developers

The Java Library Plugin

[Java]StreamのgroupingByで、カテゴリごとの合計(Sum)を求める方法

前提

例えば以下のような、カテゴリとその量が定義されているクラスがあったとします。

public record Obj(
        String category,
        int amount
) {
}

そのリストから、各カテゴリごとの、量の集計処理を考えます。

// AとBはそれぞれいくつ?
List<Obj> objs = List.of(
        new Obj("A", 100),
        new Obj("A", 200),
        new Obj("B", 300),
        new Obj("B", 400),
        new Obj("B", 500)
);

結果がMap

int, long, doubleの場合

Collectors#groupingBy()と、Collectors#summingInt()を使用します。

objs.stream().collect(Collectors.groupingBy(
        Obj::category, // キーのマッピング
        Collectors.summingInt(Obj::amount) // オブジェクトから合計対象値のマッピング
));

> {A=300, B=1200}

summingDoublesummingLongも用意されているので、型にあわせて使用することができます。

それ以外の型の場合

それ以外の型の場合はCollectors#reducingにて処理ができます。

objs.stream().collect(Collectors.groupingBy(
        Obj::category,
        Collectors.reducing(
                0, // 初期値 今回はintの初期値なので0
                Obj::amount, // ↑でも出てきた、値のマッピング
                Integer::sum // それぞれの値に対して行う処理 今回はintの合計のためsum
        )
));

> {A=300, B=1200}

結果が元の型のCollection

上記の結果からのオブジェクトの生成も可能ですが、以下のような書き方はどうでしょうか。
この書き方であれば、集計対象が2つ以上になった場合も対応可能です。

objs.stream().collect(Collectors.groupingBy(
                Obj::category,
                Collectors.reducing((o1, o2) -> new Obj(o1.category(), o1.amount() + o2.amount()))
        )).values().stream().map(Optional::get).collect(Collectors.toSet());

> [Obj[category=A, amount=300], Obj[category=B, amount=1200]]

参考

Collectors (Java SE 17 & JDK 17)

Group by and sum objects like in SQL with Java lambdas? - Stack Overflow