티스토리 뷰

주의 사항!

  • 이 글은 제가 직접 공부하는 중에 작성되고 있습니다.
  • 따라서 제가 이해하는 그대로의 내용이 포함됩니다.
  • 따라서 이 글은 사실과는 다른 내용이 포함될 수 있습니다.


자바에서 인터페이스(Interface)는 객체의 사용 방법을 정의한 타입입니다. 인터페이스는 객체의 교환성을 높여주기 때문에 다형성을 구현하는 데 매우 중요한 역할을 합니다. 특히 자바 8에서 람다식은 함수적 인터페이스의 구현 객체를 생성하기 때문에 그 중요성은 더욱 커졌습니다. 

 

인터페이스는 개발 코드와 객체가 서로 통신하는 접점 역할을 합니다. 개발 코드가 인터페이스의 메서드를 호출하면 인터페이스는 객체의 메서드를 호출시킵니다. 그렇기 때문에 개발 코드는 객체의 내부 구조를 알 필요가 없고 인터페이스의 메서드만 알고 있으면 됩니다.

 

(약간 이런 개념인 것 같습니다. GUI(그래픽 유저 인터페이스)는 많이 들어 봤을 겁니다. 게임 하나만 예를 들어도 캐릭터의 스킬창, 체력 바, 인벤토리, 레벨 등등의 요소들이 전부 인터페이스입니다. 유저들은 이 인터페이스를 통해 어떤 장비를 착용할지, 어떤 스킬을 사용할지, 어떤 스킬레벨을 올릴지 등의 기능을 사용할 수 있습니다. 실제 게임 안에서 그런 과정들이 어떻게 돌아가는 것인지는 전혀 이해할 필요가 없습니다. 

 

객체에  대해서도 마찬가지인 것 같습니다. 캐릭터 간 물건을 주고받는 기능을 수행하고자 할 때 우리는 인터페이스를 통해 '1대 1 교환'이라는 기능만 사용하면 끝입니다. 그것이 두 객체 간에 어떤 방법으로 어떤 메서드를 호출하게 되고, 주고받는 아이템 정보는 어떻게 삭제되고 저장되는지, 즉, 내부적으로 어떻게 돌아가서 1대 1 교환이 이뤄질 수 있는지는 전혀 알지 못해도 상관없습니다.)

 

인터페이스는 개발 코드와 객체의 사이에 위치하게 됩니다. 매개체 역할을 한다고 보면 될 것 같습니다. 개발 코드와 객체를 직접 연결하지 않고 중간에 인터페이스를 둬서 둘을 매개하도록 하는 이유는 개발 코드를 수정하지 않고, 사용하는 객체를 변경할 수 있는 장점을 가지기 때문입니다. 인터페이스는 하나의 객체가 아니라 여러 객체들과 사용이 가능하므로 어떤 객체를 사용하느냐에 따라서 실행 내용과 리턴 값이 바뀔 수 있습니다. 따라서 개발 코드 측면에서는 코드의 변경 없이도 실행 내용과 리턴 값을 다양화할 수 있는 장점을 가지게 됩니다.

 

1. 인터페이스 선언

인터페이스는 "~.java" 형태의 소스 파일로 작성되고 컴파일러를 통해 "~.class" 형태로 컴파일되기 때문에 물리적 형태는 클래스와 동일합니다. 차이점은 소스를 작성할 때 선언하는 방법이 다릅니다.

 

인터페이스 선언은 class 키워드 대신 interface 키워드를 사용합니다.

public interface 인터페이스이름 { ... }

public 접근 제한은 다른 패키지에서도 인터페이스를 사용할 수 있도록 합니다. 이클립스에서 인터페이스를 생성하려면 Package Explorer 뷰에서 인터페이스를 포함할 패키지를 우클릭하고, 'New - Interface'를 클릭하면 됩니다. 

 

