이것이 자바다 - Part08

mj·2023년 1월 16일
0
post-thumbnail

Part 08 인터페이스

인터페이스 역할

인터페이스는 사전적인 의미로 두 장치를 연결하는 접속기를 말한다.
여기서 두 장치를 서로 다른 객체로 본다면, 인터페이스는 이 두 객체를 연결하는 역할을 한다.

인터페이스를 사용하면 코드의 수정을 줄일 수 있고, 인터페이스를 사용하여 다형성도 구현한 수 있다.

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

인터페이스는 .java 형태의 소스 파일로 작성되고, .class 형태로 컴파일되기 때문에 물리적 형태는 클래스와 동일하다.
단, 소스를 작성할 때 선언하는 방법과 구성 멤버가 클래스와 다르다.

인터페이스 선언

인터페이스 선언은 class 키워드 대신 interface 키워드를 사용한다.
접근제한자로는 클래스와 마찬가지로 같은 패키지 내에서만 사용 가능한 default, 패키지와 상관없이 사용하는 public 을 붙일 수 있다.

인터페이스 선언

interface 인터페이스명 { ...  }		//default 접근 제한
public interface 인터페이스명 { ... }	//public 접근 제한

인터페이스 멤버 종류

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

구현 클래스 선언

객체 A가 인터페이스의 추상 메소드를 호출하면 인터페이스는 객체 B의 메소드를 실행한다.
그렇다면 객체 B는 인터페이스에 선언된 추상 메소드와 동일한 선언부를 가진(재정의된) 메소드를 가지고 있어야 한다.

이때 객체 B를 인터페이스를 구현한(inplement) 객체라고 한다.
인터페이스에 정의된 추상 메소드에 대한 실행 내용이 구현되어 있기 때문이다.
객체 B와 같은 구현 객체는 다음과 같이 인터페이스를 구현하고 있음을 선언부에 명시해야 한다.

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

implements 키워드 뜻

  • 해당 클래스가 인터페이스를 통해 사용할 수 있다는 표시
  • 클래스에 인터페이스의 추상 메소드를 재정의한 메소드가 있음
//interface
public interface RemoteControl {
    public void turnOn();
}
//class
public class Television implements RemoteControl {
    @Override
    public void turnOn() {
        System.out.println("TV를 켭니다.");
    }

}

변수 선언과 구현 객체 대입

인터페이스도 하나의 타입이므로 변수의 타입으로 사용할 수 있다.
인터페이스는 참조 타입에 속하므로 인터페이스 변수에는 객체를 참조하고 있지 않다는 뜻으로 null을 대입할 수 있다.

RemoteControl rc;
RemoteControl rc = null;

인터페이스를 통해 구현 객체를 사용하려면, 인터페이스 변수에 구현 객체를 대입해야 한다.

rc = new Television();

만약 Television이 implements RemoteControl 로 선언되지 않았다면 Remotecontrol 타입의 변수에 대입할 수 없다.

상수 필드

인터페이스는 public static final 특성을 갖는 불변의 상수 필드를 멤버로 가질 수 있다.

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

인어페이스에서 선언된 필드는 모두 public static final 특성을 갖기 때문에 public static final을 생략하더라도 자동적으로 컴파일 과정에서 붙게 된다.

상수명

  • 상수명은 대문자로 작성하고, 서로 다른 단어로 구성되어 있을 경우 언더바(_)로 연결하는 것이 관례이다.

추상 메소드

인터페이스는 구현 메소드가 재정의해야 하는 public 추상 메소드를 멤버로 가질 수 있다.
추상 메소드는 리턴 타입, 메소드명, 매개변수만 기술되고 중괄호 {} 를 붙이지 않는 메소드를 말한다.

추상 메소드 접근제한자

  • public abstract 를 생략하더라도 컴파일 과정에서 자동으로 붙게된다.
  • 기본적으로 public 접근 제한을 갖기 때문에 public 보다 낮은 접근 제한으로 재정의할 수 없다.
