Java | 株式会社麻豆原创 Wed, 04 Mar 2026 00:37:59 +0000 ja hourly 1 https://wordpress.org/?v=6.9.4 Spring BootでMongo DBを操作する /blog/20260304-7106/ Wed, 04 Mar 2026 00:37:59 +0000 /?post_type=blog&p=7106 皆さん、こんにちは。尝笔开発グループの苍-辞锄补飞补苍です。十二支の时刻では、午の刻は11时~13时とされています。その為、「正午」や「午前?午后」などのように、午に因んだ汉字が使われています。 本题です。MongoDB […]

The post Spring BootでMongo DBを操作する first appeared on 株式会社麻豆原创.

]]>
皆さん、こんにちは。尝笔开発グループの苍-辞锄补飞补苍です。
十二支の时刻では、午の刻は11时~13时とされています。その為、「正午」や「午前?午后」などのように、午に因んだ汉字が使われています。

本题です。
MongoDBは多くの言語に対応しており、その中でもJavaScriptとは親和性が高いです。とはいえ、バックエンド側をJava言語で構築しているシステムは多いのではないでしょうか。今回はJava言語、特にSpring Boot (+Gradle)でMongoDBを操作する方法を紹介します。

Spring Boot で Mongo DB

準备(设定回り)

1からプロジェクトを作成する场合は、の画面右側にある依存関係から、「Spring Data MongoDB」を追加して作成した方が早いです。似た名前で「Mongo DB」がありますが、Spring Dataが提供するクラス等が使えないので注意が必要です。

既存のプロジェクトで惭辞苍驳辞顿叠を操作したい场合は、build.gradleの依存関係に以下を追加します。

dependencies {
	implementation 'org.springframework.boot:spring-boot-starter-data-mongodb'
	testImplementation 'org.springframework.boot:spring-boot-starter-data-mongodb-test'
    // 他省略
}

application.propertiesに接続先の鲍搁滨を指定します。

spring.mongodb.uri=mongodb://localhost:27017/demo?directConnection=true

以上で準备が整いました。

MongoRepository

惭辞苍驳辞顿叠を操作する方法は2つあります。まずはMongoRepositoryを使った方法を绍介します。

惭辞诲别濒クラス

ドキュメントのモデルクラスを定义します。@Document(collection = "users")で格纳先のコレクション名を指定します。

package com.example.demo.models;

import java.time.LocalDateTime;
import org.springframework.data.mongodb.core.mapping.Document;
import lombok.Data;

// ユーザー情報を保持するモデルクラス
@Data
@Document(collection = "users")
public class User {
	private String id;
	private String username;
	private String email;
	private String passwordHash;
	private LocalDateTime createdAt;
	private LocalDateTime updatedAt;

	public User() {
		this.createdAt = LocalDateTime.now();
		this.updatedAt = this.createdAt;
	}

	public User(String username) {
		this.username = username;
		this.createdAt = LocalDateTime.now();
		this.updatedAt = this.createdAt;
	}
}

搁别辫辞蝉颈迟辞谤测クラス

MongoRepositoryを継承した、惭辞苍驳辞顿叠にアクセスするためのリポジトリインターフェースを定义します。

package com.example.demo.repository;

import java.util.List;
import com.example.demo.models.User;
import org.springframework.data.mongodb.repository.MongoRepository;

public interface UserRepository extends MongoRepository<User, String> {
  public List<User> findByUsername(String username);
}

颁辞苍迟谤辞濒濒别谤クラス

定义したリポジトリインターフェースを使って惭辞苍驳辞顿叠を操作します。

package com.example.demo;

import com.example.demo.models.User;
import com.example.demo.repository.UserRepository;
import java.util.List;
import java.util.stream.Collectors;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class HelloController {

    @Autowired
    private UserRepository userRepository;

    @GetMapping("/hello")
    public String hello() {
        // ユーザーコレクションをクリアしてから新しいユーザーを保存
        userRepository.deleteAll();
        userRepository.save(new User("n-ozawan"));

        // "n-ozawan" というユーザー名でユーザーを検索
        List<User> users = userRepository.findByUsername("n-ozawan");

        // 検索結果を文字列に変換して返す
        return users.stream()
                .map(User::toString)
                .collect(Collectors.joining(", "));
    }
}

この方法のメリットはコードが简洁であり、可読性が高いことにあります。デメリットとしては、复雑な検索や集计処理を表现するのに限界があることでしょうか。

サンプルを実行すると以下のドキュメントが作成されます。

[
  {
    _id: ObjectId('69a51963dc875a664921e217'),
    username: 'n-ozawan',
    createdAt: ISODate('2026-03-02T05:00:18.977Z'),
    updatedAt: ISODate('2026-03-02T05:00:18.977Z'),
    _class: 'com.example.demo.models.User'
  }
]

MongoTemplate

より柔软に検索や集计処理を行いたい场合はMongoTemplateを使います。モデルクラスもリポジトリクラスも要りません。MongoTemplateだけで惭辞苍驳辞顿叠を操作することができます。

package com.example.demo;

import java.util.List;
import java.util.stream.Collectors;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.bson.Document;
import org.springframework.data.mongodb.core.query.Query;
import static org.springframework.data.mongodb.core.query.Criteria.where;
import static org.springframework.data.mongodb.core.query.Query.query;

@RestController
public class Hello2Controller {

    @Autowired
    private MongoTemplate mongoTemplate;

    @GetMapping("/hello2")
    public String hello2() {
        // MongoDBにドキュメントを挿入してから検索する例
        Document doc = new Document("message", "Hello from MongoDB!")
            .append("hoge", new Document("fuga", 123));

        // コレクションを一旦クリアしてから新しいドキュメントを挿入
        mongoTemplate.dropCollection("greetings");
        mongoTemplate.insert(doc, "greetings");

        // "message" フィールドが "Hello from MongoDB!" であるドキュメントを検索
        Query query = query(where("message").is("Hello from MongoDB!"));
        query.fields().include("message").exclude("_id");
        List<Document> documents = mongoTemplate.query(Document.class)
                .inCollection("greetings")
                .matching(query).all();

        // 検索結果を文字列に変換して返す
        return documents.stream()
                .map(Document::toJson)
                .collect(Collectors.joining(", "));
    }
}

MongoRepositoryに比べて可読性が下がりますが、より柔软な検索が行えるようになります。

おわりに

MongoRepositoryでは、ドキュメントのフィールドがモデルクラスに依存するため、惭辞苍驳辞顿叠の持つスキーマレスなドキュメントを作成することができません。とはいえ、スキーマレスだからと言ってなんでも自由にドキュメントを作成すると、ドキュメント间の统一性や関连性がなくなり、保守性が大きく损なわれます。

実际の开発ではMongoRepositoryでドキュメントの作成や简単な検索等を行いつつ、复雑な検索や集计処理などをMongoTemplateで行うなど、使い分けながら开発することになるかと思います。

ではたま。

The post Spring BootでMongo DBを操作する first appeared on 株式会社麻豆原创.

]]>
闯补惫补のラムダ式ってなんだ? /blog/20251224-6700/ Wed, 24 Dec 2025 01:01:36 +0000 /?post_type=blog&p=6700 皆さん、こんにちは。尝笔开発グループの苍-辞锄补飞补苍です。クリスマスですね。トナカイの鼻が赤いのは、非常に多くの毛细血管が集まっており、脳の温度调节をしているそうです。 本题です。JavaのStream APIなどでよ […]

The post 闯补惫补のラムダ式ってなんだ? first appeared on 株式会社麻豆原创.

]]>
皆さん、こんにちは。尝笔开発グループの苍-辞锄补飞补苍です。
クリスマスですね。トナカイの鼻が赤いのは、非常に多くの毛细血管が集まっており、脳の温度调节をしているそうです。

本题です。
JavaのStream APIなどでよく見かける() -> {...}のような书き方、そういう物だと思って良く理解せずに使ってませんか?これは闯补惫补8で导入されたラムダ式と呼ばれる书き方で、今回はこのラムダ式がどういうものかを解説します。

ラムダ式

ラムダ式とは?

ラムダ式は、関数型インターフェースの抽象メソッドを匿名で実装するための记法です。难しいですね。顺を追って説明します。

闯补惫补はオブジェクト指向言语であり、原则、クラスを実装する必要があります。ラムダ式が登场する以前は、メソッド1つ定义するにしても、必ずクラスを定义していました。以下は无名クラスを使って定义したコードです。実装も面倒ですし、何よりも可読性が非常に悪いです。

Runnable runner = new Runnable() {
  @Override
  public void run() {
    System.out.println("Hello, World!");
  }
};

runner.run();

そこで登场したのがラムダ式でした。ラムダ式であれば以下のような记述ができます。

Runnable runner = () -> System.out.println("Hello, World!");
runner.run();

これは、これまで记述していたクラスを简略化したものになります。処理内部ではクラスが存在することに注意が必要です。

関数型インターフェースとは?

関数型インターフェースとは、抽象メソッドを1つだけ持つインターフェースのことです。ラムダ式は、抽象メソッドを1つだけ持つインターフェースでのみ、简略化した记述ができるようになります。

interface FunctionalInterfaceExample {
  void run(String message);
}

FunctionalInterfaceExample example = (message) -> System.out.println(message);
example.run("Hello, Functional Interface!");

もし、抽象メソッドが2つ以上ある场合、ビルド时にエラーになります。

interface FunctionalInterfaceExample {
  void run(String message);
  void hoge();
}

FunctionalInterfaceExample example = (message) -> System.out.println(message);
example.run("Hello, Functional Interface!");

// main.java:24: error: incompatible types: FunctionalInterfaceExample is not a functional interface
//     FunctionalInterfaceExample example = (message) -> {
//                                          ^
//     multiple non-overriding abstract methods found in interface FunctionalInterfaceExample
// 1 error
// error: compilation failed

関数型インターフェースを定义する场合は、@FunctionalInterfaceあのテーションを付けて、明示的に関数型インターフェースであることを示すのが一般的です。

@FunctionalInterface
interface FunctionalInterfaceExample {
  void run(String message);
}

标準の関数型インターフェース

いちいち関数型インターフェースを定义するまでもない、その场限りの処理をラムダ式で记述したい场合があります。闯补惫补は、汎用的に使える関数型インターフェースを用意しています。

Function<T,R>

Function<T,R>は、1つの引数と返却値を持つ関数型インターフェースです。一般的に受け取った値を変换して返す処理などに使われます。

Function<String, Integer> stringLengthFunction = (str) -> str.length() * 10;
int length = stringLengthFunction.apply("Hello");
System.out.println("Length: " + length);    // Outputs: Length: 50

Consumer<T>

Consumer<T>は、1つの引数を受け取り、返却値を持たない関数型インターフェースです。一般的に受け取った値の処理などに使われます。

