ぺんぎんらぼ

お笑いとマンガ好きなしょぼしょぼWeb系エンジニアの日記です。たまに絵を描きます。

お笑いとマンガ好きなしょぼしょぼWeb系エンジニアの日記です

EclipseでGit超入門 - リモートブランチの削除

Gitを使うのは初めて。EclipseからGitを使うことになった。そんなあなたのためのEclipseでGit超入門です。
難しいことは説明せず、必要なことだけを解りやすく説明していきます。

eclipseでリモートブランチを削除

不要になったリモートブランチの削除を頼まれました。
Eclipseからリモートブランチを削除することができます。
マツキはどうやってリモートブランチを削除するでしょうか。

うまく削除できなかったようです。さて、マツキは何を間違えたのでしょう?

リモートブランチ ≠ リモートトラッキング

「リモート・トラッキング」から「ブランチの削除」を実行したようですが、これは「選択したリモート・トラッキングの削除」が実行されます。
メニューの「ブランチの削除」という表現が紛らわしいですね。「リモート・トラッキングの削除」だったら勘違いしないのに。

リモートブランチとリモートトラッキングの違いは、以下の記事を参考にしてください。

penguinlabo.hatenablog.com

リモートブランチの削除方法

Gitリポジトリビューでリポジトリを右クリック、[リモート] - [プッシュ...]をクリックします。
[宛先Gitリポジトリー]ダイアログが表示されるので、[プレビュー >]ボタンをクリックします。

[プッシュ仕様指定]ダイアログが表示されるので、[削除するリモート参照]から削除するブランチを選択し、その横の[✖ 仕様の追加]ボタンをクリックします。

一覧にブランチが削除マークとともにリストされます。[プレビュー >]ボタンをクリックします。

[プッシュ確認]ダイアログが表示されます。削除対象のブランチが[削除]として一覧に表示されていることを確認して[プッシュ]ボタンをクリックします。

[プッシュ結果]ダイアログが表示されます。エラーが出てなければ、正常にリモートブランチが削除されています。

EclipseでGit超入門 - 複数のコミットをまとめる

Gitを使うのは初めて。EclipseからGitを使うことになった。そんなあなたのためのEclipseでGit超入門です。
難しいことは説明せず、必要なことだけを解りやすく説明していきます。

修正ミスで何回かコミットしちゃった!

こんなことありませんか?

修正漏れとか修正ミスでローカルリポジトリに複数回、コミットしちゃうことがありますよね。
このままプッシュしちゃうと、複数回、コミットした履歴ごと、リモートリポジトリに記録されてしまいます。
悪いことではないんですが、リモートリポジトリの履歴を不要な情報で汚すようですし、レビューするときにミスったことがバレるのは何となく嫌ですよね。

プッシュ前の複数のコミットをまとめる

ということで、リモートリポジトリにプッシュする前に、ローカルリポジトリのコミットを1つにまとめちゃいましょう。

1. ヒストリービューを表示

ヒストリービューを表示して、まとめたいコミットを確認します。
プロジェクトを右クリック、[チーム] - [ヒストリーに表示]で表示できます。

2. コミットをまとめる

ヒストリービューから、まとめたいコミットを複数選択して、右クリック、[変更] - [スカッシュ]をクリックします。

3. まとめたコミットのコミットメッセージの入力

「コミット・メッセージの編集」ダイアログが表示されます。
コミット・メッセージを変更したい場合、コミット・メッセージを編集して[リワード]ボタンをクリックします。

このダイアログが表示された時点では、すでにコミットがまとめられた後なので、[キャンセル]ボタンをクリックしてもコミットのまとめをキャンセルできるわけではありません。

4. まとめたコミットをプッシュ

ヒストリービューでコミットがまとめられていることがわかります。
後は、このコミットを右クリック、[プッシュ・コミット]をクリックして、リモートリポジトリにプッシュします。

