CodeGym /コース /JAVA 25 SELF /JAXB で XML を扱う: 基本とアノテーション

JAXB で XML を扱う: 基本とアノテーション

JAVA 25 SELF
レベル 47 , レッスン 3
使用可能

1. JAXB の概要

JAXB(Java Architecture for XML Binding)は、Java オブジェクトを XML に変換し、またその逆に変換する(バインディング)ための標準的な Java 技術です。JAXB を使えば、オブジェクトを簡単に XML ファイルへシリアライズし、そのファイルから復元(デシリアライズ)できます。

JAXB は Java の標準ライブラリに 11 まで含まれていました。Java 11 以降、JAXB は別モジュールに分離され、Maven/Gradle で依存関係として追加するか、手動で取得する必要があります。最新の Java では次の依存関係を追加してください:

<!-- Maven の例 -->
<dependency>
    <groupId>jakarta.xml.bind</groupId>
    <artifactId>jakarta.xml.bind-api</artifactId>
    <version>4.0.0</version>
</dependency>
<dependency>
    <groupId>org.glassfish.jaxb</groupId>
    <artifactId>jaxb-runtime</artifactId>
    <version>4.0.3</version>
</dependency>

なぜ XML が必要なのか?

  • XML は、システム間のデータ交換・設定・情報保存に広く使われる、汎用で人が読めるフォーマットです。
  • バイナリシリアライズと違って、XML は目視で読め、スキーマで妥当性検証ができ、ブラウザでも開けます。

2. JAXB の主なクラスとアノテーション

JAXB はアノテーションを基盤としており、クラスやそのフィールドに付与してシリアライズ/デシリアライズの振る舞いを制御します。

主要なアノテーション

アノテーション 用途
@XmlRootElement
XML のルート要素(クラス自体)を表す
@XmlElement
フィールド/プロパティを XML 要素としてマークする
@XmlAttribute
フィールド/プロパティを XML 属性としてマークする
@XmlType
要素の順序、型名などを制御
@XmlTransient
フィールドをシリアライズ対象から除外

主要なクラス

  • JAXBContext — エントリポイント。特定クラスのシリアライズ/デシリアライズ用コンテキストを作成します。
  • Marshaller — オブジェクトを XML に変換(マーシャリング、marshal())。
  • Unmarshaller — XML をオブジェクトに変換(アンマーシャリング、unmarshal())。

3. 例: オブジェクトを XML にシリアライズ

シリアライズするクラスを作成します。ここではゲーム用のキャラクターとします:

import jakarta.xml.bind.annotation.XmlRootElement;
import jakarta.xml.bind.annotation.XmlElement;
import jakarta.xml.bind.annotation.XmlAttribute;

@XmlRootElement(name = "player")
public class Player {
    private String name;
    private int level;
    private int health;

    public Player() {} // 必須の引数なしコンストラクタ!

    public Player(String name, int level, int health) {
        this.name = name;
        this.level = level;
        this.health = health;
    }

    @XmlElement
    public String getName() {
        return name;
    }

    public void setName(String name) { this.name = name; }

    @XmlElement
    public int getLevel() {
        return level;
    }

    public void setLevel(int level) { this.level = level; }

    @XmlAttribute
    public int getHealth() {
        return health;
    }

    public void setHealth(int health) { this.health = health; }
}
  • @XmlRootElement(name = "player") — クラスがルート要素 <player> になります。
  • @XmlElement — フィールドは個別の XML 要素になります(<name>, <level>)。
  • @XmlAttribute — フィールドはルート要素の属性になります(health="100")。
  • 引数なしコンストラクタを忘れずに。JAXB はデシリアライズ時にこれを必要とします。

オブジェクトを XML にシリアライズ

import jakarta.xml.bind.JAXBContext;
import jakarta.xml.bind.Marshaller;

public class Main {
    public static void main(String[] args) throws Exception {
        Player player = new Player("Aragorn", 5, 100);

        JAXBContext context = JAXBContext.newInstance(Player.class);
        Marshaller marshaller = context.createMarshaller();
        marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE); // 整形出力

        marshaller.marshal(player, System.out); // XML をコンソールに出力
        // marshaller.marshal(player, new File("player.xml")); // またはファイルへ
    }
}

出力結果:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<player health="100">
    <name>Aragorn</name>
    <level>5</level>
</player>

XML からオブジェクトをデシリアライズ

import jakarta.xml.bind.JAXBContext;
import jakarta.xml.bind.Unmarshaller;
import java.io.File;

public class Main {
    public static void main(String[] args) throws Exception {
        JAXBContext context = JAXBContext.newInstance(Player.class);
        Unmarshaller unmarshaller = context.createUnmarshaller();

        Player player = (Player) unmarshaller.unmarshal(new File("player.xml"));
        System.out.println(player.getName() + "、レベル: " + player.getLevel() + "、HP: " + player.getHealth());
    }
}

4. JAXB の特徴と制約

クラスへの要件

  • 公開の引数なしコンストラクタは必須。
  • 正しく動作させるために、getter と setter を使用してください。
  • すべてのシリアライズ対象フィールドは(public API 経由で)アクセス可能であること。
  • 入れ子のオブジェクトやコレクションもシリアライズ可能である必要があります(アノテーション付与と引数なしコンストラクタ)。

コレクションと入れ子のオブジェクト

たとえば、プレイヤーにインベントリ(項目のリスト)があるとします。コレクションをどうシリアライズしますか?

import jakarta.xml.bind.annotation.XmlElement;
import jakarta.xml.bind.annotation.XmlElementWrapper;
import java.util.List;

@XmlRootElement(name = "player")
public class Player {
    // ... 他のフィールド