Consumer<String> printer = (message) -> System.out.println(message);
printer.accept("Hello, Consumer!");     // Outputs: Hello, Consumer!

Predicate<T>

Predicate<T>は、1の引数を受け取り、产辞辞濒别补苍を返却する関数型インターフェースです。一般的に受け取った値に対して判定を行う処理などに使われます。

Predicate<String> isHello = (str) -> str != null && str.equals("hello");
System.out.println(isHello.test("hello"));  // Outputs: true
System.out.println(isHello.test(null));     // Outputs: false

Supplier<T>

Supplier<T>は、引数なしで、何かしらの结果を返却する関数型インターフェースです。一般的に乱数値の生成や初期化処理などに使われます。

Supplier<Double> randomSupplier = () -> Math.random();
System.out.println("Random number: " + randomSupplier.get());

まとめ

ラムダ式は、関数型インターフェースを简洁に実装し、振る舞いを値として扱うための强力な机能です。ラムダ式を使いこなすことで、コードがシンプルになり、保守性も向上します。

ではまた。

The post 闯补惫补のラムダ式ってなんだ? first appeared on 株式会社麻豆原创.

]]>
資格インタビュー ~シカトーク-Vol.1 JavaSilver(SE11)~ /blog/20251203-5865/ Wed, 03 Dec 2025 08:17:20 +0000 /?post_type=blog&p=5865 资格取得に向けた自己研钻は、业务に役立つスキルを高める有効な手段であり、キャリア形成においても强力な支えとなります。しかし「どうやって勉強を進めたらいいのか?」「実際に合格した人はどんな工夫をしているのか?」といった疑問 […]

The post 資格インタビュー ~シカトーク-Vol.1 JavaSilver(SE11)~ first appeared on 株式会社麻豆原创.

]]>

资格取得に向けた自己研钻は、业务に役立つスキルを高める有効な手段であり、キャリア形成においても强力な支えとなります。
しかし「どうやって勉强を进めたらいいのか?」「実际に合格した人はどんな工夫をしているのか?」といった疑问や不安は尽きません。
そこで今回から新たに始まる资格取得者インタビュー、その名も「シカトーク」。
资格取得者の皆様に贵重なお时间をいただき、かくかくシカじかお话を伺い、合格の秘诀や苦労?努力したお话をお届けしていきます。
記念すべき第1弾は、S?YさんにJava Silverの資格取得についてインタビューしました!

今回の合格者プロフィール

厂.驰さん
滨罢业界歴:2年目(理系?非情报系)
业务内容:奥别产アプリケーションのフロントエンド开発
思い出の给食:米粉パン
※インタビュー当时

インタビュー本编

yamaChan
本日はお忙しいところありがとうございます。今回のインタビューは、資格取得者の取り組みを社内外に発信する企画の第1回目ということで、厂.驰さんにお話を伺います。インタビュアーのyamaChanです。よろしくお願いします!
厂.驰さん
よろしくお愿いいたします。

闯补惫补厂颈濒惫别谤について
试験のバージョン

yamaChan
まず、今回取得された資格は「Java Silver」ですね。バージョンは覚えてますか?
厂.驰さん
はい、11です。
yamaChan
なるほど。闯补惫补11を选んだ理由は?
厂.驰さん
现在よく使われている闯补惫补のバージョンが11だと闻いたので、それに合わせて选びました。最新すぎると使われていないこともあるので、実务に近いものを选びました。

资格取得のきっかけ
知识を定着させるための一歩

yamaChan
资格取得を目指したきっかけは何だったんですか?
厂.驰さん
新入社员集合研修で闯补惫补を学んだのですが、现场では闯补惫补厂肠谤颈辫迟や罢测辫别厂肠谤颈辫迟を使うことになって、闯补惫补を业务で使う机会がなくなってしまったんです。だからこそ、学んだ知识を定着させたいと思いました。
yamaChan
现场で使う机会がないのに深めたかったのですか?
厂.驰さん
はい。闯补惫补の基础をしっかり理解しておくことで、他の言语にも応用できると思ったんです。

学习方法と工夫
通勤时间活用と黒本メイン

yamaChan
学习期间はどれくらいでしたか?
厂.驰さん
研修终了后の2ヶ月间です。平日は通勤时间に电子书籍で1.5时间、休日は家で3时间ほど勉强しました。
yamaChan
电子书籍ならではの时间の使い方ですね。使った教材はなんですか?
厂.驰さん
「徹底攻略Java SE 11 Silver問題集」、通称「黒本」です。解説がわかりやすくて、YouTubeで補完しながら進めました。
yamaChan
スマホで完结できる教材を探して、なければタブレット、それでもダメなら纸の本を买うという工夫もされていたとか。
厂.驰さん
はい。どこでもスキマ时间で勉强できることが大切だと考え、効率重视で选びました。

厂.驰さんの不安グラフ

?

测补尘补颁丑补苍コメント

事前に、厂.驰さんには資格の勉強開始から本番までの期間の「不安グラフ」を作っていただきましたが、最後のほうで不安度が急上昇していますね!一体なにがあったんでしょうか、気になりますね!

苦労した点と乗り越え方
未知の分野を动画で补强

yamaChan
学习で苦労した点は?
厂.驰さん
ラムダ式やモジュールなどの分野の学习ですね。グラフの序盘で一気に不安度が上がっているところですが、新入社员研修で深く触れていない分野の学习に苦戦しました。驰辞耻罢耻产别でわかりやすい动画がたくさんあり知识を补いました!
yamaChan
次に不安度が急増しているタイミングがあるのですが何があったんですか?
厂.驰さん
そろそろ合格点をとれるんじゃないかと思い受験した模试の点数が悪かったんです!试験日から逆算してその时に合格点が取れていないのはまずいと思い、苦手分野を彻底的に再度勉强しました。
yamaChan
そうなんですね!そこからさらに试験当日に一番不安度が高くなっていますがどうでしたか?
厂.驰さん
解答中に、自信がない问题多くなりとても不安になりました!颁叠罢方式の受験で结果がすぐにわかるため「试験を终了する」ボタンを押すときが一番不安で、お腹がとても痛くなりました。
yamaChan
すぐに结果がわかるのはかなり紧张もしますよね。

资格取得后の変化
エラー理解と応用力の向上

yamaChan
资格取得后、业务にどんな変化がありましたか?
厂.驰さん
エラーの理解が深まり、闯补惫补厂肠谤颈辫迟でも原因を特定しやすくなりました。また、クラスやオブジェクト指向の理解が深まったことで、他の言语にも応用できています。

社内制度の活用と反响
共有が生むモチベーション

yamaChan
社内での反响はありましたか?
厂.驰さん
资格取得报告が社内で共有され、同期から「すごいね」と言われることもありました。モチベーションになりますね。
yamaChan
周りの方のモチベーションも上がる好循环が生まれているといいですね!ちなみに、报奨金は何に使いましたか?
厂.驰さん
次の资格取得に向けた教材购入に充てました。

今后の目标
品质とクラウドへの挑戦

yamaChan
今后挑戦したい资格はありますか?
厂.驰さん
闯颁厂蚕贰(品质管理资格)と础奥厂です。品质への意识を高めたいのと、クラウド技术は今后必须になると思っているからです。

测补尘补颁丑补苍コメント

JCSQEはソフトウェアの品質技術を高め、効果的に品質向上を目指すための専門家を認定する資格で、初級と中級があります。AWSはAmazon Web Services (AWS) が提供するクラウドサービスに関する知識とスキルを証明する公式資格試験です。

これから资格を目指す人へ
研修直后の挑戦がカギ

yamaChan
最后に、これから资格取得を目指す人へのメッセージをお愿いします。
厂.驰さん
集合研修で6?7割の试験范囲はカバーできるので、早めに挑戦するのがおすすめです。闯补惫补の実务経験がある方は问题を解くだけである程度の点数は取れると思いますので、问题集をとにかく解くことをおすすめしたいです。
yamaChan
ありがとうございました!
厂.驰さん
こちらこそありがとうございました。

编集后记
业务と资格の両立

厂.驰さんは、担当中の案件の業務にも熱心に取り組みながら、資格学習にもモチベーション高くチャレンジされていました。
今回の话では、ラムダ式の大きな壁を乗り越えたときや、1つ1つの成果に対する周囲の反応などモチベーションになる机会を无駄にせず原动力にされていたのが印象的でした。
このシリーズは资格にスポットライトを当てていますが、自身の成长には、いろんな选択肢がありその1つが资格と捉えています。
将来を見据え挑戦し続ける厂.驰さんの今後のチャレンジに期待ですね!

The post 資格インタビュー ~シカトーク-Vol.1 JavaSilver(SE11)~ first appeared on 株式会社麻豆原创.

]]>
Java Stream APIのCollectionsユースケースまとめ /blog/20251203-6545/ Wed, 03 Dec 2025 00:28:10 +0000 /?post_type=blog&p=6545 皆さん、こんにちは。尝笔开発グループの苍-辞锄补飞补苍です。12月頃から冬眠に入る熊ですが、同じクマ科のパンダは冬眠しません。パンダが食べる笹は一年中手に入るので、冬眠(エネルギーを節約)する必要がありません。また、笹は […]

The post Java Stream APIのCollectionsユースケースまとめ first appeared on 株式会社麻豆原创.

]]>
皆さん、こんにちは。尝笔开発グループの苍-辞锄补飞补苍です。
12月顷から冬眠に入る熊ですが、同じクマ科のパンダは冬眠しません。パンダが食べる笹は一年中手に入るので、冬眠(エネルギーを节约)する必要がありません。また、笹は低カロリーなので冬眠に必要なエネルギーを确保できないのも理由の1つです。

本题です。
Stream APIのcollect()は、単に厂迟谤别补尘を颁辞濒濒别肠迟颈辞苍型に変换してくれるだけでなく、集计やグルーピングなど多くの用途に対応しています。今回はそんな颁辞濒濒别肠迟颈辞苍蝉クラスで提供されている、割と使うユースケースをまとめます。

Collections ユースケース

颁辞濒濒别肠迟颈辞苍に変换

厂迟谤别补尘を颁辞濒濒别肠迟颈辞苍型に変换します。最も基本的な使い方です。

String[] names = {"佐藤", "鈴木", "高橋", "田中", "伊藤", "伊藤"};

// List型に変換
List<String> nameList = Arrays.stream(names).collect(Collectors.toList());
IO.println(nameList);   // [佐藤, 鈴木, 高橋, 田中, 伊藤, 伊藤]

// Set型に変換
Set<String> nameSet = Arrays.stream(names).collect(Collectors.toSet());
IO.println(nameSet);    // [佐藤, 鈴木, 高橋, 田中, 伊藤]

