Java

[Java]Generic(Interface에서의 활용)

goblin- 2024. 9. 28. 16:40

**제네릭(Generic)**은 인터페이스에서도 유용하게 사용됩니다. 제네릭을 사용하면 인터페이스를 구현하는 클래스들이 구체적인 데이터 타입을 명시할 수 있어, 보다 유연하고 타입 안전한 코드를 작성할 수 있습니다. 이 방식은 특히 다양한 타입을 처리해야 하는 클래스를 작성할 때 많이 사용됩니다.

 

1. 제네릭을 사용한 인터페이스 정의

 

제네릭을 사용한 인터페이스는 타입 매개변수를 통해 다양한 타입의 객체를 처리할 수 있습니다. 제네릭 타입을 인터페이스 선언부에 추가하면, 인터페이스를 구현하는 클래스는 그 타입을 명시하여 구체화할 수 있습니다.

 

예시: 제네릭을 사용한 인터페이스

// 제네릭 인터페이스 선언
public interface Repository<T> {
    T findById(int id);
    void save(T entity);
}

여기서 **T**는 타입 매개변수로, 구체적인 데이터 타입을 지정하지 않았습니다. **T**는 나중에 인터페이스를 구현하는 클래스에서 결정됩니다.

 

2. 제네릭 인터페이스 구현

 

제네릭 인터페이스를 구현할 때, 구현 클래스에서 구체적인 타입을 지정할 수 있습니다. 제네릭 인터페이스를 통해 다양한 데이터 타입을 처리할 수 있게 됩니다.

 

예시: 제네릭 인터페이스 구현

// User 엔티티
public class User {
    private int id;
    private String name;

    // 생성자, getter, setter 생략
}

// UserRepository는 User 타입의 데이터를 처리하는 인터페이스 구현체
public class UserRepository implements Repository<User> {

    @Override
    public User findById(int id) {
        // 데이터베이스에서 User를 찾는 로직 (가상 로직)
        return new User(id, "User" + id);
    }

    @Override
    public void save(User user) {
        // 데이터베이스에 User를 저장하는 로직 (가상 로직)
        System.out.println("User saved: " + user.getName());
    }
}

UserRepository는 **Repository<User>**로 제네릭 타입을 User로 지정하였습니다.

Repository<T>에서 사용된 모든 T 타입User로 구체화됩니다. 즉, findByIdsave 메서드는 User 객체를 처리하게 됩니다.

 

3. 다양한 타입을 처리하는 인터페이스 구현

 

같은 제네릭 인터페이스를 다양한 타입으로 구현할 수 있습니다. 예를 들어, Product라는 클래스가 있고, 같은 제네릭 인터페이스를 사용하여 Product를 처리할 수 있습니다.

 

예시: 다른 타입을 사용한 제네릭 인터페이스 구현

// Product 엔티티
public class Product {
    private int id;
    private String name;

    // 생성자, getter, setter 생략
}

// ProductRepository는 Product 타입의 데이터를 처리하는 인터페이스 구현체
public class ProductRepository implements Repository<Product> {

    @Override
    public Product findById(int id) {
        // 데이터베이스에서 Product를 찾는 로직 (가상 로직)
        return new Product(id, "Product" + id);
    }

    @Override
    public void save(Product product) {
        // 데이터베이스에 Product를 저장하는 로직 (가상 로직)
        System.out.println("Product saved: " + product.getName());
    }
}

이번에는 **ProductRepository**가 **Repository<Product>**로 구체화되었습니다.

동일한 인터페이스 **Repository<T>**를 사용하지만, 각각 **User**와 **Product**에 대해 다른 데이터 타입을 처리할 수 있습니다.

 

4. 제네릭 타입 경계 설정 (Bounded Generics)

 

때로는 제네릭 인터페이스에서 타입에 제한을 두고 싶을 때가 있습니다. 이때는 제네릭의 상한 경계(Upper Bounded Generics)를 설정하여, 특정 클래스나 인터페이스를 상속한 타입만 사용하도록 제한할 수 있습니다.

 

