[Java] 08. 인터페이스

JK·2024년 4월 15일
0

[Java]

목록 보기
8/11

인터페이스 역할

인터페이스(interface)는 사전적인 의미로 두 장치를 연결하는 접속기를 뜻한다. 자바에서 인터페이스는 두 장치를 객체로 보고, 두 객체를 연결하는 역할을 한다고 말할 수 있다.

인터페이스를 사용하는 이유는 객체와 객체가 직접 상호 작용하는 대신, 인터페이스를 통해 상호 작용하기 때문에 인터페이스를 통해 사용 중이던 객체가 다른 객체로 변경되어도 실행 결과만 달라질 뿐 코드를 변경할 필요가 없다. 이러한 성질을 이용해 다형성을 구현하는데에 주된 기술로 사용된다.

인터페이스와 구현 클래스 선언

인터페이스는 자바 소스 파일(.java)로 생성되어 바이트코드 파일(.class)로 컴파일된다는 점에서 물리적인 형태는 클래스와 동일하다. 하지만 선언 방법과 구성 멤버가 클래스와는 다르다.

인터페이스 선언

[public] interface 인터페이스명 {
  // public 상수 필드
  // public 추상 메소드
  // public 디폴트 메소드
  // public 정적 메소드
  // private 메소드
  // private 정적 메소드
}

인터페이스 선언은 class 키워드 대신 interface 키워드를 사용한다. 접근 제한은 public으로 선언하거나, 생략해서 default로 선언할 수 있다. 인터페이스는 구성 멤버로 public 접근 제한을 가진 상수 필드, 추상 메소드, 디폴트 메소드, 정적 메소드를 가질 수 있고, private 접근 제한을 갖는 메소드와 정적 메소드를 가질 수 있다.

구현 클래스 선언

객체 A가 인터페이스의 추상 메소드를 호출하면, 인터페이스는 그 추상 메소드를 구현하고 있는 객체 B의 구현 메소드를 실행한다. 그렇기 때문에 객체 B는 인터페이스의 추상 메소드를 재정의해야 하고, 이 객체 B를 인터페이스의 구현 클래스라고 한다.

public class B implements 인터페이스명 { ... }

구현 클래스를 선언할 때는 implements 키워드를 통해 어떤 인터페이스를 구현하고 있는지 명시해야 한다. implements 키워드는 해당 클래스가 해당 인터페이스를 통해 사용할 수 있다는 표시이다.

변수 선언과 구현 객체 대입

인터페이스명 인터페이스변수 = null;

인터페이스변수 = new 구현객체();

인터페이스도 하나의 타입이므로 변수의 타입으로 사용 가능하다. 인터페이스는 참조 타입으로, 아무 값도 참조하고 있지 않다는 뜻으로 null을 대입할 수도 있다.

인터페이스를 통해 구현 객체를 사용하려면 인터페이스 변수에 구현 객체를 대입해줘야 한다. 구현 객체에 재정의되어 있는 메소드를 실행하기 때문에, 어떤 구현 객체를 대입하느냐에 따라 실행 결과가 달라진다.


상수 필드

[public static final] 타입 상수명 = 값;

인터페이스는 public static final 특성을 갖는 불변의 상수 필드를 멤버로 가질 수 있다. 인터페이스에 선언된 필드는 모두 public static final 특성을 갖기 때문에 선언 시 생략하더라도 컴파일 과정에서 자동으로 붙게 된다.

상수는 구현 객체와 관련 없는 인터페이스의 소속 멤버이므로 구현 객체없이도 바로 접근이 가능하다.

추상 메소드

[ public abstract ] 리턴타입 메소드명(매개변수, ... ); 

인터페이스는 구현 클래스가 재정의해야하는 public 추상 메소드를 멤버로 가질 수 있다. public abstract를 생략해도, 컴파일 과정에서 자동으로 붙게 된다.

추상 메소드는 실행 내용없이 선언부를 명시하여 어떻게 인터페이스를 통해 메소드를 호출할 수 있는지 알려주는 역할을 한다.

인터페이스의 추상 메소드는 반드시 public 접근 제한을 갖기 때문에, 재정의할 경우에는 public보다 낮은 접근 제한으로 재정의할 수 없다.

디폴트 메소드

[public] default 리턴타입 메소드명(매개변수, ...) { ... }

인터페이스에 완전한 실행 코드를 가진 디폴트 메소드를 멤버로 선언할 수도 있다. 선언 방법은 클래스 메소드와 동일하지만 default 키워드가 리턴 타입 앞에 붙어야 한다.

디폴트 메소드의 실행부에 상수 필드를 읽거나 추상 메소드를 호출하는 코드를 작성할 수도 있다. 디폴트 메소드를 호출하려면 구현 객체를 생성하여 인터페이스 변수에 대입한 후에 호출할 수 있다.

디폴트 메소드를 재정의하는 것도 가능하지만, 디폴트 메소드는 public 접근 제한을 가지기 때문제 재정의한 메소드도 public 접근 제한자를 붙이고 default 키워드는 생략하여 재정의해야 한다.

정적 메소드

[public | private] static 리턴타입 메소드명(매개변수, ...) { ... }

정적 메소드는 구현 객체가 없어도 인터페이스만으로 호출이 가능하다. 선언 방법은 클래스 정적 메소드와 동일하다.

private 접근 제한으로 선언하는 경우를 제외하고는 public 접근 제한을 생략하더라도 자동으로 컴파일 과정에서 public 접근 제한이 붙게 된다.

