본문 바로가기
스터디/[white-ship] 자바 스터디(study hale)

8주자 과제: 인터페이스

by doyoungKim 2021. 1. 8.

목표

자바의 인터페이스에 대해 학습하세요.

학습할 것 (필수)

  • 인터페이스 정의하는 방법
  • 인터페이스 구현하는 방법
  • 인터페이스 레퍼런스를 통해 구현체를 사용하는 방법
  • 인터페이스 상속
  • 인터페이스의 기본 메소드 (Default Method), 자바 8
  • 인터페이스의 static 메소드, 자바 8
  • 인터페이스의 private 메소드, 자바 9

인터페이스

위키백과에서 인터페이스는 서로 다른 두 개의 시스템, 장치 사이에서 정보나 신호를 주고받는 경우의 접점이나 경계면이다. 

자바 라는 객체지향 언어에서는 상속을 통한 다형성은 조상클래스의 프로퍼티와 기능을 확장 시킨 것이라면,
인터페이스의 다형성은 두 시스템 에서 상호작용 할 수 있는 접점부분에서 규약을 만듬으로써 보다 다양한 상호작용을 하는 것이다.

자바에서는 이런 규약을 인터페이스라고 부른다.

인터페이스 정의하는 방법

자바에서는 interface 라는 예약어를 통해 인터페이스를 정의한다.

예를 들어, 데이터베이스와 자바 어플리케이션 사이에서의 접점 부분에서 상호작용하는 규약(인터페이스)를 만들 수 있다.

interface CrudRepository {

}

 

그 상호작용하는 내용중에서는 자바 어플리케이션이 데이터베이스 테이블에 데이터를 조회 하는 상호작용이 있을 수 있다.

위에서 방금 테이블에 데이터를 조회 한다고 했지 어떤 테이블에 데이터를 조회 한다고는 안했다.

추상적으로 테이블 데이터를 조회 (기능 = 메소드) 한다고 했다.

따라서 인터페이스 내부에는 추상메소드 가 존재한다.

public interface CrudRepository<T, ID> {

    Iterable<T> findAll();

    Optional<T> findById(ID id);

}

 

인터페이스 구현하는 방법

추상메소드를 구체화 시켜 유저 테이블, 공지사항 테이블 등 같은 추상화된 규약을 따라 구체적으로 테이블을 조회 하는 클래스를 구현 할 수 있다.

위의 인터페이스를 추상적인 메소드를 구체적으로 작동하도록 구현하기위해서 implement 라는 예약어를 통해서 클래스를 생성 할 수 있다.

public class UserRepository implements CrudRepository<User, Long> {
}

 

위와 같이 작성하면 아래와 같은 오류메시지를 볼 수 있다. 왜냐하면 추상적인 상호작용을 구체화 시키지 않았기 때문에 구현하라는 오류메시지이다.

오류메시지

따라서 오버라이딩을 통해 구체적으로 구현할 수 있다.

public class UserRepository implements CrudRepository<User, Long> {
    @Override
    public Iterable<User> findAll() {
        System.out.println("user 객체 전부를 조회");
        return null;
    }

    @Override
    public Optional<User> findById(Long id) {
		System.out.printf("%d 가 아이디인 user 객체 조회 \n", id);
        return Optional.empty();
    }
}

 

 

인터페이스 레퍼런스를 통해 구현체를 사용하는 방법

다형성을 공부하면 자손클래스의 인스턴스를 부모타입의 참조변수로 참조하는 것이 가능하다는 것을 알 수 있다.

인터페이스도 이를 구현한 클래스의 부모라 할 수 있으므로 해당 인터페이스 타입의 참조변수로클래스의 인스턴스를 참조할 수 있으며, 인터페이스 타입으로 형변환도 가능하다.

public class UserService {
    public static void main(String[] args) {
        final CrudRepository userRepository = new UserRepository();
        final Optional user = userRepository.findById(1L);

        final UserRepository userRepository2 = new UserRepository();
        final Optional user2 = userRepository2.findById(1L);
    }
}
// 출력 결과
// 1 가 아이디인 user 객체 조회 
// 1 가 아이디인 user 객체 조회 

 

인터페이스 하위성

public interface CrudRepository<T, ID> {

    Iterable<T> findAll();

    Optional<T> findById(ID id);

    Iterable<T> findAll(Sort var1);

    Page<T> findAll(Pageable var1);

}

 