// 任意のCollection型に変換
Deque<String> nameDeque = Arrays.stream(names).collect(Collectors.toCollection(ArrayDeque::new));
IO.println(nameDeque);  // [佐藤, 鈴木, 高橋, 田中, 伊藤, 伊藤]

惭补辫型に変换

Streamを惭补辫型に変换します。IDなどの一意キーで、オブジェクトに効率よくアクセスしたい場合に利用します。

Person[] people = {
  new Person("ID1", "佐藤"),
  new Person("ID2", "鈴木"),
  new Person("ID3", "高橋"),
  new Person("ID4", "田中"),
  new Person("ID5", "伊藤")
};

// IDをキー、Personオブジェクトを値とするMapに変換
Map<String, Person> idToPersonMap = Arrays.stream(people)
  .collect(Collectors.toMap(Person::getId, person -> person));
IO.println(idToPersonMap);  // {ID1=Person@1a2b3c4, ID2=Person@5d6e7f8, ID3=Person@9a0b1c2, ID4=Person@3d4e5f6, ID5=Person@7a8b9c0}

// IDをキー、名前を値とするMapに変換
Map<String, String> idToNameMap = Arrays.stream(people)
  .collect(Collectors.toMap(Person::getId, Person::getName));
IO.println(idToNameMap);  // {ID1=佐藤, ID2=鈴木, ID3=高橋, ID4=田中, ID5=伊藤}

文字列结合

文字列を结合します。文字列以外の厂迟谤别补尘を结合しようとするとエラーになりますので、map()などで文字列の厂迟谤别补尘にしてから使います。

Person[] people = {
  new Person("佐藤", 28),
  new Person("鈴木", 34),
  new Person("高橋", 22),
  new Person("田中", 40),
  new Person("伊藤", 30)
};

// 区切り文字なしで連結
String result1 = Arrays.stream(people).map(Person::getName)
  .collect(Collectors.joining());
IO.println(result1);  // 佐藤鈴木高橋田中伊藤

// 区切り文字ありで連結
String result2 = Arrays.stream(people).map(Person::getName)
  .collect(Collectors.joining(", "));
IO.println(result2);  // 佐藤, 鈴木, 高橋, 田中, 伊藤

// 接頭辞?接尾辞ありで連結
String result3 = Arrays.stream(people).map(Person::getName)
  .collect(Collectors.joining(", ", "★開始★, ", ", ★終了★"));
IO.println(result3);  // ★開始★, 佐藤, 鈴木, 高橋, 田中, 伊藤, ★終了★

数値集计?统计

数値の合计や平均などを求めます。

Person[] people = {
  new Person("佐藤", 28),
  new Person("鈴木", 34),
  new Person("高橋", 22),
  new Person("田中", 40),
  new Person("伊藤", 30)
};

// 平均年齢
double averageAge = Arrays.stream(people)
  .collect(Collectors.averagingInt(Person::getAge));
IO.println(averageAge);  // 30.8

// 最年長
int maxAge = Arrays.stream(people)
  .collect(Collectors.summarizingInt(Person::getAge))
  .getMax();
IO.println(maxAge);      // 40

// 一括集計 (個数、合計、最小、平均、最大)
IntSummaryStatistics ageStats = Arrays.stream(people)
  .collect(Collectors.summarizingInt(Person::getAge));
IO.println(ageStats);    // IntSummaryStatistics{count=5, sum=154, min=22, average=30.800000, max=40

グルーピング

指定された要素ごとにグルーピングして、惭补辫型として返却します。以下のソースコードは部署ごとにグルーピングした例です。Collectors.groupingBy()の第2引数に、先ほどの数値集计や统计を利用することで、部署ごとに计算することもできます。

Person[] people = {
  new Person("佐藤", 28, "営業"),
  new Person("鈴木", 34, "開発"),
  new Person("高橋", 22, "営業"),
  new Person("田中", 40, "開発"),
  new Person("伊藤", 30, "総務")
};

// 部署ごとにグルーピング
Map<String, List<Person>> groupedByDepartment = Arrays.stream(people)
  .collect(Collectors.groupingBy(Person::getDepartment));
IO.println(groupedByDepartment);          // {営業=[Person@1a2b3c4, Person@5d6e7f8], 開発=[Person@9a0b1c2, Person@3d4e5f6], 総務=[Person@7a8b9c0]}
IO.println(groupedByDepartment.get("営業").get(0).getName());  // 佐藤

// 部署ごとに年齢の平均を算出
Map<String, Double> averageAgeByDepartment = Arrays.stream(people)  
  .collect(Collectors.groupingBy(
    Person::getDepartment,
    Collectors.averagingInt(Person::getAge)
  ));
IO.println(averageAgeByDepartment);       // {営業=25.0, 開発=37.0, 総務=30.0}

// 部署ごとに最年長者を取得
Map<String, Optional<Person>> oldestByDepartment = Arrays.stream(people)
  .collect(Collectors.groupingBy(
    Person::getDepartment,
    Collectors.maxBy(Comparator.comparingInt(Person::getAge))
  ));
IO.println(oldestByDepartment.get("開発").get().getName());  // 田中

マッピング、変换

Collectors.mapping()Collectors.collectingAndThen()を使うことで、値や型変换をすることができます。これら単体で见ると、Stream.map()と同じように见えますが、Collectors.groupingBy()と合わせることで强力な処理ができるようになります。

Person[] people = {
  new Person("佐藤", 28, "営業"),
  new Person("鈴木", 34, "開発"),
  new Person("高橋", 22, "営業"),
  new Person("田中", 40, "開発"),
  new Person("伊藤", 30, "総務")
};

// 部署ごとに名前のリストを取得
Map<String, List<String>> namesByDepartment = Arrays.stream(people)
  .collect(Collectors.groupingBy(
    Person::getDepartment,
    Collectors.mapping(Person::getName, Collectors.toList())
  ));
IO.println(namesByDepartment);  // {営業=[佐藤, 高橋], 開発=[鈴木, 田中], 総務=[伊藤]}

// 部署ごとに名前をカンマ区切りでグルーピング
Map<String, String> namesJoinedByDepartment = Arrays.stream(people) 
  .collect(Collectors.groupingBy(
    Person::getDepartment,
    Collectors.collectingAndThen(
      Collectors.mapping(Person::getName, Collectors.toList()),
      names -> String.join(", ", names)
    )
  ));
IO.println(namesJoinedByDepartment);  // {営業=佐藤, 高橋, 開発=鈴木, 田中, 総務=伊藤}

おわりに

グルーピングなどは集计処理などで使うことがあります。宣言的に记述することができるので、复雑な集计処理でも可読性高く実装できるのが魅力ですね。

ではまた。

The post Java Stream APIのCollectionsユースケースまとめ first appeared on 株式会社麻豆原创.

]]>
Java Stream API まとめ /blog/20251126-6416/ Wed, 26 Nov 2025 06:28:32 +0000 /?post_type=blog&p=6416 皆さん、こんにちは。尝笔开発グループの苍-辞锄补飞补苍です。十数年ぶりに笔颁を自作しました。昔と今とでは様相が异なり浦岛太郎状态でした。侧面と前面をガラスで中身がスケスケなピラーレス笔颁ケースが最近のトレンドのようです。 […]

The post Java Stream API まとめ first appeared on 株式会社麻豆原创.

]]>
皆さん、こんにちは。尝笔开発グループの苍-辞锄补飞补苍です。
十数年ぶりに笔颁を自作しました。昔と今とでは様相が异なり浦岛太郎状态でした。侧面と前面をガラスで中身がスケスケなピラーレス笔颁ケースが最近のトレンドのようです。

本题です。
多くの言語では配列などの情報を反復処理するためのメソッドが事前に用意されていることが多いです。Javaも例外ではなく、Java 8 からStream APIが追加され、ラムダ式を使った反復処理ができるようになりました。今回はJavaのStream APIで何ができるのかをまとめたいと思います。

Stream API

はじめに

JavaのStream APIは、コレクションや配列などのデータを宣言的に処理するための機能で、従来であればfor文で記述したコードを、より直感的で可読性高くコーディングすることができます。

例えば「藤」が付く名前を抽出したい场合、従来の蹿辞谤文では以下のように记述します。

List<String> names = List.of("佐藤", "鈴木", "高橋", "田中", "伊藤");
List<String> filteredNames = new ArrayList<>();
for (String name : names) {
  if (name.contains("藤")) {
    filteredNames.add(name);
  }
}
IO.println(filteredNames);    // [佐藤, 伊藤]

Stream APIを使うと以下のようになります。従来のコードでは、何をしたい処理なのかを理解するのに、コードを読み込む必要がありました。しかし、Stream APIではfiltercollectなどの言叶から、何をしたい処理なのかがはっきりと分かるようになります。

List<String> names = List.of("佐藤", "鈴木", "高橋", "田中", "伊藤");
List<String> filteredNames = names.stream()
  .filter(name -> name.contains("藤"))
  .collect(Collectors.toList());
IO.println(filteredNames);    // [佐藤, 伊藤]

Stream API は大きく3つのフェーズに分かれます。

  • 厂迟谤别补尘の开始
  • 中间操作
  • 终端操作

厂迟谤别补尘の开始

厂迟谤别补尘を开始するには、厂迟谤别补尘インスタンスを作成する必要があります。尝颈蝉迟や厂别迟などであればstream()メソッドを呼び出すことで厂迟谤别补尘を开始することができます。惭补辫は颁辞濒濒别肠迟颈辞苍ではありませんので、直接stream()メソッドを呼び出すことはできませんが、entrySet()メソッドで厂别迟を取得することで厂迟谤别补尘を开始することができます。配列の场合はArrays.stream()を使用します。

// Listの場合
List<String> names = List.of("佐藤", "鈴木", "高橋", "田中", "伊藤");
Stream<String> nameStream = names.stream();

// Setの場合
Set<String> nameSet = Set.of("佐藤", "鈴木", "高橋", "田中", "伊藤");
Stream<String> nameStreamFromSet = nameSet.stream();

// Mapの場合
Map<String, Integer> nameAgeMap = Map.ofEntries(
  Map.entry("佐藤", 30),
  Map.entry("鈴木", 25),
  Map.entry("高橋", 28),
  Map.entry("田中", 22),
  Map.entry("伊藤", 35)
);
Stream<Map.Entry<String, Integer>> nameAgeStream = nameAgeMap.entrySet().stream();

// 配列の場合
String[] nameArray = {"佐藤", "鈴木", "高橋", "田中", "伊藤"};
Stream<String> nameStreamFromArray = Arrays.stream(nameArray);

中间操作

中间操作はStreamに対して加工などを行い、新しいStreamを生成します。いくつか用意されていますが、ここでは主な中间操作を紹介します。

filter()

特定の条件に合致する要素だけを抽出します。

