Notice
Recent Posts
Recent Comments
Link
«   2025/08   »
1 2
3 4 5 6 7 8 9
10 11 12 13 14 15 16
17 18 19 20 21 22 23
24 25 26 27 28 29 30
31
Tags
more
Archives
Today
Total
관리 메뉴

백엔드 개발 공부 일지

JAVA Basic - 인터페이스 본문

Java 입문

JAVA Basic - 인터페이스

JungCat 2022. 12. 8. 16:48

● 인터페이스란?

인터페이스는 추상클래스에서도 잠시 언급했듯이 모든 메서드가 추상메서드로 되어있는 클래스 이다.

 

정확히는 인터페이스는 상수, 추상메서드로만 이루어져 있고 클래스 혹은 프로그램이 제공하는 기능을 명시적으로 선언하는 역할을한다.

이는 추상클래스보다도 구현된 코드가 없기 떄문에 인스턴스도 마찬가지로 생성할 수 없다.

 

다음은 인터페이스의 예제이다. (계산기를 만들기 위해 선언한 코드)

package interfaceex;

public interface Calc {

    // 인터페이스에서 선언한 변수는 컴파일 과정에서 상수로 변환됨.
    double PI = 3.14;
    int ERROR = -99999999;

    // 인터페이스에서 선언한 메서드는 컴파일 과정에서 추상 메서드로 변환됨.
    int add(int num1, int num2);

    int substract(int num1, int num2);

    int times(int num1, int num2);

    int divide(int num1, int num2);
}

이렇게 선언한 인터페이스를 클래스가 사용하는 것을 '클래스에서 인터페이스를 구현한다'라고 표현합니다.

전에 공부하였던 상속에서는 상위 클래스에 구현한 기능을 하위 클래스에서 확장한다는 의미로 'extends'예약어를 사용하였는데, 인터페이스에서는 인터페이스에 선언한 기능을 클래스가 구현한다는 의미로 'implements' 예약어를 사용한다.

 

다음 예제를 살펴보자.

package interfaceex;

public abstract class Calculator implements Calc {

    @Override
    public int add(int num1, int num2) {
        return num1 + num2;
    }

    @Override
    public int substract(int num1, int num2) {
        return num1 - num2;
    }

}

Calculater라는 추상클래스를 작성했고 interface에서 구현하기로한 add와 substract메소드만 구현하였다.

(모두 구현하지 않았으므로 반드시 추상클래스로 만들어야 한다.)

 

다음과 같이 클래스를 모두 완성하고 실행할 수 있다.

package interfaceex;

public class CompleteCalc extends Calculator{
    @Override
    public int times(int num1, int num2){
        return num1 * num2;
    }

    @Override
    public int divide(int num1, int num2){
        if (num2 != 0)
            return num1/num2;
        else
            return Calc.ERROR;
    }

    public void showInfo(){
        System.out.println("Calc 인터페이스를 구현하였습니다.");
    }
}
package interfaceex;

public class CalculatorTest {
    public static void main(String[] args){
        int num1 =10;
        int num2 = 5;

        CompleteCalc calc = new CompleteCalc();
        System.out.println(calc.add(num1,num2));
        System.out.println(calc.substract(num1,num2));
        System.out.println(calc.times(num1,num2));
        System.out.println(calc.divide(num1,num2));
        calc.showInfo();
    }
}
15
5
50
2
Calc 인터페이스를 구현하였습니다.

calc 객체는 CompletCalc형으로 선언하였지만 묵시적 형변환에 이해 calc객체는 Calc형이며 Calculater형이며 CompletCalc형이라고 할 수 있다.

 

calc 객체는 Calc 형으로 선언하여도 add, substract, times, divide메소드는 사용할 수 있지만 CompletCalc에서 추가 작성한 showInfo메서드는 사용이 불가능하다.

● 인터페이스와 다향성