プッシュ済みのブランチで修正ミス・修正漏れが見つかった!

こんなこともありませんか?

プッシュ済みの複数のコミットをまとめる

同様の方法で複数のコミットをまとめます。そして、同様の方法でリモートブランチにプッシュします。
プッシュ時に表示されるダイアログで[リモート・ブランチが存在し分岐されている場合は強制的に上書きする]チェックボックスをチェックし、強制的にプッシュします。

RustでToDoアプリ②~jsonファイルから読み取ったデータをDtoにセット~

この記事はRustでCLIベースのToDoアプリを作っていくシリーズの第二回です。 第一回はこちら

今回はToDo用のデータを入れておくためのDtoを定義し、jsonファイルから読み取ったデータをそのDtoに設定する処理を書いていきましょう。

コード(main.rs)

use std::io;
use std::fs::File; // 追加
use std::io::BufReader; // 追加
use serde::*;// 追加

fn main() {

    println!("■■■■■ToDoリスト■■■■■");// 追加
    let todo_list = read_data("C:\\Users\\matsuki\\todo-app\\todo-data\\data.json"); // 追加
    println!("{:?}", todo_list); // 追加

    println!("▶ 実行したい内容を選択してください");
    println!("▶ 登録:0,編集:1,削除:2");
    let mut input_data = String::new();
    io::stdin()
        .read_line(&mut input_data)
        .expect("標準入力の読み込みに失敗しました");
    let trimmed_input = input_data.trim();
    println!("input_data is {}", trimmed_input);
    
}

/**
 * 追加
 */
#[derive(Debug, Deserialize)]//  (7)
struct ToDo {// (1)
    id: i32,
    name: String,
    deadline: String,
}

/**
 * 追加
 */
fn read_data(file_path: &str) -> Vec<ToDo> { //(2)
    let file = File::open(file_path); // (3)
    match file {
        Ok(f) => {
            let buf_reader = BufReader::new(f); // (4)
            serde_json::from_reader(buf_reader).expect("デシリアライズに失敗しました")//(5)
        }
        Err(_) => {//(6)
            panic!("ファイルが存在しませんでした。")
        }
    }
}

data.jsonの中身

[
  {
    "id": 1,
    "name": "牛乳を買う",
    "deadline": "2023-10-01"
  },
  {
    "id": 2,
    "name": "銀行に行く",
    "deadline": "2023-10-02"
  },
  {
    "id": 3,
    "name": "月見バーガーを食べる",
    "deadline": "2023-10-05"
  }
]

出力結果

■■■■■ToDoリスト■■■■■
[ToDo { id: 1, name: "牛乳を買う", deadline: "2023-10-01" }, ToDo { id: 2, name: "銀行に行く", deadline: "2023-10-02" }, ToDo { id: 3, name: "月見バーガーを食べる", deadline: "2023-10-05" }]
▶ 実行したい内容を選択してください
▶ 登録:0,編集:1,削除:2
0 <-- 値を入力
input_data is 0

解説(1)Rustの「構造体」について

struct ToDo {
    id: i32,
    name: String,
    deadline: String,
}
  • JavaでいうDtoやVOといった構造体データをRustで使用するにはstructキーワードを使用します。structはJavaのClassにあたるものです。Rustは色んな言語の特徴を併せ持っていますが、ここら辺はオブジェクト指向と同じです。
  • このToDo構造体は3つの「フィールド」(id、name、deadline)を持っており、フィールド名の後ろにコロンをつけて、i32(整数型)やString(文字列型)など型を書きます。
  • なお、Rustで「構造体」というと、狭義の意味と広義の二つの意味があります。狭義では今述べたDtoなどのユーザー定義オブジェクトを指します。一方で広義では、VecやHashMapなどのコレクション型も構造体と呼ばれます。
  • Rustの場合、メソッドはstructの中に定義せず別途implの中に書きます
  • 今回のコードでは割愛しましたが、以下のようにコンストラクタ的役割のメソッドを定義してインスタンスを生成する書き方もよく使われます。