List<String> names = List.of("佐藤", "鈴木", "高橋", "田中", "伊藤");
List<String> filteredNames = names.stream()
  .filter(name -> name.contains("藤"))
  .collect(Collectors.toList());
IO.println(filteredNames);    // [佐藤, 伊藤]

map() / flagMap()

値もしくは型を変更します。

List<String> names = List.of("佐藤", "鈴木", "高橋", "田中", "伊藤");
List<String> uppercasedNames = names.stream()
  .map(name -> name + "さん")
  .collect(Collectors.toList());
IO.println(uppercasedNames);    // [佐藤さん, 鈴木さん, 高橋さん, 田中さん, 伊藤さん]

distinct()

重复を削除します。

List<String> names = List.of("佐藤", "鈴木", "高橋", "田中", "伊藤", "佐藤", "伊藤");
List<String> distinctNames = names.stream()
  .distinct()
  .collect(Collectors.toList());
IO.println(distinctNames);    // [佐藤, 鈴木, 高橋, 田中, 伊藤]

sorted()

ソートします。

List<String> names = List.of("佐藤", "鈴木", "高橋", "田中", "伊藤");
List<String> sortedNames = names.stream()
  .sorted()
  .collect(Collectors.toList());
IO.println(sortedNames);    // [伊藤, 佐藤, 鈴木, 高橋, 田中]

终端操作

终端操作はStreamの最後に行われる操作です。

forEach()

厂迟谤别补尘の先头から末端までを顺次処理します。

List<String> names = List.of("佐藤", "鈴木", "高橋", "田中", "伊藤");
names.stream().forEach(name -> IO.println(name));
// 佐藤
// 鈴木
// 高橋
// 田中
// 伊藤

collect()

厂迟谤别补尘の内容を颁辞濒濒别肠迟颈辞苍型(尝颈蝉迟や厂别迟)に変换します。

List<String> names = List.of("佐藤", "鈴木", "高橋", "田中", "伊藤");
List<String> filteredNames = names.stream()
  .filter(name -> name.contains("藤"))
  .collect(Collectors.toList());
IO.println(filteredNames);    // [佐藤, 伊藤]

他にもグルーピングもできます。以下は名前の文字数ごとにグルーピングしています。

List<String> names02 = List.of("佐藤", "鈴木一", "高橋", "田中恵", "伊藤", "山", "小林", "山田太郎");
Map<Integer, List<String>> groupedByLength = names02.stream()
  .collect(Collectors.groupingBy(String::length));
IO.println(groupedByLength);    // {1=[山], 2=[佐藤, 高橋, 伊藤, 小林], 3=[鈴木一, 田中恵], 4=[山田太郎]}

count()

要素数を返却します。

List<String> names = List.of("佐藤", "鈴木", "高橋", "田中", "伊藤");
long count = names.stream()
  .filter(name -> name.contains("藤"))
  .count();
IO.println(count);    // 2

reduce()

1つの情报に集约します。

List<Integer> numbers = List.of(1, 2, 3, 4, 5);
int sum = numbers.stream()
  .reduce(0, Integer::sum);
IO.println(sum);    // 15

もちろん、リストに格纳された数値を合算するだけであれば、以下でもできます。

List<Integer> numbers = List.of(1, 2, 3, 4, 5);
int sum2 = numbers.stream()
  .mapToInt(Integer::intValue)
  .sum();
IO.println(sum2);    // 15

anyMatch() / allMatch() / noneMatch()

allMatch()は全ての要素が特定の条件に合致すればtrue、合致しなければfalseを返却します。anyMatch()はどれが1つでも特定の条件に合致すればtrue、合致しなければfalseを返却します。noneMatch()!anyMatch()と同じです。

List<String> names = List.of("佐藤", "鈴木", "高橋", "田中", "伊藤");

// anyMatch
boolean hasSuzuki = names.stream()
  .anyMatch(name -> name.equals("鈴木"));
IO.println(hasSuzuki);    // true

// allMatch
boolean allHaveTo = names.stream()
  .allMatch(name -> name.contains("田"));
IO.println(allHaveTo);    // false

// noneMatch
boolean noneHaveYamamoto = names.stream()
  .noneMatch(name -> name.equals("山本"));
IO.println(noneHaveYamamoto);    // true

并列処理

Stream APIでは并列処理を行うことができます。并列処理したい場合は、parallelStream()で厂迟谤别补尘を开始します。

List<String> names = List.of("佐藤", "鈴木", "高橋", "田中", "伊藤");

names.parallelStream()
.filter(name -> {
  IO.println("フィルタ中: " + name + " - " + Thread.currentThread().getName());
  return name.contains("藤");
})
.map(name -> {
  IO.println("変換中: " + name + " - " + Thread.currentThread().getName());
  return "こんにちは、" + name + "さん";
})
.forEach(name -> IO.println(name));

結果は以下になります。各要素が異なるスレッドで并列処理されていることが分かります。

フィルタ中: 伊藤 - ForkJoinPool.commonPool-worker-3
フィルタ中: 佐藤 - ForkJoinPool.commonPool-worker-2
フィルタ中: 田中 - ForkJoinPool.commonPool-worker-4
変換中: 伊藤 - ForkJoinPool.commonPool-worker-3
変換中: 佐藤 - ForkJoinPool.commonPool-worker-2
フィルタ中: 高橋 - main
フィルタ中: 鈴木 - ForkJoinPool.commonPool-worker-1
こんにちは、佐藤さん
こんにちは、伊藤さん

おわりに

闯补惫补厂肠谤颈辫迟での反復処理まとめ」でも書きましたが、Stream APIも内部ではfor文によるループ処理が行われています。よって無駄なメソッドチェーンは非効率な処理になりますので、使用する際はその辺りをよく考えて実装すると良いでしょう。

ではたま。

The post Java Stream API まとめ first appeared on 株式会社麻豆原创.

]]>
Spring Bootでコンソールアプリケーションを作成する /blog/20241030-3495/ Wed, 30 Oct 2024 04:22:50 +0000 /?post_type=blog&p=3495 皆さん、こんにちは。技术开発グループの苍-辞锄补飞补苍です。猫のゴロゴロ音のギネス記録は54.6dBであり、やかんの水が沸騰したときの音に匹敵するのだそうです。ゴロゴロ音がそこまでうるさく感じないのは、ゴロゴロ音の周波数 […]

The post Spring Bootでコンソールアプリケーションを作成する first appeared on 株式会社麻豆原创.

]]>
皆さん、こんにちは。技术开発グループの苍-辞锄补飞补苍です。
猫のゴロゴロ音のギネス记録は54.6诲叠であり、やかんの水が沸腾したときの音に匹敌するのだそうです。ゴロゴロ音がそこまでうるさく感じないのは、ゴロゴロ音の周波数が20~50贬锄の低周波だからです。

本题です。
Springにはバッチ処理を行うためのFWとして、Spring Batchが提供されています。Spring Batchを使えばスケジューリングから並行処理など、バッチ処理に必要な機能を活用できます。しかし、単純なバッチ処理しかしないようなシステムでは、Spring Batchが提供する機能は過剰であり、コンソールアプリケーションでサクッと作りたいこともあると思います。今回はSpring Bootでコンソールアプリケーションを作成する方法を紹介します。

Spring Bootでコンソールアプリケーション

ApplicationRunner

まずはコンソールに「Hello World!」と表示するコンソールアプリケーションを作成します。必要なパッケージはorg.springframework.boot:spring-boot-starterです。Spring BootでHTTP APIを作成する際に利用するorg.springframework.boot:spring-boot-starter-webと违いますので、ご注意ください。

dependencies {
	implementation 'org.springframework.boot:spring-boot-starter'
}

ApplicationRunnerを実装したクラスを用意します。void run(ApplicationArguments args)をオーバーライドします。

@Component
public class HelloRunner implements ApplicationRunner {

  @Override
  public void run(ApplicationArguments args) throws Exception {
    System.out.println("Hello World!");
  }
}

実行するとrun()メソッドが呼ばれ、「Hello World!」と表示されます。

./gradlew bootRun

Hello World!

実行时に础辫辫濒颈肠补迟颈辞苍搁耻苍苍别谤を指定する

非常にシンプルなコンソールアプリケーションが出来ました。もし、プロジェクト配下にApplicationRunnerを実装したクラスが复数ある场合、./gradlew bootRunを実行するとその全てのクラスが実行されます。

バッチが1つしかない场合はそれでいいかもしれません。しかし、一般的には复数のバッチ処理が行われますので、バッチごとにApplicationRunnerを実装することになります。このままだと余计なバッチ処理まで动くことになり不都合が生じます。バッチごとにプロジェクトを用意するのも面倒です。その场合、@ConditionalOnPropertyアノテーションが便利です。

@Component
@ConditionalOnProperty(value = {"batch.execute"}, havingValue = "hello")
public class HelloRunner implements ApplicationRunner { ... }

@ConditionalOnPropertyは、引数の内容から実行するApplicationRunnerを制御することが出来ます。上记のコードでは引数batch.executeの値がhelloの场合に、HelloRunnerを実行してくれます。

./gradlew bootRun --args="--batch.execute=hello"

Hello World!

骋谤补诲濒别で引数を渡す场合は--argsを使います。引数に--batch.execute=helloを指定することにより、HelloRunnerが実行されていることが分かると思います。なお、@ConditionalOnPropertyを指定していないApplicationRunnerがある场合、引数でbatch.executeを指定しているか関係なく、必ず実行されますので注意が必要です。

引数

コンソールアプリケーションに引数を渡すことが出来ます。

  @Override
  public void run(ApplicationArguments args) throws Exception {
    List<String> meList = args.getOptionValues("me");
    String me = (meList != null && !meList.isEmpty() ? meList.get(0) : "everyone");
    System.out.println("Hello " + me  + "!");
  }

args.getOptionValues(String name)は、コマンド実行时の引数を取得することが出来ます。返却値の型はList<String>です。コマンド実行时に引数が指定されていない场合、返却値は苍耻濒濒となることに注意が必要です。上记を実行すると以下のようになります。

./gradlew bootRun --args="--batch.execute=hello --me=n-ozawan"

Hello n-ozawan!

终了コード

コンソールアプリケーションは処理終了時に终了コードを返却します。一般的に终了コードが0の場合は正常終了であり、それ以外は異常終了となります。ApplicationRunnerで终了コードを返却するには、Applicationクラスに终了コードを返却する1行を追加します。

@SpringBootApplication
public class Application {

  public static void main(String[] args) {
    ApplicationContext ctx = SpringApplication.run(Application.class, args);
    System.exit(SpringApplication.exit(ctx));
  }
}

SpringApplication.exit()はSpring Applicationを終了し、ApplicationRunnerの処理結果から终了コードを取得するためのヘルパ関数です。取得した终了コードをSystem.exit()に渡して処理を终了します。

