인터페이스(Interface)
- 클래스가 아니므로 선언 시 class 키워드를 사용하지 않고 interface 키워드를 사용하여 정의
- 인터페이스는 상수와 추상 메서드만 가질 수 있다
-> 모든 멤버 변수는 public static final이 붙은 상수로 취급됨
(public static final 생략 가능)
(public 으로 인해 누구나 접근 가능, static 으로 인해 클래스명만으로 접근 가능, final로 인해 값 변경 불가)
-> 모든 메서드는 public abstract가 붙은 추상 메서드로 취급됨
(public abstract 생략 가능)
(메서드 바디를 가질 수 없으며, public으로 인해 누구나 접근 가능)
- 추상 클래스와 마찬가지로 추상 메서드를 포함하므로 객체 생성 불가능
-> 단, 참조 변수 타입으로는 사용 가능(= 다형성을 활용한 업캐스팅 가능)
- 서브 클래스(구현 클래스)에서 인터페이스를 상속받아 구현해야 하는 경우 extends 키워드 대신 implements 키워드 사용
-> 클래스는 상속받아 확장(extends)의 개념이 강하지만, 인터페이스는 상속받아 추상 메서드를 구현(implements)의 개념이 강함
- 클래스는 서브 클래스에서 다중 상속이 불가능하지만, 인터페이스는 서브 클래스에서 다중 상속(구현)이 가능하다!
-> implements 키워드 뒤에 복수개의 인터페이스 지정 가능
- 인터페이스끼리 상속받을 경우 implements가 아닌 extends로 상속
-> 추상 메서드는 구현(implements) 하지 못하기 때문에!
< 인터페이스 정의 기본 문법 >
[접근제한자] interface 인터페이스명 {
// 상수
// 추상메서드
}
< 인터페이스를 상속받은(=구현하는) 서브 클래스(구현 클래스) 정의 문법 >
[접근제한자] class 클래스명 implements 인터페이스명 {}
abstract class MyClass {
// 클래스 내의 모든 멤버변수는 상수가 아니며, 선언 방법에 따라 달라짐
public static final int NUM1 = 10; //상수
int NUM2 = 20; // 인스턴스 멤버 변수 (상수X)
public MyClass() {} // 생성자
// -> 추상클래스는 생성자를 가질 수 있다!
public abstract void method1(); // 추상 메서드
// -> 추상 메서드를 갖는 클래스는 반드시 추상 클래스로 선언해야 한다!
public void method2() {} // 일반 메서드
// -> 추상 클래스는 일반 메서드도 가질 수 있다!
}
// 인터페이스 정의
// -> class 키워드 대신 interface 키워드를 사용하여 정의
interface MyExInterface {
// 인터페이스 내의 모든 멤버 변수는 상수(public static final)이다!
public static final int NUM1 = 10; // 상수
int NUM2 = 20; // 상수(public static final 생략되어 있음)
// 인터페이스는 생성자를 가질 수 없다!
// public MyExInterface() {} // 컴파일 에러 발생!
// -> Interfaces cannot have constructors
// 인터페이스 내의 모든 메서드는 추상 메서드(public abstract)이다!
public abstract void method1(); // 추상 메서드
// public void method2() {} // 일반 메서드(컴파일 에러 발생!)
// -> Abstract methods do not specify a body
// -> 추상 메서드여야 하므로 메서드 바디{}를 가질 수 없다!
public void method2(); // 추상 메서드(abstract 생략되어 있음)
void method3(); // 추상 메서드(public abstract 생략되어 있음)
}
// 인터페이스를 상속받아 구현하는 서브 클래스(구현 클래스) MySubClass 정의
// -> 서브 클래스 정의 시 클래스명 뒤에 implements 키워드를 쓰고 인터페이스명 지정
class MySubClass implements MyExInterface {
@Override
public void method1() {
System.out.println("서브 클래스에서 구현한 추상 메서드 method1()!");
}
@Override
public void method2() {
System.out.println("서브 클래스에서 구현한 추상 메서드 method2()!");
}
@Override
public void method3() {
// 인터페이스(MyExInterface)에서 method3() 메서드 정의 시 접근제한자를
// 생략했지만 인터페이스 내의 모든 메서드는 public 접근제한을 가지므로
// 오버라이딩을 통한 구현 시 무조건 public 접근제한자만 사용 가능!
System.out.println("서브 클래스에서 구현한 추상 메서드 method3()!");
}
}
package interface_;
public class Ex2 {
public static void main(String[] args) {
고래상어 고래상어 = new 고래상어();
고래상어.번식();
동물 동물 = 고래상어; // 업캐스팅
동물.번식();
}
}
// 고래와 상어의 특징을 추상화하여 동물 인터페이스로 정의
interface 동물 {
// 인터페이스 내의 모든 메서드는 추상 메서드
public abstract void 번식();
}
// 헤엄칠 수 있는 특징을 갖는 인터페이스 '헤엄칠 수 있는' 정의
interface 헤엄칠수있는 {}
// 동물 인터페이스를 상속받는 서브 인터페이스 고래, 상어 정의
// -> 인터페이스끼리 상속은 implements가 아닌 extends 사용
// -> 인터페이스끼리 상속은 다중 상속도 가능
interface 고래 extends 동물, 헤엄칠수있는 {}
interface 상어 extends 동물, 헤엄칠수있는 {}
// 고래와 상어 인터페이스를 동시에 상속받아 수현하는 서브클래스 고래상어 정의
// -> 클래스에서 인터페이스 상속(구현)을 받을 경우 implements 키워드 사용하며
// 반드시 인터페이스가 가진 추상 메서드를 오버라이딩하여 구현해야 한다!
// -> 또한, 복수개의 인터페이스를 상속받을 수 있다!
class 고래상어 implements 고래, 상어 {
// 인터페이스 내에서는 슈퍼클래스(인터페이스) 모든 메서드가 추상메서드이므로
// 중복되는 메서드에 대한 구별없이 직접 구현하면 되기 때문에
// 중복된 메서드로 인한 혼란이 없음(= 다이아몬드 상속에서의 문제점이 사라짐)
@Override
public void 번식() {
System.out.println("알을 낳아 번식한다!");
}
}
// ================================== 다이아몬드 상속에서의 문제점(이슈) =====================================
// 번식() 메서드를 갖는 슈퍼클래스 동물 클래스를 정의하고
// 고래와 상어 클래스에서 동물클래스를 상속받기
abstract class 동물2 {
public abstract void 번식();
}
class 상어2 extends 동물2 {
@Override
public void 번식() {
System.out.println("알을 낳아 번식!");
}
}
class 고래2 extends 동물2 {
@Override
public void 번식() {
System.out.println("새끼를 낳아 번식!");
}
}
// 만약 다중 상속을 허용할 경우 발생하는 문제점
// -> 고래와 상어 클래스를 동시에 상속받는 고래상어 클래스 정의
// class 고래상어2 extends 고래2, 상어2 {
// 외부 또는 내부에서 고래와 상어클래스가 공통적으로 갖는
// 번식() 메서드를 호출하게 되면 어느 객체의 번식() 메서드를 호출하는지
// 분명하지 않기 때문에 자바에서는 다중 상속이 금지되어 있음!
//}
TEST
package interface_;
public class Test2 {
public static void main(String[] args) {
BumbleBee BumbleBee = new BumbleBee();
BumbleBee.fight();
BumbleBee.speak();
BumbleBee.transform(BumbleBee.MODE_NORMAL);
BumbleBee.transform(BumbleBee.MODE_CAR);
BumbleBee.transform(BumbleBee.MODE_AIRPLANE);
}
}
/*
* 전투 기능을 갖는 Fightable 인터페이스
* -> fight() 메서드
*
*
* 변신 기능을 갖는 Transformable 인터페이스
* -> transform() 메서드(리턴값 없음, 파라미터(int mode))
* -> 상수 값(MODE_CAR = 1, MODE_AIRPLANE = 2, MODE_NORMAL = 3)
*
* Robot 인터페이스 정의
* -> Fightable 인터페이스, Transformable 인터페이스 상속
* -> 말하기 기능(speak())
*/
interface Fightable {
public abstract void fight();
}
interface Transformable {
public static final int MODE_CAR = 1;
public static final int MODE_AIRPLANE = 2;
public static final int MODE_NORMAL = 3;
public abstract void transform(int mode);
}
interface Robot extends Fightable, Transformable {
public abstract void speak();
}
/*
* Robot 인터페이스를 구현하는 BumbleBee 클래스 정의
* -> fight() 메서드 호출 시 "로켓 발사!" 출력
* -> transform() 메서드 호출 시 mode 값에 따라 다른 형태로 변신
* MODE_NORMAL일 경우 "기본 모드(로봇)로 변신!" 출력
* MODE_CAR일 경우 "자동차로 변신" 출력
* MODE_AIRPLANE일 경우 "비행기로 변신!" 출력
* -> speak() 메서드 호출 시 "라디오로 말하기!" 출력
*/
class BumbleBee implements Robot {
@Override
public void fight() {
System.out.println("로켓 발사!");
}
@Override
public void transform(int mode) {
if(mode == Transformable.MODE_NORMAL) {
System.out.println("기본 모드(로봇)로 변신!");
} else if(mode == Transformable.MODE_CAR) {
System.out.println("자동차로 변신!");
} else if(mode == Transformable.MODE_AIRPLANE) {
System.out.println("비행기로 변신!");
}
}
@Override
public void speak() {
System.out.println("라디오로 말하기!");
}
}
인터페이스의 필요성
1. 구현의 강제로 코드의 통일성 향상(=표준화)
2. 인터페이스를 통한 간접적인 클래스 사용으로 모듈 교체가 용이
-> 특정 클래스를 직접 다루는 대신 부모 인터페이스 타입으로 클래스를 다루게 되면 실제 인스턴스가 바뀌더라도 기본 코드를 수정할 필요가 없어짐
3. 서로 상속 관계가 없는 클래스간의 인터페이스를 통한 상속 관계 부여
-> 다형성 확장
4. 모듈간 독립적 프로그래밍으로 인한 개발 기간 단축
2. 인터페이스를 통한 간접적인 클래스를 사용으로 모듈 교체가 용이
-> 인터페이스 사용 시 손쉬운 모듈 교체를 지원한다!
PrinterClient pc = new PrinterClient();
// PrinterClient 인스턴스의 setPrinter() 메서드를 호출하여
// 각 프린터기 인스턴스를 파라미터로 전달하면 업캐스팅 일어남
pc.setPrinter(new LaserPrinter()); // LaserPrinter -> Printer 업캐스팅
// -> setPrinter(Printer printer)로 정의되어 있으므로
// Printer printer = new LaserPrinter();가 됨
// 좌변의 타입이 부모클래스 타입이고, 오른쪽의 인스턴스 생성이 자식 클래스이라면
// 묵시적 형변환 즉, 업캐스팅이 일어남!
pc.print("Hello.java");
// -> PrinterClient 인스턴스의 print() 메서드를 호출하면
// 인스턴스 내의 Printer 타입 변수에 저장된 인스턴스 print() 호출됨
// -> 결국 실제 저장된 LaserPrinter 인스턴스의 print() 메서드가 호출됨
// 현재 LaserPrinter를 InkjetPrinter로 교체 시
// setPrinter() 메서드에 InkjetPrinter 인스턴스만 전달하면
// 자동으로 출력 대상이 변경됨
pc.setPrinter(new InkjetPrinter()); // InkjetPrinter -> Printer 업캐스팅
pc.print("Hello.java");
pc.setPrinter(new DotPrinter()); // DotPrinter -> Printer 업캐스팅
pc.print("Ex3.java");
}
}
// 각 프린터를 직접 다루지 않고 상위 타입인 Printer 인터페이스를 다루는
// PrintClient 클래스 정의
class PrinterClient {
// 각각의 프린터 클래스를 다루기 위한 슈퍼클래스 타입에 해당하는
// Printer 인터페이스 타입 변수 선언
private Printer printer;
// Setter 메서드를 통한 Printer 타입 변수 초기화
public void setPrinter(Printer printer) {
this.printer = printer;
}
// 외부로부터 출력할 파일을 전달받아 실제 프린터에 해당하는 각 인스턴스의
// print() 메서드를 호출한 뒤 파일을 전달하여 출력 작업을 수행
public void print(String fileName) {
// Printer 타입 변수에 저장된 각 프린터의 인스턴스를 통해
// print() 메서드를 호출하면 해당 프린터의 출력 기능을 사용 기능함
printer.print(fileName);
}
}
/*
* 문서 등을 프린터로 출력하기 위한 각 프린터 클래스 정의
* -> 각 프린터의 출력 기능을 갖는 printer 인터페이스를 정의하고
* 각 프린터 클래스에서 상속받아 구현
*/
interface Printer { // 프린터
// 프린터기 공통 기능인 출력(print()) 기능을 추상메서드 정의
public abstract void print(String fileName);
}
class LaserPrinter implements Printer { // 레이저 프린터
// 파일(Stirng 타입 fileName)을 전달받아 출력 작업을 수행하는 print() 메서드 정의
// -> Printer 인터페이스로부터 상속받아 구현
@Override
public void print(String fileName) {
System.out.println("Laser Printer로 "+ fileName + "출력하기!");
}
}
class InkjetPrinter implements Printer { // 잉크젯 프린터
@Override
public void print(String fileName) {
System.out.println("InkjetPrinter Printer로 "+ fileName + "출력하기!");
}
}
class DotPrinter implements Printer { // 도트 프린터
@Override
public void print(String fileName) {
System.out.println("DotPrinter로 "+ fileName + "출력하기!");
}
}
LaserPrinter lp = new LaserPrinter();
lp.print("a.txt");
InkjetPrinter ip = new InkjetPrinter();
ip.print("b.txt");
DotPrinter dp = new DotPrinter();
dp.print("c.txt");
Printer p = lp; // 업캐스팅
p.print("d.txt");
3. 서로 상속 관계가 없는 클래스간에 인터페이스를 통한 상속 관계 부여
-> 다형성 확장
public void noRelationShip() {
// NoteBookPc notebook = new NoteBookPc();
// notebook.charge();
// SmartPhone smartPhone = new SmartPhone();
// smartPhone.charge();
// 두 개의 인스턴스를 하나의 배열로 관리해야 할 경우
// NoteBookPc와 SmartPhone의 공통 타입은 Object 타입밖에 없음
Object[] objArr = {new NoteBookPc(), new SmartPhone()};
// 반복문을 사용하여 배열 크기만큼 반복
for(int i = 0; i < objArr.length; i++) {
// objArr[i].charge();
// -> 참조 영역 축소로 인해 Object 타입으로 charge() 메서드 호출 불가!
// instanceof 연산자를 사용자 NoteBookPc와 SmartPhone 타입 판별
// -> 다운캐스팅을 통해 각 인스턴스를 따로 접근해야 함!
// 접근하고자 하는 charge() 메서드는 각 클래스에서 직접 정의한
// 인스턴스 멤버 메서드이기 때문!
if (objArr[i] instanceof NoteBookPc) {
// Object -> NoteBookPc 타입으로 다운캐스팅 후 charge() 호출
NoteBookPc notebook = (NoteBookPc)objArr[i];
notebook.charge(); // 노트북의 충전기를 통해 충전
} else if(objArr[i] instanceof SmartPhone) {
// Object -> SmartPhone 타입으로 다운캐스팅 후 charge() 호출
SmartPhone smartPhone = (SmartPhone)objArr[i];
smartPhone.charge(); // 스마트폰의 충전기를 통해 충전
}
}
}
public void hasRelationShip() {
// 인터페이스를 사용하여 공통된 멤버를 갖는 상속 관계를 부여할 경우
// 해당 인터페이스 타입으로 다운캐스팅 할 필요 없이
// 업캐스팅 된 상태 그대로 멤버에 접근 가능!
// -> 다형성으로 인한 코드 절약
Chargeable c = new NoteBookPc2(); // NoteBookPc2 -> Chargeable 업캐스팅
Chargeable c2 = new SmartPhone2(); // SmartPhone2 -> Chargeable 업캐스팅
// Chargeable 타입 배열로 두 클래스 인스턴스 모두 관리 가능(업캐스팅)
Chargeable[] chargeableArr = {new NoteBookPc2(), new SmartPhone2()};
// 업캐스팅 후에도 공통 메서드 charge()를 호출 가능하므로
// 별도의 다운캐스팅 없이 바로 charge() 메서드 접근 가능
for(int i = 0; i < chargeableArr.length; i++) {
chargeableArr[i].charge();
}
}
} // Ex4 클래스 끝
// Object 클래스 외에 슈퍼클래스가 없는 NoteBookPc 와 SmartPhone 의
// 공통 인터페이스 Chargeable 인터페이스 정의
interface Chargeable {
// 두 클래스에서 공통으로 사용할 충전(charge()) 기능을 추상메서드 정의
public abstract void charge();
}
// 기존에 Pc 클래스를 상속받고 있는 상태에서
// 추가로 인터페이스를 구현해야 하는 경우 상속 코드 뒤에 구현 코드를 기술
// => implements Chargeable 코드 추가
// => 아무 관계도 없던 두 클래스에 동일한 부모 인터페이스가 추가되어
// 서로 상속 관계로 묶이게 됨
class NoteBookPc2 extends Pc implements Chargeable {
@Override
public void charge() {
System.out.println("노트북 충전 중...");
}
}
class SmartPhone2 extends HandPhone implements Chargeable {
@Override
public void charge() {
System.out.println("스마트폰 충전 중...");
}
}
// ======================================================
class Pc {}
class NoteBookPc extends Pc {
public void charge() {
System.out.println("노트북 충전 중...");
}
}
class HandPhone {}
class SmartPhone extends HandPhone {
public void charge() {
System.out.println("스마트폰 충전 중...");
}
}
Ex4 ex = new Ex4();
ex.noRelationShip();
4. 모듈간 독립적 프로그래밍으로 인한 개발 기간 단축
// 개발자와 디자이너의 규칙을 인터페이스로 정의
interface LoginProcess {
// 공통 기능으로 login() 메서드를 정의하여 파라미터와 리턴타입 지정
public abstract String login(String id, String pass);
}
// 디자이너의 경우
// => 로그인 처리 과정은 중요하지 않고 전달 데이터와 리턴 데이터만 중요함
// => LoginProcess 인터페이스를 구현하는 클래스를 정의하여
// login() 메서드 구현
class Designer implements LoginProcess {
@Override
public String login(String id, String pass) {
// 메서드에 전달되는 데이터가 정확한지만 확인하고
// 리턴값이 외부로 잘 전달되는지만 확인하면 된다!
System.out.println("디자이너가 전달받은 아이디 : " + id);
System.out.println("디자이너가 전달받은 패스워드 : " + pass);
return "성공";
}
}
class DeignerClient { // 디자이너
Designer designer = new Designer();
public void login() {
String id = "admin";
String pass = "1234";
// login() 메서드를 호출하여 파라미터로 아이디, 패스워드를 전달하고
// 로그인 결과로 리턴되는 값을 출력하여 확인 작업 수행
String result = designer.login(id, pass);
System.out.println("로그인 결과 : " + result);
if(result.equals("성공")) {
// 로그인 성공 페이지로 이동 처리 작업 수행
} else {
// 로그인 실패 페이지로 이동 처리 작업 수행
}
}
}
// 개발자의 경우
// => 전달받은 값은 중요하지 않고 전달받은 값을 사용하여 로그인 처리 작업 수행
// 로그인 처리 후 리턴되는 값이 정상적으로 전달되는지만 확인
// => LoginProcess 인터페이스를 구현하는 Developer 클래스 정의
class Developer implements LoginProcess {
// 추상 메서드 구현 필수!
@Override
public String login(String id, String pass) {
// 디자이너로부터 전달받은 아이디와 패스워드를 사용하여
// 로그인 작업을 처리한 후 "성공" 또는 "실패" 문자열 리턴
System.out.println("개발자가 전달받은 아이디 : " + id);
System.out.println("개발자가 전달받은 패스워드 : " + pass);
System.out.println("아이디와 패스워드로 로그인 작업 처리 완료!");
return "성공";
}
}
class DeveloperClient {
Developer developer = new Developer();
public void login() {
// 디자이너가 전달하게 될 아이디와 패스워드를 임의로 설정하여 전달하고
// 개발자 코드를 통해 로그인 작업을 수행한 후
// 리턴되어지는 값이 정상적인지 확인
String result = developer.login("admin", "1234");
System.out.println("로그인 결과 : " + result);
}
}
DeignerClient desinger = new DeignerClient();
desinger.login();
System.out.println("-----------------------------");
DeveloperClient developer = new DeveloperClient();
developer.login();
TEST
package interface_;
public class Test4 {
public static void main(String[] args) {
Photoshop p = new Photoshop();
p.draw(new Circle());
p.draw(new Rectangle());
}
}
// ------------------------------------------------------------
// 모든 도형의 그리기 기능을 제공하는 Shape 인터페이스 정의
// => draw() 추상메서드 정의(리턴값 없음, 파라미터 없음)
interface Shape {
public abstract void draw();
}
// Shape 인터페이스를 구현하는 Circle, Rectangle 클래스 정의
// => 추상메서드 draw() 를 오버라이딩하여 각자 도형 그리기 기능을 구현
// ex) Circle 은 "원 그리기", Rectangle 은 "사각형 그리기" 출력
// => Circle : 멤버변수(반지름, radius, 실수형, 접근제한자 : private)
// Setter() 메서드,
// draw() 메서드 호출 시 "원그리기! 반지름 : 반지름 길이" 출력
// => Rectangle : 멤버변수(가로, width, 실수형, 접근제한자 : private
// 세로, height, 실수형, 접근제한자 : private)
// Setter() 메서드,
// draw() 메서드 호출 시 "가로 : 가로길이, 세로 : 세로길이 의 사각형 그리기!"
class Circle implements Shape {
private double radius = 3.0;
public void setRadius(double radius) {
this.radius = radius;
}
@Override
public void draw() {
System.out.println("원 그리기! - 반지름 : " + radius);
}
}
class Rectangle implements Shape {
private double width = 2.0;
private double height = 1.5;
public void setWidth(double width) {
this.width = width;
}
public void setHeight(double height) {
this.height = height;
}
@Override
public void draw() {
System.out.println("가로 : " + width + ", 세로 : " + height + " 의 사각형 그리기!");
}
}
// Photoshop 클래스 정의
// draw() 메서드 정의
// => Circle, Rectangle 인스턴스를 전달받아 해당 인스턴스의 draw() 호출
// 두 인스턴스의 공통 타입으로 Shape 인터페이스를 파라미터 타입으로 지정
class Photoshop {
// draw() 메서드 정의
// => Circle, Rectangle 인스턴스를 전달받아 해당 인스턴스의 draw() 호출
// 두 인스턴스의 공통 타입으로 Shape 인터페이스를 파라미터 타입으로 지정
public void draw(Shape s) { // Circle & Rectangle -> Shape 업캐스팅
// Shape 타입 변수의 draw() 메서드 호출 시 실제 인스턴스의 draw() 호출됨
s.draw();
}
}
'⛏️ > JAVA' 카테고리의 다른 글
[JAVA] 29. String (0) | 2023.10.11 |
---|---|
[JAVA] 28. 오브젝트 (0) | 2023.10.11 |
[JAVA] 26. 추상 메서드, 추상 클래스 (0) | 2023.10.05 |
[JAVA] 25. 상수 (0) | 2023.10.05 |
[JAVA] 24. 다형성 (0) | 2023.09.26 |