1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【Java】Lombokで脱・定型コード!アノテーション別に完全解説

1
Posted at

はじめに

Javaでクラスを書くたびに、getter/setter/コンストラクタ/toString...と定型コードを量産していませんか?

Lombokを使えば、アノテーション1つで定型コード(ボイラープレート)を自動生成できます。

この記事では、Lombokの主要アノテーションをLombokなしのコードと比較しながら解説します。

Lombokとは

LombokはJavaのコンパイル時にコードを自動生成するライブラリです。

  • Spring専用ではなく、どんなJavaプロジェクトでも使える
  • コンパイル時に処理されるため、実行時のオーバーヘッドはゼロ
  • 生成されるコードはIDEの「Structure」や「Outline」で確認可能

導入方法(Maven)

<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>1.18.34</version>
    <scope>provided</scope>
</dependency>

導入方法(Gradle)

dependencies {
    compileOnly 'org.projectlombok:lombok:1.18.34'
    annotationProcessor 'org.projectlombok:lombok:1.18.34'
}

scopeprovided(Maven)/ compileOnly(Gradle)なのは、コンパイル時のみ必要で実行時には不要なためです。

主要アノテーション一覧

アノテーション 生成されるもの
@Getter 全フィールドのgetter
@Setter 全フィールドのsetter
@NoArgsConstructor 引数なしコンストラクタ
@AllArgsConstructor 全フィールド引数のコンストラクタ
@RequiredArgsConstructor finalフィールドのみ引数にとるコンストラクタ
@ToString toString()
@EqualsAndHashCode equals() + hashCode()
@Data 上記の主要アノテーションをまとめたもの
@Builder Builderパターン
@Slf4j ロガーフィールドの自動生成

1. @Getter / @Setter

Lombokなし

public class User {
    private String name;
    private int age;
    private boolean active;

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

    public int getAge() { return age; }
    public void setAge(int age) { this.age = age; }

    // boolean型のgetterは isXxx() になる(JavaBeans規約)
    public boolean isActive() { return active; }
    public void setActive(boolean active) { this.active = active; }
}

Lombokあり

import lombok.Getter;
import lombok.Setter;

@Getter
@Setter
public class User {
    private String name;
    private int age;
    private boolean active;
}

フィールドが増えても、アノテーションはそのままです。

フィールド単位での指定

クラス全体ではなく、特定のフィールドだけに付けることもできます。

import lombok.Getter;
import lombok.Setter;

public class User {
    @Getter
    private String name;    // getterのみ生成

    @Getter @Setter
    private int age;        // getter + setter両方生成

    private boolean active; // 何も生成されない
}

アクセス修飾子の制御

import lombok.AccessLevel;
import lombok.Getter;
import lombok.Setter;

@Getter
@Setter
public class User {
    private String name;

    // setterを外部に公開したくない場合
    @Setter(AccessLevel.PROTECTED)
    private int age;

    // setterを生成しない
    @Setter(AccessLevel.NONE)
    private String id;
}

2. @NoArgsConstructor / @AllArgsConstructor

Lombokなし

public class Product {
    private Long id;
    private String name;
    private int price;

    // 引数なしコンストラクタ
    public Product() {}

    // 全フィールド引数コンストラクタ
    public Product(Long id, String name, int price) {
        this.id = id;
        this.name = name;
        this.price = price;
    }
}

Lombokあり

import lombok.NoArgsConstructor;
import lombok.AllArgsConstructor;

@NoArgsConstructor
@AllArgsConstructor
public class Product {
    private Long id;
    private String name;
    private int price;
}

JPAの@Entityは引数なしコンストラクタを必要とするため、@NoArgsConstructorはJPAエンティティでよく使われます。

3. @RequiredArgsConstructor

finalフィールドと@NonNullフィールドだけを引数にとるコンストラクタを生成します。

Lombokなし

public class OrderService {
    private final OrderRepository orderRepository;
    private final NotificationService notificationService;

    public OrderService(OrderRepository orderRepository,
                        NotificationService notificationService) {
        this.orderRepository = orderRepository;
        this.notificationService = notificationService;
    }
}

Lombokあり

import lombok.RequiredArgsConstructor;

@RequiredArgsConstructor
public class OrderService {
    private final OrderRepository orderRepository;
    private final NotificationService notificationService;
}

Spring Bootではコンストラクタが1つだけの場合、@Autowiredを省略できるため、@RequiredArgsConstructorとの相性が非常に良いです。

import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;