[ public abstract ] 리턴타입 메소드명(매개변수, ...);

추상 메소드는 객체 A가 인터페이스를 통해 어떻게 메소드를 호출할 수 있는지 방법을 알려주는 역할을 한다.
인터페이스 구현 객체 B는 추상 메소드의 실행부를 갖는 재정의된 메소드가 있어야 한다.

디폴트 메소드

인터페이스는 완전한 실행 코드를 가진 디폴트 메소드를 선언할 수 있다.
클래스 메소드와의 차이점은 default 키워드가 리턴 타입 앞에 붙는다는 것이다.

[ public ] 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("무음 해제합니다.");
        }
    }
}

구현 클래스는 인터페이스의 디폴트 메소드를 재정의해서 자신에게 맞게 수정할 수도 있다.

default 메소드 재정의 시 주의점

  • public 접근 제한자를 반드시 붙여야 함
  • default 키워드를 생략해야 함.

정적 메소드

인터페이스에는 정적 메소드도 선언이 가능하다.

추상 메소드와 디폴트 메소드는 구현 객체가 필요하지만, 정적 메소드는 구현 객체가 없어도 인터페이스만으로 호출할 수 있다.

선언 방법은 클래스 정적 메소드와 완전 동일하다.

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

정적 메소드 작성시 주의점

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

private 메소드

인터페이스의 상수 필드, 추상 메소드, 디폴트 메소드, 정적 메소드는 모두 public 접근 제한을 갖는다.

이 멤버들을 선언할 때는 public 을 생략하더라도 컴파일 과정에서 public 접근 제한자가 붙어 항상 외부에서 접근이 가능하다.

또한 인터페이스에 외부에서 접근할 수 없는 private 메소드 선언도 가능하다.

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

private 메소드는 디폴트 메소드 안에서만 호출이 가능한 반면, private 정적 메소드는 디폴트 메소드 뿐만 아니라 정적 메소드 안에서도 호출이 가능하다.

private 메소드의 용도는 디폴트와 정적 메소드들의 중복 코드를 줄이기 위함이다.

다중 인터페이스 구현

구현 객체는 여러 개의 인터페이스를 implements 할 수 있다. 구현 객체가 인터페이스 A와 인터페이스 B를 구현하고 있다면, 각각의 인터페이스를 통해 구현 객체를 사용할 수 있다.

구현 클래스는 다음과 같이 인터페이스 A와 인터페이스 B를 implements 뒤에 쉼표로 구분해서 작성해, 모든 인터페이스가 가진 추상 메소드를 재정의해야 한다.

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

인터페이스 A와 인터페이스 B를 구현한 객체는 다음과 같이 두 인터페이스 타입의 변수에 각각 대입될 수 있다.

인터페이스A 변수 = new 구현클래스명(...);
인터페이스B 변수 = new 구현클래스명(...);

각 인터페이스의 인스턴스 변수는 해당 인스턴스에 구현된 추상 메소드에 한해서만 호출할 수 있다.

인터페이스 상속

인터페이스도 다른 인터페이스를 상속할 수 있으며, 클래스와는 달리 다중 상속을 허용한다.

인터페이스의 상속

  • extends 키워드 뒤에 상속할 인터페이스들을 나열하면 된다.
public interface 자식인터페이스 extends 부모인터페이스1, 부모인터페이스2{ ... }

자식 인터페이스의 구현 클래스는 자식 인터페이스의 메소드 뿐만 아니라 부모 인터페이스의 모든 추상 메소드를 재정의해야 한다.

그리고 구현 객체는 다음과 같이 자식 및 부모 인터페이스 변수에 대입될 수 있다.

자식인터페이스 변수 = new 구현클래스(...);
부모인터페이스1 변수 = new 구현클래스(...);
부모인터페이스2 변수 = new 구현클래스(...);

