Eclipseで最初のJavaプロジェクトを作る(パッケージ/クラス設計の基本)

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

本記事のゴールと前提

  • Eclipseで新規Javaプロジェクトを正しく作れる
  • 適切なパッケージ構成クラス分割で小さなアプリを組み立てられる
  • Run As > Java Application で実行して結果を確認できる

JDKやEclipseの導入がまだの方は、先に 導入記事(JDK設定まで) をご覧ください。

新規プロジェクト作成(Eclipse)

  1. File > New > Java Project を選択。
  2. Project namefirst-java-project(空白や日本語は避ける)
  3. JREUse an execution environment JRE: JavaSE-21(またはインストール済みJDK)
  4. Project layout:既定のままでOK(ソースは src
  5. Module(module-info.java)について:最初は作らないほうが簡単。もし自動作成された場合は一旦削除して構いません。
  6. 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) 実行と確認

  1. Package ExplorerMain を右クリック → Run As > Java Application
  2. コンソールに > が出たら成功。以下の例を試す:
add 牛乳を買う
add 本を10ページ読む
list
done 1
rename 2 本を15ページ読む
list
exit

メモリ内リポジトリなので、Eclipseを終了するとデータは消えます。後日、ファイル保存やDBに差し替えてみましょう(repository層だけ差し替えればOK)。

設計の基本原則(最初に押さえる3点)

  1. 関心の分離(SoC):入出力(app)と業務ロジック(service)とデータアクセス(repository)を分ける。
  2. 不変条件の保持:domainのコンストラクタ/メソッドで不正な状態を拒否(例:タイトルの空文字禁止)。
  3. 依存方向は内側へ:app → service → repository → domain の一方向。下位層は上位層を参照しない。

この3つを守るだけで、クラスが巨大化しにくく、機能追加や差し替えが楽になります。

よくあるエラーと対処

  • Could not find or load main classMain のパッケージ宣言(package com.example.todo.app;)とフォルダ構成が一致しているか確認。
  • 違うJREで実行されるProject > Properties > Java Build Path → JRE System Library をJDK21に。Project FacetsCompiler のバージョンも見直し。
  • 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)。
  • FormatterPreferences > Java > Code Style > Formatter でプロファイル作成(チーム運用の第一歩)。
  • Organize Imports:ショートカット割当(例:Windows: Ctrl+Shift+O, Mac: ⌘⇧O)。
  • Quick FixCtrl+1 / ⌘1 で警告から即修正。

この次に知りたい情報

導入〜完成チェックリスト

  • Java Project を first-java-project で作成した
  • com.example.todo 配下に domain/repository/service/app を作成した
  • 各クラスを入力し、エラーがない(電球マークはQuick Fixで解消)
  • MainRun As で実行してコマンドが動作した
  • Save Actions / Formatter を設定してコードが自動整形される

FAQ

パッケージ名は日本語でもいい?

推奨しません。英小文字+ドット区切り(例:com.example.todo)に統一しましょう。

クラスは1ファイル1クラス?

基本は1ファイル1公開クラス(public)です。小規模の補助クラスはパッケージプライベートで同パッケージに置いてOK。

テストはどう書く?

次段のMaven/Gradle導入後に JUnit を使うのが定番です。まずは service のメソッド単位から始めましょう。

著者:まーくん|更新日:2025-08-25

コメントを残す

*

CAPTCHA