클래스는 필드, 생성자, 메서드를 구성 멤버로 가지는데 비해, 인터페이스는 상수와 메서드만을 구성 멤버로 가집니다. 인터페이스는 객체로 생성할 수 없기 때문에 생성자를 가질 수 없습니다. 자바 7 이전까지는 인터페이스의 메서드는 실행 블록이 없는 추상 메서드로만 선언이 가능했지만, 자바 8부터는 디폴트 메서드와 정적 메서드도 선언이 가능합니다.

interface interfaceName
{
	//상수
	double PI = 3.14159;

	//추상 메서드
	void methodName1();

	//디폴트 메서드
	default void methodName2() { ... }

	//정적 메서드
	static void methodName3() { ... }
}

위의 코드는 인터페이스를 선언한 예입니다.

 

1. 1. 상수 필드

인터페이스는 객체 사용 설명서이므로 런타임 시 데이터를 저장할 수 있는 필드를 선언할 수 없습니다. 그러나 상수 필드는 선언이 가능합니다. 상수는 인터페이스에 고정된 값으로 런타임 시에 데이터를 바꿀 수 없습니다. 따라서 상수를 선언할 때는 반드시 초기값을 대입해야 합니다. 상수를 선언할 때는 상수명을 반드시 대문자로 합니다.

 

(그런데 인터페이스의 상수는 조금 특이한 부분이 있습니다. 앞서 상수를 선언할 때는 다음과 같이 static과 final 키워드를 사용해야 했습니다.

static final double PI = 3.14159;

그런데 인터페이스에서는 이런 키워드를 사용하지 않았습니다. 인터페이스에서 선언되는 필드는 모두 상수로 취급되는 것 같습니다. 그래서 의도적으로 다음과 같이 초기화하지 않으면,

double PI;

다음과 같은 에러가 발생합니다.

//The blank final field PI may not have been initialized

final 필드인 PI가 초기화되지 않았을 수 있다는 에러입니다.)

 

1. 1. 1. 상수 필드 선언

인터페이스는 데이터를 저장할 수 없습니다. 따라서 인스턴스 또는 정적 필드를 선언할 수 없습니다. 대신 상수 필드만 선언할 수 있습니다. 인터페이스에 선언된 필드는 모두 public static final의 특성을 갖습니다. public, static, final을 생략하더라도 자동적으로 컴파일 과정에서 붙게 됩니다.

/*public static final*/ double PI = 3.14159;

인터페이스 상수는 static {} 블록으로 초기화할 수 없습니다. 따라서 반드시 선언과 동시에 초기값을 지정해야 합니다.

 

다음은 RomoteControl 인터페이스에 MAX_VALUE와 MIN_VALUE를 선언한 모습입니다.

public interface RemoteControl
{
	int MAX_VOLUME = 10;
	int MIN_VOLUME = 0;
}

 

1. 2. 추상 메서드

추상 메서드는 객체가 가지고 있는 메서드를 설명한 것으로 호출할 때 어떤 매개 값이 필요하고, 리턴 타입이 무엇인지만 알려줍니다. 실제 실행부는 객체(구현 객체)가 가지고 있습니다.

 

(추상 메서드가 객체가 가지고 있는 메서드를 설명한다니 무슨 말이지? 뭔가 다른가? 싶을 겁니다. 이런 혼란이 생긴다 싶으면 그냥 이런 설명은 싹 무시해도 됩니다. 앞서 추상 클래스에서 배웠던 추상 메서드라고 생각하면 됩니다. 전혀 다른 것이 없습니다. 다른 것이 있다면 인터페이스에서 추상 메서드는 avstract 키워드를 사용하지 않는다는 것입니다. 

 

이 역시 앞에서 설명한 상수 필드와 마찬가지고 인터페이스에 선언된 메서드는 모두 추상 메서드로 취급되기 때문에 굳이 abstract 키워드를 사용하지 않아도 되는 것으로 보입니다.)

 

1. 2. 1. 추상 메서드 선언