구현 객체가 자식 인터페이스 변수에 대입되면 자식 및 부모 인터페이스의 추상 클래스를 모두 호출할 수 있지만, 부모 인터페이스 변수에 대입되면 부모 인터페이스에 선언된 추상 메소드만 호출 가능하다.

타입 변환

인터페이스의 타입 변환은 인터펳이스와 구현 클래스 간에 발생한다.

인터페이스 변수에 구현 객체를 대입하면 구현 객체는 인터페이스 타입으로 자동 타입 변환된다.

반대로 인터페이스 타입을 구현 클래스 타입으로 변환시킬 수 있는데 이때는 강제 타입 변환이 필요하다.

자동 타입 변환

구현클래스에서 인터페이스로 자동 타입 변환

인터페이스 변수 = 구현 객체;
인터페이스 변수 = new 클래스명;

부모 클래스가 인터페이스를 구현하고 있다면 자식 클래스도 인터페이스 타입으로 자동 타입 변환 될 수 있다.

강제 타입 변환

강제 타입 변환은 캐스팅 기호를 사용해서 인터페이스 타입을 구현 클래스 타입으로 변환시키는 것을 말한다.

인터페이스에서 구현클래스로 강제 타입 변환

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

구현 객체가 인터페이스 타입으로 자동 변환되면, 인터페이스에 선언된 메소드만 사용 가능하다.

자동 타입 변환 후 구현 클래스에 선언된 메소드를 호출하고 싶다면 캐스팅 기호를 사용하여 강제 타입 변환을 해야한다.

예를 들어 인터페이스와 구현 클래스가 아래와 같이 구성되어있다고 하자

RemoteControlTelevision
turnOn();turnOn(...) { ... };
turnOff();turnOff(...) { ... };
setVolume();setVolume(...) { ... };
turnOn(...) { ... };
turnOn(...) { ... };

아래 자동 타입 변환에서는 RemoteControl 인터페이스에 정의된 메소드만 호출 가능하다.

RemoteControl rc = new Television();
rc.turnOn();
rc.turnOff();
rc.setVolume();

Television 클래스에 정의된 메소드도 사용할려면 강제 타입 변환을 해야한다.

Television tv = (Television) rc;
tv.turnOn();
tv.turnOff();
tv.setvolume();
tv.setTime();
tv.record();

다형성

인터페이스는 다형성을 구현하는 주된 기술로 사용되며, 상속보다는 인터페이스를 통해서 다형성을 구현하는 경우가 더 많다.

다형성이란 사용 방법은 동일하지만 다양한 결과가 나오는 성질을 말한다.

상속의 다형성과 마찬가지로 인터페이스 역시 다형성을 구현하기 위해 재정의와 자동 타입 변환 기능을 이용한다.

메소드 재정의

+

자동 타입 변환

=

다형성

인터페이스의 추상 메소드는 구현 클래스에서 재정의 해야 하면, 재정의되는 내용은 구현 클래스 마다 다르다.

구현 객체는 인터페이스 타입으로 자동 타입 변환이 되고, 잍너페이스 메소드 호출 시 구현 객체의 재정의된 메소드가 호출되어 다양한 실행 결과를 얻을 수 있다.

필드의 다형성

매개변수의 다형성

메소드 호출 시 매개값을 다양화하기 위해 상속에서는 매개변수 타입을 부모 타입으로 선언하고 호출할 때에는 다양한 자식 객체를 대입했다.
이것은 자동 타입 변환 때문인데, 비슷한 원리로 매개변수 타입을 인터페이스로 선언하면 메소드 호출 시 다양한 구현 객체를 대입할 수 있다.

//코드 작성 예정

객체 타입 확인

상속에서는 객체 타입을 확인하기 위해 instanceof 연산자를 사용했는데, 인터페이스에서도 사용할 수 있다.