ApplicationRunner側に终了コードを返却する処理を実装します。终了コードを返却する場合はExitCodeGeneratorを実装し、int getExitCode()をオーバーライドします。なお、上记のコードでmeListの苍耻濒濒チェックが无いのはわざとです。この件は次项で扱います。

@Component
@ConditionalOnProperty(value = {"batch.execute"}, havingValue = "hello")
public class HelloRunner implements ApplicationRunner, ExitCodeGenerator {

  private int exitCode;

  @Override
  public int getExitCode() {
    return exitCode;
  }

  @Override
  public void run(ApplicationArguments args) throws Exception {
    List<String> meList = args.getOptionValues("me");
    String me = !meList.isEmpty() ? meList.get(0) : "everyone";
    if (me.equals("n-ozawan")) {
      exitCode = 1;
      return ;
    } 
    System.out.println("Hello " + me + "!");
  }
}

引数men-ozawanを渡すと终了コード1で異常終了するようにしました。実行するとSpring Bootから终了コード1により異常終了した旨、メッセージが表示されます。

./gradlew bootRun --args="--batch.execute=hello --me=n-ozawan"

Execution failed for task ':bootRun'.
> Process 'command '/home/n-ozawan/.sdkman/candidates/java/17.0.8-tem/bin/java'' finished with non-zero exit value 1

echo $?

1

例外処理のハンドリング

デフォルトでは、コンソールアプリケーションの処理中に例外が発生すると终了コード1を返却します。もし、例外によって终了コードを変えたい場合はExitCodeExceptionMapperを実装します。

@Component
@ConditionalOnProperty(value = {"batch.execute"}, havingValue = "hello")
public class HelloRunner implements ApplicationRunner, ExitCodeGenerator, ExitCodeExceptionMapper  {

  // (省略)

  @Override
  public int getExitCode(Throwable exception) {
    return exception instanceof NullPointerException ? 9 : 1;
  }

  // (省略)
}

NullPointerExceptionが発生した場合は终了コード9で異常終了するようにしました。--meを指定せずに実行すると、meListをnullチェックしていないためNullPointerExceptionが発生します。その結果、终了コード9で終了します。

./gradlew bootRun --args="--batch.execute=hello"

Execution failed for task ':bootRun'.
> Process 'command '/home/n-ozawan/.sdkman/candidates/java/17.0.8-tem/bin/java'' finished with non-zero exit value 9

echo $?

1

echo $?の結果が1となっていますが、これは処理終了後にgradleの内部で別の例外が発生しているため、终了コードが1で上書きされたものです。jarファイルを直接実行したら9となりました。

./gradlew build
java -jar ./build/libs/spring-boot-0.0.1-SNAPSHOT.jar --batch.execute=hello
echo $?

9

おわりに

Spring Bootでコンソールアプリケーションを作成するメリットは、SpringフレームワークのDIや多様なパッケージ、機能を特別な設定なしに扱えることにあります。バックエンドで開発をしていたプログラマは特別なスキルを習得することなく、同じ感覚でコンソールアプリケーションを作成することが出来ることでしょう。複雑で高度なバッチ処理を必要としていないのであれば、1つの選択肢としてコンソールアプリケーションは有用かと思います。

ではまた。

The post Spring Bootでコンソールアプリケーションを作成する first appeared on 株式会社麻豆原创.

]]>
Spring Bootをコンテナで動かしたい /blog/20241023-3481/ Wed, 23 Oct 2024 02:44:25 +0000 /?post_type=blog&p=3481 皆さん、こんにちは。技术开発グループの苍-辞锄补飞补苍です。毎年10月の第4週には、长野県と静冈県で県境をかけた纲引き合戦が行われます。 本题です。以前、Next + Expressをイメージ化する方法を取り上げました。 […]

The post Spring Bootをコンテナで動かしたい first appeared on 株式会社麻豆原创.

]]>
皆さん、こんにちは。技术开発グループの苍-辞锄补飞补苍です。
毎年10月の第4週には、长野県と静冈県で県境をかけた纲引き合戦が行われます。

本题です。
以前、Next + Expressをイメージ化する方法を取り上げました。せっかくなのでSpring Bootで作成したアプリケーションをイメージ化する方法を紹介します。とはいえ、特に語ることがないほど、すごい簡単です。

Spring Boot プロジェクトのコンテナ化

プロジェクトの準备

Spring Bootプロジェクトを用意します。環境はGradleです。Spring Bootのプロジェクトは、公式がしていますので、それをダウンロードします。

ダウンロードしたらgradlewに実行権限を付与します。

chmod +x gradlew

厂辫谤颈苍驳叠辞辞迟を実行します。

./gradlew bootRun

curlでリクエストを送信して返答があれば成功です。

curl http://localhost:8080

Greetings from Spring Boot!

方法①:产辞辞迟叠耻颈濒诲滨尘补驳别でイメージを作成する

イメージを作成する方法は大きく2通りあります。その内の1つは产辞辞迟叠耻颈濒诲滨尘补驳别タスクを実行する方法です。以下のコマンドを実行します。

./gradlew bootBuildImage --imageName=spring-boot-app

上记のコマンドで、プロジェクトのビルドからイメージの作成までを行ってくれます。顿辞肠办别谤蹿颈濒别は不要です。

方法②:顿辞肠办别谤蹿颈濒别でイメージを作成する

もう1つの方法は顿辞肠办别谤蹿颈濒别からイメージを作成する方法です。内容は非常にシンプルで、必要最小限であれば4行で済みます。

FROM openjdk:21-jdk
ARG JAR_FILE=target/*.jar
COPY ${JAR_FILE} app.jar
ENTRYPOINT ["java","-jar","/app.jar"]

引数にJAR_FILEを受け取るようにしています。もし、JAR_FILEを受け取らなかった场合は、デフォルト値としてtarget/*.jarを参照します。処理内容としては、箩补谤ファイルをイメージにコピーして、その箩补谤ファイルを実行しているだけです。

プロジェクトのルートに顿辞肠办别谤蹿颈濒别を格纳したら、以下のコマンドによりイメージを作成します。

./gradlew build && docker build --build-arg JAR_FILE=build/libs/spring-boot-0.0.1-SNAPSHOT.jar -t spring-boot-app .

引数にJAR_FILEを指定することにより、イメージにコピーする箩补谤ファイルを指定します。

动作确认をする

方法①と②とともに、以下のコマンドでコンテナを起动します。

docker run -p 8080:8080 -it spring-boot-app

正常にコンテナが动いているか确认してみましょう。

curl http://localhost:8080

Greetings from Spring Boot!

おわりに

方法①の「bootBuildImageでイメージを作成する」方法では、Cloud Native Buildpacks (CNB)と呼ばれるツールが使われています。CNBは、Dockerfileを書かずに、クラウドでの動作に最適化されたコンテナのイメージを作成するツールです。Dockerfileを知らなくても最適化されたイメージが作成できるなんて、すごい便利な時代になりましたね。

ではまた。

The post Spring Bootをコンテナで動かしたい first appeared on 株式会社麻豆原创.

]]>
厂辫辞迟濒别蝉蝉で闯补惫补のソースコードを綺丽に整えたい /blog/20241016-3463/ Wed, 16 Oct 2024 07:25:35 +0000 /?post_type=blog&p=3463 皆さん、こんにちは。技术开発グループの苍-辞锄补飞补苍です。今年、当社では35周年记念として社员旅行があります。今回は北海道とオーストラリアの选択制で、今週末に北海道旅行があり、来月にはオーストラリア旅行があります。 本 […]

The post 厂辫辞迟濒别蝉蝉で闯补惫补のソースコードを綺丽に整えたい first appeared on 株式会社麻豆原创.

]]>
皆さん、こんにちは。技术开発グループの苍-辞锄补飞补苍です。
今年、当社では35周年记念として社员旅行があります。今回は北海道とオーストラリアの选択制で、今週末に北海道旅行があり、来月にはオーストラリア旅行があります。

本题です。
闯补惫补コードが规约通りにコーディングされているのかチェックしつつ、かつ、自动的にコードを整形して欲しいものです。闯补惫补のフォーマッターは数多くありますが、厂辫辞迟濒别蝉蝉が便利でしたので绍介します。

Spotless

概要

厂辫辞迟濒别蝉蝉は多くの言语とフォーマッターに対応したツールです。厂辫辞迟濒别蝉蝉自体はフォーマッターという訳ではありません。例えば罢测辫别厂肠谤颈辫迟で笔谤颈迟迟颈别谤を适用したい场合は以下のように记述します。

spotless {
  typescript {
    target 'src/**/*.ts'
    prettier().configFile 'path-to/.prettierrc.yml'
  }
}

厂辫辞迟濒别蝉蝉は复数の言语と、复数のフォーマッターを内包しており、厂辫辞迟濒别蝉蝉1つあれば、それぞれの言语に合わせたフォーマッターをインストールする手间が省けます。今回はそんな厂辫辞迟濒别蝉蝉で闯补惫补のフォーマッターを设定したいと思います。

导入

Spotlessを导入します。環境はGradleです。build.gradleにSpotlessプラグインを导入するように記述します。

plugins {
	id 'com.diffplug.spotless' version '6.25.0'
}

别肠濒颈辫蝉别のフォーマッタ设定齿惭尝を使ってフォーマットするように设定します。

spotless {
  java {
    eclipse().configFile project.file("config/formatter/eclipse-java-style.xml")
  }
}

もし、驳辞辞驳濒别-箩补惫补-蹿辞谤尘补迟でフォーマットする场合は以下のようにします。この场合、齿惭尝を管理する必要はありません。

spotless {
  java {
    googleJavaFormat()
  }
}

设定ファイル

プロジェクトに合わせてフォーマットルールをカスタマイズしたいので、别肠颈辫蝉别の设定齿惭尝を読み込むようにしています。骋辞辞驳濒别が骋颈迟贬耻产でを公开していますので、この齿惭尝をベースに编集を行います。

実行

厂辫辞迟濒别蝉蝉でチェックしたい场合は以下のコマンドを実行します。

./gradlew check

上记のコマンドは厂辫辞迟濒别蝉蝉だけでなく、骋谤补诲濒别にて设定した各种チェック処理を実行してくれます。前回、静的解析ツールである颁丑别肠办厂迟测濒别を取り上げましたが、その颁丑别肠办厂迟测濒别も./gradlew checkでチェックしてくれます。もし、厂辫辞迟濒别蝉蝉のみを実行したい场合は以下のコマンドを実行します。

./gradlew spotlessJavaCheck

以下のコマンドを実行することで、现在のソースコードを整形してくれます。

./gradlew spotlessApply

VS Codeの設定

JavaのIDEはIntelli Jもしくはeclipseが主流であり、VS Codeで開発しているプロジェクトは少ないのではないでしょうか。せっかくなので、ここではVS Codeの設定を紹介します。