하지만 이렇게 인터페이스를 수정한다면 이 인터페이스를 기반으로 개발된 모든 Repository 에서 추가된 추상메소드를 구현해야 할 일 이 생긴다. 따라서 굳이 구현해야할 필요 없는 Repository 도 포함되기에 다음과 같이 구현부분이 빈 메소드들이 생길 것이다.

public class UserRepository implements CrudRepository<User, Long> {

	...
    
    @Override
    public Iterable<User> findAll(Sort var1) {
        return null;
    }

    @Override
    public Page<User> findAll(Pageable var1) {
        return null;
    }
}

 

 

 

인터페이스 상속

기존에 만든 CrudRepository 의 인터페이스는 데이터를 조회 하는 상호작용에서 조회한 데이터를 정렬하거나 페이징 하는 상호작용을 추가 할 수있다.

따라서 자바에서는 이런 상황에서 상속을 통해 해결 할 수 있다.

상속을 통해서 상호작용을 확장하고 필요한 구현체에서만 적용하면 되기 때문이다.

public interface PagingAndSortingRepository<T, ID> extends CrudRepository<T, ID> {
    Iterable<T> findAll(Sort var1);

    Page<T> findAll(Pageable var1);
}

 

 

인터페이스의 기본 메소드 (Default Method), 자바 8

다형성인 측면에서 좀더 객체에 역할을 작고 다양하게 하기 위해서는 인터페이스에서 상속을 사용하지만, 위 문제를 상속으로 해결하게 되면
상속받아 만든 새로운 인터페이스를 구현해야하는 상황이 생긴다 또한, 만약에 오픈소스로 제공되었던 인터페이스라면 골칫거리일 것이다. 따라서 자바에서는 추상메소드가 아닌 기본메소드를 인터페이스에서 제공하기로한다 그것이 바로 default method 이다.

public interface CrudRepository<T, ID> {

    Iterable<T> findAll();

    Optional<T> findById(ID id);

    default Iterable<T> findAll(Sort var1) {
        return null;
    }

    default Page<T> findAll(Pageable var1) {
        return null;
    }

}

 

그대로인 UserRepository

public class UserRepository implements CrudRepository<User, Long> {
    @Override
    public Iterable<User> findAll() {
        System.out.println("user 객체 전부를 조회");
        return null;
    }

    @Override
    public Optional<User> findById(Long id) {
        System.out.printf("%d 가 아이디인 user 객체 조회 \n", id);
        return Optional.empty();
    }
    
}

 

 

인터페이스의 static 메소드, 자바 8

인스턴스에 바로 접근해서 사용할 수 있는 static 메소드이다. 또한 다른 특징은 구현체에서 다시 재정의 할 수 없다.

interface User {
    static String getType() {
        return "User";
    }

    void say();
}

class Korean implements User {

//  에러 발생
//	@override 
//    static String getType() {
//        return "Korean";
//    }
    
    public void say() {
        System.out.println("korea");
    }
}

class App {
    public static void main(String[] args) {
        User kor = new Korean();
        String userType = User.getType();
        String korType = kor.getType();	// 에러발생
    }
}

 

static 메소드도 마찬가지로 하위호환성에 좋다. 예시로 자바 9 에서 List 인터페이스 static 메소드 of 가 새로 생겼지만 하위 문제성없이 사용할 수 있었다.

 

 

인터페이스의 private 메소드, 자바 9

인터페이스에 추상메소드가 아닌 일반메소드가 등장하기 시작하며서 일반메소드 내에 중복적인 코드를 제거할 수있는 private 메소드가 등장했다.

public interface privateInterface {
    default void hello() {
        say("hello");
    }

    default void bye() {
        say("bye");
    }

    private void say(String message) {
        System.out.println(message);
    }
}

 

 

 

만약 다중으로 구현할 때 디폴트 메소드 이름이 겹치면?

public interface JoinGroup {
    default void preJoin (){ }
    default void afterJoin (){ }
}


public interface JoinMember {
    default void preJoin (){ }
    default void afterJoin (){ }
}


 

다음과 같이super를 통해서 접근 할 수있다.

public class HelloMemberJoin implements JoinGroup, JoinMember {

    @Override
    public void preJoin() {
        JoinGroup.super.preJoin();
        JoinGroup.super.afterJoin();
    }

    @Override
    public void afterJoin() {

    }
}

 

 

출처

728x90

댓글