출처 : 이것이 자바다
인터페이스
8.1 인터페이스의 역할
v 인터페이스란?
개발 코드와 객체가 서로 통신하는 접점
• 개발 코드는 인터페이스의 메소드만 알고 있으면 OK
- 인터페이스의 역할
• 개발 코드가 객체에 종속되지 않게 -> 객체 교체할 수 있도록 하는 역할
• 개발 코드 변경 없이 리턴값 또는 실행 내용이 다양해 질 수 있음 (다형성)
8.2 인터페이스 선언
- 인터페이스 이름 - 자바 식별자 작성 규칙에 따라 작성
- 소스 파일 생성
• 인터페이스 이름과 대소문자가 동일한 소스 파일 생성
- 인터페이스 선언
8.2.1인터페이스 선언
인터페이스의 구성 멤버
상수필드(Constant Field)
인터페이스는 객체 사용 설명서이므로 런타임 시 데이터를 저장할 수 있는 필드를 선언할 수 없다.
그러나 상수 필드는 선언이 가능하다.
상수는 인터페이스에 고정된 값으로 런타임 시에 데이터를 바꿀 수 없다.
상수를 선언할 때에는 반드시 초기값을 대입
추상 메소드(Abstract Method)
추상 메소드는 객체가 가지고 있는 메소드를 설명한 것
매개값이 필요하고, 리턴 타입이 무엇인지만 알려준다.
실제 실행부는 객체(구현 객체)가 가지고 있다.
디폴트 메소드(Default Method)
디폴트 메소드는 인터페이스에 선언되지만 사실은 객체가 가지고 있는 인스턴스 메소드라고 생각
디폴트 메소드를 허용하는 이유는 기존 인터페이스를 확장 새로운 기능 추가
정적 메소드(Static Method)
자바8부터 작성, 디폴트 메소드와 달리 객체가 없어도 인터페이스만으로 호출이 가능
8.2.2 상수 필드 선언
• 인터페이스는 상수 필드만 선언 가능
데이터 저장하지 않음
• 인터페이스에 선언된 필드는 모두 public static final
자동적으로 컴파일 과정에서 붙음
• 상수명은 대문자로 작성
서로 다른 단어로 구성되어 있을 경우에는 언더 바(_)로 연결
• 선언과 동시에 초기값 지정
static { } 블록 작성 불가 - static {} 으로 초기화 불가
상수 필드 선언
public interface RemoteControl {
public int MAX_VOLUME = 10;
public int MIN_VOLUME = 0;
}
8.2.3추상 메소드 선언
인터페이스 통해 호출된 메소드는 최종적으로 객체에서 실행
• 인터페이스의 메소드는 기본적으로 실행 블록이 없는 추상 메소드로 선언
• public abstract를 생략하더라도 자동적으로 컴파일 과정에서 붙게 됨
추상메소드 (메소드 선언부) (호출 방법만 기술) |
재정의된 메소드 (실제 실행 메소드) |
메소드 선언
public interface RemoteControl {
public int MAX_VOLUME = 10;
public int MIN_VOLUME = 0;
//추상 메소드
public void turnOn(); //메소드 선언부만 작성 (추상 메소드)
public void turnOff();
public void setVoulume(int volume);
}
8.2.4디폴트 메소드 선언
• 자바8에서 추가된 인터페이스의 새로운 멤버
• 실행 블록을 가지고 있는 메소드
• default 키워드를 반드시 붙여야
• 기본적으로 public 접근 제한
생략하더라도 컴파일 과정에서 자동 붙음
메소드 선언
public interface RemoteControl {
public int MAX_VOLUME = 10;
public int MIN_VOLUME = 0;
//추상 메소드
public void turnOn(); //메소드 선언부만 작성 (추상 메소드)
public void turnOff();
public void setVoulume(int volume);
//디폴트 메소드
default void setMute(boolean mute) { //실행 내용까지 작성
if(mute) {
System.out.println("무음 처리합니다.");
} else {
System.out.println("무음 해제합니다.");
}
}
}
8.2.5 정적 메소드 선언
• 자바8에서 추가된 인터페이스의 새로운 멤버
메소드 선언
public interface RemoteControl {
public int MAX_VOLUME = 10;
public int MIN_VOLUME = 0;
//추상 메소드
public void turnOn(); //메소드 선언부만 작성 (추상 메소드)
public void turnOff();
public void setVoulume(int volume);
//디폴트 메소드
default void setMute(boolean mute) { //실행 내용까지 작성
if(mute) {
System.out.println("무음 처리합니다.");
} else {
System.out.println("무음 해제합니다.");
}
}
//정적 메소드
static void changeBattery() {
System.out.println("건전지를 교환합니다.");
}
}
8.3 인터페이스 구현
v 구현 객체와 구현 클래스
인터페이스의 추상 메소드 대한 실체 메소드를 가진 객체 = 구현 객체
구현 객체 |
- 구현 객체를 생성하는 클래스 = 구현 클래스
8.3.1 구현 클래스 선언
- 자신의 객체가 인터페이스 타입으로 사용할 수 있음
• implements 키워드로 명시
v 추상 메소드의 실체 메소드를 작성하는 방법
메소드의 선언부가 정확히 일치해야
인터페이스의 모든 추상 메소드를 재정의하는 실체 메소드 작성해야
• 일부만 재정의할 경우, 추상 클래스로 선언 + abstract 키워드 붙임
구현 클래스
public class Television implements RemoteControl{
//필드
private int volume;
@Override
public void turnOn() {
System.out.println("TV를 켭니다.");
}
@Override
public void turnOff() {
System.out.println("TV를 끕니다.");
}
@Override
public void setVoulume(int volume) { //인터페이스 상수를 이용해서 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);
}
}
구현 클래스
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 setVoulume(int volume) { //인터페이스 상수를 이용해서 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 class RemoteControlExample {
public static void main(String[] args) {
RemoteControl rc;
rc = new Television();
rc = new Audio();
}
}
8.3.2 익명 구현 객체
- 명시적인 구현 클래스 작성 생략하고 바로 구현 객체를 얻는 방법
• 이름 없는 구현 클래스 선언과 동시에 객체 생성
• 인터페이스의 추상 메소드들을 모두 재정의하는 실체 메소드가 있어야
• 추가적으로 필드와 메소드 선언 가능하나 익명 객체 안에서만 사용
• 인터페이스 변수로 접근 불가
익명 구현 클래스
public class RemoteControlExample {
public static void main(String[] args) {
//RemoteControl rc;
//rc = new Television();
//rc = new Audio();
RemoteControl rc = new RemoteControl() {
@Override
public void turnOn() {
// TODO Auto-generated method stub
}
@Override
public void turnOff() {
// TODO Auto-generated method stub
}
@Override
public void setVoulume(int volume) {
// TODO Auto-generated method stub
}
};
}
}
8.3.3다중 인터페이스 구현 클래스
인터페이스
public interface Searchable {
void search(String url);
}
다중 인터페이스 구현 클래스
public class SmartTelevision implements RemoteControl,Searchable{
private int volume;
@Override
public void turnOn() {
System.out.println("TV를 켭니다.");
}
@Override
public void turnOff() {
System.out.println("TV를 끕니다.");
}
@Override
public void setVoulume(int volume) { //인터페이스 상수를 이용해서 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);
}
@Override
public void search(String url) {
System.out.println(url + "을 검색합니다.");
}
}
8.4 인터페이스 사용
인터페이스에 구현 객체를 대입하는 방법
public class MyClass {
//필드
RemoteControl rc = new Television();
//생성자
MyClass(RemoteControl rc){
this.rc = rc;
}
//메소드
void methodA() {
//로컬 변수
RemoteControl rc = new Audio();
}
void methodB(RemoteControl rc ) {}
}
8.4.1 추상 메소드 사용
인터페이스 사용
public class RemoteControlExample {
public static void main(String[] args) {
RemoteControl rc = null; //인터 페이스 변수 선언
rc = new Television(); //Television 객체를 인터페이스 타입에 대입
rc.turnOn();
rc.setMute(true);
rc = new Audio(); //Audio 객체를 인터페이스 타입에 대입
rc.turnOn();
rc.setMute(true);
}
}
8.4.2 디폴트 메소드 사용
- 인터페이스만으로는 사용 불가
• 구현 객체가 인터페이스에 대입되어야 호출할 수 있는 인스턴스 메소드
- 모든 구현 객체가 가지고 있는 기본 메소드로 사용
• 필요에 따라 구현 클래스가 디폴트 메소드 재정의해 사용
구현 클래스
public class Audio implements RemoteControl{
//필드
private int volume;
private boolean mute;
//turnOn() 추상 메소드의 실체 메소드
@Override
public void turnOn() {
System.out.println("Audio를 켭니다.");
}
//turnOff() 추상 메소드의 실체 메소드
@Override
public void turnOff() {
System.out.println("Audio를 끕니다.");
}
//setVolume() 추상 메소드의 실체 메소드
@Override
public void setVoulume(int volume) { //인터페이스 상수를 이용해서 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);
}
//디폴트 메소드 재정의
@Override
public void setMute(boolean mute) {
this.mute = mute;
if(mute) {
System.out.println("Audio 무음 처리합니다.");
} else {
System.out.println("Audio 무음 해제합니다.");
}
}
}
디폴트 메소드 사용
public class RemoteControlExample {
public static void main(String[] args) {
RemoteControl rc = null; //인터 페이스 변수 선언
rc = new Television(); //Television 객체를 인터페이스 타입에 대입
rc.turnOn();
rc.setMute(true);
rc = new Audio(); //Audio 객체를 인터페이스 타입에 대입
rc.turnOn();
rc.setMute(true);
}
}
8.4.3 정적 메소드 사용
인터페이스로 바로 호출 가능
정적 메소드 사용
public class RemoteControlExample {
public static void main(String[] args) {
RemoteControl.changeBattery();
}
}
8.5 타입 변환과 다형성
v 다형성 (p.362~364)
하나의 타입에 여러 가지 객체 대입해 다양한 실행 결과를 얻는 것
다형성을 구현하는 기술
• 상속 또는 인터페이스의 자동 타입 변환(Promotion)
• 오버라이딩(Overriding)
다형성의 효과
• 다양한 실행 결과를 얻을 수 있음
• 객체를 부품화시킬 수 있어 유지보수 용이 (메소드의 매개변수로 사용)
8.5.1 자동 타입 변환(Promotion)
8.5.2 필드의 다형성
인터페이스
public interface Tire {
public void roll(); //roll() 메소드 호출 방법 설명
}
구현 클래스
public class HankookTire implements Tire{
@Override
public void roll() {
System.out.println("한국 타이어가 굴러갑니다.");
}
}
구현 클래스
public class KumhoTire implements Tire{
@Override
public void roll() {
System.out.println("금호 타이어가 굴러갑니다.");
}
}
필드 다형성
public class Car {//인터페이스 타입 필드 선언과 초기 구현 객체 대입
Tire frontLeftTire = new HankookTire();
Tire frontRightTire = new HankookTire();
Tire backLeftTire = new HankookTire();
Tire backRightTire = new HankookTire();
void run() { //인터페이스에서 설명된 roll() 메소드 호출
frontLeftTire.roll();
frontRightTire.roll();
backLeftTire.roll();
backRightTire.roll();
}
}
필드 다형성 테스트
public class CarExample {
public static void main(String[] args) {
Car myCar = new Car();
myCar.run();
myCar.frontLeftTire = new KumhoTire();
myCar.frontRightTire = new KumhoTire();
myCar.run();
}
}
8.5.3 인터페이스 배열로 구현한 객체 관리
필드 다형성
package textbook.chapter8.exam01;
public class Car {
Tire[] tires = {
new HankookTire(),
new HankookTire(),
new HankookTire(),
new HankookTire()
};
void run() {
for (Tire tire : tires) {
tire.roll();
}
}
}
필드 사형성 테스트
package textbook.chapter8.exam01;
public class CarExample {
public static void main(String[] args) {
Car myCar = new Car();
myCar.run();
myCar.tires[0] = new KumhoTire();
myCar.tires[1] = new KumhoTire();
myCar.run();
}
}
8.5.4매개변수의 다형성
- 매개 변수의 타입이 인터페이스인 경우
• 어떠한 구현 객체도 매개값으로 사용 가능
• 구현 객체에 따라 메소드 실행결과 달라짐
자동 타입 변환은 필드의 값을 대입할 때도 발생하지만, 주로 메소드를 호출할 때 많이 발생한다.
매개 변수의 인터페이스화
public class Driver {
public void drive(Vehicle vehicle) {
vehicle.run();
}
}
인터페이스
public interface Vehicle {
public void run();//interface는 메소드만 작성한다.
}
Vehicle이 interface이다.
구현 클래스
public class Bus implements Vehicle{
@Override
public void run() {
System.out.println("버스가 달립니다.");
}
}
구현 클래스
public class Taxi implements Vehicle {
@Override
public void run() {
System.out.println("택시가 달립니다.");
}
}
매개 변수의 다형성 테스트
public class DriverExample {
public static void main(String[] args) {
Driver driver = new Driver();
Bus bus = new Bus();
Taxi taxi = new Taxi();
driver.drive(bus); //자동 타입 변환 Vehicle vehicle =bus;
driver.drive(taxi);//자동 타입 변환 Vehicle vehicle =taxi;
}
}
8.5.5 강제 타입 변환(Casting)
인터페이스 타입으로 자동 타입 변환 후, 구현 클래스 타입으로 변환
• 필요성: 구현 클래스 타입에 선언된 다른 멤버 사용하기 위해
구현클래스 변수 = (구현클래스)인터페이스변수;
인터페이스
public interface Vehicle {
public void run();//interface는 메소드만 작성한다.
}
구현 클래스
public class Bus implements Vehicle{
@Override
public void run() {
System.out.println("버스가 달립니다.");
}
public void checkFare() {
System.out.println("승차요금을 체크합니다.");
}
}
강제 타입 변환
public class DriverExample {
public static void main(String[] args) {
Vehicle vehicle = new Bus();
vehicle.run();
//vehicle.checkFare(); //vehicle interface에는 checkFare이 없습니다.
Bus bus = (Bus)vehicle;//강제 타입 변환
bus.run();
bus.checkFare();
}
}
8.5.6 객체 타입 확인(instanceof 연산자) (p.375~377)
강제 타입 변환 전 구현 클래스 타입 조사
객체 타입 확인
public class Driver {
public void drive(Vehicle vehicle) {
if(vehicle instanceof Bus) {
Bus bus=(Bus)vehicle;
bus.checkFare();//Bus타입으로 강제 타입 변환을 하는 이유
}
vehicle.run();
}
}
타입인지 확인 하는 것은 instanceof이다. 이것으로 확인하고 처리하는게 좋다.
8.6 인터페이스 상속
v 인터페이스간 상속 가능
- 하위 인터페이스 구현 클래스는 아래 추상 메소드를 모두 재정의해야
• 하위 인터페이스의 추상 메소드
• 상위 인터페이스1의 추상 메소드
• 상위 인터페이스2의 추상 메소드
- 인터페이스 자동 타입 변환
• 해당 타입의 인터페이스에 선언된 메소드만 호출 가능
부모 인터페이스
public interface InterfaceA {
public void methodA();
}
부모 인터페이스
public interface InterfaceB {
public void methodB();
}
하위 인터페이스
public interface InterfaceC extends InterfaceA,InterfaceB{
public void methodC();
}
하위 인터페이스 구현
public class ImplementationC implements InterfaceC{
@Override
public void methodA() {
System.out.println("methodA() 실행");
}
@Override
public void methodB() {
System.out.println("methodB() 실행");
}
@Override
public void methodC() {
System.out.println("methodC() 실행");
}
}
호출 가능 메소드
public class Example {
public static void main(String[] args) {
ImplementationC impl = new ImplementationC();
InterfaceA ia = impl;
ia.methodA();
System.out.println(); //InterfaceA 변수는 methodA()만 호출 가능
InterfaceB ib = impl;
ib.methodB();
System.out.println(); //InterfaceB 변수는 methodB()만 호출 가능
InterfaceC ic = impl;
ic.methodA();
ic.methodB(); //InterfaceC 변수는 methodA(),methodB(),methodC() 모두 호출 가능
ic.methodC();
}
}
8.7 디폴트 메소드와 인터페이스 확장
8.7.1 디폴트 메소드의 필요성
v 디폴트 메소드와 확장 메소드 사용하기 (p.379~382)
interface에 새로운 기능을 추가할 경우 원래의 구현한 메소드에서 오류가 난다. 그래서 이것을 해결하기 위하여 새로운 interface에 디폴트 메소드를 추가한다.
기존 인터페이스
public interface MyInterface {
public void method1();
public default void method2() {
System.out.println("MyInterface-method2 실행"); //디폴트 메소드
}
}
기존 구현 클래스
public class MyClassA implements MyInterface{
@Override
public void method1() {
System.out.println("MyCalssA-method1() 실행");
}
}
새로운 구현 클래스
public class MyClassB implements MyInterface{
@Override
public void method1() {
System.out.println("MyCalssA-method1() 실행");
}
@Override
public void method2() {
System.out.println("MyClassB-method2() 실행"); //디폴트 메소드 재정의
}
}
디폴트 메소드 사용
public class DefaultMethodExampl {
public static void main(String[] args) {
MyInterface mi1 = new MyClassA();
mi1.method1();
mi1.method2();
MyInterface mi2 = new MyClassB();
mi2.method1();
mi2.method2();
}
}
8.7.2 디폴트 메소드가 있는 인터페이스 상속
- 부모 인터페이스의 디폴트 메소드를 자식 인터페이스에서 활용 방법
• 디폴트 메소드를 단순히 상속만 받음
• 디폴트 메소드를 재정의(Override)해서 실행 내용을 변경
• 디폴트 메소드를 추상 메소드로 재선언
부모 인터페이스
public interface ParentInterface {
public void method1();
public default void method2() {/*실행문*/}
}
자식 인터페이스
public interface ChildInterface1 extends ParentInterface{
public void method3();
}
자식 인터페이스
public interface ChildInterface2 extends ParentInterface{
@Override
public default void method2() {/*실행문*/};//재정의
public void method3();
}
자식 인터페이스
public interface ChildInterface3 extends ParentInterface{
@Override
public void method2();//추상 메소드로 재선언
public void method3();
}
사용법
public class ChildExample {
public static void main(String[] args) {
ChildInterface1 ci1 = new ChildInterface1(){
@Override
public void method1() {/*실행문*/}
@Override
public void method3() {/*실행문*/}
};
ci1.method1();
ci1.method2();//ParentInterface의 method2()호출
ci1.method3();
ChildInterface2 ci2 = new ChildInterface2(){
@Override
public void method1() {/*실행문*/}//재정의
@Override
public void method3() {/*실행문*/}
};
ci2.method1();
ci2.method2();//ChildInterface2의 method2()호출
ci2.method3();
ChildInterface3 ci3 = new ChildInterface3(){
@Override
public void method1() {/*실행문*/}
@Override
public void method2() {/*실행문*/}
@Override
public void method3() {/*실행문*/}
};
ci3.method1();
ci3.method2();//ChildInterface3의 구현 객체의 method2()호출
ci3.method3();
}
}
연습문제
1. 인터페이스에 대한 설명으로 틀린 것은 무엇입니까? (3)
1. 인터페이스는 객체 사용 설명서 역할을 한다.
2. 구현 클래스가 인터페이스의 추상 메소드에 대한 실체 메소드를 가지고 있지 않으면 추상 클래스가 된다.
3. 인터페이스는 인스턴스 필드를 가질 수 있다.
4. 구현 객체는 인터페이스 타입으로 자동 변환된다.
2. 인터페이스의 다형성과 거리가 먼 것은? (4)
1. 필드가 인터페이스 타입일 경우 다양한 구현 객체를 대입할 수 있다.
2. 매개 변수가 인터페이스 타입일 경우 다양한 구현 객체를 대입할 수 있다.
3. 배열이 인터페이스 타입일 경우 다양한 구현 객체를 저장할 수 있다.
4. 구현 객체를 인터페이스 타입으로 변환하려면 강제 타입 변환을 해야 한다.
3. 다음은 Soundable 인터페이스입니다. sound() 추상 메소드는 객체의 소리를 리턴합니다.
package sec7;
public interface Soundable {
String sound();
}
package sec7;
public class SoundableExample {
private static void printSound(Soundable soundable) {
System.out.println(soundable.sound());
}
public static void main(String[] args) {
printSound(new Cat());
printSound(new Dog());
}
}
package sec7;
public class Cat implements Soundable{
@Override
public String sound() {
return "야옹";
}
}
package sec7;
public class Dog implements Soundable{
@Override
public String sound() {
return "멍멍";
}
}
야옹
멍멍
4. DaoExample 클래스의 main() 메소드에서 dbWork() 메소드를 호출할 때 OracleDao 와 MySqlDao 객체를 매개값으로 주고 호출했습니다. dbWork() 메소드는 두 객체를 모두 매개값으로 받기 위해 DateAccessObject 타입의 매개 변수를 가지고 있습니다. 실행 결과를 보고 DataAccessObject 인터페이스와 OracleDao, MySqlDao 구현 클래스를 각각 작성해보세요.
package sec7;
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());
}
}
package sec7;
public interface DataAccessObject {
public void select();
public void insert();
public void update();
public void delete();
}
package sec7;
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에서 삭제");
}
}
Oracle DB에서 검색
Oracle DB에서 삽입
Oracle DB에서 수정
Oracle DB에서 삭제
MySql DB에서 검색
MySql DB에서 삽입
MySql DB에서 수정
MySql DB에서 삭제
5. 다음은 Action 인터페이스입니다. work() 추상 메소드는 객체의 작업을 시작시킵니다.
ActionExample 클래스의 main() 메소드에서 Action의 익명 구현 객체를 만들어 다음과 같은 실행 결과가 나올 수 있도록 박스 안에 들어갈 코드를 작성해보세요.
package sec8;
public class ActionExample {
public static void main(String[] args) {
Action action = new Action() {
@Override
public void work() {
System.out.println("복사를 합니다.");
}
};
action.work();
}
}
package sec8;
public interface Action {
void work();
}
복사를 합니다.