인터페이스를 통해 호출된 메서드는 최종적으로 객체에서 실행됩니다. 그렇기 때문에 인터페이스의 메서드는 실행 블록이 필요 없는 추상 메서드로 선언합니다. 추상 메서드는 리턴 타입, 메서드명, 매개 변수만 기술되고 중괄호{}를 붙이지 않는 메서드를 말합니다. 인터페이스에 선언된 추상 메서드는 모두 public abstract의 특성을 갖기 때문에 이를 생략하더라도 자동적으로 컴파일 과정에서 붙게 됩니다.

 

다음은 RemoteControl 인터페이스에 turnOn(), turnOff(), setVolume() 추상 메서드를 선언한 것입니다.

public interface RemoteControl
{
	//상수
	int MAX_VOLUME = 10;
	int MIN_VOLUME = 0;

	//추상 메서드
	void turnOn();
	void turnOff();
	void setVolume(int volume);
}

 

1. 3. 디폴트 메서드

디폴트 메서드는 인터페이스에 선언되지만 사실은 객체(구현 객체)가 가지고 있는 인스턴스 메서드라고 생각해야 합니다. 자바 8에서 디폴트 메서드를 허용한 이유는 기존 인터페이스를 확장해서 새로운 기능을 추가하기 위함입니다. 

 

(디폴트 메서드를 활용한 다른 무언가가 더 있을 것 같다는 생각이 듭니다. 하지만 아직은 이에 관해 다른 설명을 하지 않고 있습니다.)

 

1. 3. 1. 디폴트 메서드 선언

디폴트 메서드의 형태는 클래스의 인스턴스 메서드와 동일하지만, default 키워드가 리턴 타입 앞에 붙습니다. 디폴트 메서드는 public 특성을 갖기 때문에 public을 생략하더라도 자동적으로 컴파일 과정에서 붙게 됩니다.

/*public*/ default void methodeName(...) { ... }

 

다음은 RemoteControl 인터페이스에서 무음 처리 기능을 제공하는 setMute() 디폴트 메서드를 선언한 것입니다.

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("무음 처리합니다.");
		}
		else
		{
			System.out.println("무음 해제합니다.");
		}
	}
}

 

1. 4. 정적 메서드

정적 메서드도 자바 8부터 작성할 수 있는데, 디폴트 메서드와는 달리 객체가 없어도 인터페이스만으로 호출이 가능합니다.

 

1. 4. 1. 정적 메서드 선언

정적 메서드의 형태는 클래스의 정적 메서드와 완전히 동일합니다. 전적 메서드는 public 특성을 갖기 때문에 public을 생략하더라도 자동적으로 컴파일 과정에서 붙게 됩니다.

/*public*/ static void methodName(...) { ... }

다음은 RemoteControl 인터페이스에서 배터리를 교환하는 기능을 가진 changeBattery() 정적 메서드를 선언한 것입니다.

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("무음 처리합니다.");
		}
		else
		{
			System.out.println("무음 해제합니다.");
		}
	}

	//정적 메서드
	static void ChangeBattery()
	{
		System.out.println("배터리를 교환합니다.");
	}
}

 

2. 인터페이스 구현

개발 코드가 인터페이스 메서드를 호출하면 인터페이스는 객체의 메서드를 호출합니다. 객체는 인터페이스에서 정의된 추상 메서드와 동일한 메서드 이름, 매개 타입, 리턴 타입을 가진 실체 메서드를 가지고 있어야 합니다. 이러한 객체를 인터페이스의 구현 객체라고 하고, 구현 객체를 생성하는 클래스를 구현 클래스라고 합니다.

 

2. 1. 구현 클래스

구현 클래스는 보통의 클래스와 동일합니다. 하나 다른 점이 있다면, 인터페이스 타입으로 사용할 수 있음을 알려주기 위해 클래스 선언부에 implements 키워드를 추가하고 인터페이스명을 명시해야 합니다.

