KDT/Java

231220 Java

001cloudid 2023. 12. 20. 17:40
728x90

인터페이스

구현 코드가 없는 인터페이스

인터페이스(interface)는 클래스 혹은 프로그램이 제공하는 기능을 명시적으로 선언하는 역할

인터페이스는 추상 메서드와 상수로만 이루어져 있음

구현된 코드가 없기 때문에 인스턴스를 생성할 수 없음

package interface_;

 

public interface Calc {

//인터페이스에서 선언한 변수는 컴파일 과정에서 상수로 변환됨

double PI = 3.14;

int ERROR = -9999999;

 

//인터페이스에서 선언한 메소드는 컴파일 과정에서 추상 메소드로 변환됨

int add(int num1, int num2);

int substract(int num1, int num2);

int times(int num1, int num2);

int divide(int num1, int num2);

 

}

Calc 인터페이스에 원주율 PI, 변수와 오류가 났을 때 사용할 ERROR 변수, 사칙연산을 수행하기 위해 메소드를 선언

구현 코드가 없는 추상 메소드임.

public abstract 예약어를 명시적으로 쓰지 않아도 컴파일 과정에서 자동으로 추상 메소드로 변환됨.

인터페이스에 선언한 변수는 모두 컴파일 과정에서 값이 변하지 않는 상수로 자동 변환

 

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

인터페이스를 클래스가 사용하는 것을 '클래스에서 인터페이스를 구현한다(implements)'라고 표현

인터페이스에 선언한 기능을 클래스에 구현한다는 의미로 implements 예약어를 사용

Calc 인터페이스를 Calculator 클래스에서 구현하는 방법은 그림1과 같다.

그림 1. 클래스에서 인터페이스 구현 1

그림 1과같이 코드를 작성하면 오류가 발생하는데, 추상 메소드를 구현하지 않으면 Calculator클래스도 추상 클래스가 됨.

오류 메세지는 Calc 인터페이스에 포함된 추상 메소드를 구현하거나 Calculator 클래스를 추상 클래스로 만들어라는 의미

추상 메소드를 구현하여 오류를 해결

package interface_;

 

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;

}

 

}

추상 메소드 times()와 divide()를 구현하지 않았으므로 Calculator는 추상 클래스

 

클래스 완성하고 실행

package interface_;

 

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; //num2가 0, 즉 나누는 수가 0인 경우에 대해 오류 반환

}

 

public void show() {

System.out.println("Calc 인터페이스를 구현"); //CompleteCalc에서 추가로 구현한 메소드

}

}

 

package interface_;

 

public class CalculatorTest {

 

public static void main(String[] args) {

int num1 = 10;

int num2 = 2;

 

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.show();

 

}

 

}

그림 2. 클래스 완성 후 실행

 

인터페이스 구현과 형 변환

Calculator 클래스는 인터페이스에서 선언한 추상 메소드 중 일부 메소드만 구현했으므로 추상 클래스임.

이를 상속받은 CompleteCalc 클래스는 Calculator 클래스에서 구현하지 않은 나머지 추상 메소드를 모두 구현하고

show() 메소드를 추가로 구현함.

 

인터페이스와 다형성

인터페이스의 역할

클라이언트 프로그램에 어떤 메소드를 제공하는지 미리 알려주는 명세(specification) 또는 약속의 역할을 함

즉, 인터페이스의 역할은 인터페이스를 구현한 클래스가 어떤 기능의 메소드를 제공하는지 명시하는 것이며, 클라이언트 프로그램은 인터페이스에서 약속한 명세대로 구현한 클래스를 생성해서 사용하면 됨.

 

인터페이스와 다형성

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

즉 클라이언트 프로그램을 많이 수정하지 않고 기능을 추가하거나 다른 기능을 사용할 수 있음

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("업무스킬값이 높은 상담원에게 우선적으로 배분");

 

}

 

}

 

package scheduler;

 

import java.io.IOException;

 

