JPA가 제공하는 기본 키 생성 전략

  • 직접 할당 전략 : 기본 키애플리케이션에서 직접 할당합니다.
  • 자동 생성 전략 : 대리 키를 사용하는 방식으로 IDENTITY, SEQUENCE, TABLE 전략이 있습니다.
    1. IDENTITY : 기본 키 생성을 데이터베이스위임하는 방식입니다.
    2. SEQUENCE : 데이터베이스시퀀스를 사용해서 기본 키할당하는 방식입니다.
    3. TABLE : 키 생성 테이블을 이용하는 방식입니다.
자동 생성 전략을 사용하려면 persistence.xmlhibernate.id.new_generator_mappings = true 속성을 추가해야 합니다. 이는 하이버네이트에서 JPA 규격에 맞으며 더 효과적으로 개발한 새로운 키 생성 전략이며 과거 버전과의 호환성유지를 위해 기본값false속성입니다. 이 옵션을 설정하면 키 생성 성능최적화하는 allocationSize 속성을 사용하는 방식이 달라진다고 합니다. 

 

기본 키 직접 할당 전략

  • 기본 키직접 할당하기 위해서는 @Id만 사용하여 매핑해줍니다.
  • @Id 적용 가능한 자바 타입은 다음과 같습니다. 
    • 자바 기본형
    • 자바 래퍼( Wrapper )형
    • String
    • java.util.Date
    • java.math.BigDecimal
    • java.math.BigInteger
  • em.persist( )엔티티저장하기 전에 애플리케이션에서 기본 키직접 할당하는 방법입니다. 
Member member = new Member();
member.setId("member1");	// 기본 키 직접 할당
em.persist(member);

 

IDENETITY 전략

  • IDENTITY기본 키 생성데이터베이스위임하는 전략입니다.
  • 주로 MySQL, PostgreSQL, SQL Server, DB2에서 사용합니다. 
  • @GeneratedValue 어노테이션을 사용하여 식별자 생성 전략IDENTITY로 선택합니다. 
  • 데이터베이스에 값을 저장할 때 ID 컬럼을 비워두면 데이터베이스식별자 값을 생성하여 채워줍니다.
  • 이 전략을 사용하는 경우, JPA기본 키 값을 얻어오기 위하여 데이터베이스추가로 조회합니다. 
@Entity
public class Member{
	@Id
    	@GeneratedValue(strategy = GenerationType.IDENTITY)	// IDENTITY 속성을 사용
    	private Long id;
    	...
}
- IDENTITY 전략을 사용하는 경우 데이터를 데이터베이스INSERT한 후에 기본키 값을 조회할 수 있습니다. 따라서, 엔티티식별자 할당하려면 DB추가로 조회해야 합니다.
- JDBC3에 추가된 Statement.getGeneratedKeys( )를 사용하여 데이터베이스 저장과 동시에 기본 키 값을 얻어 올 수 있습니다. 하이버네이트는 이 메서드를 이를 사용하여 데이터베이스한 번만 통신합니다.
- 엔티티영속상태가 되려면 식별자가 반드시 필요합니다. IDENTITY 전략을 사용하는 경우 식별자 값매핑하기 위해 em.persist( )호출하는 즉시 INSERT SQL이 데이터베이스에 전달됩니다. 따라서 이 전략트랜잭션을 지원하는 쓰기 지연이 동작하지 않습니다.

 

SEQUENCE 전략

  • 데이터베이스 시퀀스유일한 값순서대로 생성하는 데이터베이스 오브젝트입니다.
  • SEQUENCE 전략은 시퀀스를 사용하여 기본 키생성하는 전략입니다.
  • 시퀀스를 지원하는 오라클, PostgreSQL, DB2, H2 데이터베이스에서 사용할 수 있습니다.

SEQUENCE 전략을 사용하는 방법

1. 데이터베이스에 시퀀스 생성

CREATE TABLE BOARD{
	ID BIGINT NOT NULL PRIMARY KEY,
    DATA VARCHAR( 255 )
}