public class 구현클래스이름 implements 인터페이스이름
{
	//인터페이스에 선언된 추상 메서드의 실체 메서드 선언
}

(생김새가 마치 상속하는 것 같습니다. 상속에서는 implements 키워드 대신 extends 키워드를 사용했습니다.)

 

그리고 인터페이스에 선언된 추상 메서드의 실체 메서드를 선언해야 합니다. 다음은 Television과 Audio라는 이름을 가지고 있는 RemoteControl 구현 클래스를 작성하는 방법을 보여줍니다. RemoteControl에는 3개의 추상 메서드가 있기 때문에 Television과 Audio는 이 추상 메서드들에 대한 실체 메서드를 가지고 있어야 합니다.

public class Television implements RemotControl
{
	//필드
	private int volume;

	//실체 메서드
	public void turnOn()
	{
		System.out.println("TV를 켭니다.");
	}

	public void turnOff()
	{
		System.out.println("TV를 끕니다.");
	}

	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("현재 Audio 볼륨 : " + this.volume);
	}
}
public class Audio implements RemotControl
{
	//필드
	private int volume;

	//실체 메서드
	public void turnOn()
	{
		System.out.println("AUDIO를 켭니다.");
	}

	public void turnOff()
	{
		System.out.println("AUDIO를 끕니다.");
	}

	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("현재 Audio 볼륨 : " + this.volume);
	}
}

구현 클래스에서 인터페이스의 추상 메서드들에 대한 실체 메서드를 작성할 때 주의할 점은 인터페이스의 모든 메서드는 기본적으로 public 접근 제한을 갖기 때문에 public보다 더 낮은 접근 제한으로 작성할 수 없습니다. public을 생략하면 에러를 발생합니다. 만약 인터페이스에 선언된 추상 메서드에 대응하는 실체 메서드를 구현 클래스가 작성하지 않으면 구현 클래스는 자동적으로 추상 클래스가 됩니다. 그렇기 때문에 클래스 선언부에 abstract 키워드를 추가해야 합니다.

public abstract class Television implements RemoteControl
{
	public void turnOn() { ... }
	public void turnOff() { ... }
	//setVolume() 실체 메서드가 정의되지 않음
}

 

구현 클래스가 작성되면 new 연산자로 객체를 생성할 수 있습니다. 문제는 어떤 타입의 변수에 대입하느냐입니다. 다음과 같이 Television 객체를 생성하고 Television 변수에 대입한다고 인터페이스를 사용하는 것이 아닙니다.

Television tv = new Television();

인터페이스로 구현 객체를 사용하려면 인터페이스 변수를 선언하고 구현 객체를 대입해야 합니다. 인터페이스 변수는 참조 타입이기 때문에 구현 객체가 대입될 경우 구현 객체의 번지를 저장합니다. RemoteControl 인터페이스로 구현 객체인 Television과 Audio를 사용하려면 다음과 같이 RemoteControl 타입 변수 rc를 선언하고 구현 객체를 대입해야 합니다.

RemoteControl rc;
rc = new Television();
re = new Audio();

RemoteControl rc = new Television();
RemoteControl rc = new Audio();

 

2. 2. 익명 구현 객체

구현 클래스를 만들어 사용하는 것이 일반적이고, 클래스를 재사용할 수 있기 때문에 편리하지만, 일회성의 구현 객체를 만들기 위해 소스 파일을 만들고 클래스를 선언하는 것은 비효율적입니다. 자바는 소스 파일을 만들지 않고도 구현 객체를 만들 수 있는 방법을 제공하는데, 그것이 익명 구현 객체입니다.

 

자바는 UI 프로그래밍에서 이벤트를 처리하기 위해, 그리고 임시 작업 스레드를 만들기 위해 익명 구현 객체를 많이 활용합니다. 자바 8에서 지원하는 람다식은 인터페이스의 익명 구현 객체를 만들기 때문에 익명 구현 객체의 코드 패턴을 잘 익혀두길 바랍니다. 

 

