
初回プロジェクトの作成から、正しいパッケージ設計・クラス分割・動作確認までをステップ形式で解説。記事のとおりに進めれば、Eclipseで「動くJavaアプリ」を作りながら、現場でも通用する設計の土台が身につきます。
本記事のゴールと前提

- Eclipseで新規Javaプロジェクトを正しく作れる
- 適切なパッケージ構成とクラス分割で小さなアプリを組み立てられる
Run As > Java Application
で実行して結果を確認できる
JDKやEclipseの導入がまだの方は、先に 導入記事(JDK設定まで) をご覧ください。
新規プロジェクト作成(Eclipse)
- File > New > Java Project を選択。
- Project name:
first-java-project
(空白や日本語は避ける) - JRE:
Use an execution environment JRE: JavaSE-21
(またはインストール済みJDK) - Project layout:既定のままでOK(ソースは
src
) - Module(module-info.java)について:最初は作らないほうが簡単。もし自動作成された場合は一旦削除して構いません。
- Finish(初回だけ「Open Associated Perspective?」は Yes でOK)。
続けて、src を右クリック → New > Package でパッケージを作ります(次章)。
パッケージ設計の基礎(命名規則と層)
パッケージは全部小文字で、逆ドメイン形式が一般的です。個人開発や学習では com.example
から始めると無難。
com.example.todo
├─ domain (アプリの中心的なデータ構造:Taskなど)
├─ repository (データの保存・取得:DBやメモリ)
├─ service (業務ロジック:検証・集約・操作)
└─ app (UI層/エントリポイント:mainメソッド)
- domain:ビジネスで扱う名詞(Task, User, Order など)
- repository:データのCRUDを隠蔽。後でDBに差し替えても他層に影響が少ない
- service:ユースケース(「タスク追加」「完了にする」等)の手順を1か所に集約
- app:入出力部分。今回はコンソール。将来Web/UIに変わっても他層は再利用できる
この4層に分けるだけで、コードの見通しと拡張性が大幅に上がります。