위의 인터페이스를 살펴본결과... 인터페이스는 그저 '껍데기'같은 존재이다.

도대체 인터페이스는 왜 쓰는 걸까?

인터페이스는 클라이언트 프로그램에 어떤 메서드를 제공하는지 알려주는 명세(specification) 또는 약속의 역할을 합니다.

(이로 인해 클라이언트 프로그램은 인터페이스에서 약속한 명세대로 구현한 클래스를 생성해서 사용하면 된다.)

 

인터페이스 장점

1.개발 시간을 단축시킬 수 있다.

2. 표준화가 가능하다.

3. 서로 관계없는 클래스들에게 관계를 맺어 줄 수 있다.

4. 독립적인 프로그래밍이 가능하다.

 

<예제 시나리오>고객 센터에는 전화 상담을 하는 상담원들이 있습니다. 일단 고객센터로 전화가 오면 대기열에 저장됩니다. 상담원이 지정되기 전까지 대기 상태가 됩니다. 각 전화를 상담원에게 배분하는 정책은 다음과 같은 여러 방식으로 구현할 수 있다.

 

1. 순서대로 배분하기 : 모든 상담원이 동일한 상담 건수를 처리하도록 들어오는 전화 순서대로 상담원에게 하나씩 배분한다.2. 짧은 대기열 찾아 배분하기 : 고객 시간을 줄이기 위해 상담을 하지 않는 상담원이나 가장 짧은 대기열을 보유한 상담원에게 배분한다.

3. 우선순위에 따라 배분하기 : 고객 등급이 높은 고객의 전화를 우선 가져와서 업무 능력이 좋은 상담원에게 우선 배분한다.

 

다음과 같이 인터페이스(Scheduler)를 정의하자.

 

package Scheduler;

public interface Scheduler {
    public void getNextCall();
    public void senCallToAgent();

}

 

package Scheduler;

public class RoundRobin implements Scheduler{
    @Override
    public void getNextCall(){
        System.out.println("상담전화를 순서대로 대기열에서 가져옵니다");
    }

    @Override
    public void sendCallToAgent(){
        System.out.println("다음 순서 상담원에게 배분합니다.");
    }
}
package Scheduler;

public class LeastJob implements Scheduler {
    @Override
    public void getNextCall(){
        System.out.println("상담전화를 순서대로 대기열에서 가져옵니다");
    }

    @Override
    public void sendCallToAgent(){
        System.out.println("현재 상담 업무가 없거나 대기가 가장 적은 상담원에게 할당합니다.");
    }

}
package Scheduler;

public class PriorityAllocation implements Scheduler {
    @Override
    public void getNextCall(){
        System.out.println("상담전화를 순서대로 대기열에서 가져옵니다");
    }

    @Override
    public void sendCallToAgent(){
        System.out.println("현재 상담 업무가 없거나 대기가 가장 적은 상담원에게 할당합니다.");
    }

}
package Scheduler;

import java.io.IOException;

public class SchedulerTest {

    // 문자를 입력받는 System.in.read()를 사용하려면 IOException에서 오류를 처리햐아한다. 지금은 이정도로만 이해하고 추후 예외처리에서 자세히 다룰 예정
    public static void main(String[] args) throws IOException {
        System.out.println();
        System.out.println("전화 상담 할당 방식을 선택하세요.");
        System.out.println("R : 한명식 차례로 할당");
        System.out.println("L : 쉬고 있거나 대기가 가장 적은 상담원에게 할당");
        System.out.println("P : 우선순위가 높은 고객 먼저 할당");

        int ch = System.in.read(); // 할당 방식을 입력받아 ch 변수에 대입
        Scheduler scheduler = null;

        if (ch == 'R' || ch == 'r') {
            scheduler = new RoundRobin();
        } else if (ch == 'L' || ch == 'l') {
            scheduler = new LeastJob();
        } else if (ch == 'P' || ch == 'p') {
            scheduler = new PriorityAllocation();
        } else {
            System.out.println("지원되지 않는 기능입니다.");
            return;
        }
        scheduler.getNextCall();
        scheduler.sendCallToAgent();
    }

}

