본문 바로가기

Java-Study

Java Study : 6주차 정리 - 인터페이스

인터페이스

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

구현하기

package interfaceex;

public interface Calc {

    double PI = 3.14;
    int ERROR = -999999999;

    int add(int num1, int num2);
    int substract(int num1, int num2);
    int times(int num1, int num2);
    int divide(int num1, int num2);
}

인터페이스를 사용하기 위해서는 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;
	}
}

클래스 완성하고 실행하기

계산기 클래스를 만들어서 실행을 해본다.

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;	//나누는 수가 0인 경우 오류 반환
    }

    public void showInfo() {	//CompleteCalc에서 추가로 구현한 메소드
        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();
    }
}

 

인터페이스 구현과 형 변환

  • 상속과 마찬가지로 인터페이스에서도 하위 클래스가 상위 클래스 자료형을 묵시적 형 변환이 가능하다.
  • 인터페이스를 구현한 클래스가 있을 때 그 클래스는 해당 인터페이스형으로 묵시적 형 변환이 이루어지고
    형 변환되었을 때 사용할 수 있는 메서드는 인터페이스에서 선언한 메서드 뿐이다.

인터페이스와 다형성

인터페이스의 역할

인터페이스의 역할은 인터페이스를 구현한 클래스가 어떤 기능의 메서드를 제공하는 명시하는 것이다.

 

인터페이스와 다형성

인터페이스를 사용하면 다형성을 구현하여 확장성 있는 프로그램을 만들 수 있다.

 

Schedule 인터페이스를 구현하는 각 세 클래스를 직접 만들어서 확인해 보자

package scheduler;

public interface Scheduler {	//인터페이스 정의

    public void getNextCall();
    public void sendCallToAgent();

}

 

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("업무 skill 값이 높은 상담원에게 우선적으로 배분합니다.");
		
	}
}
package scheduler;

import java.io.IOException;

public class SchedulerTest {

    public static void main(String[] args) throws IOException {
        //문자를 입력받는 System.in.read()를 사용하기 위해서는 IOException에서 오류를 처리해야 한다.

        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'){ //입력받은 값이 R 또는 r이면 RoundRobin 클래스 생성
            scheduler = new RoundRobin();
        }
        else if(ch == 'L' || ch == 'l'){    //입력받은 값이 L 또는 l이면 LeastJob 클래스 생성
            scheduler = new LeastJob();
        }
        else if(ch == 'P'|| ch == 'p'){ //입력받은 값이 P 또는 p이면 PriorityAllocation 클래스 생성
            scheduler = new PriorityAllocation();
        }
        else{
            System.out.println("지원되지 않는 기능입니다.");
            return;
        }

        scheduler.getNextCall();    //정책에 상관 없이 인터페이스에 선언한 메서드 호출
        scheduler.sendCallToAgent();
    }
}

이 방식에서 분배 방식을 추가하려면 인터페이스를 구현하는 새 클래스를 만들면 된다. 이것이 인터페이스의 다형성이다.

 

인터페이스 요소 살펴보기

인터페이스 상수

  • 인터페이스는 추상 메서드로 이루어지기 때문에 인스턴스 생성이 불가능하고, 멤버 변수도 사용할 수 없다.
    하지만 변수를 선언해도 인터페이스에 선언한 변수는 컴파일하면 상수로 변환되기 때문에 변수를 선언할 수 있다.

 

디폴트 메서드

  • 디폴트 메서드는 인터페이스에서 구현하지만 이후 인터페이스를 구현한 클래스가 생성되면
    그 클래스에서 사용할 기본 기능이다.
  • 디폴트 메서드를 선언할 때는 default 예약어를 사용한다.
  • 디폴트 메서드는 하위 클래스에서 재정의가 가능하다.

앞에서 만든 Calc 인터페이스에 디폴드 메서드를 구현하고 호출해 보자

package interfaceex;

public interface Calc {