상수 필드를 제외한 추상 메소드, 디폴트 메소드, private 메소드 등을 호출할 수 없다.

private 메소드

인터페이스의 상수 필드, 추상 메소드, 디폴트 메소드, 정적 메소드는 모두 public 접근 제한을 가지고, 생략해도 컴파일 시 자동으로 public 접근 제한으로 생성된다.

구분설명
private 메소드구현 객체가 필요한 메소드
private 정적 메소드구현 객체가 필요 없는 메소드

외부에서 접근 불가능한 private 메소드도 인터페이스의 멤버로 선언할 수 있다. private 메소드는 일반 메소드와 정적 메소드만 선언이 가능하다. private 일반 메소드는 디폴트 메소드 안에서만 호출이 가능하고, private 정적 메소드는 디폴트 메소드와 정적 메소드에서 호출이 가능하다.

private 메소드는 디폴트 메소드와 정적 메소드에 중복되는 코드가 많이 존재할 경우, 중복 코드를 줄이기 위해 private 메소드를 선언하여 중복 코드를 따로 작성하는 대신 private 메소드를 호출하는 방식으로 사용한다.


다중 인터페이스 구현

public class 구현클래스명 implements 인터페이스A, 인터페이스B {
  // 모든 추상 메소드 재정의
}

하나의 구현 객체는 여러 개의 인터페이스를 implements 할 수 있다. 이 경우 구현 객체를 어떤 인터페이스 변수에 대입하느냐에 따라 변수를 통해 호출 가능한 추상 메소드가 결정된다.

인터페이스 상속

public interface 자식인터페이스 extends 부모인터페이스1, 부모인터페이스2 { ... }

인터페이스는 클래스와 달리 다중 상속을 허용한다. 이 경우 자식 인터페이스의 구현 클래스는 자식 인터페이스의 추상 메소드와 부모 인터페이스의 추상 메소드를 모두 재정의해야 한다.

구현 객체는 자식 및 부모 인터페이스 변수에 모두 대입 가능하고, 대입한 인터페이스 변수에 따라 실행 가능한 메소드가 결정된다. 부모 인터페이스 변수에 대입될 경우, 자식 인터페이스의 추상 메소드를 재정의한 메소드는 실행이 불가능하다.

타입 변환

인터페이스의 타입 변환은 인터페이스와 구현 클래스 간에 발생한다. 인터페이스 변수에 구현 객체를 대입할 경우 구현 객체는 인터페이스 타입으로 자동 타입 변환된다. 반대로 인터페이스 타입을 구현 클래스 타입으로 변환하고 싶을 경우 강제 타입 변환이 필요하다.

자동 타입 변환

인터페이스 변수 = 구현객체;

부모 클래스가 인터페이스를 구현하고 있을 경우에는 자식 클래스도 인터페이스 타입으로 자동 타입 변환이 가능하다.

강제 타입 변환

구현클래스 변수 = (구현클래스) 인터페이스변수;

강제 타입 변환이 필요한 경우, 캐스팅(casting) 기호를 사용하여 인터페이스 타입을 구현 클래스 타입으로 변환할 수 있다.

구현 객체가 인터페이스 타입으로 자동 타입 변환되면 인터페이스에 선언된 메소드만 사용이 가능하다. 만약 구현 객체가 갖고 있던 메소드를 사용하고 싶다면 다시 클래스 타입으로 강제 타입 변환해주어야 한다.

다형성

클래스 상속을 통해 다형성을 구현하기도 하지만, 인터페이스 또한 다형성을 구현하는 주된 기술로 사용된다. 인터페이스도 클래스 상속으로 다형성을 구현할 때와 마찬가지로, 재정의와 자동 타입 변환 기능을 통해 다형성을 구현한다.

필드의 다형성

필드 타입을 인터페이스 타입으로 선언한 뒤 필드에 구현 객체를 대입할 경우 구현 객체는 인터페이스 타입으로 자동 타입 변환된다. 구현 객체마다 인터페이스의 추상 메소드를 각각 재정의하고 있기 때문에 어떤 구현 객체를 대입하느냐에 따라 다른 실행 결과를 도출할 수 있다.

매개변수의 다형성

메소드의 매개변수를 인터페이스 타입으로 선언할 경우 해당 인터페이스를 구현하고 있는 모든 객체를 매개값으로 대입할 수 있다. 이를 통해 구현 객체에 따라 다양한 실행 결과를 얻을 수 있다.

객체 타입 확인

매개값이 특정 구현 객체일 경우에만 강제 타입 변환하고 싶다면, instanceof 연산자를 통해 객체 타입을 확인할 수 있다.

Java 12 버전부터는 instanceof 연산의 결과가 true일 경우, 강제 타입 변환을 하지 않아도 우측 타입 변수의 사용이 가능하다.

봉인된 인터페이스

public sealed interface InterfaceA permits Interfaceb { ... }

Java 15부터 무분별한 자식 인터페이스 생성을 막기 위해 생겨난 기능으로, sealed 키워드로 봉인된 인터페이스로 선언한 뒤 permits 키워드 뒤에 상속 가능한 자식 인터페이스를 지정한다.

지정된 자식 인터페이스는 non-sealed 키워드로 봉인을 해제하거나 sealed 키워드를 통해 또 다른 봉인 인터페이스로 선언해야 한다.


"한빛 미디어 출판 도서, 이것이 자바다"를 읽고 학습한 내용을 토대로 작성되었습니다.

0개의 댓글