위와 같이 인터페이스를 활용하여 프로그래밍을 할 경우 앞에서 정의한 상담 전화 배분정책이 바뀔 때마다 쉽게 유지 보수가 가능하다.

 

예를 들어 VIP전담 상담원이 따로 필요할 경우 클래스를 추가하더라도 scheduler인터페이스에서 작성하였던 메소드 2개로 동일하게 수행된다.

 

scheduler.getNextCall();

scheduler.sendCallToAgent();

 

● 인터페이스 요소 살펴보기

- 인터페이스 상수

인터페이스 내에서는 어떤 인스턴스도 생성할 수 없고 멤버 변수도 사용할 수 없다.

만일 인터페이스내에 멤버 변수와 같이 선언하여도 에러는 나지않지만 컴파일 과정에서 상수로 변환이 된다. (자동으로)

 

- 디폴트 메서드와 정적 메서드

인터페이스에서는 코드를 구현할 수 없으므로 만일, 같은 기능을 각 클래스가 수행해야 할 경우 반복해서 구현해야하는 단점이 있다. 또한 클래스를 생성하지 않아도 사용할 수 있는 메서드가 필요한 경우가 있는데 인터페이스의 추상메서드로는 불가능한 방법이다.

 

그래서, 자바 8부터 인터페이스의 활용성을 높이기 위해해 디폴트 메서드와 정적 메서드 기능을 제공한다.

 

디폴트 메서드 : 인터페이스에 구현 코드까지 작성한 메서드.

정적 메서드 : 인스턴스 생성과 상관없이 사용할 수 있는 메서드.

 

히자만, 디폴드 메서드나 정적메서드를 추가했다고 해서 인터페이스가 인스턴스를 생성할 수 있는 것은 아니다.

그러나 인터페이스에 구현하는 디폴트 메서드와 정적 메서드가 무엇인지, 어떻게 호출하고 사용하는지 살펴보자.

 

- 디폴트 메서드

기존에 작성했던 Calc 인터페이스에 디폴트 메서드를 다음과 같이 쓸 수 있다.

 

package interfaceex;

public interface Calc {

    // 인터페이스에서 선언한 변수는 컴파일 과정에서 상수로 변환됨.
    double PI = 3.14;
    int ERROR = -99999999;

    // 인터페이스에서 선언한 메서드는 컴파일 과정에서 추상 메서드로 변환됨.
    int add(int num1, int num2);
    int substract(int num1, int num2);
    int times(int num1, int num2);
    int divide(int num1, int num2);
    
    default void description() {
        System.out.println("정수계산기를 구현합니다.");
    }
}

디폴트 메스드는 말 그대로 기본으로 제공되는 메서드이다. 

디폴트 메서드는 인터페이스에서 구현하지만, 이후 인터페이스를 구현한 클래스가 생성되면 그 클래스에서 사용할 기본 기능이다.

디폴트 메서드를 선언할때는 default 예약어를 사용한다.

 

디폴트 메서드는 예약어를 제외하고 일반 메서드와 똑같이 구현하면 되고 위의 default 메서드는 다음과 같이 호출할 수 있다. (인스턴스를 활용하여 호출)

package interfaceex;

public class CalculatorTest {
    public static void main(String[] args){
        int num1 =10;
        int num2 = 5;

        CompleteCalc calc = new CompleteCalc(); 
        System.out.println(calc.add(num1,num2));
        System.out.println(calc.substract(num1,num2));
        System.out.println(calc.times(num1,num2));
        System.out.println(calc.divide(num1,num2));
        calc.showInfo();
        calc.description(); // 디폴트 메서드 호출

    }
}