//ToDo構造体
struct ToDo {
    id: i32,
    name: String,
    deadline: String,
}

impl ToDo {
    // コンストラクタ的役割のメソッド
    pub fn new(id: i32, name: String, deadline: String) -> Self {
        ToDo {
            id,
            name,
            deadline,
        }
    }
}

fn main() {
    // ToDo構造体のインスタンスを生成
    let todo = ToDo::new(1, String::from("Task 1"), String::from("2023-08-31"));

    // 生成したインスタンスを使用
    println!("ID: {}", todo.id);
    println!("Name: {}", todo.name);
    println!("Deadline: {}", todo.deadline);
}

解説(2)Rustのコレクション型について

fn read_data(file_path: &str) -> Vec<ToDo> {
  // 割愛
}
  • このread_data関数の戻り値型「Vec < ToDo >」はJavaでいう「ArrayList < ToDo > 」です。
  • JavaでHashMapやSet、LinkedListなど様々なコレクションAPIがあるようにRustにも様々なAPIがあります。詳細は公式ドキュメントを参照ください。

解説(3)Fileオブジェクトからの読み取り処理

    let file = File::open(file_path); // (3)
    match file {
        Ok(f) => {
            let buf_reader = BufReader::new(f);
            serde_json::from_reader(buf_reader).expect("デシリアライズに失敗しました")
        }
        Err(_) => {
            panic!("ファイルが存在しませんでした。")
        }
    }
  • std::fs::Fileはファイルに対して読み書き機能を提供するオブジェクトです(公式ドキュメント)。
  • ここでは、読み取り対象のファイルパスをString型で渡しFileオブジェクトを生成し、パスの指すファイルが存在しない場合はpanicを起こす(処理を中断する)ようにしています。
  • 上記のコードはmatch式とBufReaderが入っていて長いコードになっていますが、ファイル読み取り処理は最小3行で書くことができます。以下は公式ページのサンプルコードです。
    let mut file = File::open("foo.txt")?; // 【補足】
    let mut contents = String::new();
    file.read_to_string(&mut contents)?;
  • 【補足】 上記サンプルコードではエラーハンドリングにmatch式ではなく "?" 演算子が使われています。"?"演算子を使うとエラーは処理されず、そのまま呼び出し元にエラーが伝播します。なので呼び出し元の方でエラーを処理してあげる必要があります。成功時は単純にResult のOkバリアントから取得された値(つまりFileオブジェクト)が返されます。

解説(4)BufReaderを使おう

    let file = File::open(file_path);
    match file {
        Ok(f) => {
            let buf_reader = BufReader::new(f); // (4)
                 serde_json::from_reader(buf_reader).expect("デシリアライズに失敗しました")
        }
        Err(_) => {
            panic!("ファイルが存在しませんでした。")
        }
    }
  • ここでは、取得したFileオブジェクトから更にBufReaderオブジェクトを生成しています。RustのBufReaderはJavaのBufferedReaderと同じで、データをバッファに溜めつつ読み込むことで、ファイル読み取り操作のオーバーヘッドにかかるコストを低減します。
  • RustのBufReaderを使うことでファイルの内容を1文字ずつではなくデフォルトで8Kバイトずつ読み込みます。

解説(5)serde_json外部ライブラリクレート

let buf_reader = BufReader::new(f);
serde_json::from_reader(buf_reader).expect("デシリアライズに失敗しました")//(5)
  • serde_jsonクレートは、JSONデータのシリアライズ(データをJSON形式に変換すること)およびデシリアライズJSONデータをRustのデータ型に変換すること)をするライブラリグレーです。
  • 前回、ファイルの読み書き機能を提供する「std::ioクレート」について紹介しました。std::ioクレートはRustに標準で組み込まれているクレートです。その一方でserde_jsonは「外部」ライブラリクレートで、外部ライブラリクレートはRustコミュニティの開発者達が「便利なライブラリ作ったから使って」という体で提供しているサードパーティ製のライブラリになります。これらの外部ライブラリクレートは、コード中で使用するためにはcargo.tomlに依存関係を追記しておく必要があります。
