[혼공자] 08-1. 인터페이스

Benjamin·2023년 3월 15일
0

혼공자

목록 보기
24/27

08. 인터페이스

08-1. 인터페이스

인터페이스(Interface) = 객체의 사용 방법을 정의한 타입
-> 다양한 객체를 동일한 사용 방법으로 이용할 수 있다.

인터페이스는 개발 코드와 객체가 서로 통신하는 접점 역할을 한다.
개발 코드가 인터페이스의 메소드를 호출하면 인터페이스는 객체의 메소드를 호출시킨다.
개발 코드는 객체의 내부 구조를 알 필요가 없고, 인터페이스의 메소드만 알고 있으면 된다.

개발 코드가 직접 객체의 메소드를 호출하면 간단한데, 왜 중간에 인터페이스를 둘까?
그 이유는 개발 코드를 수정하지 않고 사용하는 객체를 변경할 수 있도록 하기 위해서이다.
인터페이스는 하나의 객체가 아니라 여러 객체들과 사용 가능하므로 어떤 객체를 사용하느냐에 따라 실행 내용과 리턴값이 다를 수 있다.
따라서 개발 코드 측면에서는 코드 변경없이 실행 내용과 리턴값을 다양화할 수 있다는 장점을 갖게된다.

인터페이스 선언

인터페이스는 '~.java' 형태의 소스파일로 작성되고, 컴파일러(javac)를 통해 '~.class'형태로 컴파일되기 때문에 물리적인 형태는 클래스와 동일하다.

그러나 소스를 작성할 때 선언하는 방법이 다르다.

class 키워드 대신 interface 키워드를 사용한다.
[public] interface 인터페이스이름 {...}
인터페이스 이름은 클래스 이름을 작성하는 방법과 동일하다.
영어 대소문자를 구분하며, 첫 글자를 대문자로 하고 나머지는 소문자로 작성하는 것이 관례이다.

인터페이스는 상수 필드와 추상 메소드만을 구성 멤버로 가진다.
인터페이스는 객체로 생성할 수 없기 때문에 생성자를 가질 수 없다.

interface 인터페이스이름 {
	//상수
    타입 상수이름  = 값;
    
    //추상 메소드
    타입 메소드이름(매개변수,...);
}

상수 필드 선언

인터페이스는 객체 사용 방법을 정의한 것이므로 실행 시 데이터를 저장할 수 있는 인스턴트 또는 정적 필드를 선언할 수 없다.

상수필드(constant field)는 선언이 가능하다.
상수는 인터페이스에 고정된 값으로 실행 시에 데이터를 바꿀 수 없다.

인터페이스에 선언된 필드는 모두 public static final 특성을 갖는다.
이를 생략하더라도 컴파일 과정에서 자동으로 붙게된다.
[public static final] 타입 상수이름 = 값;

상수 이름은 대문자로 작성하되, 서로 다른 단어로 구성되어 있을 경우에는 언더바(_)로 연결하는것이 관례이다.
인터페이스 상수는 반드시 선언과 동시에 초기값을 지정해야한다.

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

추상 메소드 선언

인터페이스를 통해 호출된 메소드는 최종적으로 객체에서 실행된다.
따라서 인터페이스의 메소드는 실행 블록이 필요 없는 추상 메소드로 선언한다.
인터페이스에 선언된 추상 메소드는 모두 public abstract의 특성을 갖기때문에, 이를 생략하더라도 컴파일 과정에서 자동으로 붙게된다.

package sec01.exam03.;
public interface RemoteControl {
	//상수
	public int MAX_VOLUME = 10;
    public int MIN_VOLUME = 0;
    
    //추상 메소드
    public void turnOn();
    public void turnOff();
    public void setVolume(int volume); 
}

인터페이스 구현

개발 코드가 인터페이스 메소드를 호출하면 인터페이스는 객체의 메소드를 호출한다.
객체는 인터페이스에서 정의된 추상 메소드의 실체 메소드를 갖고있어야한다.
이러한 객체를 인터페이스의 구현(implement)객체라고 하고, 구현 객체를 생성하는 클래스를 구현 클래스라고 한다.

구현 클래스

인터페이스 타입으로 사용할 수 있음을 알려주기 위해 클래스 선언부에 implements 키워드를 추가하고 인터페이스 이름을 명시해야한다.

그리고 인터페이스의 추상메소드의 실체 메소드를 선언해야한다.

  • implements : 구현 클래스는 어떤 인터페이스로 사용 가능한지(어떤 인터페이스를 구현하고있는지)를 기술하기 위함
public class 구현클래스이름 implements 인터페이스이름{
	//인터페이스에 선언된 추상 메소드의 실체 메소드 선언 
}

다음은 Television, Audio라는 이름을 가진 RemoteControl의 구현 클래스를 작성하는 방법이다.
이 두 클래스는 RemoteControl인터페이스로 사용 가능하다. 또한 추상메소드들에 대한 실체 메소드를 갖고있어야한다.

package sec01.exam04;

import sec01.exam03.RemoteControl;

public class Television implements RemoteControl {
	//필드 
	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) { //인터페이스 상수 이용해 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);
	}
}
package sec01.exam04;

import sec01.exam03.RemoteControl;

public class Audio implements RemoteControl {
	//필드 
	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) { //인터페이스 상수 이용해 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보다 더 낮은 접근 제한으로 작성할 수 없다는 것이다.
    public을 생략하면 "Cannot reduce the visibility of the inherited method from ~~" 라는 컴파일에러는 만나게된다.

구현 클래스가 작성되면 new 연산자로 객체를 생성할 수 있다.
하지만 다음 코드는 인터페이스를 사용한 것이아니다.
Television tv = new Television();

인터페이스로 구현 객체를 사용하려면 다음처럼 인터페이스 변수를 선언하고, 구현 객체를 대입해야한다.
인터페이스 변수는 참조 타입이기때문에 구현 객체가 대입될 경우 구현 객체의 번지를 저장한다.

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

RemoteControl인터페이스로 구현 객체인 Television,Audio를 사용하려면 다음 예제처럼 RemoteControl타입 변수 rc를 선언하고, 구현 객체를 대입해야한다.

package sec01.exam04;

import sec01.exam03.RemoteControl;

public class RemoteControlExample {

	public static void main(String[] args) {
		RemoteControl rc;
		rc = new Television();
		rc = new Audio();
	}
}

구현객체를 인터페이스 변수에 대입해서 사용한다는것을 기억하자!

다중 인터페이스 구현 클래스

객체는 다음과같이 다수의 인터페이스 타입으로 사용할 수 있다.

인터페이스 A와 인터페이스 B가 객체의 메소드를 호출할 수 있으려면 객체는 이 두 인터페이스를 모두 구현해야 한다.

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

다중 인터페이스 구현할 경우, 모든 인터페이스의 추상 메소드에 대해 실체 메소드를 작성해야한다.

package sec01.exam05;

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

SmartTelevision이 인터넷 검색 기능도 제공한다면, RemoteControl, Searchable를 모두 구현한 클래스를 작성할 수 있다.

package sec01.exam05;

import sec01.exam03.RemoteControl;

public class SmartTelevision implements RemoteControl, Searchable {
	//필드 
	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) { //인터페이스 상수 이용해 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);
	}
	
	public void search(String url) {
		System.out.println(url +"을 검색합니다.");
	}
}

SmartTelevision 클래스는 RemoteControl, Searchable인터페이스를 모두 구현하고있기떄문에 SmartTelevsion 객체를 RemoteControl타입 변수와 Searchable타입 변수에 각각 대입할 수 있다.

package sec01.exam05;

import sec01.exam03.RemoteControl;

public class SmartTelevisionExample {

	public static void main(String[] args) {
		SmartTelevision tv = new SmartTelevision();
		
		RemoteControl rc = tv;
		Searchable s = tv;
	}
}

인터페이스 사용

인터페이스로 구현 객체를 사용하는 방법을 알아보자.

클래스를 선언할 때 인터페이스는 필드, 생성자 또는 메소드의 매개 변수, 생성자 또는 메소드의 로컬 변수로 선언될 수 있다.

public class MyClass {
	//필드
	RemoteControl rc = new Television();
    
    //생성자
    MyClass(RemoteControl rc) { //생성자의 매개값으로 구현객체 대입 MyClass mc = new MyClass(new Television());
    	this.rc = rc;
    }
    
    메소드
    void methodA() {
    	//로컬변수
    	RemoteControl rc = new Audio();
    }
    
    void methodB(RemoteControl rc){...} //생성자의 매개값으로 구현 객체 대입 : mc.methodB(new Audio());
}
  1. 인터페이스가 필드 타입으로 사용될 경우, 필드에 구현 객체를 대입할 수 있다.
  2. 인터페이스가 생성자의 매개 변수 타입으로 사용될 경우, new 연산자로 객체를 생성할 때 구현객체를 생성자의 매개값으로 대입할 수 있다.
  3. 인터페이스가 로컬 변수 타입으로 사용될 경우, 변수에 구현 객체를 대입할 수 있다.
  4. 인터페이스가 메소드의 매개변수 타입으로 사용될 경우, 메소드 호출 시 구현 객체를 매개값으로 대입할 수 있다.

구현 객체가 인터페이스 타입에 대입되면 인터페이스에 선언된 추상 메소드를 개발 코드에서 사용할 수 있게된다.

RemoteControl의 변수 rc로 turnOn(), turnOff() 메소드를 호출하면 구현 객체의 turnOn(), turnOff()가 자동 실행된다.

  1. 필드로 선언된 rc는 다음과 같이 사용될 수 있다.
MyClass myClass = new MyClass();
myClass.rc.turnOn(); Television의 turnOn()이 실행
myClass.rc.setVolume(5); //Television의 setVolume(5)가 실행 
  1. 생성자의 매개 변수 타입으로 선언된 rc는 다음과 같이 사용될 수 있다.
MyClass(RemoteControl rc) {
	this.rc = rc;
    rc.turnOn();
    rc.setVolume(5);
}

만약 다음처럼 MyClass 객체가 생성되었을 경우에는 Audio의 turnOn(), setVolume()메소드가 실행된다.
MyClass myClass = new MyClass(new Audio());

  1. 로컬 변수로 선언된 rc는 다음과같이 사용할 수 있다.
void methodA() {
	RemoteControl rc = new Audio();
    rc.turnOn(); //Audio의 turnOn()이 실행
    rc.setVolume(5); // Audio의 setVolume(5)가 실행
}
  1. 메소드의 매개 변수 타입으로 선언된 rc는 다음과 같이 사용될 수 있다.
void methodB(RemoteControl rc) {
	rc.turnOn();
    rc.setVolume(5);
}

만약 다음처럼 methodB() 메소드가 호출되었을 경우에는 Television의 turnOn()과 setVolume() 메소드가 실행된다.

MyClass myClass = new MyClass();
myClass.methodB(new Television());

인터페이스 사용 과정을 도식화하면 다음과 같다.

인터페이스(RemoteControl)는 개발 코드 (MyClass)와 구현 객체(Television, Audio) 사이에서 접점 역할을 담당한다.
개발 코드는 인터페이스에 선언된 추상 메소드(turnOn())를 호출하고, 인터페이스는 구현 객체의 재정의 메소드(turnOn())를 호출한다.

0개의 댓글