디폴트 메서드도 마찬가지로 하위클래스에서 재정의 할 수 있으며, 이는 기존의 메소드 오버라이딩 방법과 동일하다.

 

- 정적 메서드

정적 메서드는 static 예약어를 사용하여 선언하며 클래스 생성과 무관하게 사용할 수 있다.

정적메서드를 사용할 때 인터페이스의 이름으로 직접 참조하여 사용한다.

 

package interfaceex;

public interface Calc {

    // 인터페이스에서 선언한 변수는 컴파일 과정에서 상수로 변환됨.
    double PI = 3.14;
    int ERROR = -99999999;

    // 인터페이스에서 선언한 메서드는 컴파일 과정에서 추상 메서드로 변환됨.
    int add(int num1, int num2);
    int substract(int num1, int num2);
    int times(int num1, int num2);
    int divide(int num1, int num2);

    default void description() {
        System.out.println("정수계산기를 구현합니다.");
    }
    
    // 정적메소드 추가
    static int total(int[] arr){
        int total = 0;
        for (int i : arr){
            total +=i;
        }
        return total;
    }
}
package interfaceex;

public class CalculatorTest {
    public static void main(String[] args){
        int num1 =10;
        int num2 = 5;

        CompleteCalc calc = new CompleteCalc();
        System.out.println(calc.add(num1,num2));
        System.out.println(calc.substract(num1,num2));
        System.out.println(calc.times(num1,num2));
        System.out.println(calc.divide(num1,num2));
        calc.showInfo();
        calc.description(); // 디폴트 메서드 호출

        int[] arr ={1,2,3,4,5};
        System.out.println(Calc.total(arr));

    }
}
15
5
50
2
Calc 인터페이스를 구현하였습니다.
정수계산기를 구현합니다.
15

 

- private 메서드

자바 9부터는 인터페이스에 private 메서드를 구현 할 수 있다.

 

private 메서드는 인터페이스를 구현한 클래스에서 아용하거나 재정의할수 없다.

따라서, 기존에 구현된 코드를 변경하지 않고 인터페이스를 구현한 클래스에서 공통으로 사용하는 경우에 private 메서드로 구현하면 코드 재사용성을 높일 수 있다.

 

private 메서드는 코드를 모두 구현해야 하므로 "추상 메서드"에 private 예약어를 사용할 수 없지만, static 예약어와 함께 사용할 수 있다. 

package interfaceex;

public interface Calc {

    // 인터페이스에서 선언한 변수는 컴파일 과정에서 상수로 변환됨.
    double PI = 3.14;
    int ERROR = -99999999;

    // 인터페이스에서 선언한 메서드는 컴파일 과정에서 추상 메서드로 변환됨.
    int add(int num1, int num2);
    int substract(int num1, int num2);
    int times(int num1, int num2);
    int divide(int num1, int num2);

    default void description() {
        System.out.println("정수계산기를 구현합니다.");
        myMethod();
    }

    // 정적메소드 추가
    static int total(int[] arr){
        int total = 0;
        for (int i : arr){
            total +=i;
        }
        myStaticMethod();
        return total;
    }

    private void myMethod(){
        System.out.println("private 메서드입니다.");
    }

    private static void myStaticMethod(){
        System.out.println("private static 메서드입니다.");
    }
}

 

15
5
50
2
Calc 인터페이스를 구현하였습니다.
정수계산기를 구현합니다.
private 메서드입니다.
private static 메서드입니다.
15

 

● 인터페이스 활용하기

지금까지, 인터페이스를 공부하여 봤는데 인터페이스가 활용되는 예시를 다음과 같이 살펴보자.

1) 한클래스가 여러 인터페이스를 구현하는 경우

지금까지 하나의 인터페이스를 구현하였지만 인터페이스는 구현 코드나 멤버 변수를 가지지 않기 때문에 여러개를 동시에 구현 할 수 있다. 또한, 두 인터페이스에 이름이 같은 메서드가 선언되었다고 해도 구현은 클래스에서 이루어 지므로, 어떤 메서드를 호출해야 하는지 모호하지 않다.

 