다음은 익명 구현 객체를 생성해서 인터페이스 변수에 대입하는 코드입니다. 작성 시 주의할 점은 하나의 실행문이므로 끝에는 세미콜론이 반드시 붙어야 합니다.

인터페이스이름 변수 = new 인터페이스이름()
{
	//인터페이스에 선언된 추상 메서드의 실체 메서드 선언
};    //세미콜론 반드시 붙이기

new 연산자 뒤에는 클래스 이름이 와야 하는데, 이름이 없습니다. 인터페이스() { ... }는 인터페이스를 구현해서 중괄호 {}와 같이 클래스를 선언하라는 뜻이고, new 연산자는 이렇게 선언된 클래스를 객체로 생성합니다. 중괄호 {}에는 인터페이스에 선언된 모든 추상 메서드들의 실체 메서드를 작성해야 합니다. 그렇지 않으면 컴파일 에러가 발생합니다. 추가적으로 필드와 메서드를 선언할 수 있지만, 익명 객체 안에서만 사용할 수 있고 인터페이스 변수로 접근할 수 없습니다. 

 

다음은 RemoteControl의 익명 구현 객체를 만들어 본 것입니다.

RemoteControl rc = new RemoteControl() {
	public void turnOn() { ... }
	public void turnOff() { ... }
	public void setVolume() { ... }
};

 

2. 3. 다중 인터페이스 구현 클래스

하나의 객체를 다수의 인터페이스 타입으로 사용할 수 있습니다. 인터페이스 A와 인터페이스 B가 객체의 메서드를 호출할 수 있으려면 객체는 이 두 인터페이스를 모두 구현해야 합니다. 따라서 구현 클래스는 다음과 같이 작성되어야 합니다.

public class 구현클래스이름 implements 인터페이스A, 인터페이스B
{
	//인터페이스 A에 선언된 추상 메서드의 실체 메서드 선언
	//인터페이스 B에 선언된 추상 메서드의 실체 메서드 선언
}

다중 인터페이스를 구현할 경우, 구현 클래스는 모든 인터페이스의 추상 메서드에 대해 실체 메서드를 작성해야 합니다. 만약 하나라도 없으면 추상 클래스로 선언해야 합니다.

 

다음은 인터넷을 검색할 수 있는 Searchable 인터페이스입니다. search() 추상 메서드는 매개 값으로 URL을 받습니다.

pulic interface Searchable
{
	void search(String url);
}

만약 SmartTelevision이 인터넷 검색 기능도 제공한다면 RemoteControl과 Searchable을 모두 구현한 SmartTelevision 클래스를 다음과 같이 작성할 수 있습니다.

public class SmartTelevision implements RemoteControl, Searchable
{
	private int volume;

	public void turnOn() { ... }
	public void turnOff() { ... }
	public void setVolume() { ... }
	
	public void search(String url)
	{
		System.out.println(url + "을 검색합니다.");
	}
}

 

3. 인터페이스 사용

개발 코드에서 인터페이스는 클래스의 필드, 생성자, 메서드의 매개 변수, 생성자 또는 메서드의 로컬 변수로 선언될 수 있습니다.

public class MyClass
{
	//필드로서 사용
	RemoteControl rc = new Television();

	//생성자의 매개 변수로 사용
	MyClass(RemoteControl rc)
	{
		this.rc = rc;
	}

	//메서드의 로컬 변수로 사용
	void method1()
	{
		RemoteControl rc = new Audio();
	}

	//메서드의 매개 변수로 사용
	void method2(RemoteControl rc) { ... }
}

 

3. 1. 추상 메서드 사용

구현 객체가 인터페이스 타입에 대입되면 인터페이스에 선언된 추상 메서드를 개발 코드에서 호출할 수 있습니다. 개발 코드에서 RemoteControl의 변수 rc로 turnOn() 또는 turnOff() 메서드를 호출하면 구현 객체의 turnOn()과 turnOff() 메서드가 자동 실행됩니다.