@Service
@RequiredArgsConstructor  // コンストラクタインジェクションを自動化
public class TodoService {
    private final TodoRepository todoRepository;
    // @Autowired 不要!
}

4. @ToString

Lombokなし

public class User {
    private String name;
    private int age;

    @Override
    public String toString() {
        return "User(name=" + name + ", age=" + age + ")";
    }
}

Lombokあり

import lombok.ToString;

@ToString
public class User {
    private String name;
    private int age;
}
// User(name=太郎, age=25) のように出力される

特定フィールドの除外

パスワードなどの機密情報を出力から除外できます。

import lombok.ToString;

@ToString
public class User {
    private String name;

    @ToString.Exclude
    private String password;  // toStringに含まれない
}

5. @EqualsAndHashCode

equals()hashCode()を全フィールドから自動生成します。

Lombokなし

import java.util.Objects;

public class User {
    private String name;
    private int age;

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        User user = (User) o;
        return age == user.age && Objects.equals(name, user.name);
    }

    @Override
    public int hashCode() {
        return Objects.hash(name, age);
    }
}

Lombokあり

import lombok.EqualsAndHashCode;

@EqualsAndHashCode
public class User {
    private String name;
    private int age;
}

注意:JPAエンティティでの使用

JPAエンティティで@EqualsAndHashCodeを使う場合は注意が必要です。

import lombok.EqualsAndHashCode;
import jakarta.persistence.*;

@Entity
@EqualsAndHashCode(onlyExplicitlyIncluded = true)  // 明示的に指定したフィールドのみ使用
public class Todo {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @EqualsAndHashCode.Include  // IDのみで比較
    private Long id;

    private String title;
    private boolean completed;
}

全フィールドで比較すると、遅延ロードされた関連エンティティへのアクセスが発生し、パフォーマンス問題やLazyInitializationExceptionの原因になります。

6. @Data

@Getter + @Setter + @ToString + @EqualsAndHashCode + @RequiredArgsConstructor をまとめたアノテーションです。

import lombok.Data;

@Data
public class UserDto {
    private String name;
    private int age;
    private String email;
}

これだけで、getter/setter/toString/equals/hashCode/コンストラクタがすべて生成されます。

@Dataを避けるべきケース

ケース 理由 代替
JPAエンティティ @EqualsAndHashCodeが全フィールド比較になり危険 @Getter @Setterを個別指定
イミュータブルにしたいクラス @Setterが生成されてしまう @Valueを使う

7. @Builder

Builderパターンを自動生成します。フィールドが多いクラスで特に有効です。

Lombokなし

public class User {
    private String name;
    private int age;
    private String email;
    private String phone;

    // Builderクラスを手動で書くと数十行に...
}

Lombokあり

import lombok.Builder;
import lombok.ToString;

@Builder
@ToString
public class User {
    private String name;
    private int age;
    private String email;
    private String phone;
}
public class Main {
    public static void main(String[] args) {
        User user = User.builder()
                .name("太郎")
                .age(25)
                .email("taro@example.com")
                // phoneは省略可能
                .build();

        System.out.println(user);
        // User(name=太郎, age=25, email=taro@example.com, phone=null)
    }
}

8. @Slf4j

ロガーフィールドを自動生成します。

Lombokなし

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class TodoService {
    private static final Logger log = LoggerFactory.getLogger(TodoService.class);

    public void create() {
        log.info("Todo作成");
    }
}

Lombokあり

import lombok.extern.slf4j.Slf4j;

@Slf4j
public class TodoService {
    public void create() {
        log.info("Todo作成");
    }
}

9. @Value(イミュータブルクラス)

@Dataのイミュータブル版です。全フィールドがprivate finalになり、setterは生成されません。

import lombok.Value;

@Value
public class Money {
    int amount;     // 自動的に private final になる
    String currency;
}
public class Main {
    public static void main(String[] args) {
        Money money = new Money(1000, "JPY");
        System.out.println(money.getAmount());    // 1000
        System.out.println(money.getCurrency());  // JPY
        // money.setAmount(2000);  // コンパイルエラー!setterがない
    }
}

実践:Spring Boot + Lombokの定番パターン

// Entity
import jakarta.persistence.*;
import lombok.*;

@Entity
@Getter @Setter
@NoArgsConstructor
@AllArgsConstructor
public class Todo {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String title;
    private boolean completed;
}
// Repository
import org.springframework.data.jpa.repository.JpaRepository;