// 시퀀스 생성하기
CREATE SEQUENCE BOARD_SEQ START WITH 1 INCREMENT BY 1;	// 1부터 1씩 증가하며 할당합니다.

2. 데이터베이스의 시퀀스와 엔티티 매핑

@Entity
@SequenceGenerator(
	name = "BOARD_SEQ_GENERATOR",	// 시퀀스 생성기 이름 
    	sequenceName = "BOARD_SEQ",	// 매핑할 데이터베이스 시퀀스 이름
    	initialValue = 1, allocationSize = 1)
public class Board{
	@Id
    	@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "BOARD_SEQ_GENERATOR")
	private Long id;
    ...
}
  • @SequenceGenerator을 사용하여 BOARD_SEQ_GENERATOR 시퀀스 생성기등록하였습니다.
  • sequenceName 속성을 사용하여 실제 데이터베이스의 시퀀스BOARD_SEQ 시퀀스매핑합니다.
  • 키 생성 전략GenerationType.SEQUENCE로 설정하고 generator 속성으로 방금 생성한 시퀀스 생성기를 선택합니다.
SEQUENCE 전략은 em.persist( )호출할 때 먼저 데이터베이스 시퀀스를 이용하여 식별자조회합니다.
그리고 조회한 식별자 엔티티할당한 후 엔티티영속성 컨텍스트저장합니다.
이후 트랜잭션커밋해서 플러시가 일어나면 엔티티데이터베이스저장합니다.
이와 달리 IDENTITY 전략은 엔티티데이터베이스저장한 후 식별자조회해서 엔티티식별자할당합니다.

@SequenceGenerator

속성 기능 기본값
name 식별자 생성기 이름 필수
sequenceName 데이터베이스에 등록되어 있는 시퀀스 이름 hibernate_sequence
initialValue DDL 생성 시에만 사용됨. 시퀀스 DDL을 생성할 때 처음 시작하는 수를 지정한다. 1
allocationSize 시퀀스 한 번 호출에 증가하는 수
( 성능 최적화에 사용됨 )
50
catalog, schema 데이터베이스 catalog, schema 이름  

SEQUENCE 전략과 최적화

  • SEQUENCE 전략은 데이터베이스 시퀀스를 통해 식별자조회하는 추가 작업이 필요합니다. 따라서 데이터베이스2번 통신합니다. 
  • JPA시퀀스접근하는 횟수를 줄이기 위해 @SequenceGenerator.allocationSize를 사용합니다.
  • allocationSize50인 경우 시퀀스를 한 번에 50 증가시킨 다음 1 ~ 50까지는 메모리에서 식별자할당합니다. 이후 51이 되면 시퀀스의 값을 100으로 증가시킨 이후 51 ~ 100까지 메모리에서 식별자할당하여 최적화를 수행합니다.
  • 이 방법은 시퀀스 값을 선점하므로 여러 JVM이 동시에 동작해도 기본 키 값이 충돌하지 않는다는 장점이 있지만, 데이터베이스직접 접근해서 데이터를 등록할 때 시퀀스 값이 한번에 많이 증가한다는 점을 염두해두어야 합니다.
    이런 상황이 부담스럽고, INSERT 성능이 중요하지 않은 경우 allocationSize 값을 1로 설정하면 됩니다.
  • 최적화 방법은 hibernate.id.new_generator_mappings 속성을 true로 설정해야 적용되며, 설정하지 않는 경우 과거 hibernate가 사용하던 최적화 방법을 사용합니다.
    ( 과거에는 시퀀스 값을 하나씩 할당받고 애플리케이션에서 allocationSize 만큼 사용했습니다. ) 

 

TABLE 전략

  • TABLE 전략은 키 생성 전용 테이블을 하나 만들고 여기에 이름으로 사용할 컬럼을 만들어 데이터베이스 시퀀스를 흉내내는 전략입니다.

TABLE 전략을 사용하는 방법

1. 데이터베이스에 키 생성 용도로 사용할 테이블 생성

CREATE TABLE MY_SEQUENCES (
	sequence_name varchar(255) not null,	// 시퀀스 이름으로 사용
    	next_val bigint,			// 시퀀스 값으로 사용
    	primary key ( sequence_name )
)

