Interface Declaration and Implementation with Java

민준·2023년 3월 8일
0
post-thumbnail

1. Interface

인터페이스 는 구현 코드가 없는 추상 메서드(Abstract Method)들로 이루어져 있습니다.

글로만 보면 감이 안 올 것 같은데, 일반적으로 우리가 웹이나 게임 등에서 보는 '인터페이스'라고 생각해도 될 것 같습니다.
여기에서 '인터페이스'는 화면 구성을 보여주는 것이지만 결국 하는 일은
정해진 버튼 등으로 사용자의 행동을 유도하고 그 밖의 행위를 통제하는 것입니다.

클래스에서 인터페이스 구현하기

'인터페이스'에 선언된 모든 메서드는 'public abstract'로 추상 메서드가 되고
모든 변수는 'public static final'로 상수가 됩니다.
일반적으로 추상 메서드를 선언할 때 'abstract'를 붙였는데,
'interface' 클래스 안에서는 메서드를 그냥 선언해도 추상 메서드로 인식합니다.

// 인터페이스
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);
}

타입 상속 은 인터페이스 타입을 상속받아 메서드를 구현하는 것입니다.
일반적으로 클래스를 상속받을 때와 달리 'extends'가 아니라 'implements'를 사용합니다.
아래 코드에서는 위에 선언한 4개의 메서드 중 2개만 구현합니다.
이 경우처럼 인터페이스의 모든 메서드를 구현하지 않으면 이 클래스는 추상 클래스가 되어 'abstract'를 붙이고
하위 클래스에서 나머지 메서드를 구현해야합니다.

// 타입 상속 (Interface Implements), 'Calculator' 클래스에서 'Calc' 인터페이스 구현
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;
	}
}

인터페이스인 'Calc'를 하위 클래스에서 'extends'로 상속받으려 하면
아래와 같은 오류로 컴파일 되지 않습니다.
The type Calc cannot be the superclass of CompleteCalc; a superclass must be a class

위 코드에서는 인터페이스를 상속받아 일부 메서드만 구현했고 아래 코드에서는
그 클래스를 상속받아 나머지 메서드를 구현합니다.
이렇게 추상 클래스를 상속받아 메서드를 구현하는 것을 구현 상속 이라고 합니다.

// 구현 상속 (Class Extends)
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;
		}
		return ERROR;
	}

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

인터페이스와 추상 클래스는 인스턴스화 할 수 없습니다.
반면 모든 메서드를 구현한 'CompleteCalc' 클래스는 인스턴스화 할 수 있습니다.

public class CalculatorTest {

	public static void main(String[] args) {

		int num1 = 10;
		int num2 = 2;
		Calc calc = new CompleteCalc(); // 업캐스팅
		//Calc calc1 = new Calc(); // 인터페이스는 인스턴스화 할 수 없습니다.
		//Calc calc2 = new Calculator(); // 추상 클래스는 인스턴스화 할 수 없습니다.
		
		System.out.println(calc.add(num1, num2));
		//calc.showInfo(); 타입의 메서드만 호출할 수 있습니다.
		
	}
}

인터페이스 구현과 형 변환

인터페이스를 구현한 클래스는 인터페이스 형으로 선언한 변수로 형 변환 할 수 있습니다. (업캐스팅)
단, 일반적인 클래스 상속과 달리 구현 코드가 없기 때문에 여러 인터페이스(쉼표 구분)를 구현할 수 있습니다.
형 변환 시 인터페이스에 선언된 메서드만 사용할 수 있습니다.


2. Interface and Polymorphism

다양한 구현이 필요한 "인터페이스를 설계하는 일"은 매우 중요한 일입니다.

'인터페이스'는 '클라이언트 코드'와 서비스를 제공하는 '객체' 사이의 약속입니다.
그래서 '클라이언트'가 어떻게 구현되었는지 상관없이 '인터페이스'의 정의만을 보고 사용할 수 있습니다.
Client Code 란 인터페이스를 기반으로 구현된 인스턴스 클래스를 생성해 사용하는 코드를 말합니다.