# Cargo.toml
[dependencies]
serde_json = "1.0.105"
  • Rustの外部ライブラリクレートの公式サイトを紹介します。Rustコミュニティが作成したさまざまなクレートがここに登録されており、例えば "json" と検索し、直近でUpdateされた順にソートすると以下のような感じで数時間おきにアップされておりコミュニティの活発さを伺い知ることができます。

  • コードの話に戻ります。Rustの型推論は非常に強力で、serde_json::from_readerメソッドに戻り値型の情報(Vec < ToDo >)について何も渡していないにも関わらず、serde_jsonJSONファイルの内容を解析してToDo構造体に変換します。JSONファイルの内容とToDo構造体(structで定義したオブジェクトのフィールドメンバー)の内容が一致していない時はデシリアライズに失敗します。

解説(6)expectマクロ

    match file {
        Ok(f) => {
            let buf_reader = BufReader::new(f);
            serde_json::from_reader(buf_reader).expect("デシリアライズに失敗しました")
        }
        Err(_) => {
            panic!("ファイルが存在しませんでした。")
        }
    }
  • ここで、Fileのオープン処理では今回はエラーハンドリングにmatch式ではなくexpectを使っています。第一回の記事でも少し触れました。
  • expectは、エラーが発生した場合に開発者が指定したエラーメッセージを表示してプログラムをパニック状態に移行させます。この通り、expectはエラーが発生した際にプログラムを停止させてしまうため、エラーハンドリング方法は用途に応じて使い分ける必要があります。ここでは処理の見通しをよくするためにexpectを使っています。
  • 参考までに今回のexpectを使っているコードをmatch式に置き換えると以下のようになります。
fn read_data(file_path: &str) -> Vec<ToDo> {
    let file = File::open(file_path);

    match file {
        Ok(f) => {
            let buf_reader = BufReader::new(f);
            match serde_json::from_reader(buf_reader) {
                Ok(data) => data,
                Err(_) => Vec::new(),
            }
        }
        Err(_) => {
            panic!("ファイルが存在しませんでした。");
        }
    }
}

解説(7)derive属性