예를 들어 Vehicle 인터페이스 변수에 대입된 객체가 Bus 인지 확인하는 코드는 다음과 같다.

if( vehicle instanceof Bus ) {
	//vehicle에 대입된 객체가 Bus 일 경우 실행
}

메소드의 매개변수가 인터페이스 타입일 경우, 메소드 호출 시 매개값은 해당 인터페이스를 구현하는 모든 객체가 될 수 있다.
만약 매개값이 특정 구현 객체일 경우에만 강제 타입 변환을 하고 싶다면 instanceof 연산자를 사용해서 매개값의 타입을 검사해야 한다.

public void method( Vehicle vehicle ) {
	if( vehicle instanceof Bus ) {
    	Bus bus = (Bus) vehicle;
    }
}

Java 12 부터는 instanceof 연산의 결과가 true 일 경우, 우측 타입 변수를 사용할 수 있기 때문에 강제 타입 변환이 필요 없다.

public void method( Vehicle vehicle ) {
	if( vehicle instanceof Bus bus ) {
    	//bus 를 사용하는 작업
    }
}

봉인된 인터페이스

Java 15부터는 무분별한 자식 인터페이스 생성을 방지하기 위해 봉인된(sealed) 인터페이스를 사용할 수 있다.

InterfaceA 의 자식 인터페이스는 InterfaceB 만 가능하고, 그 외에는 자식 인터페이스가 될 수 없도록 다음과 같이 InterfaceA를 봉인된 인터페이스로 선언할 수 있다.

public sealed interface InterfaceA permits InterfaceB { ... }

sealed 된 키워드를 사용하면 permits 키워드 뒤에 상속 가능한 자식 인터페이스를 지정해야 한다.

봉인된 InterfaceA를 상속하는 InterfaceB는 non-sealed 키워드로 다음과 같이 선언하고나 sealed 키워드를 사용해서 또 다른 봉인 인터페이스로 선언해야 한다.

public non-sealed interface InterfaceB extends InterfaceA { ... }
public interface InterfaceC extends InterfaceB { ... }

문제

  1. 인터페이스에 대한 설명으로 틀린 것은 무엇입니까?
    ➊ 인터페이스로 객체(인스턴스)를 생성할 수 있다.
    ➋ 인터페이스는 다형성의 주된 기술로 사용된다.
    ➌ 인터페이스를 구현한 객체는 인터페이스로 동일하게 사용할 수 있다.
    ➍ 인터페이스를 사용함으로써 객체 교체가 쉬워진다.
  • 답 : ➊
  1. 인터페이스의 구성 멤버에 대한 설명으로 틀린 것은 무엇입니까?
    ➊ 인터페이스는 인스턴스 필드가 없고 상수를 멤버로 가진다.
    ➋ 추상 메소드는 구현 클래스가 재정의해야 하는 멤버이다.
    ➌ 디폴트 메소드는 구현 클래스에서 재정의할 수 없다.
    ➍ 정적 멤버는 구현 객체가 없어도 사용할 수 있는 멤버이다.
  • 답 : ➌
  1. 인터페이스 다형성에 대한 설명으로 틀린 것은 무엇입니까?
    ➊ 필드가 인터페이스 타입일 경우 다양한 구현 객체를 대입할 수 있다.
    ➋ 매개변수가 인터페이스 타입일 경우 다양한 구현 객체를 대입할 수 있다.
    ➌ 배열이 인터페이스 타입일 경우 다양한 구현 객체를 저장할 수 있다.
    ➍ 구현 객체를 인터페이스 타입으로 변환하려면 강제 타입 변환을 해야 한다.
  • 답 : ➍
  1. 인터페이스 A를 B와 C가 구현하고 B를 상속해서 D 클래스를, C를 상속해서 E 클래스를 만들었
    습니다. 다음 빈칸에 들어올 수 있는 것을 모두 선택하세요.
  • 답 : ➊, ➋, ➌, ➍
  1. TV 클래스를 실행했을 때 “TV를 켰습니다.”라고 출력되도록 밑줄과 박스에 들어갈 코드를 작성
    해보세요.