어떤 객체가 'interface' 타입이라 함은 그 '인터페이스'가 제공하는 메서드를 구현했다는 의미입니다. (타입 상속)

인터페이스

public interface Scheduler {

	void getNextCall();
	void sendCallToAgent();	
}

메서드 구현 클래스, 전화상담 배분 방식 별 클래스

public class RoundRobin implements Scheduler{

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

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

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

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

	@Override
	public void getNextCall() {
		System.out.println("등급이 높은 고객의 전화를 먼저 가져옵니다.");
	}

	@Override
	public void sendCallToAgent() {
		System.out.println("업무 스킬이 가장 높은 상담원의 대기열 앞에 우선 배분합니다.");
	}
}

메인 메서드

public class SchedulerTest {
	
	public static void main(String[] args) throws IOException {
		
		System.out.println("전화 상담 배분 방식을 선택하세요. R, L, P");
		
		int ch = System.in.read(); // Unhandled exception type IOException
		Scheduler scheduler = null; // '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();
	}
}

3. Interface

ㄱ) 인터페이스의 요소

요소내용
상수모든 변수는 상수로 변환 됩니다.
추상 메서드모든 메서드는 추상 메서드가 되며 하위 클래스에서 구현해야 합니다.
default 메서드기본 구현을 가지는 메서드이며 구현 클래스에서 재정의 할 수 있습니다.
static 메서드인스턴스 생성과 상관 없이 인터페이스 타입으로 사용할 수 있습니다.
private 메서드인터페이스를 구현한 클래스에서 사용하거나 재정의 할 수 없습니다.
인터페이스 내부에서만 기능을 제공하기 위해 구현하는 메서드입니다.

a) Default Method

인터페이스 안에서는 기본적으로 메서드를 구현할 수 없습니다.
하지만 default 키워드를 사용하면 공통적으로 사용할, 또는 기본적인 기능의 메서드를 구현할 수 있습니다.
물론 추후 메서드를 재정의(오버라이딩) 할 수도 있습니다.

// 인터페이스 안에 디폴트 메서드 구현하기
default void description() {
	System.out.println("정수 계산기를 구현합니다.");
}

b) Static Method

정적 메서드 는 인스턴스의 생성과 상관없이 인터페이스의 이름을 참조하는 것만으로 호출이 가능합니다.

// 인터페이스 안에 정적 메서드 구현하기
static int total(int[] arr) {
	int total = 0;
		
	for (int i: arr) {
		total += i;
	}
	return total;
}

c) Private Method

인터페이스 내부에 private 또는 private static 으로 선언한 메서드를 구현할 수 있습니다.

'private'으로 선언한 메서드는 '디폴트 메서드'에서 사용이 가능하고
'private static'으로 선언한 메서드는 '정적 메서드'에서 호출해 사용이 가능합니다.
다만 하위 클래스에서 오버라이딩 할 수 없습니다.

인터페이스 내부에서 이러한 메서드들의 정의와 호출은 아래와 같은 형식입니다.

public interface A {

	default void B() {
    	pMethod();
    }
    
    static int C() {
       	psMethod();
        return null;
    }
}

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

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

ㄴ) 두 개의 인터페이스 구현하기

자바에서는 클래스 다중 상속을 할 수 없습니다. 하지만 인터페이스는 구현 코드가 없기 때문에
여러 인터페이스를 실제 구현 클래스에서 다중 상속이 가능합니다.

2개의 인터페이스

public interface Buy {
	
	void buy();
	
	default void order() {
		System.out.println("구매주문");
	}
}
public interface Sell {

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

두 개의 인터페이스를 구현하는 클래스

위의 두 인터페이스에 같은 이름의 디폴트 메서드가 구현되어있습니다.
이 경우 두 개의 인터페이스를 구현하는 클래스에서는 아래와 같은 오류가 나게 됩니다.
"Duplicate default methods named order with the parameters () and ()
are inherited from the types Sell and Buy"
내용인즉, 디폴트 메서드의 중복으로 오버라이딩이 필요한 것입니다.

public class Customer implements Buy, Sell {

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

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