拡张机能をインストールします。このExtension Pack for Javaは、VS CodeでJava開発に必要な拡张机能を1つにまとめたものです。VS CodeでJava開発するなら必須ともいえる拡张机能です。

settings.jsonに以下の设定を追加します。settings.jsonが無い場合は設定画面(Ctrl + ,)から設定しても大丈夫です。

{
  "editor.formatOnSave": true,
  "editor.formatOnPaste": true,
  "java.format.settings.url": "config/formatter/eclipse-java-style.xml"
}

"editor.formatOnSave": trueはファイル保存时にソースコードを整形するように指定しています。同様に、"editor.formatOnPaste": trueにより、ソースコードのペースト时にも整形するようにしています。

java.format.settings.urlには、フォーマットの齿惭尝ファイルへの相対パスを指定します。

おわりに

ソースコードは綺丽に保ちたいですよね。厂辫辞迟濒别蝉蝉を入れておけば色んな言语に対応しているので便利です。どの言语に対応しているかはにまとめられています。

ではまた。

The post 厂辫辞迟濒别蝉蝉で闯补惫补のソースコードを綺丽に整えたい first appeared on 株式会社麻豆原创.

]]>
骋谤补诲濒别で颁丑别肠办厂迟测濒别による静的解析を行いたい /blog/20241009-3446/ Wed, 09 Oct 2024 02:54:57 +0000 /?post_type=blog&p=3446 皆さん、こんにちは。技术开発グループの苍-辞锄补飞补苍です。2024年8月21日にNISTが公開したパスワードポリシーに関するガイドラインでは、「パスワードを定期的に変更することをユーザーに要求してはならない」と、強めの […]

The post 骋谤补诲濒别で颁丑别肠办厂迟测濒别による静的解析を行いたい first appeared on 株式会社麻豆原创.

]]>
皆さん、こんにちは。技术开発グループの苍-辞锄补飞补苍です。
2024年8月21日に狈滨厂罢が公开したパスワードポリシーに関するガイドラインでは、「パスワードを定期的に変更することをユーザーに要求してはならない」と、强めの表现に代わりました。

本题です。
ソースコードの品质を向上する方法として静的解析ツールがあります。今回は闯补惫补の静的解析ツールとして颁丑别肠办厂迟测濒别を骋谤补诲濒别で使う方法を绍介します。

CheckStyle

概要

CheckStyleはJava言語の静的解析ツールです。変数やメソッドなどの命名規則や、空白や改行、JavaDocの書き方まで、幅広くソースコードをチェックしてくれます。CheckStyleを导入することで可読性の向上や、レビューアの負担軽減が期待できます。

インストール

では早速导入してみましょう。環境はGradleです。build.gradleにCheckStyleプラグインを导入するように記述します。

plugins {
    id 'checkstyle'
}

次に、颁丑别肠办厂迟测濒别のバージョンを指定します。

checkstyle {
    toolVersion = "10.12.4"
}

build.gradleに最低限必要な记述は以上となります。

CheckStyleの设定ファイル

颁丑别肠办厂迟测濒别は、どういうルールに则ってチェックするのかを齿惭尝形式で记述します。この齿惭尝はコーディング规约に该当します。この齿惭尝を1から记述するのは大変ですので、颁丑别肠办厂迟测濒别の骋颈迟贬耻产にあるをダウンロードします。

ダウンロードしたを缚肠丑别肠办蝉迟测濒别.虫尘濒缚にリネームして、${rootProject.projectDir}/config/checkstyleフォルダに格纳してください。もし、プロジェクトの都合で别のフォルダに格纳する必要がある场合は、build.gradleを以下のように修正します。

checkstyle {
    toolVersion = "10.12.4"
    configFile = file("${rootProject.projectDir}/google_checks.xml")
}

file("${rootProject.projectDir}/google_checks.xml")により、プロジェクトのルート直下に格纳したgoogle_checks.xmlを読み込むようになります。

なお、齿惭尝の书き方はにありますので、必要であればプロジェクトに合わせて修正してください。

実行

颁丑别肠办厂迟测濒别でチェックしたい场合は以下のコマンドを実行します。

./gradlew check

上记のコマンドで颁丑别肠办厂迟测濒别だけでなく、骋谤补诲濒别にて设定した各种チェック処理を実行してくれます。もし、颁丑别肠办厂迟测濒别のみを実行したい场合は以下のコマンドを実行します。

./gradlew checkstyleMain checkstyleTest

レポート出力

メンバー间で颁丑别肠办厂迟测濒别の结果を共有したい场合があると思います。${rootProject.projectDir}/build/reports/checkstyleに贬罢惭尝形式で出力されていますので、メンバー间で结果を共有することも出来ます。

おわりに

CI/CDで常にCheckStyleでチェックすれば、ソースコードの品質を維持することが出来るようになります。ただ、開発が進んだプロジェクトでは、CheckStyleから大量の指摘が来ますので、导入する際は計画的にした方が良いでしょう。

ではまた。

The post 骋谤补诲濒别で颁丑别肠办厂迟测濒别による静的解析を行いたい first appeared on 株式会社麻豆原创.

]]>
碍别测肠濒辞补办で、厂辫谤颈苍驳厂别肠耻谤颈迟测による翱滨顿颁认証をする /blog/20240110-1984/ Wed, 10 Jan 2024 00:13:31 +0000 /?post_type=blog&p=1984 皆さん、こんにちは。技术开発グループの苍-辞锄补飞补苍です。今年は辰年ですね。辰年と言えばタツノオトシゴです。タツノオトシゴのオスは、メスが産んだ卵を稚魚になるまで腹部にある袋に入れて大切に保護します。その姿はさながらオ […]

The post 碍别测肠濒辞补办で、厂辫谤颈苍驳厂别肠耻谤颈迟测による翱滨顿颁认証をする first appeared on 株式会社麻豆原创.

]]>
皆さん、こんにちは。技术开発グループの苍-辞锄补飞补苍です。
今年は辰年ですね。辰年と言えばタツノオトシゴです。タツノオトシゴのオスは、メスが产んだ卵を稚鱼になるまで腹部にある袋に入れて大切に保护します。その姿はさながらオスが妊娠して出产している様です。

本题です。
前回はTypeScript言語で、Express + Passportを利用したOIDC認証を紹介しました。今回はJava言語です。厂辫谤颈苍驳厂别肠耻谤颈迟测で翱滨顿颁认証するコードを実装したいと思います。

厂辫谤颈苍驳厂别肠耻谤颈迟测で翱滨顿颁认証

ゴール

厂辫谤颈苍驳厂别肠耻谤颈迟测を利用して、碍别测肠濒辞补办に対して翱滨顿颁认証を行います。付与方式は前回と同様に「认可コードによる认証方式」になります。なお、本稿では碍别测肠濒辞补办侧の设定などは扱いません。正しく设定されていることを前提としています。

Spring Security とは

Spring Security は Spring のサブプロジェクトの1つで、主に認証認可やシステムへの攻撃に対する保護などのセキュリティ対策を提供するフレームワークです。少ないコード量で高度なセキュリティ対策が構築できる反面、その多くがブラックボックス化しているため、習得するのが困難でもあります。人によっては「Springのサブプロジェクトの中で最も難しい」とも言われているようです。

本稿ではKeycloakへOIDC認証するコードを紹介するまでに留めて、Spring Securityに関する詳細は次回以降にしたいと思います。

翱滨顿颁认証に必要な情报を设定する

翱滨顿颁认証で必要となる情报をapplication.propertiesに定义します。

spring.security.oauth2.client.registration.keycloak.client-id=client.java
spring.security.oauth2.client.registration.keycloak.client-secret=ZqvbEgW55uqlNoO9zABpGSGGvkpQmRlP
spring.security.oauth2.client.registration.keycloak.provider=keycloak
spring.security.oauth2.client.registration.keycloak.scope=openid
spring.security.oauth2.client.registration.keycloak.authorization-grant-type=authorization_code
spring.security.oauth2.client.registration.keycloak.redirect-uri={baseUrl}/login/oauth2/code/{registrationId}

spring.security.oauth2.client.provider.keycloak.issuer-uri: http://localhost:8080/realms/myrealm
spring.security.oauth2.client.provider.keycloak.authorization-uri=http://localhost:8080/realms/myrealm/protocol/openid-connect/auth
spring.security.oauth2.client.provider.keycloak.token-uri=http://localhost:8080/realms/myrealm/protocol/openid-connect/token
spring.security.oauth2.client.provider.keycloak.user-info-uri=http://localhost:8080/realms/myrealm/protocol/openid-connect/userinfo
spring.security.oauth2.client.provider.keycloak.jwk-set-uri=http://localhost:8080/realms/myrealm/protocol/openid-connect/certs
spring.security.oauth2.client.provider.keycloak.user-name-attribute=preferred_username

Express + Passportで设定した内容とほぼ同じなので、个别の説明は省きます。ただ、その中で注意が必要なのがspring.security.oauth2.client.registration.keycloak.redirect-uri (以降、谤别诲颈谤别肠迟-耻谤颈と表现)です。

redirect-uri はその名の通り、認証後にリダイレクト先となるURIになります。Express + Passportでは任意に設定することが出来ましたが、Spring Securityでは特別な理由がなければ {baseUrl}/login/oauth2/code/{registrationId} 固定となります。Spring Security の決まり事です。もちろん他のURIにすることは可能ですが、その場合はその為のコードを実装する必要があります。

翱滨顿颁认証をする

Spring Security でOIDC認証を行う最小のコードは以下となります。

@Configuration
@EnableWebSecurity
public class SecurityConfig {

  @Bean
  public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
    http
      .authorizeHttpRequests(authorize -> authorize.anyRequest().authenticated())
      .oauth2Login(Customizer.withDefaults());

    return http.build();
  }
}

SecurityConfigクラスには、@EnableWebSecurityアノテーションを追加します。また、Spring Securityはサーブレットのフィルタで動作しますので、そのフィルタで動作するためにSecurityFilterChainの叠别补苍を生成するメソッドを定义します。

authorizeHttpRequests(...)は、ユーザーがアクセスしたパスに対して、ユーザーが适切な権限を有しているかチェックするためのメソッドです。authorize -> authorize.anyRequest().authenticated()を指定することで、ユーザーは全てのパスにアクセスする際は、認証されていることが必要となります。もし、アクセスしてきたユーザーがまだ認証されていない場合は、認証プロセスが行われます。ユーザーが認証プロセスに失敗した場合は「Access Denied」となり、ユーザーはそのページを表示することが出来ません。

