この記事で学ぶこと
この記事では、ラッパークラスという重要なプログラミングテクニックを学びます。
今回のゴール
- ラッパークラスとは何かを理解する
- なぜラッパークラスが必要なのかを知る
- ラッパークラスの作り方を学ぶ
- 実践的な使い方を身につける
ラッパークラスって何?
ラッパークラス(Wrapper Class) は、「使いにくいものを使いやすく包む」 クラスのことです。
「Wrap(ラップ)」= 「包む」という意味から来ています。
身近な例で考えよう
例1:スマホケース
スマホ本体(複雑):
- 落とすと壊れやすい
- 持ちにくい
- 傷がつきやすい
スマホケース(ラッパー):
- スマホを保護してくれる
- 持ちやすくなる
- 可愛くデコレーションできる
👉 スマホケース = ラッパークラス と考えればOK!
例2:お弁当箱
ご飯やおかず(中身):
- そのままだと持ち運べない
- バラバラになる
- こぼれる
お弁当箱(ラッパー):
- 綺麗に整理できる
- 持ち運びやすい
- 見た目も良い
👉 お弁当箱 = ラッパークラス 中身を扱いやすくする!
例3:電子レンジ
内部(複雑):
- マイクロ波の発生装置
- 電力制御システム
- 温度センサー
- 回転モーター
操作パネル(シンプル):
- 「温めボタン」を押すだけ
- 時間を設定するだけ
- 誰でも簡単に使える
👉 操作パネル = ラッパークラス 複雑な内部を隠して簡単に使える!
プログラミングでの例
ラッパークラスを使わない場合
// Propertiesを直接使う(大変!)
Properties props = new Properties();
FileInputStream fis = new FileInputStream("config.properties");
props.load(fis);
fis.close();
// 毎回文字列から変換が必要
String volumeStr = props.getProperty("sound.volume", "80");
int volume = Integer.parseInt(volumeStr);
// 範囲チェックも自分でやる必要がある
if (volume < 0) volume = 0;
if (volume > 100) volume = 100;
// 保存も大変
props.setProperty("sound.volume", String.valueOf(volume));
FileOutputStream fos = new FileOutputStream("config.properties");
props.store(fos, "Configuration");
fos.close();
問題点:
- コードが長くて読みにくい
- 毎回同じ処理を書く必要がある
- ミスが起きやすい
- 文字列変換を忘れやすい
ラッパークラスを使う場合
// GameConfigを使う(簡単!)
GameConfig config = new GameConfig();
config.load();
// 整数でそのまま取得できる
int volume = config.getBgmVolume();
// 範囲チェックは自動でやってくれる
config.setBgmVolume(999); // 自動的に100に制限される
// 保存も簡単
config.save();
メリット:
- コードが短くて読みやすい
- 型変換は自動
- 範囲チェックも自動
- ミスが減る
ラッパークラスの作り方
ステップ1:使いにくいクラスを「包む」
public class GameConfig { // ラッパークラス
// 内部でPropertiesを持つ(包む)
private Properties properties;
private ConfigManager configManager;
// フィールド(使いやすい型で保持)
private int bgmVolume;
private int seVolume;
private boolean fullscreen;
public GameConfig() {
configManager = new ConfigManager();
}
}
ステップ2:読み込みメソッドを作る
public void load() throws IOException {
// 内部で複雑な処理を行う
configManager.loadConfig();
// 文字列→整数に変換して保持
bgmVolume = configManager.getInt("sound.bgm.volume", 80);
seVolume = configManager.getInt("sound.se.volume", 70);
fullscreen = configManager.getBoolean("screen.fullscreen", false);
}
ステップ3:簡単なゲッター・セッターを作る
// 使いやすいメソッドを提供
public int getBgmVolume() {
return bgmVolume; // 整数でそのまま返す
}
public void setBgmVolume(int volume) {
// 範囲チェックも自動で行う
this.bgmVolume = Math.max(0, Math.min(100, volume));
}
public boolean isFullscreen() {
return fullscreen; // 真偽値でそのまま返す
}
public void setFullscreen(boolean fullscreen) {
this.fullscreen = fullscreen;
}
ステップ4:保存メソッドを作る
public void save() throws IOException {
// 整数→文字列に変換して保存
configManager.setProperty("sound.bgm.volume", String.valueOf(bgmVolume));
configManager.setProperty("sound.se.volume", String.valueOf(seVolume));
configManager.setProperty("screen.fullscreen", String.valueOf(fullscreen));
// 内部で複雑な処理を行う
configManager.saveConfig();
}
比較表:ラッパーあり vs なし
| 項目 | ラッパーなし | ラッパーあり |
|---|---|---|
| コード量 | 多い(10行以上) | 少ない(3行程度) |
| 型変換 | 毎回必要 | 自動 |
| 範囲チェック | 自分で書く | 自動 |
| エラー | 起きやすい | 起きにくい |
| 読みやすさ | 読みにくい | 読みやすい |
| 保守性 | 低い | 高い |
ラッパークラスの設計パターン
パターン1:型変換を隠す
Before(使いにくい):
String volumeStr = props.getProperty("volume");
int volume = Integer.parseInt(volumeStr);
After(使いやすい):
int volume = config.getVolume();
パターン2:バリデーションを隠す
Before(使いにくい):
int volume = config.getVolume();
if (volume < 0) volume = 0;
if (volume > 100) volume = 100;
config.setVolume(volume);
After(使いやすい):
config.setVolume(150); // 自動的に100に制限
パターン3:複雑な処理を隠す
Before(使いにくい):
File file = new File("config.properties");
FileInputStream fis = new FileInputStream(file);
Properties props = new Properties();
props.load(fis);
fis.close();
After(使いやすい):
config.load();
実践例:他のラッパークラス
例1:ファイル操作のラッパー
public class EasyFile {
private File file;
public EasyFile(String path) {
this.file = new File(path);
}
// 複雑なBufferedReaderの処理を隠す
public String readAllText() throws IOException {
StringBuilder sb = new StringBuilder();
try (BufferedReader br = new BufferedReader(new FileReader(file))) {
String line;
while ((line = br.readLine()) != null) {
sb.append(line).append("\n");
}
}
return sb.toString();
}
// 複雑なBufferedWriterの処理を隠す
public void writeAllText(String text) throws IOException {
try (BufferedWriter bw = new BufferedWriter(new FileWriter(file))) {
bw.write(text);
}
}
}
使い方:
// シンプル!
EasyFile file = new EasyFile("data.txt");
String content = file.readAllText();
file.writeAllText("新しい内容");
例2:リストのラッパー
public class PlayerList {
private List<Player> players;
public PlayerList() {
this.players = new ArrayList<>();
}
// 追加の際に重複チェック
public boolean addPlayer(Player player) {
if (findByName(player.getName()) != null) {
return false; // すでに存在
}
players.add(player);
return true;
}
// 名前で検索(便利メソッド)
public Player findByName(String name) {
for (Player p : players) {
if (p.getName().equals(name)) {
return p;
}
}
return null;
}
// レベルでソート(便利メソッド)
public void sortByLevel() {
players.sort((p1, p2) -> Integer.compare(p2.getLevel(), p1.getLevel()));
}
}
ラッパークラス設計のポイント
1. シンプルなインターフェース
// ❌ 複雑
config.getPropertyAsInteger("sound.bgm.volume", 80, 0, 100);
// ✅ シンプル
config.getBgmVolume();
2. 型安全性
// ❌ 文字列で何でも受け入れる
props.setProperty("volume", "abc"); // エラーにならない
// ✅ 正しい型のみ受け入れる
config.setVolume(80); // int型のみ
3. エラー処理
public int getVolume() {
String value = props.getProperty("volume");
if (value == null) {
return 80; // デフォルト値
}
try {
return Integer.parseInt(value);
} catch (NumberFormatException e) {
System.err.println("警告: 音量の値が不正です。デフォルト値を使用します。");
return 80;
}
}
4. バリデーション
public void setVolume(int volume) {
// 範囲チェック(0-100)
if (volume < 0) {
this.volume = 0;
} else if (volume > 100) {
this.volume = 100;
} else {
this.volume = volume;
}
}
// または Math を使ってシンプルに
public void setVolume(int volume) {
this.volume = Math.max(0, Math.min(100, volume));
}
ラッパークラスのメリットまとめ
| メリット | 説明 | 効果 |
|---|---|---|
| コードが短い | 複雑な処理を隠す | 生産性UP |
| 読みやすい | 意図が明確 | 保守性UP |
| 型安全 | コンパイル時にエラー検出 | バグ減少 |
| 再利用 | 同じ処理を何度も書かない | 効率UP |
| 変更が容易 | 内部実装の変更が簡単 | 柔軟性UP |
実践課題
課題1:SimpleLoggerラッパー
ファイルへのログ出力を簡単にするラッパークラスを作成してください。
要件:
- ログファイルのパスを指定
-
info(),warn(),error()メソッドを実装 - 日時を自動で追加
- ファイルの開閉を自動管理
使い方:
SimpleLogger logger = new SimpleLogger("game.log");
logger.info("ゲーム開始");
logger.warn("HPが低下しています");
logger.error("ファイルの読み込みに失敗");
ログの出力例:
[2026-01-06 11:40:15] [INFO] ゲーム開始
[2026-01-06 11:40:15] [WARN] HPが低下しています
[2026-01-06 11:40:15] [ERROR] ファイルの読み込みに失敗
[2026-01-06 11:40:15] [INFO] プレイヤーがログインしました
[2026-01-06 11:40:15] [WARN] 接続が不安定です
[2026-01-06 11:40:15] [ERROR] データベース接続エラー
[2026-01-06 17:38:11] [INFO] ゲーム開始
[2026-01-06 17:38:11] [WARN] HPが低下しています
[2026-01-06 17:38:11] [ERROR] ファイルの読み込みに失敗
[2026-01-06 17:38:11] [INFO] プレイヤーがログインしました
[2026-01-06 17:38:11] [WARN] 接続が不安定です
[2026-01-06 17:38:11] [ERROR] データベース接続エラー
解答例を見る
import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
/**
* ファイルへのログ出力を簡単にするラッパークラス
*/
public class SimpleLogger {
private final String logFilePath;
private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
/**
* コンストラクタ
* @param logFilePath ログファイルのパス
*/
public SimpleLogger(String logFilePath) {
this.logFilePath = logFilePath;
}
/**
* INFOレベルのログを出力
* @param message ログメッセージ
*/
public void info(String message) {
writeLog("INFO", message);
}
/**
* WARNレベルのログを出力
* @param message ログメッセージ
*/
public void warn(String message) {
writeLog("WARN", message);
}
/**
* ERRORレベルのログを出力
* @param message ログメッセージ
*/
public void error(String message) {
writeLog("ERROR", message);
}
/**
* ログをファイルに書き込む
* @param level ログレベル
* @param message ログメッセージ
*/
private void writeLog(String level, String message) {
String timestamp = LocalDateTime.now().format(FORMATTER);
String logEntry = String.format("[%s] [%s] %s%n", timestamp, level, message);
try (BufferedWriter writer = new BufferedWriter(new FileWriter(logFilePath, true))) {
writer.write(logEntry);
} catch (IOException e) {
System.err.println("ログ書き込みエラー: " + e.getMessage());
}
}
}
動作確認クラス
public class SimpleLoggerExample {
public static void main(String[] args) {
// ログファイルのパスを指定してインスタンス化
SimpleLogger logger = new SimpleLogger("game.log");
// 各レベルのログを出力
logger.info("ゲーム開始");
logger.warn("HPが低下しています");
logger.error("ファイルの読み込みに失敗");
// 追加の使用例
logger.info("プレイヤーがログインしました");
logger.warn("接続が不安定です");
logger.error("データベース接続エラー");
System.out.println("ログファイル 'game.log' に出力されました");
}
}
参考資料
デザインパターン
- Adapter Pattern: 既存のクラスを新しいインターフェースに適合させる
- Facade Pattern: 複雑なサブシステムに対して簡単なインターフェースを提供
- Decorator Pattern: オブジェクトに動的に機能を追加
参考リンク
関連記事
Java Quest Part 2
まとめ
今回学んだこと
1.ラッパークラスとは
- 使いにくいものを使いやすく包むクラス
- スマホケース、お弁当箱、電子レンジのボタンと同じ考え方
2.メリット
- コードが短くて読みやすい
- 型変換・範囲チェックが自動
- ミスが減る
- 保守性が高い
3.作り方
- 内部で複雑なクラスを持つ
- 簡単なメソッドを外部に公開
- 型変換・バリデーションを自動化
-
設計のポイント
- シンプルなインターフェース
- 型安全性
- エラー処理
- バリデーション
実践で使おう
ラッパークラスは実務でも非常によく使われる重要なテクニックです。
こんな時に使える:
- 複雑なライブラリを使いやすくしたい
- 同じ処理を何度も書いている
- エラー処理をまとめたい
- チームで共通のコードを使いたい
ぜひ自分のプロジェクトでも活用してみてください!
本記事は教育目的で作成されています。RPGの例を通じてプログラミング概念を楽しく学習できることを目指しています。