	// 인터페이스 디폴트 메서드의 중복으로 메서드 재정의
	@Override
	public void order() {
		System.out.println("고객 판매 주문");
	}
}

인스턴스를 인터페이스 타입의 변수에 바인딩하는 경우, 각 인터페이스에서 선언한 메서드만 호출 할 수 있습니다.
반면 두 인터페이스의 메서드를 구현한 클래스의 인스턴스의 경우 모두 호출할 수 있습니다.

그런데 두 인터페이스에서 중복된 메서드를 구현 클래스에서 재정의하고
이를 인터페이스 타입의 변수와 구현 클래스의 인스턴스로 호출한 결과는
모두 인스턴스의 오버라이딩한 메서드가 호출됩니다.

public class CustomerTest {

	public static void main(String[] args) {

		Customer customer = new Customer();
		
        // 구현 클래스를 바인딩한 인터페이스 타입의 변수는 각 인터페이스의 메서드만 호출 가능
		Buy buyer = customer;
		buyer.buy();
		
		Sell seller = customer;
		seller.sell();
		
        // 두 인터페이스의 메서드를 구현한 클래스의 인스턴스는 두 메서드 모두 호출 가능
		customer.buy();
		customer.sell();
		
        // 모두 오버라이딩한 메서드가 호출됨
		customer.order();
		buyer.order();
		seller.order();
	}
}

ㄷ) 인터페이스 상속

인터페이스 간에도 상속이 가능합니다. 구현 코드의 상속이 아니므로 형 상속(Type Inheritance) 라고 합니다.

인터페이스

public interface X {

	void x();
}
public interface Y {

	void y();
}

인터페이스를 상속받는 인터페이스

public interface MyInterface extends X, Y {

	void myMethod();
}

구현 클래스와 메인 메서드

public class MyClass implements MyInterface{
	
	// 인터페이스를 상속받은 인터페이스를 implements 하면 모두 구현해야함.
	@Override
	public void x() {
		System.out.println("x()");
	}

	@Override
	public void y() {
		System.out.println("y()");
	}

	@Override
	public void myMethod() {
		System.out.println("myMethod()");
	}
	
    // 메인 메서드
	public static void main(String[] args) {
		
		MyClass myClass = new MyClass();
		
        // 상속받은 인터페이스를 구현한 클래스의 인스턴스는 모든 메서드를 호출 할 수 있습니다.
		myClass.x();
		myClass.y();
		myClass.myMethod();
		
		X xClass = myClass;
		xClass.x();
		
	}
}

ㄹ) 인터페이스 구현과 클래스 상속 함께 사용하기

Queue : First In, First Out '선입선출(FIFO)'을 구현하는 프로그램을 만듭니다.

1) 인터페이스

public interface Queue {
	
	void enQueue(String title); 
	String deQueue();			
	int getSize();				
}

2) 상위 클래스

import java.util.ArrayList;

public class Shelf {
	
	// protected : 상속받은 클래스에서 사용가능합니다.
	protected ArrayList<String> shelf;
	
	public Shelf() {
		shelf = new ArrayList<String>();
	}
	
	public ArrayList<String> getShelf() {
		return shelf;
	}
	
	public int getCount() {
		return shelf.size();
	}
}

인터페이스 구현과 클래스 상속

인터페이스에서 선언된 메서드를 구현하면서 상속받은 클래스의 메서드와 변수를 사용합니다.

public class BookShelf extends Shelf implements Queue {

	// shelf = new ArrayList<String>();
	@Override
	public void enQueue(String title) {
		shelf.add(title);
	}

	// shelf의 0번째 인덱스의 값을 제거함과 동시에 반환.
	@Override
	public String deQueue() {
		return shelf.remove(0);
	}

	// return shelf.size();
	@Override
	public int getSize() {
		return getCount();
	}
}

메인 메서드

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.getSize());
		
		System.out.println(shelfQueue.deQueue());
		System.out.println(shelfQueue.deQueue());
		System.out.println(shelfQueue.deQueue());
		System.out.println(shelfQueue.getSize());
	}
}
profile
백엔드 포지션 공부 중입니다.

0개의 댓글