public interface TodoRepository extends JpaRepository<Todo, Long> {
}
// Service
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import java.util.List;

@Service
@RequiredArgsConstructor  // コンストラクタインジェクション
public class TodoService {
    private final TodoRepository todoRepository;

    public List<Todo> findAll() {
        return todoRepository.findAll();
    }
}
// Controller
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*;
import java.util.List;

@RestController
@RequestMapping("/api/todos")
@RequiredArgsConstructor  // コンストラクタインジェクション
public class TodoController {
    private final TodoService todoService;

    @GetMapping
    public List<Todo> findAll() {
        return todoService.findAll();
    }
}

ポイントは**Service/Controllerでの@RequiredArgsConstructor**です。finalフィールドへのDIを簡潔に書けます。

まとめ

用途 アノテーション
getter/setter @Getter @Setter
コンストラクタ @NoArgsConstructor @AllArgsConstructor @RequiredArgsConstructor
toString @ToString
equals/hashCode @EqualsAndHashCode
全部入り(DTO向け) @Data
全部入り(イミュータブル) @Value
Builderパターン @Builder
ロガー @Slf4j
DI(Spring Boot) @RequiredArgsConstructor + finalフィールド

JPAエンティティには@Dataではなく@Getter @Setterを個別に使うのが安全です。

演習問題

問題1 ⭐(基本)

以下のクラスにLombokアノテーションを付けて、手書きのgetter/setter/コンストラクタを削除してください。

public class Book {
    private Long id;
    private String title;
    private String author;
    private int price;

    public Book() {}

    public Book(Long id, String title, String author, int price) {
        this.id = id;
        this.title = title;
        this.author = author;
        this.price = price;
    }

    public Long getId() { return id; }
    public void setId(Long id) { this.id = id; }
    public String getTitle() { return title; }
    public void setTitle(String title) { this.title = title; }
    public String getAuthor() { return author; }
    public void setAuthor(String author) { this.author = author; }
    public int getPrice() { return price; }
    public void setPrice(int price) { this.price = price; }
}
模範解答
import lombok.*;

@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
public class Book {
    private Long id;
    private String title;
    private String author;
    private int price;
}

問題2 ⭐⭐(応用)

以下の要件を満たすSpring Bootのサービスクラスを、Lombokを使って書いてください。

  • クラス名: BookService
  • BookRepositoryNotificationServiceをDIする
  • @Autowiredは使わない
  • ログ出力用のロガーを持つ
模範解答
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

@Service
@RequiredArgsConstructor
@Slf4j
public class BookService {
    private final BookRepository bookRepository;
    private final NotificationService notificationService;

    public void create(Book book) {
        log.info("本を登録: {}", book.getTitle());
        bookRepository.save(book);
        notificationService.notify("新しい本が登録されました");
    }
}

@RequiredArgsConstructorfinalフィールドを引数にとるコンストラクタを生成し、Springが自動的にDIしてくれます。

問題3 ⭐⭐⭐(チャレンジ)

以下の要件を満たすOrderRequestクラスをLombokで作成してください。

  • フィールド: productName(String), quantity(int), unitPrice(int), note(String)
  • Builderパターンで生成できる
  • インスタンス生成後は変更不可(イミュータブル)
  • toString()で中身を確認できる
  • noteのデフォルト値は"なし"
模範解答
import lombok.Builder;
import lombok.Value;

@Value
@Builder
public class OrderRequest {
    String productName;
    int quantity;
    int unitPrice;

    @Builder.Default
    String note = "なし";
}
public class Main {
    public static void main(String[] args) {
        OrderRequest order1 = OrderRequest.builder()
                .productName("ノートPC")
                .quantity(2)
                .unitPrice(150000)
                .build();

        System.out.println(order1);
        // OrderRequest(productName=ノートPC, quantity=2, unitPrice=150000, note=なし)

        OrderRequest order2 = OrderRequest.builder()
                .productName("マウス")
                .quantity(5)
                .unitPrice(3000)
                .note("ワイヤレスタイプ希望")
                .build();

        System.out.println(order2);
        // OrderRequest(productName=マウス, quantity=5, unitPrice=3000, note=ワイヤレスタイプ希望)
    }
}

@Valueはフィールドをprivate finalにしてsetterを生成しないため、イミュータブルになります。@Builder.DefaultでBuilderパターン使用時のデフォルト値を指定できます。

参考


@kotaro_ai_lab
AI活用や開発効率化について発信しています。フォローお気軽にどうぞ!

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?