    private List<String> inventory;

    public Player() {}

    // ... 他の getter/setter

    @XmlElementWrapper(name = "inventory")
    @XmlElement(name = "item")
    public List<String> getInventory() {
        return inventory;
    }

    public void setInventory(List<String> inventory) {
        this.inventory = inventory;
    }
}

シリアライズ結果:

<player health="100">
    <name>Aragorn</name>
    <level>5</level>
    <inventory>
        <item>Sword</item>
        <item>Shield</item>
    </inventory>
</player>
  • @XmlElementWrapper — コレクションのラッパー要素を作成します(要素 <inventory>)。
  • @XmlElement(name = "item") — リストの各要素は <item> としてシリアライズされます。

入れ子のオブジェクト(たとえば Position)がある場合も、アノテーションを付与し、引数なしコンストラクタを用意してください。

5. 実践: オブジェクトの XML シリアライズとデシリアライズ

import jakarta.xml.bind.annotation.XmlRootElement;
import jakarta.xml.bind.annotation.XmlElement;
import jakarta.xml.bind.annotation.XmlElementWrapper;
import jakarta.xml.bind.annotation.XmlAttribute;
import java.util.List;

@XmlRootElement(name = "player")
public class Player {
    private String name;
    private int level;
    private int health;
    private List<String> inventory;
    private Position position;

    public Player() {}

    public Player(String name, int level, int health, List<String> inventory, Position position) {
        this.name = name;
        this.level = level;
        this.health = health;
        this.inventory = inventory;
        this.position = position;
    }

    @XmlElement
    public String getName() { return name; }

    @XmlElement
    public int getLevel() { return level; }

    @XmlAttribute
    public int getHealth() { return health; }

    @XmlElementWrapper(name = "inventory")
    @XmlElement(name = "item")
    public List<String> getInventory() { return inventory; }

    @XmlElement
    public Position getPosition() { return position; }

    // setter は簡潔のため省略
}

@XmlRootElement(name = "position")
class Position {
    private int x;
    private int y;

    public Position() {}

    public Position(int x, int y) { this.x = x; this.y = y; }

    @XmlAttribute
    public int getX() { return x; }

    @XmlAttribute
    public int getY() { return y; }

    // setter は省略
}

シリアライズ:

Player player = new Player(
    "Aragorn",
    5,
    100,
    List.of("Sword", "Shield", "Potion"),
    new Position(10, 20)
);

JAXBContext context = JAXBContext.newInstance(Player.class);
Marshaller marshaller = context.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
marshaller.marshal(player, System.out);

XML の出力:

<player health="100">
    <name>Aragorn</name>
    <level>5</level>
    <inventory>
        <item>Sword</item>
        <item>Shield</item>
        <item>Potion</item>
    </inventory>
    <position x="10" y="20"/>
</player>

デシリアライズも同様に動作します。クラスが正しく定義されていれば、JAXB が入れ子オブジェクトやコレクションを自動的に解析します。

6. 表: JAXB の主要アノテーションとその効果

アノテーション 使用箇所 XML 上の効果
@XmlRootElement
クラス ルート要素
@XmlElement
getter/フィールド XML 内の要素
@XmlAttribute
getter/フィールド 要素の属性
@XmlElementWrapper
コレクションの getter コレクションのラッパー(例: <list>)
@XmlTransient
フィールド/getter フィールドをシリアライズから除外
@XmlType
クラス 要素順序や型名を制御

7. JAXB の細かな挙動と制限

要素の順序

既定では JAXB が要素をアルファベット順で出力する場合があります。順序を明示したい場合は、@XmlType とプロパティ propOrder を使います:

@XmlType(propOrder = {"name", "level", "inventory", "position"})

フィールドの除外

フィールド/ゲッターをシリアライズしたくない場合は、@XmlTransient を使用します:

@XmlTransient
public String getSecretCode() { ... }

コレクションに関する注意点

  • ジェネリクスなしの「生の」コレクションは使わないでください。List<Type> と書き、List だけにしないこと。
  • コレクションがオブジェクトを保持する場合は、そのクラスにもアノテーションを付与し、引数なしコンストラクタを用意してください。

エラー

  • 引数なしコンストラクタがない — アンマーシャリング時に JAXBException が発生します。
  • 入れ子クラスにアノテーションがない — JAXB はそのクラスをシリアライズ/デシリアライズできません。
  • 非標準の型(例: LocalDate)はアダプター(@XmlJavaTypeAdapter)が必要です。

8. JAXB でよくあるミス

エラー No.1: 引数なしコンストラクタがない。 JAXB は、シリアライズ対象クラスに公開の引数なしコンストラクタを必須とします。これがない場合、アンマーシャリング時に JAXBException が発生します。

エラー No.2: 入れ子オブジェクトにアノテーションがない。 フィールドがオブジェクトで、そのクラスに @XmlRootElement または少なくとも @XmlType が付いていないと、JAXB は正しくシリアライズ/デシリアライズできません。

エラー No.3: コレクションの問題。 JAXB は要素型を指定していない「生の」コレクションを理解しません。ジェネリクスを使い、コレクションに適切なアノテーション(@XmlElementWrapper + @XmlElement)を付与してください。

エラー No.4: 要素順序の明示的な制御をしていない。 統合のために XML の要素順が重要な場合は、@XmlTypepropOrder を使用してください。そうしないと、JAXB が別の順序(たとえばアルファベット順)で出力することがあります。

エラー No.5: アダプターなしで非標準型を使う。 JAXB は一部の型(たとえば LocalDate)をアダプターなしではシリアライズできません。@XmlJavaTypeAdapter を使用するか、値を文字列としてシリアライズしてください。

コメント
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION