인터페이스 역할
인터페이스는 사전적인 의미로 두 장치를 연결하는 접속기를 말한다.
여기서 두 장치를 서로 다른 객체로 본다면, 인터페이스는 이 두 객체를 연결하는 역할을 한다.
두 객체는 메소드를 호출하여 상호작용을 한다. 그러면 메소드를 호출하면 간단할 텐데 왜 인터페이스를 중간에 거치도록 할까?
두 객체는 서로 어떤 객체인지 알 필요가 없고 각 객체는 인터페이스의 기능만 알면 된다. 인터페이스에게 전달만 해주면 인터페이스가 대신 메소드를 호출해주고 리턴값을 다시 넘겨준다.
객체가 변경 된 것에 관심이 없기 때문에 객체간의 교체가 쉽다. (부품 교체)
이 특징으로 인해 인터페이스는 다형성 구현에 주된 기술로 이용왼다. 상속을 이용해서 다형성을 구현할 수 있지만, 인터페이스를 이용해서 다형성을 구현하는 경우가 더 많다.
인터페이스와 구현 클래스 선언
인터페이스를 통해서 사용할 수 있는 객체를 만드는 것을 구현 클래스라고 한다.
인터페이스 선언
인터페이스 선언은 class 키워드 대신 interface 키워드를 사용한다. 접근 제한자로는 클래스와 마찬가지로 같은 패키지 내에서만 사용 가능한 default, 패키지와 상관없이 사용하는 public을 붙일 수 있다.
interface 인터페이스명{...}
public interface 인터페이스명{...}
//인터페이스가 가지는 멤버들 선언
public interface 인터페이스명 {
//public 상수 필드
//public 추상 메소드
//public 디폴트 메소드
//public 정적 메소드
//private 메소드
//private 정적 메소드
}
public interface RemoteControl {
//public 추상 메소드
//abstract 생략 가능
//public 을 떼면 default가 public 이다.
public void turnOn();
}
구현 클래스 선언
실제로 인터페이스를 통해서 사용되는 객체가 구현 객체이고, 이 구현 객체를 만들기 위한 클래스를 구현 클래스라고 한다.
따라서 구현 클래스는 인터페이스가 가지고 있는 기능을 가지고 있어야 한다. 즉, 인터페이스에 선언된 추상 메소드와 동일한 선언부를 가진 재정의된 메소드를 가지고 있어야 한다.
이러한 객체를 인터페이스를 구현한(implement)객체라고 한다. 인터페이스에 정의된 추상 메소드에 대한 실행 내용이 구현되어 있기 때문이다. 구현 객체는 다음과 같이 인터페이스를 구현하고 있음을 선언부에 명시해야 한다.
public class B implements 인터페이스명 {...} //인터페이스를 구현해서 B 클래스를 만들겠다.
public interface RemoteControl {
public void turnOn();
}
public class Television implements RemoteControl { //인터페이스로 사용 가능하도록 구현객체 선언하겠다.
@Override
public void turnOn(){ //인터페이스에서 선언된 추상 메소드 재정의
System.out.println("TV를 켭니다");
}
}
변수 선언과 구현 객체 대입
인터페이스도 하나의 타입이므로 변수의 타입으로 사용할 수 있다.
인터페이스는 참조 타입에 속하므로 인터페이스 변수에는 객체를 참조하고 있지 않다는 뜻으로 null을 대입할 수 있다.
RemoteControl rc;
RemoteControl rc = null; //인터페이스를 가지고 사용할 구현객체가 없다는 뜻도 가능
인터페이스를 통해 구현 객체를 사용하려면 인터페이스 변수에 구현 객체를 대입해야한다.
RemoteControl rc;
rc = new Television(); //인터페이스를 통해서 객체를 사용하겠다.
//변수를 통해 인터페이스의 추상 메소드 호출
rc.turnOn();
상수 필드
상수는 불변의 값이다. 한번 생성되면 값을 바꿀 수 없다.
[ public static final ] 타입 변수명 = 값;
인터페이스로 선언된 필드는 모두 public static final 특성을 갖기 때문에 public static final을 생략하더라고 자동적으로 컴파일 과정에서 붙게된다. 상수명은 대문자로 작성하되 서로 다른 단어로 구성되어 있는 경우 언더바로 연결하는 것이 관례이다.
package sec02.exam02_constant_field;
public interface RemoteControl {
/* Even if you do not add 'public static final',
you can see that it is automatically applied as a constant.
The interface must specify the initial value when declaring a field. */
int MAX_VOLUME = 10;
int MIN_VOLUME = 0;
/* You can not use static blocks in an interface.
static {
}
*/
}
public class RemoteControlExample {
public static void main(String[] args){
System.out.println(RemoteControl.MAX_VOLUME);
}
}
추상 메소드
인터페이스는 구현 클래스가 재정의해야하는 public 추상 메소드를 멤버로 가질 수 있다. 추상 메소드는 리턴 타입, 메소드명, 매개변수만 기술되고 중괄호를 붙이지 않는 메소드를 말한다. public abstract를 생량하더라도 컴파일 과정에서 자동으로 붙게 된다.
package sec03.exam03_multi_implement_class;
public interface RemoteControl {
int MAX_VOLUME = 10;
int MIN_VOLUME = 0;
//추상 메소드
void turnON();
void turnOff();
void setVolume(int volume);
}
package sec03.exam03_multi_implement_class;
public class SmartTelevision implements RemoteControl {
private int volume;
@Override
public void turnON() {
System.out.println("TV를 켭니다.");
}
@Override
public void turnOff() {
System.out.println("TV를 끕니다.");
}
@Override
public void setVolume(int volume) { //setter, 외부에서 필드값을 바꿔치기 하는 것을 막음
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);
}
}
package sec03.exam01_name_implement_class;
public class Audio implements RemoteControl {
private int volume;
@Override
public void turnON() {
System.out.println("Audio를 켭니다.");
}
@Override
public void turnOff() {
System.out.println("Audio를 끕니다.");
}
@Override
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);
}
}
package sec03.exam01_name_implement_class;
public class RemoteControlExample {
public static void main(String[] args) {
RemoteControl rc;
rc = new Television();
rc = new Audio();
rc.turnON();
rc.turnOff();
}
}
디폴트 메소드
인터페이스에는 완전한 실행 코드를 가진 디폴트 메소드를 선언할 수 있다. 추상 메소드는 실행부 (중괄호)가 없지만, 디폴트 메소드는 실행부가 있다. 선언 방법은 클래스 메소드와 동일한데, 차이점은 default 키워드가 리턴 타입 앞에 붙는다.
인터페이스는 객체를 생성할 용도로 사용하지 않고 객체를 어떻게 이용할지 설명만 해 놓은 설명서라고 생각하면 된다. 따라서 객체 없이는 인터페이스를 쓸 수 없다.
디폴트 인스턴스 메소드는 인터페이스에 들어가 있지만, 구현 객체가 반드시 있어야 메소드 실행이 가능하다.
따라서 모든객체가 동일한 실행 내용으르 가지고 있을 경우 재정의를 하지 않고 디폴트 메소드를 호출해서 사용이 가능하다.
package sec03.exam02_noname_implement_class;
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("무음 해제합니다."); }
}
}
인터페이스에서 default 메소드로 선언이 되었지만 구현 클래스에서 맞지 않을 경우, default 메소드를 다시 재정의 할 수 있다.
재정의 시 주의할 점은 public접근 제한자를 반드시 붙여야하고, default 키워드를 생략해야한다.
package ch08.sec05;
public class Audio implements RemoteControl{
private int volume;
@Override
public void turnOn() {
System.out.println("Audio를 켭니다.");
}
@Override
public void turnOff() {
System.out.println("Audio를 끕니다.");
}
@Override
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 볼륨 : "+volume);
}
//추가된 필드 선언
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); // 원래 볼륨을 복구 하는 코드
}
}
}
정적 메소드
인터페이스에는 정적메소드도 선언이 가능하다. 추상메소드와 디폴트 메소드는 구현 객체가 필요하지만 정적 메소드는 구현 객체 없어도 인터페이스만으로 호출할 수 있다. 선언 방법은 클래스 정적 메소드와 완전 동일하다. 단, public을 생략하더라도 자동으로 컴파일 과정에서 붙는다.
package ch08.sec06;
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("리모콘 건전지를 교환합니다.");
}
}
public static void main(String[] args) {
RemoteControl rc;
rc = new Television();
rc.turnOn();
rc.setVolume(5);
rc.setMute(true);
rc.setMute(false);
System.out.println();
rc = new Audio();
rc.turnOn();
rc.setVolume(5);
//재정의한 디폴트 메소드 호출
rc.setMute(true);
rc.setMute(false);
//정적 메소드 호출 (인터페이스 타입을 이용해서 접근)
RemoteControl.changeBattery();
}
private 메소드
인터페이스의 상수 필드, 추상 메소드, 디폴트 메소드, 정적 메소드 모두 public 접근 제한을 갖는다. 이 멤버들을 선언할 때 public을 생략하더라고 컴파일 과정에서 public 접근 제한자가 붙어 항상 외부에서 접근이 가능하다.
또한 인터페이스 외부에서 접근할 수 없는 private 선언이 가능하다.
구분 | 설명 |
private 메소드 | 구현 객체가 필요한 메소드 |
private 정적 메소드 (static) | 구현 객체가 필요 없는 메소드 |
main 메소드에는 public밖에 호출을 못한다.
그러면 private는 언제 쓸까 ? default, static 메소드의 공통된 부분을 뽑아낸 것, 인터페이스 안에서만 쓸 수 있다.
package ch08.sec07;
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 static void staticCommon(){
System.out.println("staticMethod 중복 코드 C");
System.out.println("staticMethod 중복 코드 D");
}
}
package ch08.sec07;
public class ServiceImpl implements Service{ }
package ch08.sec07;
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();
}
}
다중 인터페이스 구현
인터페이스를 하나 이상 implements 할 수 있다.
인터페이스는 사용설명서라고했다. 따라서 사용하는 방법을 다르게 해서 객체를 이용하고 싶을 수 있다.
구현 객체가 어떤 인터페이스 변수에 대입되느냐에 따라 변수를 통해 호출할 수 있는 추상 메소드가 결정된다.
public class 구현클래스명 implements 인터페이스A, 인터페이스B{
//모든 추상 메소드 재정의
}
인터페이스A 변수 = new 구현클래스명(...);
인터페이스B 변수 = new 구현클래스명(...);
예제
package ch08.sec08;
public interface RemoteControl {
void turnOn();
void turnOff();
}
package ch08.sec08;
public interface Searchable {
void search(String url);
}
package ch08.sec08;
public class SmartTelevision implements RemoteControl, Searchable{
//2개의 인터페이스에 대한 모든 추상 메소드를 재정의 한다.
@Override
public void turnOn() {
System.out.println("TV를 켭니다.");
}
@Override
public void turnOff() {
System.out.println("TV를 끕니다.");
}
@Override
public void search(String url) {
System.out.println(url+"을 검색합니다.");
}
}
package ch08.sec08;
public class MultiInterfaceImplExample {
public static void main(String[] args) {
RemoteControl rc = new SmartTelevision();
rc.turnOn(); //RemoteControl 인터페이스에 선언된 추상 메소드만 호출 가능
rc.turnOff(); //RemoteControl 인터페이스에 선언된 추상 메소드만 호출 가능
Searchable searchable = new SmartTelevision();
//Searchable 인터페이스에 선언된 추상 메소드만 호출 가능
searchable.search("https://www.youtube.com");
}
}
인터페이스 상속
자바는 다중 상속을 허용하지 않는다.
하지만 인터페이스는 다중 상속을 허용한다.
public interface 자식인터페이스 extends 부모인터페이스1, 부모인터페이스2 { ... }
부모인터페이스를 상속하게 되면 , 추상메소드,디폴트메서드가 상속의 대상이 된다.
자식 인터페이스의 구현 클래스는 자식 인터페이스의 메소드 뿐만 아니라, 부모 인터페이스의 모든 추상 메소드를 재정의해야한다.
그리고 구현 객체는 다음과 같이 자식 및 부모 인터페이스 변수에 대입될 수 있다.
자식인터페이스 변수 = new 구현클래스(...);
부모인터페이스1 변수 = new 부모인터페이스1(...);
부모인터페이스2 변수 = new 부모인터페이스2(...);
구현 객체가 자식 인터페이스 변수에 대입되면 자식 및 부모 인터페이스의 추상 메소드를 모두 호출할 수 있으나, 부모 인터페이스 변수에 대입되면 부모 인터세피으싀 선언된 추상메소드만 호출 가능하다.
package ch08.sec09;
public interface InterfaceA {
void methodA();
}
package ch08.sec09;
public interface InterfaceB {
void methodB();
}
package ch08.sec09;
//InterfaceC는 세개의 추상메소드를 가지고 있음
public interface InterfaceC extends InterfaceA, InterfaceB
{
void methodC();
}
package ch08.sec09;
//인터페이스 C를 구현한 클래스를 만들때, 물려받는 메소드 + 자기가 가진 메소드 = 3개의 메소드를 재정의 해야한다.
public class InterfaceCImpl implements InterfaceC{
@Override
public void methodA() {
System.out.println("InterfaceCImpl-methodA() 실행");
}
@Override
public void methodB() {
System.out.println("InterfaceCImpl-methodB() 실행");
}
@Override
public void methodC() {
System.out.println("InterfaceCImpl-methodC() 실행");
}
}
package ch08.sec09;
public class ExtendsExample
{
public static void main(String[] args) {
//인터페이스 C 타입의 변수에다가 인터페이스 C 구현클래스 타입으로 객체를 만들었다
InterfaceCImpl impl = new InterfaceCImpl();
InterfaceA ia = impl;
ia.methodA();
//ia.methodB(); InterfaceA와 InterfaceB는 상속관계가 아니므로 불가능
InterfaceB ib = impl;
//ia.methodA(); InterfaceA와 InterfaceB는 상속관계가 아니므로 불가능
ib.methodB();
InterfaceC ic = impl;
ic.methodA();
ic.methodB();
ic.methodC();
}
}
타입 변환
자식 객체는 부모타입으로 자동 타입 변환된다.
하지만 부모 객체는 자식 객체 타입으로 자동 타입 변환이 안되고 강제 타입 변환을 해줘야한다.
여기서의 타입 변환은 구현객체가 인터페이스 타입으로의 타입변환이다.
인터페이스 변수 = 구현객체; //자동 타입변환
Television tv = new Television();
//Television구현 객체를 RemoteControl 인터페이스를 통해서 사용한다.
RemoteControl rc = new Television();
부모 클래스가 인터페이스를 구현하고 있다면 자식클래스도 인터페이스 타입으로 자동 타입 변환 될 수 있다.
강제 타입 변환
강제 타입변환은 캐스팅 기호를 사용해서 인터페이스 타입을 구현 클래스 타입으로 변환시키는 것을 말한다.
구현클래스 변수 = (구현클래스) 인터페이스변수 ; //강제 타입 변환
구현 객체가 인터펲이스 타입으로 자동 변환되면, 인터페이스에 선언된 메스드만 사용이 가능하다.
자동 타입 변환 후에 Bus의 있는 메소드를 호출하고 싶다면 캐스팅 기호를 사용해서 원래 Bus로 강제타입 변환 해야한다.
package ch08.sec10.exam02;
public interface Vehicle {
void run();
}
package ch08.sec10.exam02;
public class Bus implements Vehicle{
@Override
public void run() {
System.out.println("버스가 달립니다.");
}
public void checkFare(){
System.out.println("승차요금을 체크합니다.");
}
}
package ch08.sec10.exam02;
public class CastingExample {
public static void main(String[] args) {
Vehicle vehicle = new Bus();
vehicle.run();
//vehicle.checkFare(); 사용이 불가능 하다.
Bus bus = (Bus) vehicle; //강제 타입 변환
bus.run();
bus.checkFare();
}
}
인터페이스의 다형성 (메소드 정의 + 자동 타입 변환 => 다형성 )
다형성이란 사용 방법은 동일하지만 다양한 결과가 나오는 성질을 말한다.
상속의 다형성과 마찬가지로 인터페이스 역시 다형성을 구현하기 위해 재정의와 자동 타입 변환 기능을 이용한다.
인터페이스의 추상 메소드는 구현 클래스에서 재정의를 해야 하며, 재정의 되는 내용은 구현 클래스 마다 다르다.
구현 객체는 인터페이스 타입으로 자동 타입 변환이 되고, 인터페이스 메소드 호출 시 구현 객체의 재정의된 메소드가 호출되어 다양한 실행결과를 얻을 수 있다.
필드의 다형성
필드 타입으로 타이어 인터페이스를 선언하면 , 필드값으로 인터페이스로 사용 가능한 객체를 대입할 수 있다. 이는 자동 타입 변환 때문이다.
package ch08.sec11.exam01;
public interface Tire {
void roll();
}
package ch08.sec11.exam01;
public class HankookTire implements Tire{
@Override
public void roll() {
System.out.println("한국 타이어가 굴러갑니다.");
}
}
package ch08.sec11.exam01;
public class KumhoTire implements Tire{
@Override
public void roll() {
System.out.println("금호 타이어가 굴러갑니다.");
}
}
package ch08.sec11.exam01;
public class Car {
Tire tire1 = new HankookTire();
Tire tire2 = new HankookTire();
void run(){
tire1.roll();
tire2.roll();
}
}
package ch08.sec11.exam01;
public class CarExample {
public static void main(String[] args) {
Car myCar = new Car();
myCar.run();
//타이어 객체 교체
//필드의 다형성에 의해 금호타이어로 바꿔 다른 결과를 출력시킬 수 있다.
myCar.tire1 = new KumhoTire();
myCar.tire2 = new KumhoTire();
myCar.run();
}
}
매개변수의 다형성
메소드 호출 시 매개값을 다양화하기 위해 상속에서는 매개변수 타입을 부모 타입으로 선언하고 호출할 때에는 다양한 객체를 대입했다.
이것은 자동 타입 변환 때문인데, 비슷한 원리로 매개변수 타입을 인터페이스로 선언하면 메소드 호출 시 다양한 구현 객체를 대입할 수 있다.
매개변수가 인터페이스타입으로 되어있으면, 메소드를 호출 할 때 인터페이스로 구현한 객체면 다 올 수 있다.
package ch08.sec11.exam02;
public interface Vehicle {
void run();
}
package ch08.sec11.exam02;
public class Driver {
void drive(Vehicle vehicle){
vehicle.run();
}
}
package ch08.sec11.exam02;
public class Bus implements Vehicle{
@Override
public void run() {
System.out.println("버스가 달립니다.");
}
}
package ch08.sec11.exam02;
public class Taxi implements Vehicle{
@Override
public void run() {
System.out.println("택시가 달립니다.");
}
}
package ch08.sec11.exam02;
public class DriverExample
{
public static void main(String[] args) {
Driver driver = new Driver();
Bus bus = new Bus();
Taxi taxi = new Taxi();
driver.drive(bus); // 자동 타입 변환 -> 오버라이딩 메소드 호출 -> 다형성
driver.drive(taxi);
}
}
객체 타입 확인
instanceof 연산자로 확인 할 수 있다.
public static void ride(Vehicle vehicle){
if(vehicle instanceof Bus){
Bus but = (Bus) vehicle; //vehicle에 대입된 객체가 Bus일 경우에만 강제 타입 변환
}
}
'JAVA' 카테고리의 다른 글
상속 (0) | 2023.01.24 |
---|---|
클래스 (0) | 2023.01.23 |
열거(Enum) 타입 (0) | 2023.01.15 |
배열 (0) | 2023.01.15 |
참조 타입, 문자열 (0) | 2023.01.15 |