#[derive(Deserialize,Debug)]//  (7)
struct ToDo {
  • Rustの「derive属性」はJavaアノテーションにあたるものです。ここでは構造体をデシリアライズできるようにDesirialize値を、構造体をデバッグ出力できるようにDebug値を設定しています。
  • Debugは、main.rsの10行目で「println!("{:?}", todo_list); 」としてToDo構造体のリストの内容を出力するために必要です。
  • JavalombokやJacksonなどでアノテーションをクラス宣言に付与することによって、コンパイル時や実行時の振る舞いをカスタマイズできるのと同じです。Javaで@SerializableとDtoに付与することでオブジェクトのシリアライズ(直列化)とデシリアライズが可能になるように、ここではserdeライブラリと組み合わせて、struct構造体にDeserialize属性を付与することで、開発者が頑張ってデシリアライズのコードを書かずとも、デシリアライズができるようにしてくれています。
  • Javaアノテーションと同じようにRustではマクロを定義してderive属性の値に設定することで、開発者が振る舞いをカスタマイズすることが可能です。公式ドキュメント

おわり

第二回の今回は、ToDo用のstruct構造体や、ファイルからのデータの読み取り方を解説しました。次回はコレクションの操作やファイル書き込み処理のコードを書いていきまましょう。

RustでToDoアプリ①~標準入力の内容を出力~

RustでToDoアプリ(CLIで入力してjson形式でファイル保存)

何回かに分け、コマンドラインから値を入力しjsonファイルとして保存するToDoアプリケーションを作っていこうと思います。 その中で併せてRust言語の特徴を説明していきます。

第一回のこの記事では、コマンドラインから値を受け取り出力する処理を紹介します。

コード(main.rs)
use std::io;

fn main() {
    println!("▶ 実行したい内容を選択してください");
    println!("▶ 登録:0,編集:1,削除:2");

    let mut input_data = String::new(); // (1)

    io::stdin()
      .read_line(&mut input_data)/* (2) */
      .expect("標準入力の読み込みに失敗しました");// (3)

    let trimmed_input = input_data.trim(); // (4)

    println!("input_data is {}", input_data); // (5)
  }
解説(1)
let mut input_data = String::new();
  • 右辺で空のString型を宣言し左辺にバインドしています。
  • この変数に、コマンドラインから入力された値を設定したいと思います。
  • Rustではコマンドラインなどから受け取ったデータはString型になります。
  • Rustでは変数は全て「immutable(不変)」となります。初期化後に値を書き換えたい場合は "mut" キーワードを使って「mutable(可変)」にする必要があります。
  • input_data変数を可変にしている理由は、この後この変数に標準入力から受け取ったデータを入れるためです。
解説(2)
io::stdin().read_line(&mut input_data)
  • 上記のように書くとコマンドラインから入力した値をinput_data変数に設定することができます。
  • io::stdin()メソッドでStdin型を返却し、次のread_line()メソッドではResult型を返却しています。
  • 以下は実際のread_lineメソッドの実装です。
    #[stable(feature = "rust1", since = "1.0.0")]
    pub fn read_line(&self, buf: &mut String) -> io::Result<usize> {
        self.lock().read_line(buf)
    }
  • 注目してほしいのがread_lineメソッドの2つの引数のうちの「buf:&mut String」の部分です。 呼び出し側は第2引数の変数bufに対して、可変参照のString型を渡さなければなりません(第1引数の&selfはインスタンス自身を指す記述で、今回は説明しません)。
  • 可変変数にするためにはmutキーワード、参照変数にするためには&キーワードを使います
  • したがって呼び出し側は「read_line(&mut input_data)」と、変数input_dataの頭に&mutを付与して可変参照の変数を渡すようにしています。
解説(2)補足
use std::io;
  • ここではソースコード中で使うstd::ioクレートをimportしています。
  • std::ioクレートはRustの標準ライブラリの一つです。標準ライブラリとは一般的な機能を提供するライブラリで、std::ioはその名の通りファイルの読み書きや入出力に関するライブラリが含まれます。
  • Rustの「クレート」はJavaのjarやwarに近いものです。
  • クレートには二種類あり、①バイナリクレートと②ライブラリクレートがあります。バイナリクレートがそれ自体実行可能なクレート。その一方でライブラリクレートは単体で実行できません。「cargo new パッケージ名」で生成されたパッケージの中にmain.rsが含まれますが、これがバイナリクレートとなります。
解説(3)
io::stdin().read_line(&mut input_data).expect("標準入力の読み込みに失敗しました");
  • expectメソッドはいわばJavaの例外処理をtry−catch文ではなく関数的にワンライナーで書いたものです。もう一度stdinクレートのread_lineメソッドを見てみましょう。
    #[stable(feature = "rust1", since = "1.0.0")]
    pub fn read_line(&self, buf: &mut String) -> io::Result<usize> {
        self.lock().read_line(buf)
    }
  • 戻り値の型が「-> io::Result< usize> 」とResult型になっています。
  • RustにはJavaのような例外の機構はありません。代わりに失敗する可能性がある処理はResult型を返却するということになっています。
  • 基本的には、Result型を受け取った後にmatch構文(Javaのcase式に似た構文です)というものを使って結果が正常な場合〜、Errorの場合〜、と処理内容を実装します。以下はサンプルコードです。
// 呼び出し側(エラーハンドリング)
fn main() {
  let res = match return_result(0){
    Ok(val) => val,
    Err(err) => {
      println!("res is {}", err);
      panic!()
    }
  };
}

// メソッド定義(戻り値はResult型)
fn return_result(a: i32) -> Result<i32,String> {
  if a == 0 {
    Ok(a)
  } else {
    Err(String::from("0以外でした"))
  }
}
  • 「呼び出し側(エラーハンドリング)」を見てください。ご覧の通りmatch式を使うと少し冗長な記述になってしまいます。
  • そこで、expectマクロを使うと記述を省略することができます。expectマクロは「エラーが発生した場合にパニックを発生させる&指定したメッセージを出力する」という機能を提供します。
  • 他にもunwrapやis_okなど様々なエラーハンドリング用のマクロが提供されているので用途によって使い分けます。
  • 以下は先のサンプルコードをexpectマクロを使ったものに置き換えたものです。
// 呼び出し側(エラーハンドリング)
fn main() {
    let res = return_result_data(0).expect("エラーが発生しました。0以外でした");
    println!("res is {}", res);
}
解説(4)
    let trimmed_input = input_data.trim();
  • コマンドラインからの入力には最後に改行文字(\nや\r\n)が含まれます。
  • trim()メソッドは文字列の前後から空白文字を削除するので、この改行文字も削除してくれます。
解説(5)
println!("input_data is {}", input_data);
  • {}はRustでは特別な文字となります。
  • 文字列の中で値を挿入するためのプレースホルダーとして使用されます。
let x = 5;
let y = 4;
println!("x: {}, y: {}", x, y); // 「x: 5, y: 4」が出力される

おわり

以上『RustでToDoアプリ①~標準入力の内容を出力~』でした。

次回は『RustでToDoアプリ②~jsonファイルの内容を出力~』となります。

ここまで読んでくださりありがとうございました。

64bitと32bitの違い

CPUの命令セット

「64 bit OS」や「32 bit OS」と言う言葉を耳にします。

この64ビットや32ビットとは「CPUの命令セット」のことです。

64bitOSとは