    double PI = 3.14;
    int ERROR = -999999999;

    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("정수 계산기를 구현합니다");
    }
}
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.showInfo();    //디폴트 메서드 호출
    }
}

 

 

정적 메서드

  • 정적 메서드는 static 예약어를 사용하여 선언하며 클래스 생성과 무관하게 사용할 수 있다.
  • 인터페이스 이름으로 직접 참조하여 사용한다.

이번에는 Calc 인터페이스에 정적 메서드를 선언하고 호출해 보자

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.showInfo();    //디폴트 메서드 호출

        int[] arr = {1,2,3,4,5};
        System.out.println(Calc.total(arr));  //정적 메소드 사용하기

    }
}

 

private 메서드

  • private 메서드는 인터페이스를 구현한 클래스에서 사용하거나 재정의할 수 없다.
  • 추상 메서드에 private 예약어를 사용할 수는 없지만, static 예약어는 함께 사용 가능하다.
  • private static 메서드는 정적 메서드에서 호출하여 사용한다.
package interfaceex;

public interface Calc {

    double PI = 3.14;
    int ERROR = -999999999;

    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(); //디폴트 메서드에서 private 메서드 호출
    }


    static int total(int[] arr){    //정적 메서드 선언

        int total = 0;

        for(int i: arr){
            total += i;
        }
        myStaticMethod();   //정적 메서드에서 private 메서드 호출
        return total;
    }

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

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

 

인터페이스 활용하기

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

인터페이스는 한 클래스가 여러 인터페이스를 구현할 수 있다.

package interfaceex;

public interface Buy {

    void buy();
}
package interfaceex;

public interface Sell {

    void sell();
}
package interfaceex;

public class Customer implements Buy, Sell{
    //Customer 클래스는 Buy와 Sell 인터페이스를 모두 구현함
    @Override
    public void buy() {
        System.out.println("구매하기");
    }

    @Override
    public void sell() {
        System.out.println("판매하기");
    }
}
package interfaceex;

public class CustomerTest {

    public static void main(String[] args) {

        Customer customer = new Customer();

        Buy buyer = customer;
        //Customer 클래스형인 customer를 Buy 인터페이스형인 buyer에 대입하여 형 변환
        //buyer는 Buy 인터페이스의 메서드만 호출 가능
        buyer.buy();

        Sell seller = customer;
        //Customer 클래스형인 customer를 Sell 인터페이스형인 seller에 대입하여 형 변환
        //buyer는 Buy 인터페이스의 메서드만 호출 가능
        seller.sell();

        if( seller instanceof Customer){
            Customer customer2 = (Customer)seller;
            //seller를 하위 클래스형인 Customer로 다시 형 변환
            customer2.buy();
            customer2.sell();
        }
    }
}

 

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

package interfaceex;

public interface Buy {

    void buy();

    default void order(){
        System.out.println("구매 주문");
    }
}
package interfaceex;

public interface Sell {

    void sell();
    
    default void order(){
        System.out.println("판매 주문");
    }
}

두 인터페이스 모두 order() 디폴트 메서드를 가지고 있기 때문에 Customer 클래스에서 재정의 해야 사용이 가능하다.

package interfaceex;

public class Customer implements Buy, Sell{
    //Customer 클래스는 Buy와 Sell 인터페이스를 모두 구현함
    @Override
    public void buy() {
        System.out.println("구매하기");
    }

    @Override
    public void sell() {
        System.out.println("판매하기");
    }

    @Override
	public void order() {   //디폴트 메서드를 재정의
		System.out.println("고객 판매 주문");
	}
}
package interfaceex;

public class CustomerTest {

