인터페이스는 클래스나 프로그램이 제공하는 기능을 명시적으로 선언하는 역할을 하고, 추상 메서드와 상수로만 이루어져 있다.
인터페이스 역할
인터페이스는 메서드의 선언부만 있는데 왜 사용할까?
인터페이스는 클라이언트 프로그램에 어떤 메서드를 제공하는지 미리 알려주는 명세 또는 약속의 역할을 한다.
예를 들어, AA 인터페이스를 구현한 A 클래스가 있고 이 클래스를 사용하는 B프로그램이 있다고 가정하자.
AA 인터페이스에는 구현할 추상 메서드가 모두 선언되어 있고, 어떤 매개변수를 사용하고 어떤 자료형 값이 반환되는지가 선언되어 있을 것이다.
그렇다면 B프로그램에서는 A 클래스의 구현 코드를 모두 살펴보지 않고 AA 인터페이스의 선언부만 봐도 A 클래스를 어떻게 사용할 지 알 수 있는 것이다.
그리고 B 프로그램에서 AA 인터페이스를 구현한 다른 클래스인 C 클래스를 사용하고 싶다면, 인터페이스 명세에 따라
A 클래스에서 C 클래스로 교체해서 사용할 수 있다.
AA aa = new A();
AA aa = new C();
A, C 클래스가 AA 인터페이스를 구현했으므로 위 코드와 같이 B 프로그램에서 AA를 클래스형으로 선언하여 코드를 작성할 수 있다. 이렇게 B 프로그램에서 각 클래스를 사용할 때 클래스에서 구현한 내용을 몰라도 AA 인터페이스에서 선언한 메서드의 매개변수 자료형과 반환값만 알면 AA 인터페이스를 구현한 어떤 클래스든 사용할 수 있다.
정리하자면, 인터페이스의 역할은 인터페이스를 구현한 클래스가 어떤 기능의 메서드를 제공하는지 명시하는 것이다.
그리고 클라이언트 프로그램은 인터페이스에서 약속한 명세대로 구현한 클래스를 생성해서 사용하면 된다.
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);
}
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;
}
}
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 인터페이스를 구현했습니다.");
}
}
public class CalcTest {
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 인터페이스는 계산기를 만들기 위해 선언한 코드다. PI와 ERROR 변수, 사칙 연산을 수행하기 위해 추상 메서드 4개를 선언했다.
추상메서드이고 다른 클래스에서도 사용할 수 있게 public abstract를 써야할 필요 없이 인터페이스에서는 컴파일 과정에서 자동으로 추상 메서드로 변환해주고, 선언한 변수는 public static final을 쓰지 않아도 값이 변하지 않는 상수가 된다.
그리고 Calculator 클래스에서 인터페이스에서 선언한 기능을 클래스가 구현한다는 의미로 implements를 사용했다.
Calculator 클래스는 Calc 인터페이스에서 선언한 추상메서드 중 일부 메서드만 구현했기 때문에 추상 클래스이다.
CompleteCalc 클래스는 Calculator 클래스에서 구현하지 않은 나머지 메서드를 모두 구현하고 showInfo() 메서드를 추가했다.
상속 관계에서 하위 클래스는 상위 클래스 자료형으로 묵시적 형 변환이 가능하듯 인터페이스도 마찬가지다.
CompleteCalc 클래스는 상위클래스인 Calculator형이면서, Calc 인터페이스를 구현했으므로 Calc형이기도 하다.
따라서 Calc형으로 선언한 변수에 대입할 수 있다.
Calc calc = new CompleteCalc();
하지만 이렇게 선언한다면, 상속관계에서처럼 하위클래스에서 추가로 구현한 메서드는 사용할 수 없다.
즉 Calc형으로 선언한 변수에서 사용할 수 있는 메서드는 Calc 인터페이스에 선언한 메서드 뿐이다.
정리하자면, 인터페이스를 구현한 클래스가 있을 때 그 클래스는 해당 인터페이스형으로 묵시적 형 변환이 이루어지며, 형 변환 되었을 때 사용할 수 있는 메서드는 인터페이스에서 선언한 메서드뿐이다.
디폴트 메서드와 정적 메서드
디폴트 메서드란 인터페이스를 구현한 클래스에 기본적으로 제공되는 메서드이고 선언할 때는 default 예약어를 사용한다.
public interface Calc {
...
default void description() {
System.out.println("정수 계산기를 구현합니다.");
}
}
이렇게 default 예약어를 사용해서 디폴트 메서드를 구현할 수 있지만, 하위 클래스에서 디폴트 메서드를 재정의할 수도 있다.
public class CompleteCalc extends Calculator {
...
@Override
public void description() {
super.description(); // 이 부분을 지우고 원하는 코드 작성하면 됨.
}
}
super.description();은 인터페이스에 선언한 메서드를 의미한다. 하위클래스에서 재정의하고 싶다면 이 부분을 지우고
새 코드를 작성하면 된다. 그리고 CompleteCalc 클래스로 인스턴스를 생성하여 호출하면 재정의된 메서드가 호출된다.
정적메서드
정적 메서드는 static 예약어를 사용해서 선언하며, 클래스 생성과 무관하게 사용할 수 있고, 사용할 때는 인터페이스 이름으로 직접 참조하여 사용한다.
public interface Calc {
...
static int total(int[] arr) {
int total = 0;
for(int i : arr) {
total += i;
}
return total;
}
}
total() 메서드처럼 자료형 앞에 static 예약어를 사용하면 되고,
정적 메서드를 호출할 때는 인터페이스 이름을 직접 참조하여
Calc.total(arr); 처럼 호출하면 된다.
private 메서드
인터페이스 내에서만 사용가능하고, 디폴트 메서드나 정적 메서드에 사용하기 위해 작성되는 메서드다.
인터페이스를 구현하는 클래스에서 재정의하거나 사용할 수 없고, 디폴트 메서드나 정적 메서드를 통해서만 사용이 가능하다.
public interface Calc {
...
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 메서드 ");
}
}
여러 인터페이스를 구현하는 경우
한 클래스가 여러 클래스를 상속받을 순 없지만,
인터페이스는 한 클래스가 여러 인터페이스를 구현할 수 있다.
public interface Buy {
void buy();
default void order() {
System.out.println("구매 주문");
}
}
public interface Sell {
void sell();
default void order() {
System.out.println("판매 주문");
}
}
public class Customer implements Buy, Sell {
@Override
public void buy() {
System.out.println("구매하기");
}
@Override
public void sell() {
System.out.println("판매하기");
}
@Override
public void order() { // 디폴트 메서드 order()를 재정의
System.out.println("고객 판매 주문");
}
}
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;
seller.sell();
seller.order();
if(seller instanceof Customer) {
Customer customer2 = (Customer)seller; // seller를 하위 클래스형인 Customer로 형변환
customer2.buy();
customer2.sell();
}
Customer.order();
}
Customer 클래스에서 인터페이스 Buy와 Sell을 구현했기 때문에 Customer 클래스형으로 인스턴스를 생성하면
buy()와 sell() 메서드를 호출할 수 있다. 그리고 Buy와 Sell 인터페이스형으로 형 변환 한다면, 해당 인터페이스의 메서드만 사용할 수 있게 된다.
Buy와 Sell 인터페이스에 같은 이름의 디폴트 메서드인 order가 있는데, 이 두 인터페이스 모두를 구현한 클래스에서는 같은 이름의 디폴트 메서드(order())를 재정의 해줘야한다. 그리고 호출해보면, Customer 클래스에 재정의한 메서드가 호출된다.
다시 말해서 하위클래스에서 재정의한 메서드가 호출된다는 것이다.
'자바 기초' 카테고리의 다른 글
자바 Object 클래스, toString, equals - Do it! 자바프로그래밍기초 (0) | 2022.07.25 |
---|---|
자바 인터페이스 상속 - Do it! 자바프로그래밍 기초 (0) | 2022.07.22 |
자바 템플릿 메서드, final - Do it! 자바프로그래밍기초 (0) | 2022.07.22 |
자바 추상클래스 - Do it! 자바프로그래밍기초 (0) | 2022.07.22 |
자바 다형성 -Do it! 자바프로그래밍기초 (0) | 2022.07.22 |