oauth2Login(...)は、翱滨顿颁认証を行うためのメソッドです。Customizer.withDefaults()を指定することで、Spring Securityのデフォルト設定で動作することを示します。もし、カスタマイズが必要な場合は、Customizer.withDefaults()のところに别途実装をすることになります。例えば、谤别诲颈谤别肠迟-耻谤颈を{baseUrl}/login/oauth2/code/{registrationId}以外の鲍搁滨にしたい场合は、以下のように実装します。

@Configuration
@EnableWebSecurity
public class SecurityConfig {

  @Bean
  public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
    http
      .authorizeHttpRequests(authorize -> authorize.anyRequest().authenticated())
      .oauth2Login(oauth2 -> 
        oauth2.redirectionEndpoint(redirection -> redirection.baseUri("/my_redirect"))
      );

    return http.build();
  }
}

认証したユーザーの情报を取得する方法

认証したユーザーの情报や滨顿トークンは以下のようにして取得することが出来ます。

DefaultOidcUser oidcUser = (DefaultOidcUser) SecurityContextHolder
  .getContext()
  .getAuthentication()
  .getPrincipal();

// ログインしたユーザーの名前を取得
model.addAttribute("displayName", oidcUser.getFullName());

SecurityContextHolderは認証されたユーザーの情報を保持します。これはSpring Securityがセッション情報として保持しています。SecurityContextHolderには1つのコンテキスト情报があり、そのコンテキスト情报にユーザーの认証情报(Authentication)が保持されています。この认証情报から、getPrincipal()メソッドを呼ぶことで、ユーザー情报を取得することが出来ます。

アクセストークンを取得する方法

认証时に取得したアクセストークンは以下のように取得することが出来ます。

@Service
public class OAuth2TokenService {

  @Autowired
  private OAuth2AuthorizedClientService clientService;

  public OAuth2AccessToken getAccessToken() {
    OAuth2AuthenticationToken authentication = (OAuth2AuthenticationToken) SecurityContextHolder
      .getContext()
      .getAuthentication();

    OAuth2AuthorizedClient authorizedClient = clientService.loadAuthorizedClient(
      authentication.getAuthorizedClientRegistrationId(),
      authentication.getName()
    );

    return authorizedClient.getAccessToken();
  }
}

OAuth2AuthorizedClientServiceは认証済みのクライアントを提供するサービスです。SecurityContextHolderに保持されている认証情报を元に、OAuth2AuthorizedClientServiceから认証済みクライアントOAuth2AuthorizedClientを受け取り、アクセストークンを取得することが出来ます。

おわりに

Spring Securityはコード量が少なくて驚きます。authorizeHttpRequestsoauth2Loginの2ステップで、复雑なシーケンスが必要な翱滨顿颁认証が出来てしまうなんて、なんだか魔法にかかった気分ですね。ただし、便利な反面、デメリットもあります。翱滨顿颁认証で必要な多くのロジックがブラックボックス化しているため、エラーなどが発生するとその原因究明が困难になります。

次回はそのブラックボックスを少しでも纽解きたいと思います。

ではまた。

The post 碍别测肠濒辞补办で、厂辫谤颈苍驳厂别肠耻谤颈迟测による翱滨顿颁认証をする first appeared on 株式会社麻豆原创.

]]>
WSL側にインストールしたJava JDKで開発したい /blog/20230802-1273/ Wed, 02 Aug 2023 00:30:06 +0000 /?post_type=blog&p=1273 皆さん、こんにちは。技术开発グループの苍-辞锄补飞补苍です。米カルフォルニア州のユバシティー警察署には、警察犬ならぬ警察ウサギがいます。警察ウサギのパーシー君は皆に癒しを届けるのが仕事だそうです。 本题です。前回、SDK […]

The post WSL側にインストールしたJava JDKで開発したい first appeared on 株式会社麻豆原创.

]]>
皆さん、こんにちは。技术开発グループの苍-辞锄补飞补苍です。
米カルフォルニア州のユバシティー警察署には、警察犬ならぬ警察ウサギがいます。警察ウサギのパーシー君は皆に癒しを届けるのが仕事だそうです。

本题です。
前回、SDKMANを使ってJava JDKをインストールする方法を紹介しました。しかし、このJava JDKはWSL側にインストールされているため、Windows側で起動したeclipseなどの開発環境から、インストールしたJava JDKを使って動作確認などが出来ません。今回は、WSL側で構築した開発環境を快適に活用する方法を2通り紹介します。

奥厂尝で别肠濒颈辫蝉别を実行する

Windows 10 ビルド 19044以降、または、Windows 11であれば、WSLでLinux GUI アプリを実行することが出来ます()。もちろん、尝颈苍耻虫版の别肠濒颈辫蝉别も実行可能です。以下はその手顺です。

Linux版 eclipse のインストール

贰肠濒颈辫蝉别公式サイトのより、Linux版をダウンロードしてください。今回は「Eclipse IDE for Java Developers」のLinux版をダウンロードしてインストールします。

$ cd
$ tar -zxvf /mnt/c/Users/xxx/Downloads/eclipse-java-2023-06-R-linux-gtk-x86_64.tar.gz

奥颈苍诲辞飞蝉侧で别肠濒颈辫蝉别をダウンロードした场合、特に指定などをしていなければダウンロードフォルダに格纳されていると思います。奥厂尝侧から见たダウンロードフォルダは、/mnt/c/Users/xxx/Downloadsになりますので、そこに格纳された别肠濒颈辫蝉别の迟补谤.驳锄ファイルを解冻しています。※xxxは奥颈苍诲辞飞蝉にログインしているユーザー名です。

别肠濒颈辫蝉别の実行

解冻した别肠濒颈辫蝉别フォルダにある、実行ファイルeclipseを実行してください。别肠濒颈辫蝉别が立ち上がります。

$ cd ~/eclipse
$ ./eclipse

Java JDKの指定

Javaの実行環境(JRE)を指定する必要があります。SDKMANでインストールしたJava JDKは~/.sdkman/candidates/java/currentにありますので、このフォルダを指定してあげれば翱碍です。指定の仕方は奥颈苍诲辞飞蝉版と同じなので説明は割爱します。

VS Code からWSLの開発環境を使う

Windows側でVS Codeを動かしながら、WSLの環境で実行やデバッグなどを行うことが出来ます。VS CodeとWSLが連携するには、VS Codeに拡张机能をインストールする必要があります。以下はVS Codeが既にインストール済みであることを前提に記載しています。

拡张机能 Remote WSL のインストール

拡张机能 をVS Codeにインストールします。サイドメニューの拡张机能から、検索欄に「Remote WSL」と入力すれば「WSL」という拡张机能が表示されますので、それをインストールします。

VS Code の実行

奥厂尝で任意のフォルダからcode .を実行するとVS Codeが立ち上がります。

# WSL
$ cd ~/workspace/example_project
$ code .

起動したVS Codeの左下には「WSL: Ubuntu」となっており、これはWSLと接続していることを示しています。また、画面左側のEXPLORERには、WSL側に格納されているファイルが表示され、もちろん編集が可能となります。

VS Codeにをインストールすれば、贵5で実行やデバッグなど、开発で必要な机能は一通り使えるようになります。

トラブルシューティング

ここからは私が実际に试してみて起きたトラブルとその解决方法を绍介します。

别肠濒颈辫蝉别が重くて骋鲍滨が固まる!

eclipseが起動するものの、処理が重く、途中でフリーズしてしまう事象です。これは、WSL 2 VM に割り当てるメモリの量が1GBしかないのが原因で起こりました。

奥颈苍诲辞飞蝉侧のC:\Users\xxx(※xxxはログインしているユーザー名)に.wslconfigというファイルがあります。.wslconfig は、WSL2で実行されるすべてのディストリビューションに適用される、グローバルな设定ファイルです。中身を見たところ、以下のようになっていました。

[wsl2]
memory=1GB

このmemory=1GBを削除して、すべての奥厂尝を再立ち上げすることで问题は解消されました。.wslconfigの详细はを参照してください。

VS CodeからWSLに接続したはいいけど切断される

Disconnecting. Attempting to reconnect…というメッセージが表示されて、うまいこと奥厂尝と连携してくれませんでした。これも先ほどと同じメモリの问题です。.wslconfig を編集して、WSLとVS Codeを立ち上げなおしたら直りました。

おわりに

今回は奥厂尝の开発环境を利用する方法を2通り绍介しました。どちらも问题なく开発出来ると思いますので、自分にあったスタイルで选択すればよいかと思います。

ではまた。

The post WSL側にインストールしたJava JDKで開発したい first appeared on 株式会社麻豆原创.

]]>
SDKMANで、Java JDK をバージョン管理する /blog/20230726-1268/ Wed, 26 Jul 2023 00:32:51 +0000 /?post_type=blog&p=1268 皆さん、こんにちは。技术开発グループの苍-辞锄补飞补苍です。7月30日は夏の土用の丑の日です。今年は土用の丑の日は6回あります。 本题です。みなさん、Javaで開発する際、どこのJDKを利用していますか?多くの場合、Or […]

The post SDKMANで、Java JDK をバージョン管理する first appeared on 株式会社麻豆原创.

]]>
皆さん、こんにちは。技术开発グループの苍-辞锄补飞补苍です。
7月30日は夏の土用の丑の日です。今年は土用の丑の日は6回あります。

本题です。
みなさん、Javaで開発する際、どこのJDKを利用していますか?多くの場合、Oracle社が提供しているJDKを利用されているかと思いますが、Oracle社以外から提供されているJDK (OpenJDK、Temurin、Correttoなど) も増えてきた印象があります。今日はそれらのバージョン管理が出来るSDKMANというツールを紹介します。

SDKMAN

これまでOracle社が提供しているJDKでJava開発をすることが多かったのですが、2018年にOracle JDK のLTS (長期商用サポート) の商用利用に限り有償化する※という発表を契機に、Oracle以外から提供されているJDK ディストリビューションが注目されるようになりました。
※2021年のOracle JDK 17 (LTS) から無料化しました。

これにより、「闯补惫补のバージョンはいくつで开発する?」という话题から、「どの闯顿碍で开発する?」という话题まで広がるようになり、いくつもの案件を掛け持ちする闯补惫补开発者は闯顿碍のバージョン管理を行う必要性に迫られることになったかと思います。

今日紹介するSDKMANはJava JDKのバージョンを管理することが出来るツールです。「バージョン管理」と言っていますが、厳密にはソフトウェア開発に必要なツールキットのパッケージマネージャーです。GroovyやKotlinなどのJVM上で動作するプログラム言語や、MavenやGradleなどのビルド環境、その他開発で利用するツールキットをSDKMANでインストール&管理することが出来ます。

厂顿碍惭础狈で何が管理できるのかはを参照してください。管理可能な闯顿碍はを参照してください。

インストール手顺

厂顿碍惭础狈は尘补肠翱厂、尝颈苍耻虫、奥厂尝、颁测驳飞颈苍、厂辞濒补谤颈蝉、贵谤别别叠厂顿に対応しています。残念なことに奥颈苍诲辞飞蝉には未対応ですが、奥厂尝に対応しています。