サンプルで学ぶ:コンソールToDoアプリ
最小限のToDo管理を作ります。タスクの追加/一覧/完了を、レイヤごとに役割分担して実装します。
1) domain:エンティティ(Task)
package com.example.todo.domain;
import java.time.LocalDateTime;
import java.util.Objects;
public class Task {
private final long id;
private String title;
private boolean done;
private final LocalDateTime createdAt;
public Task(long id, String title) {
this.id = id;
this.title = title;
this.done = false;
this.createdAt = LocalDateTime.now();
}
public long getId() { return id; }
public String getTitle() { return title; }
public boolean isDone() { return done; }
public LocalDateTime getCreatedAt() { return createdAt; }
public void rename(String newTitle) {
if (newTitle == null || newTitle.isBlank()) {
throw new IllegalArgumentException("タイトルは1文字以上で入力してください。");
}
this.title = newTitle;
}
public void markDone() { this.done = true; }
@Override
public String toString() {
return (done ? "[x] " : "[ ] ") + id + ": " + title + " (" + createdAt + ")";
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof Task)) return false;
Task task = (Task) o;
return id == task.id;
}
@Override
public int hashCode() {
return Objects.hash(id);
}
}
2) repository:データ取得
package com.example.todo.repository;
import com.example.todo.domain.Task;
import java.util.List;
import java.util.Optional;
public interface TaskRepository {
Task save(Task task);
Optional<Task> findById(long id);
List<Task> findAll();
}
package com.example.todo.repository;
import com.example.todo.domain.Task;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
public class InMemoryTaskRepository implements TaskRepository {
private final Map<Long, Task> store = new ConcurrentHashMap<>();
@Override
public Task save(Task task) {
store.put(task.getId(), task);
return task;
}
@Override
public Optional<Task> findById(long id) {
return Optional.ofNullable(store.get(id));
}
@Override
public List<Task> findAll() {
List<Task> list = new ArrayList<>(store.values());
list.sort(Comparator.comparing(Task::getCreatedAt));
return list;
}
}
3) service:業務ロジック
package com.example.todo.service;
import com.example.todo.domain.Task;
import com.example.todo.repository.TaskRepository;
import java.util.List;
import java.util.concurrent.atomic.AtomicLong;
public class TaskService {
private final TaskRepository repository;
private final AtomicLong sequence = new AtomicLong(1L);
public TaskService(TaskRepository repository) {
this.repository = repository;
}
public Task add(String title) {
if (title == null || title.isBlank()) {
throw new IllegalArgumentException("タイトルを入力してください。");
}
long id = sequence.getAndIncrement();
Task task = new Task(id, title.trim());
return repository.save(task);
}
public List<Task> list() {
return repository.findAll();
}
public Task complete(long id) {
Task task = repository.findById(id)
.orElseThrow(() -> new IllegalArgumentException("指定IDのタスクが見つかりません: " + id));
task.markDone();
repository.save(task);
return task;
}
public Task rename(long id, String newTitle) {
Task task = repository.findById(id)
.orElseThrow(() -> new IllegalArgumentException("指定IDのタスクが見つかりません: " + id));
task.rename(newTitle);
repository.save(task);
return task;
}
}
4) app:エントリポイント(Main)
package com.example.todo.app;
import com.example.todo.repository.InMemoryTaskRepository;
import com.example.todo.repository.TaskRepository;
import com.example.todo.service.TaskService;
import java.util.Scanner;
public class Main {
private static void printHelp() {
System.out.println("=== ToDo Commands ===");
System.out.println(" add <title> : タスク追加");
System.out.println(" list : 一覧表示");
System.out.println(" done <id> : 完了にする");
System.out.println(" rename <id> <t> : タイトル変更");
System.out.println(" help : コマンド一覧");
System.out.println(" exit : 終了");
System.out.println("=====================");
}
public static void main(String[] args) {
TaskRepository repo = new InMemoryTaskRepository();
TaskService service = new TaskService(repo);
try (Scanner sc = new Scanner(System.in)) {
printHelp();
while (true) {
System.out.print("> ");
String cmd = sc.next();
switch (cmd) {
case "add":
String title = sc.nextLine().trim();
System.out.println("追加: " + service.add(title));
break;
case "list":
service.list().forEach(System.out::println);
break;
case "done":
long id = sc.nextLong();
System.out.println("完了: " + service.complete(id));
break;
case "rename":
long rid = sc.nextLong();
String nt = sc.nextLine().trim();
System.out.println("変更: " + service.rename(rid, nt));
break;
case "help":
printHelp();
break;
case "exit":
System.out.println("Bye.");
return;
default:
System.out.println("不明なコマンド。help を参照してください。");
}
}
}
}
}
5) 実行と確認
- Package Explorerで
Main
を右クリック → Run As > Java Application - コンソールに
>
が出たら成功。以下の例を試す:
add 牛乳を買う
add 本を10ページ読む
list
done 1
rename 2 本を15ページ読む
list
exit
メモリ内リポジトリなので、Eclipseを終了するとデータは消えます。後日、ファイル保存やDBに差し替えてみましょう(repository層だけ差し替えればOK)。
設計の基本原則(最初に押さえる3点)
- 関心の分離(SoC):入出力(app)と業務ロジック(service)とデータアクセス(repository)を分ける。
- 不変条件の保持:domainのコンストラクタ/メソッドで不正な状態を拒否(例:タイトルの空文字禁止)。
- 依存方向は内側へ:app → service → repository → domain の一方向。下位層は上位層を参照しない。
この3つを守るだけで、クラスが巨大化しにくく、機能追加や差し替えが楽になります。
よくあるエラーと対処
- Could not find or load main class:
Main
のパッケージ宣言(package com.example.todo.app;
)とフォルダ構成が一致しているか確認。 - 違うJREで実行される:Project > Properties > Java Build Path → JRE System Library をJDK21に。Project Facetsや Compiler のバージョンも見直し。
- module-info.javaが邪魔:最初は削除してOK。モジュールは慣れてから。
- 文字化け:Preferences > General > Workspace > Text file encoding を UTF-8 に。コンソールも UTF-8 (必要なら VM引数に
-Dfile.encoding=UTF-8
)。 - ビルドされない:Project > Build Automatically を有効化。ダメなら Project > Clean…。
Eclipse便利設定(最初に済ませると幸せ)
- Save Actions:保存時に自動整形・不要import削除(Preferences > Java > Editor > Save Actions)。
- Formatter:Preferences > Java > Code Style > Formatter でプロファイル作成(チーム運用の第一歩)。
- Organize Imports:ショートカット割当(例:Windows:
Ctrl+Shift+O
, Mac:⌘⇧O
)。 - Quick Fix:
Ctrl+1 / ⌘1
で警告から即修正。
この次に知りたい情報

- Eclipse × Git(EGit)入門:初回コミットとリモート連携 — 変更履歴の管理とチーム開発の基礎。
- Maven/Gradle超入門:依存管理とビルドの基礎 — ライブラリ導入とビルドの標準化。
導入〜完成チェックリスト
- Java Project を
first-java-project
で作成した com.example.todo
配下にdomain/repository/service/app
を作成した- 各クラスを入力し、エラーがない(電球マークはQuick Fixで解消)
Main
を Run As で実行してコマンドが動作した- Save Actions / Formatter を設定してコードが自動整形される
FAQ
パッケージ名は日本語でもいい?
推奨しません。英小文字+ドット区切り(例:com.example.todo
)に統一しましょう。
クラスは1ファイル1クラス?
基本は1ファイル1公開クラス(public
)です。小規模の補助クラスはパッケージプライベートで同パッケージに置いてOK。
テストはどう書く?
次段のMaven/Gradle導入後に JUnit を使うのが定番です。まずは service
のメソッド単位から始めましょう。