    public static void main(String[] args) {

        Customer customer = new Customer();

        Buy buyer = customer;
        //Customer 클래스형인 customer를 Buy 인터페이스형인 buyer에 대입하여 형 변환
        //buyer는 Buy 인터페이스의 메서드만 호출 가능
        buyer.buy();
        buyer.order();

        Sell seller = customer;
        //Customer 클래스형인 customer를 Sell 인터페이스형인 seller에 대입하여 형 변환
        //buyer는 Buy 인터페이스의 메서드만 호출 가능
        seller.sell();
        seller.order();

        if( seller instanceof Customer){
            Customer customer2 = (Customer)seller;
            //seller를 하위 클래스형인 Customer로 다시 형 변환
            customer2.buy();
            customer2.sell();
        }
        customer.order();
    }
}

 

인터페이스 상속하기

  • 인터페이스 간에도 상속이 가능하지만 구현 코드를 통해 기능을 상속하는 것이 아니기 때문에 형 상속이라고 부른다.
  • 인터페이스는 여러 개를 동시에 상속받을 수 있다.
package interfaceex;

public interface X {
	
	void x();

}
package interfaceex;

public interface Y {

    void y();
}
package interfaceex;

public interface MyInterface extends X, Y{
    //인터페이스 X와 Y를 여러개 상속 받음

    void myMethod();
}
package interfaceex;

public class MyClass implements MyInterface{

    @Override
    public void x() {   //X 인터페이스에서 상속받은 x() 메서드 구현
        System.out.println("x()");
    }

    @Override
    public void y() {   //Y 인터페이스에서 상속받은 y() 메서드 구현
        System.out.println("y()");
    }

    @Override
    public void myMethod() {    //MyInterface에서 상속받은 myMethod() 메서드 구현
        System.out.println("myMethod()");
    }

}
package interfaceex;

public class MyClassTest {

    public static void main(String[] args) {

        MyClass mClass = new MyClass();
        X xClass = mClass;
        //상위 인터페이스 X형으로 대입하면 X에 선언한 메서드만 호출 가능
        xClass.x();

        Y yClass = mClass;
        //상위 인터페이스 Y형으로 대입하면  Y에 선언한 메서드만 호출 가능
        yClass.y();


        //구현한 인터페이스형 변수에 대입하면 인터페이스가 상속한 모든 메서드 호출 가능
        MyInterface iClass = mClass;
        iClass.myMethod();
        iClass.x();
        iClass.y();
    }
}

 

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

한 클래스에서 클래스 상속과 인터페이스 구현을 모두 할 수 있다.

package bookshelf;

import java.util.ArrayList;

public class Shelf {

    protected ArrayList<String> shelf;
    //자료를 순차적으로 저장할 ArrayList 선언

    public Shelf(){ //디폴트 생성자로 Shelf 클래스를 생성하면 ArrayList도 생섬됨
        shelf = new ArrayList<String>();
    }

    public ArrayList<String> getShelf(){
        return shelf;
    }

    public int getCount(){
        return shelf.size();
    }
}
package bookshelf;

public interface Queue {

    void enQueue(String title); //배열의 맨 마지막에 추가
    String deQueue();   //배열의 맨 처음 항복 반환
    int getSize();  //현재 Queue에 있는 개수 반환
}
package bookshelf;

public class BookShelf extends Shelf implements Queue{ 
    //Shelf 클래스를 상속받고 Queue 인터페이스를 구현

    @Override
    public void enQueue(String title) { //배열에 요소 추가
        shelf.add(title);
    }

    @Override
    public String deQueue() {   //맨 처음 요소를 배열에서 삭제하고 반환
        return shelf.remove(0);
    }

    @Override
    public int getSize() {  //배열 요소 개수 반환
        return getCount();
    }
}
package bookshelf;

public class BookShelfTest {

    public static void main(String[] args) {

        Queue shelfQueue = new BookShelf();
        shelfQueue.enQueue("태백산맥 1");   //순서대로 요소를 추가
        shelfQueue.enQueue("태백산맥 2");
        shelfQueue.enQueue("태백산맥 3");

        System.out.println(shelfQueue.deQueue());   //입력 순서대로 요소를 꺼내서 출력
        System.out.println(shelfQueue.deQueue());
        System.out.println(shelfQueue.deQueue());
    }
}