예시: 타입 경계를 설정한 제네릭 인터페이스

// Comparable<T> 인터페이스를 상속한 타입만 처리할 수 있도록 경계 설정
public interface ComparableRepository<T extends Comparable<T>> {
    void compare(T entity1, T entity2);
}

// Comparable을 상속한 String 타입을 처리하는 구현체
public class StringRepository implements ComparableRepository<String> {

    @Override
    public void compare(String entity1, String entity2) {
        int result = entity1.compareTo(entity2);
        System.out.println("Comparison result: " + result);
    }
}

**T extends Comparable<T>**로 타입 경계를 설정했습니다. 이제 Comparable을 상속한 클래스만 제네릭 인터페이스로 사용할 수 있습니다.

**StringRepository**는 String을 처리하는 제네릭 인터페이스의 구현체입니다.

 

5. 와일드카드를 사용한 제네릭 인터페이스

 

때로는 제네릭 인터페이스를 사용할 때 타입의 범위를 유연하게 만들고 싶을 때가 있습니다. 이때는 **와일드카드(?)**를 사용하여 타입 경계를 설정할 수 있습니다.

 

예시: 와일드카드를 사용한 제네릭 인터페이스

public interface Printer<T> {
    void print(T item);
}

// 와일드카드를 사용한 메서드
public class WildcardPrinter {
    
    // Printer의 하위 타입을 모두 처리할 수 있음
    public void printItems(Printer<? extends Number> printer) {
        printer.print(123);
    }
}

**? extends Number**는 Number 타입과 그 하위 타입만 허용합니다. 이를 통해 유연한 타입 처리가 가능합니다.

와일드카드로 인해 Integer, Double다양한 타입의 Number를 처리할 수 있습니다.

 

6. 제네릭 인터페이스의 실용적 예시 (스프링의 Repository)

 

스프링 프레임워크에서는 제네릭 인터페이스가 자주 사용됩니다. 예를 들어, 스프링 데이터 JPA의 **CrudRepository<T, ID>**는 제네릭 인터페이스로 정의되어 있어, 다양한 엔티티에 대해 CRUD 기능을 제공합니다.

 

스프링 데이터 JPA에서의 제네릭 인터페이스 예시

import org.springframework.data.repository.CrudRepository;

// CrudRepository 인터페이스는 제네릭으로 다양한 타입을 처리할 수 있음
public interface UserRepository extends CrudRepository<User, Long> {
}

**CrudRepository<T, ID>**는 제네릭 인터페이스로, T는 엔티티 클래스, ID는 엔티티의 ID 타입을 나타냅니다.

UserRepositoryUser 엔티티에 대해 CRUD 기능을 제공하며, ID 타입은 **Long**으로 설정되어 있습니다.

 

7. 제네릭을 사용한 인터페이스의 장점

 

1. 재사용성: 하나의 제네릭 인터페이스로 다양한 타입의 객체를 처리할 수 있습니다.

2. 타입 안전성: 제네릭을 사용하면 컴파일 시점에 타입을 체크하므로, 런타임 타입 오류를 방지할 수 있습니다.

3. 유연성: 제네릭 인터페이스는 다양한 데이터 타입을 처리할 수 있으며, 필요에 따라 타입 경계를 설정할 수 있습니다.

 

결론

 

제네릭 인터페이스타입에 유연성을 제공하면서도 타입 안전성을 보장하는 강력한 기능입니다.

제네릭을 사용하면 하나의 인터페이스다양한 데이터 타입을 처리할 수 있으며, 이는 특히 재사용성유연성이 중요한 시스템에서 매우 유용합니다.

타입 경계를 설정하거나 와일드카드를 사용하여 더 세밀하게 타입을 관리할 수 있습니다.

제네릭 인터페이스는 특히 스프링 프레임워크와 같은 라이브러리에서 데이터 처리에 많이 사용됩니다.