public class SchedulerTest {

 

public static void main(String[] args) throws 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;

 

//입력받은 값이 R 또는 r 이면 RoundRobin 클래스 생성

if(ch=='R'|| ch== 'r') {

scheduler = new RoundRobin();

}

//입력받은 값이 L 또는 l 이면 LeastJob 클래스 생성

else if(ch=='L' || ch=='l') {

scheduler = new LeastJob();

}

//입력받은 값이 P 또는 p 이면 PriorityAllocation 클래스 생성

else if(ch=='P' || ch=='p') {

scheduler = new PriorityAllocation();

}

else {

System.out.println("지원되지 않는 기능");

}

 

//어떤 정책인가와 상관없이 인터페이스에 선언한 메소드 호출

scheduler.getNextCall();

scheduler.sendCallToAgent();

 

 

 

}

 

}

그림 3. 인터페이스와 다형성

 

클라이언트가 클래스를 사용하는 방법

클라이언트 프로그램은 각 클래스의 구현 방법을 몰라도 인터페이스에서 선언된 매개 변수, 반환 값을 보고 클래스를 사용할 수 있음.

 

인터페이스 요소

인터페이스 상수

인터페이스는 추상 메소드로 이루어지므로 인스턴스를 생성할 수 없으며 멤버 변수도 사용할 수 없음

그림 4. 인터페이스 상수

하지만 그림 4와 같이 변수를 선언해도 오류가 발생하지 않는데, 그 이유는 인터페이스에 선언한 변수를 컴파일하면 상수로 변환되기 때문.

Calc 인터페이스에 선언한 변수 PI를 컴파일하면 public static final double PI = 3.14, ERROR 역시 public static final int ERROR = -9999999로 변환되어 상수로 취급됨

 

디폴트 메소드

기본으로 제공되는 메소드를 디폴트 메소드라고 함. 디폴트 메소드는 인터페이스에서 구현하지만, 이후 인터페이스를 구현한 클래스가 생성되면 그 클래스에서 사용할 기본 기능이다. 디폴트 메소드를 선언할 때는 default예약어를 사용

 

디폴트 메소드는 일반 메소드와 똑같이 구현하면 되고, 메소드 자료형 앞에 default 예약어만 작성해주면 됨

 

만약 이미 인터페이스에 구현되어 있는 디폴트 메소드가 새로 생성한 클래스에서 원하는 기능과 맞지 않다면, 하위 클래스에서 디폴트 메소드를 재정의할 수 있음.

 

정적 메소드

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

 

private 메소드

 

인터페이스 활용

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

package interface_;

 

public interface Buy {

void buy();

 

}

 

package interface_;

 

public interface Sell {

void sell();

 

}

 

package interface_;

 

//Customer 클래스는 Buy, Sell 인터페이스를 모두 구현함

public class Customer implements Buy, Sell{

 

@Override

public void sell() {

System.out.println("판매하기");

 

}

 

@Override

public void buy() {

System.out.println("구매하기");

 

}

 

}

 

package interface_;

 

public class CustomerTest {

 

public static void main(String[] args) {

Customer customer = new Customer();

 

//Customer 클래스형인 customer를 Buy 인터페이스 형인 buyer에 대입하여 형 변환

//buyer는 Buy 인터페이스의 메소드만 호출 가능

Buy buyer = customer;

buyer.buy();

 

//Customer 클래스형인 customer를 Sell 인터페이스 형인 seller에 대입하여 형 변환

//seller는 Sell 인터페이스의 메소드만 호출 가능

Sell seller = customer;

seller.sell();

 

if(seller instanceof Customer) {

Customer customer2 = (Customer)seller; //seller를 하위 클래스형인 Customer로 다시 형 변환

customer2.buy();

customer2.sell();

}

 

 

}

 

}

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

상속 관계에서 마찬가지로 원래의 인스턴스 자료형으로 다운 캐스팅하기 위해서는 instanceof를 사용하여 본래 인스턴스 자료형으로 안전하게 변환할 수 있음.

 

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

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

package interface_;

 

public interface Buy {

void buy();

 

default void order() {

System.out.println("구매 주문");

}

 

}

 

package interface_;

 

public interface Sell {

void sell();

 

default void order() {

System.out.println("판매 주문");

}

}

 

package interface_;

 

//Customer 클래스는 Buy, Sell 인터페이스를 모두 구현함

