728x90
반응형
인터페이스 Interface
- 어떤 객체와 개발자 사이의 접점(중개) 역할
- 인터페이스 정의 시 class 키워드 대신 interface 키워드를 사용
- 인터페이스는 상수와 추상메서드만 가질 수 있음
- 상수 : public static final 을 사용하여 선언하며 생략도 가능
- 추상메서드 : public abstract 를 사용하여 정의하며 생략도 가능
- 추상클래스와 마찬가지로 객체 생성이 불가능하며, 상속 전용으로 사용 단, 데이터타입으로는 사용 가능하므로, 다형성 활용 가능
- 동일한 인터페이스를 구현하는 클래스를 사용하는 경우 하나의 인터페이스 타입으로 모든 객체를 컨트롤할 수 있다
- 추상클래스보다 강제성이 더 강하다
추상클래스는 일부 멤버(메서드)에 대한 강제성을 부여하지만 인터페이스는 모든 추상메서드와 상수에 대한 강제성을 부여
인터페이스 정의 기본 문법
[접근제한자] interface 인터페이스명 {
// 상수 선언
// 추상메서드 정의
}
// 인터페이스 정의
public interface RemoteControl {
// 인터페이스 내의 모든 변수는 상수(public static final) 로 취급됨
public static final int MAX_VOLUME = 100; // 상수
public static final int MIN_VOLUME = 0; // 상수
int MAX_CHANNEL = 100; // 상수(public static final 생략되어 있음)
public int MIN_CHANNEL = 1; // 상수(static final 생략되어 있음)
// 인터페이스 내의 모든 메서드는 추상메서드(public abstract) 로 취급됨
// public void turnPower() {} // 중괄호{} 를 포함할 수 없다!
public void turnPower(); // 추상메서드(abstract 생략되어 있음)
public abstract void channelUp(); // 추상메서드
public abstract void channelDown(); // 추상메서드
public abstract void changeChannel(int channel); // 추상메서드
void volumeUp(); // 추상메서드(public abstract 생략되어 있음)
void volumeDown(); // 추상메서드(public abstract 생략되어 있음)
}
// interfaces 패키지의 RemoteControl 인터페이스를 구현하는
// 구현체 클래스 Tv 를 정의
// => 클래스명 뒤에 implements 키워드를 쓰고 뒤에 인터페이스명을 지정
class Tv implements RemoteControl {
boolean isPowerOn = false;
// RemoteControl 인터페이스를 구현하기 위해 implements 할 경우
// 인터페이스 내의 모든 추상메서드를 구현(오버라이딩)해야한다!
@Override
public void turnPower() {
System.out.println("Tv 전원 상태 변경!");
}
@Override
public void channelUp() {
System.out.println("Tv 채널 증가!");
}
@Override
public void channelDown() {
System.out.println("Tv 채널 감소!");
}
@Override
public void changeChannel(int channel) {
System.out.println("Tv 채널 변경 - " + channel + "번!");
}
@Override
public void volumeUp() {
System.out.println("Tv 볼륨 증가!");
}
@Override
public void volumeDown() {
System.out.println("Tv 볼륨 감소!");
}
public void tvStatus() {
System.out.println("전원 상태 : " + isPowerOn);
System.out.println("최소 채널 : " + MIN_CHANNEL);
System.out.println("최대 채널 : " + MAX_CHANNEL);
System.out.println("최소 볼륨 : " + MIN_VOLUME);
System.out.println("최대 볼륨 : " + MAX_VOLUME);
}
}
class Audio implements RemoteControl {
// RemoteControl 인터페이스를 구현하기 위해 implements 할 경우
// 인터페이스 내의 모든 추상메서드를 구현(오버라이딩)해야한다!
@Override
public void turnPower() {
System.out.println("Audio 전원 상태 변경!");
}
@Override
public void channelUp() {
System.out.println("Audio 채널 증가!");
}
@Override
public void channelDown() {
System.out.println("Audio 채널 감소!");
}
@Override
public void changeChannel(int channel) {
System.out.println("Audio 채널 변경 - " + channel + "번!");
}
@Override
public void volumeUp() {
System.out.println("Audio 볼륨 증가!");
}
@Override
public void volumeDown() {
System.out.println("Audio 볼륨 감소!");
}
}
import interfaces.RemoteControl;
public class Ex4 {
public static void main(String[] args) {
Tv tv = new Tv();
tv.turnPower();
tv.tvStatus();
Audio audio = new Audio();
audio.turnPower();
System.out.println("==================");
// 인터페이스를 구현한 클래스가 존재하는 경우
// 일반적으로 인터페이스 타입으로 업캐스팅 후에
// 공통된 기능을 다형성으로 다루는 것이 보편적
// => Tv & Audio 의 부모 인터페이스인 RemoteControl 타입 사용
// 인터페이스는 인스턴스 생성(new) 불가능! = 추상클래스와 동일
// RemoteControl remote = new RemoteControl();
// Tv -> RemoteControl 업캐스팅
RemoteControl remote = new Tv(); // 업캐스팅 가능
remote.turnPower();
remote.channelUp();
remote.channelDown();
remote.volumeUp();
remote.volumeDown();
// Audio -> RemoteControl 업캐스팅
remote = new Audio(); // 업캐스팅 가능
remote.turnPower();
remote.channelUp();
remote.channelDown();
remote.volumeUp();
remote.volumeDown();
}
}
다중 구현 implements
하나의 서브클래스가 둘 이상의 부모 인터페이스를 가질 수 있다.
여러 부모인터페이스의 모든 추상메서드를 오버라이딩
-
인터페이스끼리도 상속이 가능
인터페이스간의 상속은 implements 가 아닌 extends 를 사용 추상메서드를 구현하는 것이 목적이 아니기 때문
interface MyInterface1 {
public static final int NUM1 = 10; // 상수
public abstract void method1(); // 추상메서드
}
interface MyInterface2 {
public static final int NUM2 = 20; // 상수
public abstract void method2(); // 추상메서드
}
// MyInterface1, MyInterface2 인터페이스를 구현하는 서브클래스 SubClass 정의
class SubClass implements MyInterface1, MyInterface2 {
// 부모 인터페이스로부터 상속받은 추상메서드 구현(오버라이딩) 필수
@Override
public void method1() {
System.out.println("서브클래스에서 구현한 추상메서드 method1()");
}
@Override
public void method2() {
System.out.println("서브클래스에서 구현한 추상메서드 method2()");
}
}
SubClass sc = new SubClass();
sc.method1(); // MyInterface1 의 추상메서드
sc.method2(); // MyInterface2 의 추상메서드
// 인터페이스도 instanceof 연산자의 판별 대상이 될 수 있다!
// sc 는 MyInterface1 입니까? true
if(sc instanceof MyInterface1) {
System.out.println("sc 는 MyInterface1 입니다!");
} else {
System.out.println("sc 는 MyInterface1 이 아닙니다!");
}
// sc 는 MyInterface2 입니까? true
if(sc instanceof MyInterface2) {
System.out.println("sc 는 MyInterface2 입니다!");
} else {
System.out.println("sc 는 MyInterface2 가 아닙니다!");
}
// sc -> MyInterface1 타입으로 업캐스팅
MyInterface1 mi1 = sc;
mi1.method1(); // MyInterface1 이 가진 추상메서드
// System.out.println(SubClass.NUM1); // MyInterface1 이 가진 상수
System.out.println(MyInterface1.NUM1); // MyInterface1 이 가진 상수
// sc -> MyInterface2 타입으로 업캐스팅
MyInterface2 mi2 = sc;
mi2.method2(); // MyInterface1 이 가진 추상메서드
System.out.println(SubClass.NUM2); // MyInterface1 이 가진 상수
인터페이스 간의 상속과 클래스
// 2개의 부모인터페이스를 정의하고 하나의 인터페이스에서 모두 상속
interface ParentInterface1 {
public abstract void parentMethod1();
}
interface ParentInterface2 {
public abstract void parentMethod2();
}
// ChildInterface 인터페이스 정의 - ParentInterface1, ParentInterface2 상속
// 주의1. 인터페이스간의 상속은 extends 키워드 사용
// 주의2. 부모인터페이스를 상속받은 자식인터페이스에서 추상메서드 구현 없음
// => 인터페이스 내에는 무조건 추상메서드만 존재해야하므로
interface ChildInterface extends ParentInterface1, ParentInterface2 {
// 2개의 부모인터페이스(ParentInterface1, ParentInterface2)를 상속받으면
// 부모인터페이스의 추상메서드와 자신의 추상메서드를 모두 갖게됨
// 즉, parentMethod1(), parentMethod2() 추상메서드와
public abstract void childMethod3(); // 자신의 추상메서드를 갖는다
}
// SubClass2 클래스 정의 - ChildInterface 인터페이스 구현
class SubClass2 implements ChildInterface {
@Override
public void parentMethod1() {
System.out.println("서브클래스에서 구현한 parentMethod1()");
}
@Override
public void parentMethod2() {
System.out.println("서브클래스에서 구현한 parentMethod2()");
}
@Override
public void childMethod3() {
System.out.println("서브클래스에서 구현한 childMethod3()");
}
}
// =============================================================
class ParentClass {
public void normalMethod() {
System.out.println("ParentClass 의 normalMethod()");
}
}
// 하나의 서브클래스는 하나의 슈퍼클래스와 1개 이상의 인터페이스를
// 부모로 가질 수 있다.
// => 주의! extends 와 implements 동시 사용 시 extends 를 먼저 선언해야함
class SubClass3 extends ParentClass implements ChildInterface {
@Override
public void parentMethod1() {
// TODO Auto-generated method stub
}
@Override
public void parentMethod2() {
// TODO Auto-generated method stub
}
@Override
public void childMethod3() {
// TODO Auto-generated method stub
}
}
// =========================================================
//abstract class 동물 {
// public abstract void 번식();
//}
//
//class 고래 extends 동물 {
// @Override
// public void 번식() {
// System.out.println("새끼를 낳아 번식!");
// }
//}
//
//class 상어 extends 동물 {
// @Override
// public void 번식() {
// System.out.println("알을 낳아 번식!");
// }
//}
// 만약, 클래스간의 다중 상속이 가능했다면 발생할 수 있는 문제
//class 고래상어 extends 고래, 상어 { // 실제로는 불가능하므로 오류 발생
// // 만약, 고래상어 클래스 내에서 번식() 메서드를 호출하면
// // 고래의 번식인가? 상어의 번식인가?
// // => 다이아몬드 상속 관계에서의 문제(이슈)
// // 하나의 부모로부터 두 자식이 상속을 받고
// // 다시 하나의 손자가 두 자식으로부터 상속을 받을 경우
// // 부모로부터 상속된 메서드를 두 자식이 구현하고
// // 손자가 해당 메서드에 접근하려 할 때
// // 두 자식 중 누구의 메서드인지 구분할 수 없게 된다!
// // => 따라서, 자바에서 클래스간의 다중 상속은 불가능
// public void 번식() {
// super.번식(); // 고래.번식()? 상어.번식()? = 구별 불가능
// }
//
//}
// 인터페이스는 다중 상속이 가능한 이유
interface 동물 {
public abstract void 번식();
}
interface 고래 extends 동물 {
// 인터페이스를 상속받은 인터페이스에서는 구현의 강제가 발생하지 않음!
// => 구현하고 싶어도 구현이 불가능
}
interface 상어 extends 동물 {}
class 고래상어 implements 고래, 상어 {
// 하나의 부모인터페이스로부터 두 자식인터페이스가 상속을 받고
// 다시 하나의 손자클래스가 두 자식인터페이스로부터 상속을 받을 경우
@Override
public void 번식() {
// 고래.번식()? 상어.번식()? 구분할 필요가 없음!!!
// 동일한 추상메서드이므로 구분 필요없이 바디{} 만 구현하면 된다!
System.out.println("알을 낳아 번식!");
}
}
고래상어 고래상어 = new 고래상어();
고래상어.번식();
동물 동물 = 고래상어;
동물.번식();
인터페이스의 필요성(장점)구현의 강제로 표준화
1. 구현의 강제로 표준화
- 추상메서드를 갖는 인터페이스를 서브클래스에서 상속받으면 반드시 추상메서드를 구현해야함 따라서, 개발자가 실수로 구현을 빠뜨릴 위험이 없다.
2. 모듈 교체가 쉬움
- 인터페이스를 통해 업캐스팅을 사용하여 다형성을 적용시키면 단순한 모듈(객체) 교체만으로 동일한 코드를 사용하여 여러 객체를 다룰 수 있게 됨
- 또한, 새로운 모듈이 추가되더라도 기존 인터페이스 구현체라면 별도의 코드 수정 없이 객체의 교체만으로 그대로 사용 가능
interface Printer {
public void print(String fileName);
}
class LaserPrinter implements Printer {
@Override
public void print(String fileName) {
System.out.println("LaserPrinter 로 출력중 - " + fileName);
}
}
class DotPrinter implements Printer {
@Override
public void print(String fileName) {
System.out.println("DotPrinter 로 출력중 - " + fileName);
}
}
class PrintClient {
private Printer printer; // 인터페이스 타입 참조변수 선언
// 외부로부터 Printer 타입 인스턴스를 전달받아 초기화하는 Setter 정의
public void setPrinter(Printer printer) {
// Printer 타입으로 전달받을 수 있는 인스턴스는
// LaserPrinter, DotPrinter 등 Printer 인터페이스를 구현한 구현체
this.printer = printer;
}
public void print(String fileName) {
// Printer 타입 객체 내의 print() 메서드를 호출하여
// 전달받은 fileName 에 내용 출력하도록 요청
printer.print(fileName);
}
public void printSetting() {
System.out.println("프린터기 셋팅");
}
}
LaserPrinter lp = new LaserPrinter();
lp.print("Ex.java");
DotPrinter dp = new DotPrinter();
lp.print("Ex.java");
// ------------------------------
// 일반적인 다형성 활용
// 부모 인터페이스 타입으로 업캐스팅하여 사용
// => 인터페이스 내에 존재하는 상수와 추상메서드에만 접근 가능
Printer p = new LaserPrinter();
p.print("Ex.java");
p = new DotPrinter();
p.print("Ex.java");
// ------------------------------
// PrintClient 인스턴스 생성
// 별도의 클래스를 정의하여 부모 인터페이스 타입 객체를 전달받아 사용
// => 인터페이스의 멤버 외에 별도의 클래스에서 정의한 멤버도 사용 가능
PrintClient pc = new PrintClient();
pc.setPrinter(new LaserPrinter());
pc.print("Ex.java");
pc.printSetting();
pc.setPrinter(new DotPrinter());
pc.print("Ex.java");
pc.printSetting();
// 만약, InkJetPrinter 가 추가되더라도 Printer 를 구현했다면
// 별도로 PrintClient 클래스를 수정할 필요없이
// setPrinter() 메서드 파라미터로 InkJetPrinter 객체만 교체하면
// 얼마든 새로운 Printer 타입 객체를 다룰 수 있다!
// pc.setPrinter(new InkJetPrinter());
// pc.print("Ex.java");
3. 상속 관계가 없는 클래스끼리 관계 부여 가능 => 다형성 확장
- 기존에 다른 클래스를 상속중일 때 다중 상속이 불가능한데 인터페이스를 활용하여 상속관계가 아닌 객체간에 공통 인터페이스 제공으로 새로운 상속 관계 부여가 가능
- 관계가 없는 객체의 경우 공통 타입이 Object 타입뿐이므로 Object 타입으로 변환하여 관리는 할 수 있으나, 각 객체의 메서드 호출을 위해서는 다시 다운캐스팅이 필요하지만 인터페이스를 통해 상속 관계를 부여하고, 인터페이스에서 공통메서드를 추상메서드로 제공하는 경우에는 별도의 다운캐스팅 및 타입 판별 없이 바로 공통 메서드의 호출이 가능해짐
class Phone {}
class Camera {}
class HandPhone extends Phone {
public void charge() {
System.out.println("HandPhone 충전!");
}
}
class DigitalCamera extends Camera {
public void charge() {
System.out.println("DigitalCamera 충전!");
}
}
// --------------------------------------------------------------
// HandPhone 과 DigitalCamera 사이에 특정 관계를 부여해주기 위해
// 공통 인터페이스인 Chargeable 인터페이스 정의하고
// 해당 인터페이스 내에 추상메서드로 charge() 메서드를 정의
interface Chargeable {
public abstract void charge();
}
// HandPhone2 클래스 정의 - Phone 클래스 상속, Chargeable 인터페이스 구현
class HandPhone2 extends Phone implements Chargeable {
@Override
public void charge() {
System.out.println("HandPhone2 충전!");
}
}
// DigitalCamera2 클래스 정의 - Camera 클래스 상속, Chargeable 인터페이스 구현
class DigitalCamera2 extends Camera implements Chargeable {
@Override
public void charge() {
System.out.println("DigitalCamera2 충전!");
}
}
public class Ex4 {
public static void main(String[] args) {
Ex4 ex = new Ex4();
ex.badCase();
ex.goodCase();
}
// 상속관계가 아닌 객체들을 사용하여 다형성을 적용시켜야 할 경우
public void badCase() {
// HandPhone, DigitalCamera 의 공통 타입은 Object 타입밖에 없음
// => 이 때, Object 타입으로 업캐스팅 시 charge() 메서드 호출 불가
// Object obj = new HandPhone();
// obj.charge(); // Object 타입으로 호출 불가능한 메서드
// // 다운캐스팅 통해 다시 HandPhone 타입으로 변경해야 charge() 호출 가능
// HandPhone hp = (HandPhone)obj;
// hp.charge();
//
// obj = new DigitalCamera();
// obj.charge(); // Object 타입으로 호출 불가능한 메서드
// ------------------------------------------------------------
// Object[] 타입으로 HandPhone, DigitalCamera 인스턴스를 관리
Object[] objs = {new HandPhone(), new DigitalCamera()};
// for문을 사용하여 배열 objs 의 모든 인스턴스에 차례대로 접근하여
// 각각의 타입에 맞는 다운캐스팅 수행 후 charge() 메서드 호출
// for(int i = 0; i < objs.length; i++) {
// Object o = objs[i]; // 배열 데이터(객체) 꺼내기
// if(o instanceof HandPhone) { // HandPhone 타입인지 판별
// // HandPhone 타입으로 다운캐스팅 가능
// HandPhone hp = (HandPhone)o;
// hp.charge();
// } else if(o instanceof DigitalCamera) { // DigitalCamera 타입 판별
// // DigitalCamera 타입으로 다운캐스팅 가능
// DigitalCamera dp = (DigitalCamera)o;
// dp.charge();
// }
// }
for(Object o : objs) {
if(o instanceof HandPhone) { // HandPhone 타입인지 판별
// HandPhone 타입으로 다운캐스팅 가능
HandPhone hp = (HandPhone)o;
hp.charge();
} else if(o instanceof DigitalCamera) { // DigitalCamera 타입 판별
// DigitalCamera 타입으로 다운캐스팅 가능
DigitalCamera dp = (DigitalCamera)o;
dp.charge();
}
}
}
// 상속관계가 아닌 객체들에게 인터페이스를 활용하여
// 상속관계를 부여한 후 다형성에 활용할 경우
public void goodCase() {
// HandPhone2, DigitalCamera2 객체를 담기 위한 타입이
// Object 타입 외에 Chargeable 타입도 가능
// => 따라서, Chargeable 타입 배열로 두 객체 관리가 가능
Chargeable[] objs = {new HandPhone2(), new DigitalCamera2()};
// for문을 사용하여 Chargeable[] 타입 내의 모든 객체에 접근하여
// 상속받아 구현한 공통 메서드 charge() 메서드 호출
// => Chargeable 인터페이스에 charge() 메서드가 존재하므로
// 별도의 다운캐스팅 없이도 charge() 메서드에 접근 가능
for(Chargeable obj : objs) {
obj.charge(); // 공통메서드를 직접 호출 가능(다운캐스팅 불필요)
}
}
}
4. 모듈간 독립적 프로그래밍으로 개발 시간 단축
- 여러 모듈간에 공통된 기능을 구현할 메서드를 인터페이스 내의 추상메서드로 제공하여 모듈간 통일성 부여
- 각 모듈에서 추상메서드 구현을 통해 각자에게 필요한 기능을 따로 작업한 후 차후 결합 시 쉽게 결합 가능
- 따라서, 상대방의 작업 진행 상황과 관계없이 개발이 가능하므로 개발 비용이 줄어드는 효과를 가져오게 된다
ex) 숫자 2개를 입력하여 합을 계산 후 결과를 화면에 출력하는 프로그램
디자이너(A) - 개발자(B) 협업 수행 가정했을 때 A 는 입력받은 데이터를 B 에 전달하고 결과값을 기다린 후 B 가 리턴하는 결과를 전달받아 화면에 출력해야하며, B는 A가 입력받은 데이터를 전달했을 때, 해당 데이터의 합계를 계산한 후 다시 A 에게 리턴해야한다. 이 때, 서로 상대방의 작업이 완료되지 않으면 다음 작업 수행이 불가능하므로 상호간에 작업 내용이 같이 진행되어야 한다. 따라서, 한 쪽에서 작업이 지연되면 다른쪽도 함께 지연되므로 작업에 소요되는 비용이 증가하게 됨 => 이를 해결하기 위해 인터페이스 적용 가능 A 입장 : "숫자 두 개 전달, 하나의 결과값(숫자) 리턴받아 출력" B 입장 : "숫자 두개 전달 받아 계산 후, 하나의 숫자 리턴"
interface Calculator {
// 디자이너와 개발자 모두 사용할 공통 메서드를 추상메서드로 정의
public int add(int a, int b);
}
// 디자이너 입장에서의 코드
class CalculatorDesigner {
public void add() {
// 외부로부터 두 개의 숫자를 입력받았다고 가정
int a = 10, b = 20;
// 개발자에게 두 정수를 전달한 뒤, 결과값으로 정수 1개 리턴받아 출력
// => 현재 개발자 코드가 완성되지 않았더라도
// 개발자가 사용할 메서드를 미리 구현한 클래스를 대신 사용 가능
CalculatorDesignerDev cal = new CalculatorDesignerDev();
int result = cal.add(a, b); // 입력받은 두 정수 전달 후 결과 리턴
System.out.println(a + " + " + b + " 의 결과 = " + result);
// ================================================================
// 차후 개발자의 코드가 완성되면 해당 객체를 통해 add() 메서드 호출
CalculatorDeveloper cal2 = new CalculatorDeveloper();
int result2 = cal2.add(a, b); // 입력받은 두 정수 전달 후 결과 리턴
System.out.println(a + " + " + b + " 의 결과 = " + result2);
// ================================================================
}
}
// 디자이너가 개발자에게 구현될 코드를 미리 간단히 작성하여 테스트 가능
class CalculatorDesignerDev implements Calculator {
@Override
public int add(int a, int b) {
// 디자이너 입장에서는 전달받은 두 수를
// 어떻게 계산할 지는 중요하지 않으며, 단지 확인만 수행하면 됨
System.out.println("전달된 파라미터 확인 a = " + a + ", b = " + b);
return 0;
}
}
// =============================================================
// 개발자 입장에서의 코드
class CalculatorDeveloper implements Calculator {
// 외부로부터 정수를 입력받는 코드는 중요하지 않고
// 전달받은 2개의 정수에 대한 덧셈을 수행한 후 리턴이 잘 되는지 확인만 필요
@Override
public int add(int a, int b) {
System.out.println("전달받은 파라미터 : " + a + ", " + b);
return a + b;
}
}
// 디자이너 입장에서의 코드(파라미터 및 리턴값) 확인
CalculatorDesigner designer = new CalculatorDesigner();
designer.add();
// =======================================
// 개발자 입장에서의 코드(파라미터 및 리턴값) 확인
CalculatorDeveloper developer = new CalculatorDeveloper();
int result = developer.add(100, 200);
System.out.println("리턴값 확인 : " + result);
String str = "";
728x90
반응형
'프로그래밍 언어 > JAVA' 카테고리의 다른 글
[JAVA] 자바가 제공하는 패키지 (0) | 2021.01.19 |
---|---|
[JAVA] 자바의 예외처리 Exception (0) | 2021.01.19 |
[JAVA] 자바의 상수 Constant (0) | 2021.01.19 |
[JAVA] 자바의 Static (0) | 2021.01.19 |
[JAVA] 자바의 추상 클래스 & 메서드 (0) | 2021.01.19 |