그리고 인터페이스는 클래스와 다르게 한 클래스가 여러 인터페이스를 implement 할 수 있다.

 

2) 두 인터페이스의 디폴트 메서드가 중복되는 경우

그러나, 디폴트 메서드는 인터페이스에서 구현되고 생성된 인스턴스를 활용해 호출하기 때문에 이름이 같을 경우 문제가 발생한다. 이경우에는 인터페이스를 구현한은 클래스에서 이 디폴트 메서드를 필히 재정의하여 사용하여야 한다.

 

(인터페이스 내의 정적 메서드는, 인터페이스를 활용해 호출하기 때문에 이름이 같아도 문제가 되지 않는다.)

 

3) 인터페이스 상속하기

인터페이스는 인터페이스 간의 상속이 가능하다. 

 

인터페이스 간 상속은 구현 코드를 통해 기능을 상속하는 것이 아니므로 "형 상속(type inheritance)"이라고 부른다.

인터페이스는 인터페이스를 여러개 상속 받을 수 있다. 

 

4) 인터페이스 구현과 클래스 상속 함께 쓰기

그럼 여기서 궁금할 수 도 있는게, 한클래스에서 클래스 상속과 인터페이스의 동시 상속이 가능한가? 질문을 할 수 있는데 가능하다.자세한 코드는 다음에 확인해 보는걸로.. ㅎ

 

* 실무에서 인터페이스를 사용하는 경우

 

인터페이스는 클래스가 제공할 기능을 선언하고 설계하는 것이다.

만약 여러클래스가 같은 메서드를 서로 다르게 구현한다면 어떻게 해야 할까?

우선 인터페이스에 메서드를 선언한 다음 인터페이스를 구현할 각 클래스에서 같은 메서드에 대해 다양한 기능을 구현하면 된다.

이것이 바로 인터페이스를 이용한 다형성의 구현이다.

 

이런 경우를 생각해보자

 

어느회사에서 시스템을 개발하였다. 이 시스템은 자료를 저장하기 위해 데이터 베이스를 사용한다. 처음에는 MySQL 데이터 페이스를 사용했는데, 이 시스템을 다른 회사에서 가서 설치하려고 하니 오라클 데이터베이스를 사용하여 설치해 달라고 요구합니다. 또 다른 회사는 MS-SQL 을 사용한다고 한다. 프로그램은 하나인데 사용하는 데이터베이스가 제각각인 상황이다. 이프로그램의 웹 페이지나 모바일 페이지는 데이터 베이스와 관계없이 수행되어야 한다. 데이터 베이스와 연관되는 코드는 프로그램의 특정 부분인것이다. 이런 경우 데이터베이스 기능을 수행할 인터페이스를 정의한다. 그리고 인터페이스 정의에 맞게 여러 데이터 베이스 관련 모듈을 개발하면된다. 

 

이경우, 사용자 정보를 데이터베이스의 입력하거나 업데이트하거나 삭제하는 기능을 UserInfoDao인터페이스에서 정의한다. 그리고 여러 데이터베이스으ㅔ 맞게 구현하는 것은 각 클래스가 담당한다. 웹 페이지나 그 밖의 다른 클래스에서 이기능이 필요하다면 UserInfoDao인터페이스를 구현하여 사용할수 있다. 인터페이스를 잘 정의하는 것이 확장성 있는 프로그램을 만드는 시작이다.

 

 

 

'Java 입문' 카테고리의 다른 글

JAVA Basic - 예외처리  (0) 2022.12.12
JAVA Basic - 기본 클래스  (0) 2022.12.12
JAVA Basic - 추상 클래스  (0) 2022.12.08
JAVA Basic - 상속  (0) 2022.12.07
JAVA Basic - 배열  (0) 2022.11.28
Comments