public class Customer implements Buy, Sell{

 

@Override

public void sell() {

System.out.println("판매하기");

 

}

 

@Override

public void buy() {

System.out.println("구매하기");

 

}

 

//디폴트 메소드 order()를 Customer 클래스에서 재정의함

@Override

public void order() {

System.out.println("고객 판매 주문");

}

 

}

 

package interface_;

 

public class CustomerTest {

 

public static void main(String[] args) {

Customer customer = new Customer();

 

Buy buyer = customer;

buyer.buy();

buyer.order();

 

Sell seller = customer;

seller.sell();

seller.order();

 

if(seller instanceof Customer) {

Customer customer2 = (Customer)seller;

customer2.buy();

customer2.sell();

}

customer.order();

 

}

 

}

그림 6. 두 인터페이스의 디폴트 메소드가 중복되는 경우

 

인터페이스 상속

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

클래스의 경우에는 하나의 클래스만 상속받을 수 있지만, 인터페이스는 여러 개를 동시에 상속받을 수 있음.

한 인터페이스가 여러 인터페이스를 상속받으면, 상속받은 인터페이스는 상위 인터페이스를 선언한 추상 메소드를 모두 가지게 됨.

package interface_;

 

public interface X {

void x();

 

}

 

package interface_;

 

public interface Y {

void y();

 

}

 

package interface_;

 

public interface MyInterface extends X,Y{

void myMethod();

 

}

 

package interface_;

 

public class MyClass implements X,Y,MyInterface{

 

//MyInterface인터페이스에서 상속받은 myMethod()메소드 구현

@Override

public void myMethod() {

System.out.println("myMethod()");

 

}

 

////Y인터페이스에서 상속받은 y()메소드 구현

@Override

public void y() {

System.out.println("y()");

 

}

 

//X인터페이스에서 상속받은 x()메소드 구현

@Override

public void x() {

System.out.println("x()");

 

}

 

}

 

package interface_;

 

public class MyClassTest {

 

public static void main(String[] args) {

MyClass mc = new MyClass();

//상위 인터페이스 X형으로 대입하면 X에 선언한 메소드만 호출 가능

X x = mc;

x.x();

 

//상위 인터페이스 Y형으로 대입하면 Y에 선언한 메소드만 호출 가능

Y y = mc;

y.y();

 

//구현한 인터페이스형 변수에 대입하면 인터페이스가 상속한 모든 메소드 호출 가능

MyInterface mi = mc;

mi.myMethod();

mi.x();

mi.y();

 

 

}

 

}

그림 7. 인터페이스 상속

생성한 클래스는 상위 인터페이스형으로 변환할 수 있음. 단, 상위 인터페이스로 형 변환을 하면 상위 인터페이스에 선언한 메소드만 호출 할 수 있음

 

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

Queue 인터페이스를 구현하고 Shelf 클래스를 상속받는 BookShelf 클래스

※ 처음 들어간 자료부터 꺼내 쓰는 것을 큐(Queue) 자료 구조라고 함.

package bookshelf;

 

import java.util.ArrayList;

 

public class Shelf {

//자료를 순서대로 저장할 ArrayList 선언

protected ArrayList<String> shelf;

 

//디폴트 생성자로 Shelf 클래스를 생성하면 ArrayList도 생성됨

public Shelf() {

shelf = new ArrayList<String>();

}

 

public ArrayList<String> getShelf() {

return shelf;

}

 

public void setShelf(ArrayList<String> shelf) {

this.shelf = 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 {

 

//배열에 요소 추가

@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());

 

}

 

}

그림 8. 인터페이스 구현과 클래스 상속

 

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

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

만약 여러 클래스가 같은 메소드를 서로 다르게 구현한다면 인터페이스에 메소드를 선언한 다음 인터페이스를 구현한 각 클래스에서 같은 메소드에 대해 다양한 기능을 구현하면된다.

728x90

'KDT > Java' 카테고리의 다른 글

231227 Java  (0) 2023.12.27
231221 Java  (0) 2023.12.21
231218 Java  (0) 2023.12.18
231214 Java  (0) 2023.12.14
231213 Java  (0) 2023.12.13