  • 64ビットOSは64ビットアドレスを使用してメモリにアクセスし、32ビットOSは32ビットアドレスを使用してメモリをアクセスします。
  • 64ビットOSの場合、命令は64ビット(8バイト)ごとに処理されます。
  • 特定のメモリアドレスを表現するポインタ自体も8バイトで表現されます。

メモリとしての表現幅が32ビットOSの時は

 2の32乗分(4,294,967,296ビット=約4GB

でしたが、

64ビットOSでは

 2の64乗分(18,446,744,073,709,551,616ビット=約18,000,000,000GB

のメモリを理論上使えることになります。

実際に製造されるPCのRAMは物理的制約により16GBや32GBのものが多いですが。

16進数表記について

上の図でメモリを「0x 00 00 00 00 00 00 00 00 」と表現しました。

  • "0x" は、16進数を表現するときの接頭詞です。
  • "00"が16進数の値となります。下の図のように、16進数の左の桁と右の桁はそれぞれ4ビットを表し、左右の2桁でちょうど1バイト(8ビット)になります。

1byteとは

Jakarta EE (Java EE) Topics - @Inject or @EJB ?

Java EEではコンポーネントの管理に「CDI」と「EJB」があります。
どちらかで統一したいところですが、それぞれ役割が分かれていて、Backing Beanのような画面のコンポーネントは「CDI」、ビジネスロジックや永続化のコンポーネントは「EJB」と、使い分けることがほとんどだと思います。

コンポーネントのインジェクト方法

CDI」のManaged Beanから「EJB」のビジネスロジックを呼び出すときは、「CDI」の中で「EJB」のコンポーネントをインジェクトする必要があります。
フィールドインジェクションの場合、アノテーションを指定してインジェクションしますが、その時のアノテーションが「@Inject」と「@EJB」の2種類があり、どちらを指定すればよいのか迷うところです。

@Injectアノテーション

@Inject
private SampleLogicBean bean;

ざっくり解釈で、CDIEJBの両方のBeanをインジェクトできる、です。
ただし、リモートインターフェースのEJBはインジェクトできません。

EJBアノテーション

@EJB
private SampleLogicBean bean;

ざっくり解釈で、EJBのBeanのみインジェクトできる、です。
リモートインターフェースのEJBもインジェクトできます。

結論

@Injectアノテーションで統一、でいいと思います。
リモートインターフェースのEJBを使う場合は、泣きながら@EJBアノテーションでインジェクトすることにします。

Jakarta EE (Java EE)のバージョン情報

2022/9に、5年ぶりのメジャーバージョンアップであるJakarta EE 10が発表されました。

いろいろと変更が入っていますが、注目すべき点としてはCDI 4.0で、Enterprise Bean (EJB) の代替手段がCDIに用意されたことです。これにより、今まではプレゼンテーション層のコンポーネントCDI、アプリケーション層はEJBで実装、もしくはCDIEJBをうまく混ぜて実装していましたが、CDIに統一して実装することができます。

今回は、Java EE 8からJakarta EE10までのコンポーネントのバージョンをまとめます。

サポートするJavaのバージョン

Jakarta EEのコンポーネントバージョンの前に、サポートするJavaのバージョンをまとめます。

リリース日 サポートするJava SEのバージョン
Java EE 8 2017-08-31 Java SE 8
Jakarta EE 9 2020-12-08 Java SE 8
Jakarta EE 9.1 2021-05-25 Java SE 11
Jakarta EE 10 2022-09-13 Java SE 11

見ての通り、2020年12月にリリースされたJakarta EE 9まではJava SEのバージョンが8でした。ちなみにJava SE 8のリリース日は2014年3月です。
2021年5月にリリースされたマイナーアップデートであるJakarta EE 9.1で、やっとJava SEのバージョンが11まで引き上げられました。

コンポーネントのバージョン

Jakarta EEは複数のコンポーネント(正確には仕様)の集まりで、各コンポーネントごとのバージョンを持ちます。
代表的なコンポーネントのバージョンは以下の通りです。

Jakarta EE 10 Jakarta EE 9 Java EE 8
Jakarta Contexts and Dependency Injection (CDI) 4 3 2
Jakarta Servlet 6 5 4
Jakarta Server Pages (JSP) 3.1 3 2.3
Jakarta Faces (JSF) 4 3 2.3
Jakarta RESTful Web Services (JAX-RS) 3.1 3 2.1
Jakarta WebSocket (WebSocket) 2.1 2 1.1
Jakarta JSON Processing (JSON-P) 2.1 2 1.1
Jakarta JSON Binding (JSON-B) 3 2 1.1
Jakarta Enterprise Beans (EJB) 4.0 4.0 3.2
Jakarta Persistence (JPA) 3.1 3 2.2

サポートするアプリケーションサーバのバージョン

Jakarta EEは仕様であり、その仕様に沿って実装されたアプリケーションサーバが各ベンダーや団体から提供されています。
2023年3月時点、Jakarta EE、Java EEの実装状況は以下の通りです。

Jakarta EE 10 Jakarta EE 9 Java EE 8
GlassFish 7.0.0 6.0.0 5.0
Oracle WebLogic Server 14.1.1
Open Liberty 22.0.0.13 21.0.0.12 18.0.0.2
WebSphere Liberty 23.0.0.12 21.0.0.12 18.0.0.2
WildFly 27.0.0 23.0.1 v14.x
JBoss EAP v7.2
Payara Server 6.2022.1 6.2021.1

Jakarta EE 10はリリースされて間もないこともあり、実装されたアプリケーションサーバは少ないですが、フリーソフトウェアの実装もあることから、手軽に試すことができます。