![[JAVA] 인터페이스(Interface)](https://img1.daumcdn.net/thumb/R750x0/?scode=mtistory2&fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcrDBAN%2FbtrVtSM9Gpu%2FBq5fniAClYVGiRiKCkvR7K%2Fimg.jpg)
본 게시글은 이것이 자바다 (저자 : 신용권)의 책과 유튜브 영상을 참고하였고, 개인적으로 정리하는 글임을 알립니다.
자바에서 인터페이스는 객체의 사용 방법을 정의한 타입이다. 인터페이스를 통해 다양한 객체를 동일한 사용 방법으로 이용할 수 있다.
인터페이스는 개발 코드와 객체가 서로 통신하는 접점 역할을 한다. 개발 코드가 인터페이스의 메소드를 호출하면 인터페이스는 객체의 메소드를 호출시킨다. 그렇기 때문에 개발 코드는 객체의 내부 구조를 알 필요가 없고 인터페이스의 메소드만 알고 있으면 된다.
개발 코드가 직접 객체의 메소드를 호출하면 간단한데 왜 중간에 인터페이스를 두는지 의문점이 생긴다.
그 이유는 개발 코드를 수정하지 않고 사용하는 객체를 변경할 수 있도록 하기 위해서이다. 인터페이스는 하나의 객체가 아니라 여러 객체들과 사용이 가능하므로 어떤 객체를 사용하느냐에 따라서 실행 내용과 리턴값이 다를 수 있다.
따라서 개발 코드 측면에서는 코드 변경 없이 실행 내용과 리턴값을 다양화할 수 있다는 장점을 가지게 된다.
인터페이스는 *.java 형태의 소스 파일로 작성되고 컴파일러를 통해 *.class 형태로 컴파일되기 때문에 물리적 형태는 클래스와 동일하다.
인터페이스 선언
패키지에 클래스를 추가하는 것과 같이 패키지에 인터페이스를 추가해준다.
RemoteControl이라는 이름을 가진 인터페이스를 선언하는 방법은 아래와 같다.
public interface RemoteControl {
}
- 클래스는 필드, 생성자, 메소드를 멤버로 가질 수 있지만, 인터페이스는 상수 필드와 추상 메소드만을 멤버로 가질 수 있다.
- 인터페이스는 객체로 생성할 수 없기 때문에 생성자를 가질 수 없다.
- 인터페이스는 객체 사용 방법을 정의한 것이므로 실행 시 데이터를 저장할 수 있는 인스턴스 또는 정적 필드를 선언할 수 없다. 그러나 상수 필드는 선언이 가능하다.
- 인터페이스에 선언된 필드는 모두 상수의 특징을 가지므로 public static final을 생략하더라도 컴파일 과정에서 자동으로 붙게 된다.
- 인터페이스 상수는 반드시 선언과 동시에 초기값을 지정해야한다.
상수 이름은 대문자로 작성하되, 서로 다른 단어로 구성되어 있을 경우에는 언더바로 연결하는 것이 관례이다.
인터페이스를 통해 호출된 메소드는 최종적으로 객체에서 실행된다. 그렇기 때문에 인터페이스의 메소드는 추상 메소드로 선언한다. 인터페이스에 선언된 메소드는 모두 추상 메소드의 특징을 가지므로 public abstract를 생략하더라도 컴파일 과정에서 자동으로 붙게 된다.
public interface RemoteControl {
//상수(public static final 생략 가능)
int MAX_VOLUME = 10;
int MIN_VOLUME = 0;
//추상 메소드(public abstract 생략 가능)
void turnOn();
void turnOff();
void setVolume(int volume);
}
인터페이스 구현
개발 코드가 인터페이스 메소드를 호출하면 인터페이스는 객체의 메소드를 호출한다.
객체는 인터페이스에서 정의된 추상 메소드와 동일한 메소드 이름, 매개 타입, 리턴 타입을 가진 실체 메소드를 가지고 있어야 한다.
이러한 객체를 인터페이스의 구현 객체라고 하고, 구현 객체를 생성하는 클래스를 구현 클래스라고 한다.
구현 클래스는 인터페이스 타입으로 사용할 수 있음을 알려주기 위해 클래스 선언부에 implements 키워드를 추가하고 인터페이스 이름을 명시해야 한다. 그리고 인터페이스에 선언된 추상 메소드의 실체 메소드를 선언해야 한다.
인터페이스 RemoteControl의 구현 클래스 이름이 Television이면 아래와 같이 선언한다.
public class Television implements RemoteControl{
}
아래의 코드들은 리모콘을 인터페이스로, 구현 객체를 TV와 스피커로 구현한 것이다.
RemoteControl.java(인터페이스)
package TestPackage;
public interface RemoteControl {
//상수(public static final 생략 가능)
int MAX_VOLUME = 10;
int MIN_VOLUME = 0;
//추상 메소드(public abstract 생략 가능)
void turnOn();
void turnOff();
void setVolume(int volume);
}
Television.java(구현 클래스)
package TestPackage;
public class Television implements RemoteControl{
private int volume;
//turnOn() 추상 메소드의 실체 메소드
public void turnOn() {
System.out.println("TV ON");
}
//turnOff() 추상 메소드의 실체 메소드
public void turnOff() {
System.out.println("TV OFF");
}
//setVolume() 추상 메소드의 실체 메소드
public void setVolume(int volume) {
if(volume>RemoteControl.MAX_VOLUME) {
this.volume = RemoteControl.MAX_VOLUME;
} else if(volume<RemoteControl.MIN_VOLUME) {
this.volume = RemoteControl.MIN_VOLUME;
} else {
this.volume = volume;
}
System.out.println("현재 TV 볼륨: " + this.volume);
}
}
Speaker.java
package TestPackage;
public class Speaker implements RemoteControl{
private int volume;
//turnOn() 추상 메소드의 실체 메소드
public void turnOn() {
System.out.println("Speaker ON");
}
//turnOff() 추상 메소드의 실체 메소드
public void turnOff() {
System.out.println("Speaker OFF");
}
//setVolume() 추상 메소드의 실체 메소드
public void setVolume(int volume) {
if(volume>RemoteControl.MAX_VOLUME) {
this.volume = RemoteControl.MAX_VOLUME;
} else if(volume<RemoteControl.MIN_VOLUME) {
this.volume = RemoteControl.MIN_VOLUME;
} else {
this.volume = volume;
}
System.out.println("현재 Speaker 볼륨: " + this.volume);
}
}
Test.java
package TestPackage;
public class Test {
public static void main(String[] args) {
RemoteControl rc;
rc = new Television();
rc.turnOn();
rc.setVolume(5);
rc.turnOff();
rc = new Speaker();
rc.turnOn();
rc.setVolume(5);
rc.turnOff();
}
}
/*
TV ON
현재 TV 볼륨: 5
TV OFF
Speaker ON
현재 Speaker 볼륨: 5
Speaker OFF
*/
보는 것과 같이 인터페이스 변수에 어떤 객체가 들어가느냐에 따라서 실행결과가 달라짐을 볼 수 있다.
public을 생략하면 컴파일 에러
인터페이스의 모든 멤버는 기본적으로 public 접근 제한을 갖기 때문에 구현 클래스에서는 public보다 더 강한 접근제한으로 작성할 수 없다.
따라서 public을 생략하면 default 접근제한을 갖기 때문에 컴파일 에러가 뜬다.
디폴트 메서드
인터페이스에는 완전한 실행 코드를 가진 디폴트 메서드를 선언할 수 있다.
추상 메서드는 실행부가 없지만, 디폴트 메서드는 실행부가 있다.
선언 방법은 클래스 메서드와 동일한데, 차이점은 default 키워드가 리턴 타입 앞에 붙는다.
public interface RemoteControl {
//상수 필드
int MAX_VOLUME = 10;
int MIN_VOLUME = 0;
//추상 메소드
void turnOn();
void turnOff();
void setVolume(int volume);
//디폴트 인스턴스 메소드
default void setMute(boolean mute) {
if(mute) {
System.out.println("무음 처리합니다.");
//추상 메소드 호출하면서 상수 필드 사용
setVolume(MIN_VOLUME);
} else {
System.out.println("무음 해제합니다.");
}
}
}
디폴트 메서드는 구현 객체가 필요한 메서드이다.
따라서 RemoteControl의 setMute() 메서드를 호출하려면 구현 객체인 Television 객체를 인터페이스 변수에 대입하고 나서 setMute() 를 호출해야 한다.
public class RemoteControlExample {
public static void main(String[] args) {
//인터페이스 변수 선언
RemoteControl rc;
//Television 객체를 생성하고 인터페이스 변수에 대입
rc = new Television();
rc.turnOn();
rc.setVolume(5);
//디폴트 메소드 호출
rc.setMute(true);
rc.setMute(false);
System.out.println();
//Audio 객체를 생성하고 인터페이스 변수에 대입
rc = new Audio();
rc.turnOn();
rc.setVolume(5);
//디폴트 메소드 호출
rc.setMute(true);
rc.setMute(false);
}
}
구현 클래스는 디폴트 메서드를 재정의해서 자신에게 맞게 수정할 수도 있다.
재정의 시 주의할 점은 public 접근 제한자를 반드시 붙여야 하고, default 키워드를 생략해야 한다.
//필드
private int memoryVolume;
//디폴트 메소드 재정의
@Override
public void setMute(boolean mute) {
if(mute) {
this.memoryVolume = this.volume;
System.out.println("무음 처리합니다.");
setVolume(RemoteControl.MIN_VOLUME);
} else {
System.out.println("무음 해제합니다.");
setVolume(this.memoryVolume);
}
}
정적(static) 메서드
인터페이스에는 정적 메서드도 선언이 가능하다.
추상 메서드와 디폴트 메서드는 구현 객체가 필요하지만, 정적 메서드는 구현 객체가 없어도 인터페이스만으로 호출할 수 있다.
선언 방법은 클래스 정적 메서드와 완전 동일하다.
단, public을 생략하더라도 자동으로 컴파일 과정에서 붙는 것이 차이점이다.
public interface RemoteControl {
//상수 필드
int MAX_VOLUME = 10;
int MIN_VOLUME = 0;
//추상 메소드
void turnOn();
void turnOff();
void setVolume(int volume);
//디폴트 메소드
default void setMute(boolean mute) {
//이전 예제와 동일한 코드이므로 생략
}
//정적 메소드
static void changeBattery() {
System.out.println("리모콘 건전지를 교환합니다.");
}
}
인터페이스에 선언된 정적 메서드는 구현 객체 없이 인터페이스명으로 접근해서 호출할 수 있다.
private 메서드
인터페이스에 외부에서 접근할 수 없는 private 메서드도 선언이 가능하다.
private 메서드는 디폴트 메서드 안에서만 호출이 가능한 반면, private 정적 메서드는 디폴트 메서드뿐만 아니라 정적 메서드 안에서도 호출이 가능하다.
즉, private 메서드는 객체가 있어야하고, private 정적 메서드는 객체없이 호출이 가능한 것이다.
private 메서드의 용도는 디폴트와 정적 메서드들의 중복 코드를 줄이기 위함이다.
public interface Service {
//디폴트 메소드
default void defaultMethod1() {
System.out.println("defaultMethod1 종속 코드");
defaultCommon();
}
default void defaultMethod2() {
System.out.println("defaultMethod2 종속 코드");
defaultCommon();
}
//private 메소드
private void defaultCommon() {
System.out.println("defaultMethod 중복 코드A");
System.out.println("defaultMethod 중복 코드B");
}
//정적 메소드
static void staticMethod1() {
System.out.println("staticMethod1 종속 코드");
staticCommon();
}
static void staticMethod2() {
System.out.println("staticMethod2 종속 코드");
staticCommon();
}
//private 정적 메소드
private static void staticCommon() {
System.out.println("staticMethod 중복 코드C");
System.out.println("staticMethod 중복 코드D");
}
}
public class ServiceImpl implements Service {
}
public class ServiceExample {
public static void main(String[] args) {
//인터페이스 변수 선언과 구현 객체 대입
Service service = new ServiceImpl();
//디폴트 메소드 호출
service.defaultMethod1();
System.out.println();
service.defaultMethod2();
System.out.println();
//정적 메소드 호출
Service.staticMethod1();
System.out.println();
Service.staticMethod2();
System.out.println();
}
}
'Language > Java' 카테고리의 다른 글
[JAVA] 중첩 클래스(Nested Class) (0) | 2023.01.12 |
---|---|
[JAVA] 인터페이스 - 다중 인터페이스, 상속, 타입 변환, 다형성 (0) | 2023.01.11 |
[JAVA] 추상 클래스(Abstract Class), 추상 메소드(Abstract Method) (0) | 2023.01.09 |
[JAVA] 상속 - 타입 변환과 다형성(매우 중요) (0) | 2023.01.08 |
[JAVA] 상속과 메소드 재정의(Overriding) (0) | 2023.01.07 |