인터페이스 : 사전적 의미로는 두 장치를 연결하는 접속기
두 장치를 서로 다른 객체로 본다면 인터페이스는 두 객체를 연결하는 역할을 한다.
인터페이스
다형성 구현이 주된 기술
상속을 이용한 다형성보다 인터페이스를 이용한 다형성이 더 자주 쓰인다.
객체A, B가 있고, 둘 사이에 인터페이스가 있다면
A가 인터페이스의 메소드 호출시 B의 메소드를 호출하고, 리턴값을 A로 전달해준다.
A가 B의 메소드를 직접 호출하지 않는 이유는 다형성을 위함이다.
B가 C객체로 교체되거나 했을때, 직접 B메소드 호출한경우에는 코드를 변경해주어야하지만, 인터페이스로 호출한경우에는 변경해줄 필요가 없다.
- 모든 멤버 변수는 public static final이어햐 하며, 이를 생략할 수 있다.
- 모든 메서드는 public abtract이어야 하며, 이를 생략할 수 있다.
interface 인터페이스명 {...}
-> default 접근제한public interface 인터페이스명 {...}
-> public 접근제한중괄호 안에는 인터페이스가 가지는 멤버들을 선언할수 있다.
객체 A가 인터페이스의 추상메소드를 호출하면
B는 재정의된(구현한) 메소드를 갖고있어야 실행 가능하다.
=> 인터페이스를 구현(implement)한 객체 B
package ch08.sec02;
public interface RemoteControl {
//public 추상 메소드
public void turnOn();
}
package ch08.sec02;
public class Television implements RemoteControl {
@Override
public void turnOn() {
System.out.println("TV를 켭니다.");
}
}
인터페이스도 하나의 타입(참조 타입)
인터페이스 변수에 null을 넣으면 객체를 참조하고있지 않다는 뜻
구현 객체를 사용하려면 인터페이스 변수에 구현 객체(객체의 주소)를 대입해야 함
RemoteControl rc = new Television();
이때, Television은 implements RemoteControl 로 선언되어있어야 대입 가능
rc.turnOn();
그러면 인터페이스의 추상메소드를 호출 가능해진다.
이때 호출된 turnOn() 메소드는 Television에서 재정의된 메소드가 호출된것이다.
package ch08.sec02;
public class RemoteControlExample {
public static void main(String[] args) {
RemoteControl rc;
rc = new Television();
rc.turnOn(); //Television에서 재정의된 turnOn()메소드
}
}
package ch08.sec02;
public class Audio implements RemoteControl {
@Override
public void turnOn() {
System.out.println("Audio를 켭니다.");
}
}
package ch08.sec02;
public class RemoteControlExample {
public static void main(String[] args) {
RemoteControl rc;
//rc 변수에 Television 객체를 대입
rc = new Television();
rc.turnOn(); //TV를 켭니다. (Television에서 재정의된 turnOn()메소드가 호출됨)
//rc 변수에 Audio 객체를 대입(교체시킴)
rc = new Audio();
rc.turnOn(); //Audio를 켭니다.
}
}
인터페이스는 상수 필드(public static final 특성)를 멤버로 가질수 있다.
[public static final] 타입 상수명 = 값;
인터페이스에 선언된 필드는 모두 public static final 특성을 가짐
public static final 키워드 생략해도 컴파일시 자동으로 추가된다.
상수는 구현객체와 관련없는 인터페이스 소속 멤버이므로 인터페이스로 바로 접근 가능
인터페이스명.상수명;
package ch08.sec03;
public interface RemoteControl {
int MAX_VOLUME = 10;
int MIN_VOLUME = 0;
}
package ch08.sec03;
public class RemoteControlExample {
public static void main(String[] args) {
System.out.println("리모콘 최대 볼륨 : " + RemoteControl.MAX_VOLUME);
System.out.println("리모콘 최저 볼륨 : " + RemoteControl.MIN_VOLUME);
// 리모콘 최대 볼륨 : 10
// 리모콘 최저 볼륨 : 0
}
}
추상 메소드
구현 클래스가 재정의해야하는 메소드
구현부를 가지지 않는 몸체 없는 미완성 메소드
리턴타입, 메소드명, 매개변수만 기술하고 중괄호 생략
public abstract 키워드는 생략해도 자동으로 추가
인터페이스에 추상 메소드가 있으면, 인터페이스를 구현한 객체는 추상메소드를 재정의하여 실행부를 채워주어야한다.
package ch08.sec04;
public interface RemoteControl {
//상수 필드
int MAX_VOLUME = 10;
int MIN_VOLUME = 0;
//추상 메소드
void turnOn();
void turnOff();
void setVolume(int volume);
}
package ch08.sec04;
public class Television implements RemoteControl {
//필드
private int volume;
//turnOn() 추상 메소드 오버라이딩
@Override
public void turnOn() {
System.out.println("TV를 켭니다. ");
}
//turnOff() 추상 메소드 오버라이딩
@Override
public void turnOff() {
System.out.println("TV를 끕니다. ");
}
//setVolume() 추상 메소드 오버라이딩
@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);
}
}
package ch08.sec04;
public class Audio implements RemoteControl {
//필드
private int volume;
//turnOn() 추상 메소드 오버라이딩
@Override
public void turnOn() {
System.out.println("Audio를 켭니다. ");
}
//turnOff() 추상 메소드 오버라이딩
@Override
public void turnOff() {
System.out.println("Audio를 끕니다. ");
}
//setVolume() 추상 메소드 오버라이딩
@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 볼륨 : " + volume);
}
}
구현 클래스에서 추상메소드 재정의시 주의점
추상메소드는 기본적으로 public 접근제한
public 보다 낮은 접근제한으로 재정의 불가
package ch08.sec04;
public class RemoteControlExample {
public static void main(String[] args) {
//인터페이스 변수 선언
RemoteControl rc;
//Television 객체를 생성하고 인터페이스 변수에 대입
rc = new Television();
rc.turnOn();
rc.setVolume(5);
rc.turnOff();
//Television 객체를 생성하고 인터페이스 변수에 대입
rc = new Audio();
rc.turnOn();
rc.setVolume(4);
rc.turnOff();
// TV를 켭니다.
// 현재 TV 볼륨 : 5
// TV를 끕니다.
// Audio를 켭니다.
// 현재 Audio 볼륨 : 4
// Audio를 끕니다.
}
}
디폴드 메소드
완전한 실행 코드를 가진 메소드
구현 객체가 필요한 메소드
default 키워드가 리턴타입 앞에 붙음
실행부에는 상수필드 읽기, 추상메소드 호출 코드를 작성할수 있음
인터페이스에서는 디폴트 메소드를 선언할 수 있음
추상 메소드는 실행부 { }가 없지만
디폴트 메소드는 실행부가 있다.
[public] default 리턴타입 메소드명(매개변수1,...) {...}
package ch08.sec05;
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("무음 해제합니다");
}
}
}
package ch08.sec05;
public class RemoteControlExample {
public static void main(String[] args) {
//인터페이스 변수 선언
RemoteControl rc;
//Television 객체를 생성하고 인터페이스 변수에 대입
rc = new Television();
rc.turnOn();
rc.setVolume(6);
//디폴트 메소드 호출
rc.setMute(true);
rc.setMute(false);
// TV를 켭니다.
// 현재 TV 볼륨 : 6
// 무음 처리합니다.
// 현재 TV 볼륨 : 0
// 무음 해제합니다
}
}
package ch08.sec05;
public class Audio implements RemoteControl {
//필드
private int volume;
//turnOn() 추상 메소드 오버라이딩
@Override
public void turnOn() {
System.out.println("Audio를 켭니다. ");
}
//turnOff() 추상 메소드 오버라이딩
@Override
public void turnOff() {
System.out.println("Audio를 끕니다. ");
}
//setVolume() 추상 메소드 오버라이딩
@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 볼륨 : " + 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);
}
}
}
package ch08.sec05;
public class RemoteControlExample {
public static void main(String[] args) {
//인터페이스 변수 선언
RemoteControl rc;
//Television 객체를 생성하고 인터페이스 변수에 대입
rc = new Television();
rc.turnOn();
rc.setVolume(6);
//디폴트 메소드 호출
rc.setMute(true);
rc.setMute(false);
System.out.println();
//Audio 객체를 생성하고 인터페이스 변수에 대입
rc = new Audio();
rc.turnOn();
rc.setVolume(7);
//디폴트 메소드 호출
rc.setMute(true);
rc.setMute(false);
// TV를 켭니다.
// 현재 TV 볼륨 : 6
// 무음 처리합니다.
// 현재 TV 볼륨 : 0
// 무음 해제합니다
// Audio를 켭니다.
// 현재 Audio 볼륨 : 7
// 무음 처리합니다.
// 현재 Audio 볼륨 : 0
// 무음 해제합니다.
// 현재 Audio 볼륨 : 7
}
}
디폴트 메소드는 구현 객체가 필요
구현 객체를 인터페이스 변수에 대입하고, 호출 가능
인터페이스에는 정적 메소드 선언 가능
인터페이스에 선언된 정적 메소드는 구현 객체 없이 인터페이스명으로 접근해서 호출 가능
클래스 정적 메소드 선언시와 똑같이 작성하면되는데,
public 키워드 생략하면 컴파일시 자동 추가된다.
[public | private ] static 리턴타입 메소드명(매개변수, ...) {...}
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) {
if(mute) {
System.out.println("무음 처리합니다.");
setVolume(MIN_VOLUME);
} else {
System.out.println("무음 해제합니다");
}
}
//정적 메소드 ***
static void changeBattery() {
System.out.println("리모콘 건전지를 교환합니다.");
}
}
인터페이스명.정적메소드명();
package ch08.sec06;
public class RemoteControlExample {
public static void main(String[] args) {
//인터페이스 변수 선언
RemoteControl rc;
//Television 객체를 생성하고 인터페이스 변수에 대입
rc = new Television();
rc.turnOn();
rc.setVolume(6);
//디폴트 메소드 호출
rc.setMute(true);
rc.setMute(false);
System.out.println();
//Audio 객체를 생성하고 인터페이스 변수에 대입
rc = new Audio();
rc.turnOn();
rc.setVolume(7);
//디폴트 메소드 호출
rc.setMute(true);
rc.setMute(false);
System.out.println();
//정적 메소드 호출 ***
RemoteControl.changeBattery();
}
}
정적 메소드의 실행부를 작성할때 주의할점
정적 메소드는 구현객체가 필요한 인스턴스메소드이므로
상수 필드를 제외한 추상메소드, 디폴트 메소드, private 메소드 등을 호출 할수 없다.
public 접근제한을 가지는 인터페이스 메소드
- 상수 필드
- 추상 메소드
- 디폴트 메소드
- 정적 메소드
위의 멤버들은 선언시 public 생략해도 컴파일시 자동 추가된다.
인터페이스의 외부 접근을 막은 private 메소드도 선언이 가능하다.
구분 | 설명 | |
---|---|---|
private 메소드 | 구현 객체가 필요O | 디폴트 메소드 내에서만 호출 가능 |
private 정적 메소드 | 구현 객체가 필요X | 디폴트 메소드 외에 정적 메소드 안에서도 호출 가능 |
private 메소드의 용도는 디폴트와 정적 메소드들의 중복 코드를 줄이기 위함
다음 예제는 Service 인터페이스에서
2개의 디폴트메소드, 2개의 정적메소드 중 중복 코드 부분을
각각 private 메소드와 private 정적 메소드로 선언하고 호출하는 방법을 보여줌
private 메소드는 Java 9부터 사용가능하므로 유의
package ch08.sec07;
public interface Service {
//디폴트 메소드
default void defaultMethod1() {
System.out.println("디폴트 메소드1 에만 있는 코드");
defaultCommon();
}
default void defaultMethod2() {
System.out.println("디폴트 메소드2 에만 있는 코드");
defaultCommon();
}
//private 메소드 Java 9 부터 사용 가능
private void defaultCommon() {
System.out.println("디폴트메소드 중복 코드 1");
System.out.println("디폴트메소드 중복 코드 2");
}
//정적 메소드
static void staticMethod1() {
System.out.println("정적메소드1 에만 있는 코드");
staticCommon();
}
static void staticMethod2() {
System.out.println("정적메소드2 에만 있는 코드");
staticCommon();
}
//private 정적 메소드
private static void staticCommon() {
System.out.println("정적메소드 중복 코드 1");
System.out.println("정적메소드 중복 코드 2");
}
}
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 구현클래스명(...);
구현 객체가 인터페이스 A,B를 구현하고 있다면
각각의 인터페이스를 통해 구현 객체를 사용할 수 있다.
구현 객체가 어떤 인터페이스 변수에 대입되느냐에 따라 변수를 통해 호출할 수 있는 추상 메소드가 결정된다.
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 {
//turnOn() 추상 메소드 오버라이딩
public void turnOn() {
System.out.println("Tv를 켭니다");
}
//turnOff() 추상 메소드 오버라이딩
public void turnOff() {
System.out.println("Tv를 끕니다");
}
//search() 추상 메소드 오버라이딩
public void search(String url) {
System.out.println(url + "을 검색합니다.");
}
}
package ch08.sec08;
public class MuliInterfaceImplExample {
public static void main(String[] args) {
//RemoteControl 인터페이스 변수 선언 및 구현 객체 대입
RemoteControl rc = new SmartTelevision();
//RemoteControl 인터페이스 선언된 추상 메소드만 호출 가능
rc.turnOn();
rc.turnOff();
//Searchable 인터페이스 변수 선언 및 구현 객체 대입
Searchable searchable = new SmartTelevision();
//Searchable 인터페이스에 선언된 추상 메소드만 호출 가능
searchable.search("https://www.youtube.com");
// Tv를 켭니다
// Tv를 끕니다
// https://www.youtube.com을 검색합니다.
}
}
인터페이스는 클래스와 달리 다중 상속을 허용하므로
다른 인터페이스를 상속할 수 있음
public interface 자식인터페이스 extends 부모인터페이스 1, 부모인터페이스2 {...}
자식 인터페이스의 구현 클래스는 자식, 부모 인터페이스의 모든 추상 메소드를 재정의 해야한다.
그리고 구현 객체는 자식/부모 인터페이스 변수에 대입 가능
자식인터페이스 변수 = new 구현클래스();
부모인터페이스1 변수 new 구현클래스();
부모인터페이스2 변수 = new 구현클래스();
구현객체가
package ch08.sec09;
public interface Mother {
void motherMethod();
}
package ch08.sec09;
public interface Father {
void fatherMethod();
}
package ch08.sec09;
public interface Children extends Mother, Father {
void childrenMethod();
}
package ch08.sec09;
public class InterfaceImpl implements Children {
public void motherMethod() {
System.out.println("엄마인터페이스 안에 있는 메소드임");
}
public void fatherMethod() {
System.out.println("아빠인터페이스 안에 있는 메소드임");
}
public void childrenMethod() {
System.out.println("자식인터페이스 안에 있는 메소드임");
}
}
package ch08.sec09;
public class ExtendsExample {
public static void main(String[] args) {
InterfaceImpl impl = new InterfaceImpl();
Mother mother = impl;
mother.motherMethod();
// mother.fatherMethod(); //불러올 수 없음
Father father = impl;
// father.motherMethd(); //불러올 수 없음
father.fatherMethod();
Children children = impl;
children.motherMethod();
children.fatherMethod();
children.childrenMethod();
}
}
인터페이스와 구현클래스간에 타입변환 발생
타입 변환 | 타입 | 변환 조건 | 활용 범위 |
---|---|---|---|
자동 타입 변환 | 구현 클래스 타입 -> 인터페이스 타입 | 인터페이스 변수에 구현 객체 대입시 | 인터페이스 선언 메소드만 사용 가능 |
강제 타입 변환 | 인터페이스 타입 -> 구현 클래스 타입 | (캐스팅) 기호를 사용해 강제 변환 | 인터페이스 선언 메소드 외에 구현 객체의 메소드도 사용 가능 |
자동 타입 변환
자동으로 타입 변환이 일어나는것
부모 클래스가 인터페이스 구현시, 그 자식 클래스도 인터페이스 타입으로 자동 타입변환 가능
인터페이스 A,
구현 클래스 B,
구현 클래스 C,
자식 클래스 D,
자식 클래스 E
이렇게 있고, A인터페이스를 B,C클래스가 구현함
그리고 D,E클래스가 각각 B,C클래스를 상속받음
B,C,D,E로부터 생성된 객체는 모두 A를 직.간접적으로 구현하고 있으므로 A로 자동 타입 변환 가능
강제 타입변환은 캐스팅 기호 사용해서 인터페이스 타입 -> 구현 클래스 타입으로 변환
구현클래스 변수 = (구현클래스) 인터페이스변수;
구현 객체 -> 인터페이스 타입(자동변환) 되면 인터페이스 선언 메소드만 사용가능
인터페이스에 없는, 구현객체만의 메소드를 사용하고 싶다면 강제 타입 변환을 해야한다.
package ch08.sec10.exam02;
public interface Vehicle {
void run();
}
package ch08.sec10.exam02;
public class Bus implements Vehicle {
//추상 메소드 재 정의
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();
//강제 타입 변환 후 호출
Bus bus = (Bus) vehicle;
bus.run();
bus.checkFare(); //호출 가능
// 버스가 달립니다.
// 버스가 달립니다.
// 승차요금을 체크합니다.
}
}
상속의 다형성처럼, 인터페이스도 다형성을 구현하는 주된 기술로 사용된다.
현업에서는 상속보다 인터페이스로 다형성을 구현하는 경우가 많음
다형성
사용법은 동일하지만 다양한 결과가 나오는 성질
- 메소드 재정의(오버라이딩)
- 자동 타입 변환
구현 객체 B, C 둘중 어느 객체가 인터페이스에 대입되었는지에 따라 객체 A(실행클래스)의 메소드 호출 결과가 달라진다.
인터페이스의 추상 메소드는 구현 클래스마다 각각 다른 내용으로 재정의함
구현 객체는 인터페이스 타입으로 자동 타입 변환이 되고, 인터페이스 메소드 호출시 구현 객체의 재정의 된 메소드가 호출되어 다양한 실행결과를 얻게된다.
필트타입으로 Tire 인터페이스를 선언하면, 필드값으로 한국타이어/금호타이어 객체를 대입 가능
Car 클래스의 Tire타입 tire1, tire2에 어떤 타이어 구현객체가 대입되어도 Car객체는 Tire 인터페이스에 선언된 메소드만 사용하므로 문제 되지 않음
//376
상속에서 메소드 호출시 매개값을 다양화하기 위해 매개변수의 타입을 부모 타입으로 선언하고, 호출시 자식타입으로 대입하기도 했다.
이는 자동 타입변환이 되기 때문인데, 매개변수 타입을 인터페이스로 선언하면 메소드 호출시 다양한 구현 객체를 대입할 수 있다.
//380
상속에서 객체 타입을 확인하기 위해 사용된 instanceOf 연산자를 인터페이스에서도 사용가능
if(vehicle instanceOf Bus) {
//vehicle에 대입된 객체가 Bus일 경우 실행
}
메소드의 매개변수가 인터페이스 타입일 경우 매개값으로 해당 인터페이스를 구현하는 모든 객체가 될수있다.
매개값이 특정 구현객체라면 인터페이스 타입으로 강제 타입변환을 하면되는데, 이때 instanceOf 연산자로 매개값의 타입을 검사해야한다.
public void method(Vehicle vehicle) {
if(vehicle instanceOf Bus) { //vehicle에 대입된 객체가 Bus일 경우에만 Bus로 강제 타입변환시킴
Bus bus = (Bus) vehicle;
}
//Java 12부터 instanceOf 연산의 결과가 true일 경우 우측 타입 변수 사용가능해서 강제 타입변환 필요없음
if(vehicle instanceOf Bus bs) { //vehicle에 대입된 객체가 Bus일 경우에만 Bus로 강제 타입변환시킴
//bs 변수 사용
}
}
//383
Java 15부터 무분별한 자식 인터페이스 생성을 방지하기 위해 봉인된(sealed) 인터페이스를 사용할 수 있음
InterfaceA의 자식 인터페이스는 interfaceB만 가능하고, 그 외에는 자식 인터페이스가 될수 없도록 InterfaceA를 봉인된 인터페이스로 선언
public sealed interface Interface permits InterfaceB {...}
sealed 키워드 사용시 permits 키워드 뒤에 상속 가능한 자식 인터페이스를 지정해야함
봉인된 InterfaceA를 상속하는 InterfaceB는 non-sealed 키워드로 선언하거나, sealed 키워드를 사용해서 또 다른 봉인 인터페이스로 선언해야함
public non-sealed interface InterfaceB extends InterfaceA {...}
출처: 이것이 자바다 (개정판) : JAVA 프로그래밍의 기본서 - 신용권, 임경균 저