이 사이트의 실습은 자바스크립트를 켜야 움직입니다. 아래는 같은 내용을 글로 적은 것입니다.
기초개념 › Java
인터페이스
인터페이스는 코드에서 어떻게 생겼고 무엇을 정하는가
인터페이스는 interface로 선언하는 약속입니다. "Payable 타입이라면 pay 메서드를 갖춰야 한다"처럼 메서드의 이름만 적고 본문은 적지 않습니다. 클래스는 implements Payable로 그 약속을 지키겠다고 선언하고, 약속한 pay 메서드를 실제로 작성합니다. 그러면 그 클래스의 객체를 Payable 타입 변수에 담을 수 있습니다. 이 실습은 interface 선언, implements, pay 메서드를 하나씩 지워 가며 같은 코드를 컴파일하고 실행해, 컴파일러가 무엇을 알려 주고 무엇이 출력되는지를 봅니다.
실행으로 대조 · JDK 21에서 열두 가지 조합을 javax.tools로 컴파일하고 실행해 대조
이 실습에 나오는 개념
- 인터페이스
- 클래스가 구현해야 할 메서드의 목록(계약)을 정의한 타입이다. 인스턴스 필드를 가질 수 없고, 한 클래스가 여러 인터페이스를 구현할 수 있다. 사용하는 코드가 인터페이스에만 의존하면 구현 클래스를 바꾸거나 테스트용 가짜 구현으로 교체하기 쉽다.
- implements
- implements는 클래스 선언에서 그 클래스가 어떤 인터페이스의 약속을 지키겠다고 밝히는 키워드이다. class Card implements Payable처럼 클래스 이름 뒤에 쓰고, 인터페이스는 쉼표로 여러 개 적을 수 있다. abstract가 아닌 클래스가 implements를 쓰면 그 인터페이스의 모든 추상 메서드를 직접 작성하거나 상위 클래스에서 물려받아야 하고, 하나라도 빠지면 컴파일 오류이다. implements를 쓰면 그 인터페이스가 클래스의 상위 타입이 되어, 클래스의 객체를 그 인터페이스 타입의 변수에 담을 수 있다. 같은 이름의 메서드를 갖췄더라도 implements를 쓰지 않으면 그 인터페이스 타입이 되지 않는다.
- 추상 메서드
- 추상 메서드는 이름, 매개변수, 반환 타입만 선언하고 구현(본문)은 없는 메서드이다. 인터페이스 안에서 void pay();처럼 본문 자리에 세미콜론만 쓰면 abstract를 따로 쓰지 않아도 추상 메서드이고 public이다. 본문이 세미콜론이 아니라 중괄호 블록이면 블록이 비어 있어도({ }) 구현한 메서드이다. 추상 메서드는 그것을 가진 인터페이스를 implements한 클래스가 작성해야 하며, abstract가 아닌 클래스가 작성하지 않으면 컴파일 오류이다.
- 타입
- 타입은 변수에 담을 수 있는 값의 종류를 정하는 이름이다. 자바의 타입은 기본형과 참조형으로 나뉘고, 참조형에는 클래스 타입, 인터페이스 타입, 타입 변수, 배열 타입이 있다. class Card나 interface Payable 선언을 쓰면 Card, Payable이라는 타입을 쓸 수 있게 된다. 컴파일러는 값을 변수에 담는 줄마다, 그 값의 타입이 변수의 타입과 같거나 그 하위 타입인지 확인한다. 클래스가 implements로 밝힌 인터페이스는 그 클래스의 상위 타입이다. 선언하지 않은 이름을 타입으로 쓰면 컴파일러가 그 타입을 찾지 못해 오류를 낸다.
이렇게 바꿔 보세요
- 처음 설정으로 — 세 가지가 다 있습니다. 컴파일에 성공하고 카드로 결제가 출력됩니다.
- implements를 지우면 — pay 메서드가 있어도 Card는 Payable 타입이 아니어서 컴파일 오류가 납니다.
- pay 메서드를 지우면 — 약속한 메서드를 작성하지 않아 컴파일 오류가 납니다.
- pay 메서드의 본문을 비우면 — 본문이 비어 있어도 작성한 것입니다. 컴파일에 성공하고 아무것도 출력되지 않습니다.
- interface 선언을 지우면 — Payable이라는 타입이 없어 컴파일 오류가 납니다.
처음 설정으로 실행하면
- 컴파일러가 소스를 위에서부터 읽습니다. 먼저 interface Payable 선언을 읽고, 이 선언으로 Payable이라는 타입을 알게 됩니다. Payable 타입은 이 선언에서 만들어집니다. void pay(); 줄은 이름만 있고 본문이 없는데, 인터페이스에서 이 모양은 "Payable 타입이라면 pay 메서드를 갖춰야 한다"는 약속입니다(추상 메서드).
- 컴파일러가 class Card implements Payable 줄을 읽습니다. Card가 Payable의 약속을 지키겠다는 선언입니다. 그러므로 Card 안에 약속한 pay 메서드가 작성돼 있어야 합니다.
- 컴파일러가 Card 안에서 약속한 pay 메서드를 찾습니다. public void pay() { ... } 줄이 있고 본문도 있으므로, 약속을 지킨 것입니다.
- 컴파일러가 Payable p = new Card(); 줄을 확인합니다. Card는 implements Payable로 약속했으므로 Card의 객체를 Payable 타입 변수 p에 담아도 됩니다. 이것은 실행이 아니라, 컴파일러가 담아도 되는지 미리 확인하는 일입니다.
- 컴파일이 성공해 실행할 Main 클래스 파일이 만들어집니다. 약속을 지켰는지는 실행할 때가 아니라, 지금 컴파일러가 컴파일하면서 확인했습니다.
- java Main으로 실행합니다. main이 시작되고, new Card()가 Card 객체를 만들어 Payable 타입 변수 p에 담습니다.
- p.pay()가 실행됩니다. p에 담긴 객체는 Card의 객체이므로 Card에 작성한 pay 메서드가 실행됩니다. 본문의 println이 "카드로 결제"를 출력합니다.
실행 결과
- 인터페이스는 interface로 선언하는, "이 타입이라면 이 메서드를 갖춰야 한다"는 약속입니다. 메서드의 이름만 적고 본문은 적지 않습니다. interface Payable 선언이 Payable이라는 타입을 만들고, Card는 implements Payable로 그 약속을 지키겠다고 선언한 뒤 pay 메서드를 실제로 작성합니다.
- 그래서 Card의 객체를 Payable 타입 변수 p에 담을 수 있었고, p.pay()가 Card의 pay를 실행했습니다. 약속을 지켰는지는 실행할 때가 아니라 컴파일러가 컴파일할 때 확인했습니다.
코드
// 약속: Payable이라면 pay 메서드를 갖춰야 한다 (이름만 적는다)
interface Payable {
void pay();
}
// 약속을 지키겠다는 선언
class Card implements Payable {
// 약속한 메서드를 실제로 작성한 것
public void pay() {
System.out.println("카드로 결제");
}
}
public class Main {
public static void main(String[] args) {
// Payable 타입 변수에 Card 객체를 담는다
Payable p = new Card();
p.pay();
}
}
이 모형의 전제
컴파일러와 실행 환경은 JDK 21.0.12.1(javac 21)입니다. 열두 가지 조합을 모두, 화면의 코드와 같은 소스(줄 단위로 같은 글)를 javax.tools로 컴파일하고, 컴파일에 성공한 것은 클래스 파일을 새 클래스 로더로 읽어 Main.main을 실행해 표준 출력을 잡은 결과와 대조했습니다. 대조한 것은 컴파일 성공 여부, 첫 번째 오류의 진단 코드(compiler.err.cant.resolve, cant.resolve.location, prob.found.req, does.not.override.abstract를 화면의 세 가지 오류 설명으로 옮겼습니다), 오류의 개수와 오류가 난 줄, Main 클래스 파일이 만들어졌는지, 실행했을 때의 출력입니다. 문서는 Java Language Specification(Java SE 21)을 직접 열어 확인했습니다. 본문 없이 세미콜론으로 끝나는 인터페이스 메서드가 추상 메서드이고 public이라는 것은 9.4절, 추상 클래스가 아닌 클래스는 implements한 인터페이스의 추상 메서드를 모두 구현해야 한다는 것은 8.1.5절과 8.1.1.1절, 메서드 본문이 세미콜론이 아니라 블록이면 구현이고 블록이 비어 있어도 되는 것은 8.4.7절과 8.1.1.1절의 예(alert() { })를, 인터페이스와 클래스가 모두 참조 타입이고 implements한 인터페이스가 클래스의 상위 타입이라는 것은 4.3절과 4.10.2절, 하위 타입의 값을 상위 타입 변수에 담을 수 있다는 것은 5.1.5절과 5.2절을 따랐습니다. 오류의 종류, 개수, 어느 줄에서 나는지는 명세가 정한 것이 아니라 이 javac에서 관찰한 것입니다. 오류가 여럿이면 javac는 모두 알리는데, 표에는 첫 번째 것만 적었습니다. 컴파일에 실패해도 Card 같은 일부 클래스 파일은 만들어졌지만 Main 클래스 파일은 한 번도 만들어지지 않았고, 그래서 실행할 수 없다고 적었습니다. 인터페이스는 이 밖에도 default 메서드, static 메서드, 상수를 가질 수 있고 한 클래스가 여러 인터페이스를 implements할 수 있지만 이 실습은 다루지 않았습니다. 인터페이스의 쓰임과 추상 클래스와의 차이는 "세부 동작 실습: 인터페이스와 추상 클래스"에서 다룹니다.
앞 실습: 상속 · 다음 실습: 예외