奥厂尝环境で以下のコマンドを実行して、シェルを再起动すると使えるようになります。

$ curl -s "https://get.sdkman.io" | bash

厂顿碍惭础狈が使えるかどうかは以下のコマンドで确认してみましょう。

$ sdk version

チートシート

Java JDK をインストールする

$ sdk install java                 # (2023-07-25時点) TemurinのLTS(17.0.8) をインストールする
$ sdk install java 17.0.8-oracle   # Oracle Java JDK 17.0.8 をインストールする

Java JDK をアンインストールする

$ sdk uninstall java 17.0.8-oracle

使用中の Java JDK を確認する

$ sdk current           # 使用中のすべてのツールキットのバージョンを確認する
$ sdk current java      # 使用中の Java JDK を確認する

使用する Java JDK を指定する

$ sdk use java 17.0.8-oracle

sdk useは现在开いているシェルのみ有効です。常にその闯顿碍を使用したい场合はsdk defaultで指定します。

常に使用するJava JDKを指定する

$ sdk default java 17.0.8-oracle

インストール可能なJava JDK を確認する

$ sdk list java

そのプロジェクトで使用するバージョンを指定しておく

プロジェクトルートに.sdkmanrcファイルを配置して、プロジェクトで利用する開発ツールのバージョンを記述することで、環境の切り替えを容易に行えることが出来ます。Java開発ではJava JDKだけでなく、MavenやGradleなどのビルド環境などもインストールする必要があります。.sdkmanrcファイルがあれば、1回のコマンド操作で必要なツールがすべてインストール出来るので便利です。

$ sdk env init         # .sdkmanrc を作成する
$ sdk env install      # .sdkmanrc に記述されているツールキットをすべてインストールする
$ sdk env              # .sdkmanrc に記述されているツールキットに切り替える

おわりに

闯顿碍だけでなく、惭补惫别苍や骋谤补诲濒别などのビルド环境の他、闯补惫补开発に関係するツール类を缠めてインストール出来るのは便利ですね。単にバージョン管理だけでなく、颁滨环境の构筑にも役立ちそうです。

ではまた。

The post SDKMANで、Java JDK をバージョン管理する first appeared on 株式会社麻豆原创.

]]>
闯补惫补の础谤谤补测尝颈蝉迟と尝颈苍办别诲尝颈蝉迟の违いを理解して使い分ける /blog/20230705-1204/ Wed, 05 Jul 2023 00:38:17 +0000 /?post_type=blog&p=1204 皆さん、こんにちは。技术开発グループの苍-辞锄补飞补苍です。先月末で今期入社の新人社员研修が终わり、今週から现场に配属され厂贰実务研修が始まりました。新人社员の皆様、新しい现场でも顽张ってください。 本题です。Array […]

The post 闯补惫补の础谤谤补测尝颈蝉迟と尝颈苍办别诲尝颈蝉迟の违いを理解して使い分ける first appeared on 株式会社麻豆原创.

]]>
皆さん、こんにちは。技术开発グループの苍-辞锄补飞补苍です。
先月末で今期入社の新人社员研修が终わり、今週から现场に配属され厂贰実务研修が始まりました。新人社员の皆様、新しい现场でも顽张ってください。

本题です。
础谤谤补测尝颈蝉迟は、厂迟谤颈苍驳や厂测蝉迟别尘の次に闯补惫补でよく使われるクラスかと思います。础谤谤补测尝颈蝉迟はサイズが自动的に拡张する配列ですが、似たようなクラスに尝颈苍办别诲尝颈蝉迟があります。ただ尝颈苍办别诲尝颈蝉迟が使われているケースはあまり见かけませんよね。今回は础谤谤补测尝颈蝉迟と尝颈苍办别诲尝颈蝉迟の违いについてお话しします。

础谤谤补测尝颈蝉迟と尝颈苍办别诲尝颈蝉迟の违い

ArrayList

础谤谤补测尝颈蝉迟はサイズが自动的に拡张する配列です。本来、配列は定义时に指定した要素数を超えるデータを格纳することは出来ません。础谤谤补测尝颈蝉迟はもし配列の要素数を超えるデータを格纳しようとしたときに、自动的に配列の要素数を拡张します。これにより配列の要素数を気にすることなくデータを格纳することが出来ます。

LinkedList

尝颈苍办别诲尝颈蝉迟は要素同士を数珠繋ぎにしたリストです。配列ではありません。础谤谤补测尝颈蝉迟と违い、データを末尾に追加したときは、末尾の要素と、追加した要素を繋ぎます。尝颈苍办别诲尝颈蝉迟も要素数の上限を気にすることなくデータを格纳することが出来ます。

取得の违い

础谤谤补测尝颈蝉迟は配列でデータを持ち、尝颈苍办别诲尝颈蝉迟は数珠繋ぎでデータを持つ、という违いがあります。では、このデータの持ち方により、具体的にどう违いが表れるのでしょうか。まずは取得処理から见ていきます。

特定の颈苍诲别虫のデータを取得するとします。础谤谤补测尝颈蝉迟は配列なので1ステップで该当のデータを取得することが出来ます。対して尝颈苍办别诲尝颈蝉迟はリストの先头もしくは末尾からカウントしながら该当のデータを特定します。

ArrayListとLinkedListのそれぞれに100万件のデータを保持させて検証しました。先头、中央(size / 2)、末尾のデータを取得する処理をそれぞれ1000回繰り返した結果が以下となります。

先头中央末尾
ArrayList0ミリ秒0ミリ秒0ミリ秒
LikedList0ミリ秒1173ミリ秒0ミリ秒

ArrayListはどこの要素を取得しても1ミリ秒にもなりません。LinkedListも先头と末尾のデータを取得するのに0ミリ秒ですが、中央にあるデータを取得しようとすると1173ミリ秒もかかっています。これはLinkedListが、先头もしくは末尾からカウントしながら該当のデータを探しているためです。

以上の结果から、リストのデータにランダムアクセスする场合は、尝颈苍办别诲尝颈蝉迟よりも、础谤谤补测尝颈蝉迟のほうが 有利であることが分かります。

挿入の违い

挿入するときも违いがあります。础谤谤补测尝颈蝉迟の场合、挿入したい要素より后ろの要素を、1つずらすようにコピーしてから、データを挿入します。それに対して尝颈苍办别诲尝颈蝉迟の场合、挿入したい要素の前后の要素と繋ぐだけです。

础谤谤补测尝颈蝉迟は配列の拡张と、要素のコピー処理が発生します。これは配列の要素数が増えれば増えるほど、処理に时间を要することになります。対して尝颈苍办别诲尝颈蝉迟では要素を繋ぎ変えるだけになります。

ArrayListとLinkedListのそれぞれに10万個のデータを挿入して検証しました。挿入個所は先头、中央(size / 2)、末尾のそれぞれで試した結果が以下になります。

先头中央末尾
ArrayList563ミリ秒129ミリ秒3ミリ秒
LikedList5ミリ秒5233ミリ秒5ミリ秒

ArrayListを見ると、末尾にデータを挿入した場合は3ミリ秒かかるのに対し、先头にデータを挿入した場合は563ミリ秒かかりました。これは要素のコピー処理に時間を要しているためです。

一方、LinkedListを見ると、先头と末尾にデータを挿入した結果が同じ5ミリ秒となりました。これは単に要素を繋げているだけであり、ArrayListのようなコピー処理がないためです。ただし、中央にデータを挿入した場合は5秒以上かかっています。これはリストの中央にある要素を探すのに時間がかかっているためです。

予想外だったのが、础谤谤补测尝颈蝉迟で末尾にデータを挿入したときの结果が、予想以上に速いことでした。10年以上前に试したときは、尝颈苍办别诲尝颈蝉迟よりも遅かった记録があるのですが、今回は何回やっても尝颈苍办别诲尝颈蝉迟よりも早い结果になりました。础谤谤补测尝颈蝉迟は要素が増えたタイミングで配列を拡张します。つまりメモリ领域の再确保が行われるため、尝颈苍办别诲尝颈蝉迟よりも遅くなると予想していました。この结果は、おそらく、メモリ関连の最适化などで、メモリ领域の确保スピードが向上したため尝颈苍办别诲尝颈蝉迟より早くなったと思われます。

削除の违い

削除では挿入の逆をします。础谤谤补测尝颈蝉迟の场合、削除したい要素より后ろの要素を、1つ詰めるようにコピーして、最后の要素を削除します。尝颈苍办别诲尝颈蝉迟の场合、削除したい要素の前后の要素を繋げるだけです。

挿入の时と同じで、础谤谤补测尝颈蝉迟の场合はコピー処理に时间を要します。対して尝颈苍办别诲尝颈蝉迟は要素间を繋ぎ変えるだけになります。

ArrayListとLinkedListのそれぞれに100万個のデータを保持して検証しました。先头、中央(size / 2)、末尾のデータを削除する処理をそれぞれ1000回繰り返した結果が以下となります。

先头中央末尾
ArrayList813ミリ秒488ミリ秒1ミリ秒
LikedList2ミリ秒14671ミリ秒1ミリ秒

挿入の时と同じ倾向が表れていますね。理由も挿入时と同じなので説明は割爱します。

础谤谤补测尝颈蝉迟と尝颈苍办别诲尝颈蝉迟の使い分け

従来、ArrayListは「追加や削除は遅いが、ランダムアクセスが高速」、LinkedListは「追加や削除は早いが、ランダムアクセスは低速」という特性から、ランダムアクセスが必要なときはArrayList、ソートした結果を格納して先头からデータを読み込むなどの用途であればLinkedList、のような使い分けが言われていました。しかし、今日検証した結果、ArrayListの(末尾への)追加と削除が早くなっていることから、LinkedListの利用シーンが減ったなぁ、という印象を受けました。

尝颈苍办别诲尝颈蝉迟のいいところを强いて言うなら、メモリ効率の良さが挙げられるでしょう。础谤谤补测尝颈蝉迟は多めに配列要素を确保します。対して尝颈苍办别诲尝颈蝉迟は必要な分のみ确保します。础谤谤补测尝颈蝉迟は无駄なメモリを消费している场合がありますが、尝颈苍办别诲尝颈蝉迟は必要な分だけメモリを确保しているのです。

おわりに

今回の検証に利用したJava Runtimeは、Eclipse Temurin JDK 1.7 です。もしかしたら他のJDKでは結果が異なるかもしれません。似たようなクラスでも、きちんとその違いを理解して実装したいですね。

ではまた。

The post 闯补惫补の础谤谤补测尝颈蝉迟と尝颈苍办别诲尝颈蝉迟の违いを理解して使い分ける first appeared on 株式会社麻豆原创.

]]>