//exam00.java
package chapter00.exam00;

public class exam00 
{
	public static void main(String[] args)
	{
		RemoteControl rc = new Television();
		
		rc.turnOn();
		rc.turnOff();
		
		rc = new Audio();
		rc.turnOn();
		rc.turnOff();
	}
}

/*
실행결과

TV를 켭니다.
TV를 끕니다.
AUDIO를 켭니다.
AUDIO를 끕니다.

*/

 

3. 2. 디폴트 메서드 사용

디폴트 메서드는 인터페이스에 선언되지만, 인터페이스에서 바로 사용할 수는 없습니다. 디폴트 메서드는 추상 메서드가 다인 인스턴스 메서드이므로 구현 객체가 있어야 사용할 수 있습니다. 예를 들어 RemoteControl 인터페이스는 setMute()라는 디폴트 메서드를 가지고 있지만, 이 메서드를 다음과 같이 호출할 수는 없습니다.

RemoteControl.setMute(true);

setMute() 메서드를 호출하려면 RemoteControl의 구현 객체가 필요한데, 다음과 같이 Television 객체를 인터페이스 변수에 대입하고 나서 setMute()를 호출할 수 있습니다. 비록 setMute()가 Television에 선언되지는 않았지만 Television 객체가 없다면 setMute()도 호출할 수 없습니다.

RemoteControl rc = new Television();
rc.setMute(true);

디폴트 메서드는 인터페이스의 모든 구현 객체가 가지고 있는 기본 메서드라고 생각하면 쉽습니다. 그러나 어떤 구현 객체는 디폴트 메서드의 내용이 맞지 않아 수정이 필요할 수도 있습니다. 구현 클래스를 작성할 때 디폴트 메서드를 오버 라이딩해서 자신에게 맞게 수정하면 디폴트 메서드가 호출될 때 자신을 재정의한 메서드가 호출됩니다. 다음 예제를 보면 Audio는 디폴트 메서드를 재정의했습니다. Television과 Audio 중 어떤 객체가 인터페이스에 대입되느냐에 따라서 setMute() 디폴트 메서드의 실행 결과는 달라집니다.

//Audio.java
package chapter00.exam00;

public class Audio implements RemoteControl
{
	//필드
	private int volume;
	private boolean mute;

	//실체 메서드
	public void turnOn()
	{
		System.out.println("AUDIO를 켭니다.");
	}

	public void turnOff()
	{
		System.out.println("AUDIO를 끕니다.");
	}

	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("현재 Audio 볼륨 : " + this.volume);
	}
	
	@Override
	public void setMute(boolean mute)
	{
		this.mute = mute;
		
		if(mute)
		{
			System.out.println("Audio 무음 처리합니다.");
		}
		else
		{
			System.out.println("Audio 무음 해제합니다.");
		}
	}
}
//exam00.java
package chapter00.exam00;

public class exam00 
{
	public static void main(String[] args)
	{
		RemoteControl rc = new Television();
		
		rc.turnOn();
		rc.turnOff();
		rc.setMute(true);
		
		rc = new Audio();
		rc.turnOn();
		rc.turnOff();
		rc.setMute(true);
	}
}

/*
실행결과

TV를 켭니다.
TV를 끕니다.
무음 처리합니다.
AUDIO를 켭니다.
AUDIO를 끕니다.
Audio 무음 처리합니다.

*/

 

3. 3. 정적 메서드 사용

인터페이스의 정적 메서드는 인터페이스로 바로 호출이 가능합니다. 다음 코드는 RemoteControl의 changeBattery() 정적 메서드를 호출합니다.

RemoteControl.changeBattery();
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
TAG
more
«   2025/05   »
1 2 3
4 5 6 7 8 9 10
11 12 13 14 15 16 17
18 19 20 21 22 23 24
25 26 27 28 29 30 31
글 보관함