public interface Remocon {
	public void powerOn();
}
public class TV _________________________ {

 	// 코드 작성
	public static void main(String[] args) {
 		Remocon r = new TV();
 		r.powerOn();
	}
}
  • 답 :
@Override
public void powerOn() {
	System.out.println("TV를 켰습니다.");
}
  1. Soundable 인터페이스는 다음과 같은 sound ( ) 추상 메소드를 가지고 있습니다.
    SoundableExample 클래스의 printSound() 메소드는 매개변수 타입으로 Soundable 인터페
    이스를 가집니다. printSound()를 호출할 때 Cat과 Dog 객체를 주고 실행하면 각각 “야옹”과 “멍
    멍”이 출력되도록 Cat과 Dog 클래스를 작성해보세요.
public interface Soundable {
	public String sound();
}
public class SoundableExample {
	public static void printSound(Soundable soundable) {
 		System.out.println(soundable.sound());
	}
	public static void main(String[] args) {
 		printSound(new Cat());
 		printSound(new Dog());
	}
}
  • 답 :
//Cat.java
public class Cat implements Soundable {
	@Override
	public String sound() {
    	return "야옹";
    }
}
//Dog.java
public class Dog implements Soundable {
	@Override
	public String sound() {
    	return "멍멍";
    }
}
  1. DaoExample 클래스의 main() 메소드에서 dbWork() 메소드를 호출할 때 OracleDao
    와 MySqlDao 객체를 매개값으로 주고 호출했습니다. dbWork() 메소드는 두 객체를 모두 매
    개값으로 받기 위해 DataAccessObject 타입의 매개변수를 가지고 있습니다. 실행 결과를 보고
    DataAccessObject 인터페이스와 OracleDao, MySqlDao 구현 클래스를 각각 작성해보세요.
public class DaoExample {
	public static void dbWork(DataAccessObject dao) {
 		dao.select();
 		dao.insert();
 		dao.update();
 		dao.delete();
	}
	public static void main(String[] args) {
 		dbWork(new OracleDao());
 		dbWork(new MySqlDao());
	}
}
//실행 결과
Oracle DB에서 검색
Oracle DB에 삽입
Oracle DB를 수정
Oracle DB에서 삭제
MySql DB에서 검색
MySql DB에 삽입
MySql DB를 수정
MySql DB에서 삭제
  • 답 :
//DataAccessObject.java
public interface DataAccessObject {
	public void select();
    public void insert();
    public void update();
    public void delete();
}
//OracleDao.java
public class OracleDao implements DataAccessObject{
	@Override 
    public void select() {
    	System.out.println("Oracle DB에서 검색");
    }
    @Override 
    public void insert() {
    	System.out.println("Oracle DB에 삽입");
    }
    @Override 
    public void update() {
    	System.out.println("Oracle DB를 수정");
    }
    @Override 
    public void delete() {
    	System.out.println("Oracle DB에서 삭제");
    }
}
//MysqlDao.java
public class MySqlDao implements DataAccessObject{
	@Override 
    public void select() {
    	System.out.println("MySql DB에서 검색");
    }
    @Override 
    public void insert() {
    	System.out.println("MySql DB에 삽입");
    }
    @Override 
    public void update() {
    	System.out.println("MySql DB를 수정");
    }
    @Override 
    public void delete() {
    	System.out.println("MySql DB에서 삭제");
    }
}
  1. 다음과 같이 인터페이스와 클래스가 선언되어 있습니다. action() 메소드를 호출할 때 매개값이
    C 객체일 경우에만 method2()가 호출되도록 밑줄에 들어갈 코드를 작성해보세요.

  • 답 : a instanceof C c
profile
사는게 쉽지가 않네요

0개의 댓글