2. 데이터베이스의 테이블과 엔티티 매핑

@Entity
@TableGenerator(			// 테이블 키 생성기 등록
	name = "BOARD_SEQ_GENERATOR",	// 테이블 키 생성기 이름
    	table = "MY_SEQUENCES",		// 키 생성용 테이블과 매핑
    	pkColumnValue = "BOARD_SEQ", allocationSize = 1	// 시퀀스 값 컬럼명        )
public class Board{
	
    @Id
    @GeneratedValue(strategy = GenerationType.TABLE, generator = "BOARD_SEQ_GENERATOR" )
    private Long id;
    ...
}

 

  • TABLE 전략은 시퀀스 대신에 테이블을 사용한다는 것만 제외하면 SEQUENCE 전략과 내부 동작방식같습니다.
  • 키 생성 용도 테이블에 키로 사용할 값 이름으로 BOARD_SEQ 시퀀스 으로는 식별자 값이 들어갑니다. 

@TableGenerator

속성 기능 기본값
name 식별자 생성기 이름 필수
table 키 생성 테이블 명 hibernate_sequences
pkColumnName 시퀀스 컬럼명 sequence_name
valueColumnName 시퀀스 값 컬럼명 next_val
pkColumnValue 키로 사용할 값 이름 엔티티 이름
initialValue 초기 값, 마지막으로 생성된 값이 기준이다. 0
allocationSize 시퀀스 한 번 호출에 증가하는 수 ( 성능 최적화에 사용됨 ) 50
catalog, schema 데이터베이스 catalog, schema 이름  
uniqueConstraints(DDL) 유니크 제약 조건을 지정할 수 있다.  
TABLE 전략은 값을 조회하면서 SELECT 쿼리를 사용하고 다음 값으로 증가시키기 위해 UPDATE 쿼리를 사용합니다. 이 전략은 SEQUENCE 전략과 비교해서 데이터베이스에 한 번 더 통신하는 단점이 있습니다.
TABLE 전략을 최적화하는 방법은 @TableGenerator.allocationSize를 사용할 수 있습니다.
이를 사용한 최적화 방법은 SEQUENCE 전략과 같습니다.

 

AUTO 전략

  • AUTO 전략은 선택한 데이터베이스 방언에 따라 IDENTITY, SEQUENCE, TABLE 전략 중 하나를 자동으로 선택합니다. ( 오라클을 선택하면 SEQUENCE, MySQL을 선택하면 IDENTITY를 사용합니다. )  
  • @GeneratedValue기본 값AUTO이며, 데이터베이스변경해도 코드수정할 필요가 없다는 장점을 가지고 있습니다.
  • 키 생성 전략이 아직 확정되지 않은 개발 초기 단계프로토타입 개발 시 편리하게 사용할 수 있습니다.
  • AUTO를 사용할 때 SEQUENCETABLE 전략이 선택되면 시퀀스키 생성용 테이블을 미리 만들어 두어야 하며, 스키마 자동 생성 기능을 사용한다면 하이버네이트 기본값을 사용하여 적절한 시퀀스키 생성 테이블을 만들어 줄 것입니다.

 

권장하는 식별자 선택 전략

  • 데이터베이스 기본 키는 다음 3가지 조건모두 만족해야 합니다.
    1. null 값은 허용하지 않는다.
    2. 유일해야 한다.
    3. 변해선 안 된다.
  • 테이블 기본 키를 선택하는 전략은 크게 2가지가 있습니다.
    1. 자연 키 ( natural key )
      • 비즈니스에 의미가 있는 키 ( 주민등록번호, 이메일 , 전화번호 등 )
    2. 대리 키 ( surrogate key )
      • 비즈니스와 관련 없는 임의로 만들어진 키, 대체 키로도 불립니다.
      • ( 오라클 시퀀스, auto_increment, 키 생성 테이블 사용 )
자연 키의 경우 미래에 예측할 수 없는 요구사항이나 비즈니스의 변경
( 정책이 변하여 주민등록번호를 저장하지 못하는 등 )으로 인해 기본 키의 조건을 만족하지 못할 수 있습니다
따라서, 비즈니스무관한 임의의 값 대리 키를 사용하는 것을 권장합니다. 

'JPA' 카테고리의 다른 글

[ JPA ] 영속성 컨텍스트와 엔티티 생명주기  (0) 2023.08.01

내부 클래스란?

  • 내부 클래스는 클래스 내에 선언된 클래스입니다.
  • 내부 클래스를 선언한 클래스는 외부 클래스가 됩니다.
  • 두 클래스가 서로 긴밀한 관계에 있는 경우 사용하며, 다음 장점들을 가지고 있습니다. 
내부 클래스의 장점
-
내부 클래스에서 외부 클래스의 멤버들을 쉽게 접근할 수 있다.
코드의 복잡성을 줄일 수 있다( 캡슐화 ).
class A {		// 외부 클래스
	...
    class B {	// 내부 클래스
    	...
    }
	...
}

내부 클래스의 종류와 특징

  • 내부 클래스의 종류는 변수선언위치에 따른 종류와 같습니다.
  • 인스턴스 클래스, 스태틱 클래스, 지역 클래스, 익명 클래스로 분류됩니다.
내부 클래스 특 징
인스턴스 클래스
(Instance class
외부 클래스멤버변수 선언위치에 선언하며, 외부 클래스인스턴스 멤버처럼 다루어집니다.
주로 외부 클래스인스턴스 멤버들과 관련된 작업에 사용될 목적으로 선언합니다.
스태틱 클래스
( Static class )
외부 클래스멤버변수 선언위치에 선언하며, 외부 클래스static 멤버처럼 다루어집니다.
주로 외부 클래스static 멤버, 특히 static메서드에서 사용될 목적으로 선언합니다.
지역 클래스
( Inner class
외부 클래스메서드초기화블럭 안에 선언하며, 선언된 영역 내부에서만 사용될 수 있습니다.
익명 클래스
anonymous class )
클래스의 선언객체의 생성 동시에 하는 이름없는 클래스입니다. ( 일회용 )

내부 클래스의 선언

  • 내부 클래스의 선언위치는 변수의 선언위치와 동일합니다.
  • 내부 클래스의 선언위치에 따라 같은 선언위치의 변수와 동일한 유효범위( Scope )와 접근성( Accessibility )을 가집니다.
class Outer {
	class InstanceInner{}		// 인스턴스 클래스
    static class StaticInner{}	// 스태틱 클래스
	
    void mymethod(){
    	class LocalInner{}		// 지역 클래스
    }
    
}

내부 클래스의 제어자와 접근성

  • 내부 클래스멤버변수동일한 성질을 갖습니다. 따라서, 내부 클래스외부 클래스의 멤버와 같이 간주되고, 인스턴스 멤버와 static 멤버 간의 규칙이 내부 클래스에도 똑같이 적용됩니다.  
  • 내부 클래스도 클래스이기 때문에 abstractfinal와 같은 제어자를 사용할 수 있고, 멤버변수들처럼 private, protected 접근제어자도 사용할 수 있습니다.
class InnerEx{
	class InstanceInner{}		// 인스턴스 클래스
    static class StaticInner{} 		// 스태틱 클래스
    
    // 인스턴스멤버 간에는 서로 직접 접근이 가능합니다.
    InstanceInner iv = new InstanceInner();
    // static 멤버 간에는 서로 직접 접근이 가능합니다.
    static StaticInner cv = new StaticInner();
    
    static void staticMethod(){
    	// static멤버는 인스턴스멤버에 직접 접근할 수 없습니다.
//        InstanceInner obj1 = new InstanceInner();
  		StaticInner obj2 = new StaticInner();
        
        // 굳이 접근하려면 아래와 같이 외부 클래스 객체를 생성해야 합니다.
        // 인스턴스 클래스는 외부 클래스를 먼저 생성해야만 사용할 수 있습니다.
        InnerEx outer = new InnerEx();
        InstanceInner obj1 = outer.new InstanceInner();
    }
	
    void instanceMethod(){
    	// 인스턴스메서드에서는 인스턴스멤버와 static멤버 모두 접근이 가능합니다.
        InstanceInner obj1 = new InstanceInner();
  		StaticInner obj2 = new StaticInner();
        // 메서드 내에 지역적으로 선언된 내부 클래스는 외부에서 접근할 수 없습니다.
//        LocalInner lv = new LocalInner();
    
    }
	
    void myMethod(){
    	class LocalInner{}
        LocalInner lv = new LocalInner();
    }
}

익명 클래스 ( anonymous class )

  • 클래스이름없으며, 클래스선언객체생성동시에 하기 때문에 단 한번만 사용될 수 있습니다.
  • 오직 하나의 객체만을 생성할 수 있는 일회용 클래스입니다.
  • 오직, 단 하나의 클래스상속받거나 단 하나인터페이스만을 구현할 수 있습니다.
    • 이름없기 때문에 생성자를 가질 수 없으며, 조상클래스의 이름이나 구현하려는 인터페이스의 이름을 사용해서 정의하기 때문에 하나의 클래스상속받으며 동시에 인터페이스구현하거나 둘 이상의 인터페이스구현할 수 없습니다.
new 조상클래스이름(){
	// 멤버 선언
}
	또는
    
new 구현인터페이스이름(){
	// 멤버 선언
}

 익명 클래스 사용 예 1

class InnerEx{
	Object iv = new Object(){ void method(){} };	// 익명 클래스
    static Object cv = new Object(){ void method(){} }; // 익명 클래스
    
    void myMethod(){
    	Object lv = new object(){ void method(){} };	// 익명 클래스
    }

}
  • 익명 클래스컴파일하면 다음과 같은 클래스 파일이 생성됩니다.
  • 익명 클래스는 이름이 없기 때문에 '외부 클래스명$숫자.class'의 형식으로 클래스파일명이 결정됩니다.
InnerEx6.class
InnerEx6$1.class // 익명 클래스
InnerEx6$2.class // 익명 클래스
InnerEx6$3.class // 익명 클래스

문제

연종이는 엄청난 기억력을 가지고 있다. 그래서 하루 동안 본 정수들을 모두 기억 할 수 있다. 하지만 이를 믿을 수 없는 동규는 그의 기억력을 시험해 보기로 한다. 동규는 연종을 따라 다니며, 연종이 하루 동안 본 정수들을 모두 ‘수첩1’에 적어 놓았다. 그것을 바탕으로 그가 진짜 암기왕인지 알아보기 위해, 동규는 연종에게 M개의 질문을 던졌다. 질문의 내용은 “X라는 정수를 오늘 본 적이 있는가?” 이다. 연종은 막힘없이 모두 대답을 했고, 동규는 연종이 봤다고 주장하는 수 들을 ‘수첩2’에 적어 두었다. 집에 돌아온 동규는 답이 맞는지 확인하려 하지만, 연종을 따라다니느라 너무 힘들어서 여러분에게 도움을 요청했다. 동규를 도와주기 위해 ‘수첩2’에 적혀있는 순서대로, 각각의 수에 대하여, ‘수첩1’에 있으면 1을, 없으면 0을 출력하는 프로그램을 작성해보자.

입력

첫째 줄에 테스트케이스의 개수 T가 들어온다. 다음 줄에는 ‘수첩 1’에 적어 놓은 정수의 개수 N(1 ≤ N ≤ 1,000,000)이 입력으로 들어온다. 그 다음 줄에  ‘수첩 1’에 적혀 있는 정수들이 N개 들어온다. 그 다음 줄에는 ‘수첩 2’에 적어 놓은 정수의 개수 M(1 ≤ M ≤ 1,000,000) 이 주어지고, 다음 줄에 ‘수첩 2’에 적어 놓은 정수들이 입력으로 M개 들어온다. 모든 정수들의 범위는 int 로 한다.

시간제한 : 2초

메모리제한 : 256MB


코드

  • 배열을 정렬하여도 NM을 전부 비교하면 최악의 경우 1000000 * 1000000 번 비교를 하게 됩니다. O(N^2) ( 약 1조번의 반복으로 시간 제한에 걸리게 됩니다. )
  • 따라서 이분 탐색을 사용하여야 시간 제한 내에 성공할 수 있습니다. O(logN)

이분 탐색을 사용하기 위해 우선 비교할 배열인 수첩 1을 정렬하였습니다.이후 수첩 2 배열을 순회하며 수첩 1 배열에 이분탐색을 하였습니다.

더보기
/**
 * 암기왕
 */
public class BOJ2776 {

    static int T;
    public static void main(String[] args) throws IOException {
        BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(System.in));
        BufferedWriter bufferedWriter = new BufferedWriter(new OutputStreamWriter(System.out));
        T = Integer.parseInt(bufferedReader.readLine());

        while (T-- > 0) {
            int arr_1[], arr_2[];

            // input
            int N = Integer.parseInt(bufferedReader.readLine());
            String[] s = bufferedReader.readLine().split(" ");

            // 수첩 1 초기화 및 입력 값 저장
            arr_1 = new int[N];
            for (int i = 0; i < N; i++) {
                arr_1[i] = Integer.parseInt(s[i]);
            }

            // input
            int M = Integer.parseInt(bufferedReader.readLine());
            s = bufferedReader.readLine().split(" ");

            // 수첩 2 초기화 및 입력 값 저장
            arr_2 = new int[M];
            for (int i = 0; i < M; i++) {
                arr_2[i] = Integer.parseInt(s[i]);
            }

            // 이분 탐색을 위해 배열을 정렬해줍니다.
            Arrays.sort(arr_1);

            for (int i = 0; i < M; i++) {
                // bufferedWriter.append()는 결과를 출력에 담는 함수입니다.
                // arr_2의 각 원소를 기준으로 arr_1을 이분탐색했습니다.
                bufferedWriter.append(binarySearch(arr_1, arr_2[i]) + "\n");
            }

        }
        // 출력
        bufferedWriter.flush();

        // 종료
        bufferedReader.close();
        bufferedWriter.close();
    }

    // int[] target : 탐색할 배열, find : 찾을 수
    static int binarySearch(int[] target, int find) {

        // left , right
        int l = 0;
        int r = target.length - 1;

        while (l <= r) {
            // 중간 값을 비교
            int mid = (l+r)/2;

            // 같은 값을 찾은 경우 1을 리턴
            if (find == target[mid]) {
                return 1;
            }

            // 중간 값보다 찾는 수가 작은 경우
            if (find < target[mid]) {
                r = mid - 1;
            }
            else // if ( find > target[mid] ) 중간 값보다 찾는 수가 큰 경우
            {
                l = mid + 1;
            }
        }

        // 찾지 못한 경우
        return 0;
    }

}


https://www.acmicpc.net/problem/2776

 

2776번: 암기왕

연종이는 엄청난 기억력을 가지고 있다. 그래서 하루 동안 본 정수들을 모두 기억 할 수 있다. 하지만 이를 믿을 수 없는 동규는 그의 기억력을 시험해 보기로 한다. 동규는 연종을 따라 다니며,

www.acmicpc.net

 

'PS > 백준' 카테고리의 다른 글

[ BOJ 17298 ] 오큰수 ( JAVA )  (0) 2023.08.13
[ BOJ 19941 ] 햄버거 분배 ( JAVA )  (1) 2023.08.06
[ BOJ 3649 ] 로봇 프로젝트 ( JAVA )  (0) 2023.07.31
[ BOJ 2776 ] 암기왕 ( JAVA )  (0) 2023.07.28
[ BOJ 1966 ] 프린터 큐 ( JAVA )  (0) 2023.07.26

인터페이스란 ?

  • 인터페이스추상클래스처럼 추상메서드를 갖지만 추상클래스보다 추상화 정도가 높아 몸통을 갖춘 일반 메서드 또는 멤버변수를 구성원으로 가질 수 없습니다.
  • 인터페이스는 오직 추상메서드상수만을 멤버로 가질 수 있습니다.
  • 추상클래스와 같이 불완전하므로 그 자체로 사용되기 보다는 다른 클래스를 작성하는 데 도움을 줄 목적으로 사용합니다. 

인터페이스의 작성

  • 선언 시, 키워드로 interface를 사용합니다.
  • 클래스와 같이 접근제어자public 또는 default를 사용할 수 있습니다.
  • 모든 멤버변수public static final 이어야 하며, 이를 생략할 수 있다. ( 컴파일러가 자동으로 추가 )
  • 모든 메서드public abstract 이어야 하며, 이를 생략할 수 있다. ( 컴파일러가 자동으로 추가 )
interface 인터페이스이름 {
	public static final 타입 상수이름 = 값;
    	public abstract 메서드이름 (매개변수목록);
}
인터페이스의 모든 메서드는 추상메서드이어야 하지만, JDK1.8 부터 인터페이스에 static 메서드와 디폴트 메서드 (default method)의 추가를 허용합니다. ( 이 내용은 밑에서 자세히 다룹니다. )

인터페이스의 상속

  • 인터페이스인터페이스로부터만 상속받을 수 있으며, 클래스와 달리 다중상속이 가능합니다.
interface Movable{
	void move(int x, int y)	// (x,y)로 이동하는 메서드
}

interface Attackable{
	void attack(Unit u);	// 지정된 대상( U )을 공격하는 기능의 메서드
}

interface Fightable extends Movable, Attackable{ }	// 인터페이스의 다중 상속
  • Fightable 인터페이스조상 인터페이스 ( Movable, Attackable )에 정의된 멤버를 모두 상속받습니다. 
    따라서, Fightable 인터페이스는 두 개의 추상메서드 move( int x, int y ) attack( Unit u ) 멤버로 갖게 됩니다. 

인터페이스의 구현

  • 인터페이스도 추상클래스처럼 그 자체로는 인스턴스를 생성할 수 없습니다. 
  • 추상클래스상속( extends )을 통해 추상메서드를 완성하는 것처럼, 인터페이스클래스구현( implements )을 통해 완성됩니다.
  • 인터페이스의 메서드 중 일부만 구현한다면, abstract를 붙여서 추상클래스로 선언해야 합니다.
  • 상속구현을 동시에 할 수 있습니다.
class 클래스이름 implements 인터페이스이름 {
	// 인터페이스에 정의된 추상메서드를 구현해야 함.
}

// 인터페이스 구현 예시
class Fighter implements Fightable{
	public void move( int x, int y ) { // 추상 메서드 구현 내용 생략 }
	public void attack( Unit u ) { // 추상 메서드 구현 내용 생략 }
}

// 인터페이스의 메서드 중 일부만 구현하는 경우 abstract를 사용합니다.
abstract class Fighter implements Fightable{
	public void move( int x, int y ) { // 추상 메서드 구현 내용 생략 }
}

// 상속과 구현을 동시에 하는 예시
class Fighter extends Unit implements Fightable{
	public void move( int x, int y ) { // 추상 메서드 구현 내용 생략 }
	public void attack( Unit u ) { // 추상 메서드 구현 내용 생략 }	
}
  • 오버라이딩할 때는 조상메서드보다 같거나 넓은 범위의 접근 제어자를 지정해야 합니다. ( Movable 인터페이스void move( int x, int y )public abstract생략된 것이기 때문에 구현하는 Fighter 클래스에서는 public 접근 제어자를 사용합니다.

인터페이스를 이용한 다형성

  • 자손클래스인스턴스조상타입참조변수참조할 수 있는 것처럼, 인터페이스 역시 이를 구현한 클래스조상이라 할 수 있으므로 인터페이스참조변수로 이를 구현한 클래스인스턴스참조할 수 있습니다. ( 또한 인터페이스 타입으로의 형변환도 가능합니다. ) 
Fightable f = (Fightable)new Fighter();
	또는
Fightable f = new Fighter();
void attack(Fightable f) {	// 인터페이스는 매개변수의 타입으로 사용
	...
}


Fightable method(){	// 메서드의 리턴타입으로 인터페이스의 타입을 지정
	...
	Fighter f = new Fighter();
    return f;
}
  • 인터페이스메서드매개변수타입으로 사용될 수 있습니다. ( Fightable을 구현한 클래스인스턴스매개변수로 넘겨 줄 수 있음 )  
  • 리턴타입이 인터페이스라는 것은 메서드가 해당 인터페이스를 구현한 클래스의 인스턴스를 반환한다는 뜻입니다.

인터페이스의 장점

1. 개발시간을 단축시킬 수 있다.
2. 표준화가 가능하다.
3. 서로 관계없는 클래스들에게 관계를 맺어줄 수 있다.
4. 독립적인 프로그래밍이 가능하다.
  1. 개발시간을 단축시킬 수 있다.
    • 인터페이스가 작성되면, 이를 사용해서 프로그램을 작성할 수 있습니다. 메서드를 호출하는 쪽에서는 메서드의 내용과 관계없이 선언부만 알면 되기 때문입니다. 즉, 인터페이스를 구현하는 클래스가 작성될 때까지 기다리지 않고도 인터페이스를 구현하는 쪽과 동시에 개발을 할 수 있습니다.
  2. 표준화가 가능하다.
    • 프로젝트에 사용되는 기본 틀인터페이스로 작성하고, 이를 구현하여 프로그램을 작성함으로써 일관되고 정형화프로그램개발이 가능해집니다.
  3.  서로 관계없는 클래스들에게 관계를 맺어줄 수 있다.
    •  서로 상속관계에 있지도 않고, 같은 조상클래스를 가지고 있지 않은 서로 아무런 관계가 없는 클래스들에게 하나의 인터페이스공통적으로 구현하도록 함으로써 관계를 맺어 줄 수 있습니다.
  4. 독립적인 프로그래밍이 가능하다.
    • 인터페이스를 이용하면 클래스선언구현분리시킬 수 있기 때문에 실제 구현에 독립적인 프로그램을 작성할 수 있습니다.
    • 클래스클래스간의 직접적인 관계를 인터페이스를 이용해서 간접적인 관계로 변경하면, 한 클래스변경이 다른 클래스에 영향을 미치지 않는 독립적인 프로그래밍이 가능해집니다.

디폴트 메서드와 static 메서드

  • JDK1.8부터 인터페이스에 static 메서드와 디폴트 메서드를 추가할 수 있게 되었습니다.

static 메서드

  • static 메서드인스턴스와 관계 없는 독자적인 메서드입니다.
  • static 메서드접근 제어자는 항상 public이며, 생략할 수 있습니다.

디폴트 메서드

  • 기존 인터페이스메서드추가하는 경우는 추상 메서드추가하는 것으로 인터페이스구현모든 클래스에서 새로운 메서드구현해야하는 번거로움이 존재했습니다. 
  • 디폴트 메서드는 이러한 단점을 고려하여, 추상 메서드의 기본적인 구현제공하는 메서드입니다.
  • 디폴트 메서드추상 메서드아니기 때문에 새로 추가되어도 해당 인터페이스를 구현한 클래스변경하지 않아도 된다는 장점이 있습니다.
  • 키워드 default를 붙이며, 추상 메서드와 달리 일반 메서드처럼 몸통 { }이 있어야 합니다.
  • 접근 제어자public이고 생략이 가능합니다.
  • 새로 추가디폴트 메서드가 기존의 메서드와 이름이 중복되어 충돌되는 경우가 발생할 수 있습니다.
    ( 아래 규칙 참고 )
interface MyInterface{
	void method();
    default void newMethod(){};	// 디폴트 메서드 
}

 

 

이름 충돌을 해결하는 규칙

1. 여러 인터페이의 디폴트 메서드 간의 충돌 
: 인터페이스를 구현한 클래스에서 디폴트 메서드를 오버라이딩해야 한다.

2. 디폴트 메서드와 조상 클래스의 메서드 간의 충돌
: 조상 클래스의 메서드가 상속되고, 디폴트 메서드는 무시된다.

+ Recent posts