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 |