인터페이스
📌 Java에서 클래스가 구현해야 하는 메서드들의 목록을 정의하는 일종의 계약입니다.
인터페이스 선언
public interface 인터페이스명 {
public static final char A = 'A';
static char B = 'B';
final char C = 'C';
char D = 'D';
void turnOn(); // public abstract void turnOn();
}
- 모든 멤버 변수는 public static final이어야 합니다.
- 생략 가능합니다.
- 모든 메서드는 public abstract이어야 합니다.
- 생략 가능합니다. (static 메서드와 default 메서드 예외)
- 생략되는 제어자는 컴파일러가 자동으로 추가해줍니다.
- 단순하게 클래스는 설계도, 인터페이스는 장보기 리스트 라고 생각하면 편하다
- 클래스는 내용과 기능에 대한 부분이 상세히 기록되지만, 인터페이스는 필요한 기능만 제시되어 있고 구현은 안되어 있다.
인터페이스 구현
📌 인터페이스는 추상 클래스와 마찬가지로 직접 인스턴스를 생성할 수 없기 때문에 클래스에 구현되어 생성됩니다.
- implements 키워드를 사용하여 인터페이스를 구현할 수 있습니다.
public class 클래스명 implements 인터페이스명 {
// 추상 메서드 오버라이딩
@Override
public 리턴타입 메서드이름(매개변수, ...) {
// 실행문
}
}
- 인터페이스의 추상 메서드는 구현될 때 반드시 오버라이딩 되어야 합니다.
- 만약 인터페이스의 추상 메서드를 일부만 구현해야 한다면 해당 클래스를 추상 클래스로 변경해 주면 됩니다.
- 인터페이스 선언 = 인터페이스(명세서) 제작, 인터페이스 구현 = 인터페이스를 구현한 클래스에서 기능 구현
인터페이스 상속
📌 인터페이스 간의 상속이 가능합니다.
- 인터페이스 간의 상속은 implements 가 아니라 extends 키워드를 사용합니다.
- 인터페이스는 클래스와는 다르게 다중 상속이 가능합니다.
public class Main extends D implements C {
@Override
public void a() {
System.out.println("A");
}
@Override
public void b() {
System.out.println("B");
}
@Override
void d() {
super.d();
}
public static void main(String[] args) {
Main main = new Main();
main.a();
main.b();
main.d();
}
}
interface A {
void a();
}
interface B {
void b();
}
interface C extends A, B {
}
class D {
void d() {
System.out.println("D");
}
}
- 인터페이스 C는 아무것도 선언되어 있지 않지만 인터페이스 A, B를 다중 상속받았기 때문에 추상 메서드 a, b를 갖고 있는 상태입니다.
- 따라서 Main 클래스에서 인터페이스 C가 구현될 때 a, b 추상 메서드가 오버라이딩됩니다.
- 또한 인터페이스의 구현은 상속과 함께 사용될 수 있습니다.
- 뭔가 이쯤 오면 추상 클래스가 생각날건데, 둘이 비슷한 건 맞으나, 사용 목적과 특징이 다르다.
특성 | 추상 클래스 (Abstract Class) | 인터페이스 (Interface) |
메서드 구현 | 메서드 구현이 가능 (일부는 구현되고, 일부는 추상 메서드로) | 메서드는 구현 없이 선언만 가능 (Java 8 이후 default 메서드 예외) |
상속 방식 | 단일 상속 (하나의 추상 클래스만 상속 가능) | 다중 상속 (여러 개의 인터페이스를 구현 가능) 클래스는 내용이 구현되어 있고 변수나 메소드 명이 같으면 구분이 안되는 일이 있는데 인터페이스는 내용이 없어서 다중 상속이 가능하다. |
필드 | **상태(필드)**를 가질 수 있으며, 인스턴스 변수로 사용 가능 | 상수만 가질 수 있으며, public static final이어야 함 |
사용 목적 | 공통된 기능의 구현과 일부 기능의 재사용을 위해 사용 | 여러 클래스가 공통된 기능을 갖게 하며, 다형성을 구현하기 위해 사용 |
인스턴스화 | 인스턴스화 불가 (추상 클래스 자체로 객체 생성 불가) | 인스턴스화 불가 (인터페이스 자체로 객체 생성 불가) |
디폴트 메서드와 static 메서드
📌 디폴트 메서드는 추상 메서드의 기본적인 구현을 제공하는 메서드입니다.
- 메서드 앞에 default 키워드를 붙이며 블럭{ }이 존재해야 합니다.
- default 메서드 역시 접근 제어자가 public이며 생략이 가능합니다.
- 추상 메서드가 아니기 때문에 인터페이스의 구현체들에서 필수로 재정의 할 필요는 없습니다.
public class Main implements A {
@Override
public void a() {
System.out.println("A");
}
public static void main(String[] args) {
Main main = new Main();
main.a();
// 디폴트 메서드 재정의 없이 바로 사용가능합니다.
main.aa();
}
}
interface A {
void a();
default void aa() {
System.out.println("AA");
}
}
static 메서드
📌 인터페이스에서 static 메서드 선언이 가능합니다.
- static의 특성 그대로 인터페이스의 static 메서드 또한 객체 없이 호출이 가능합니다.
- 선언하는 방법과 호출하는 방법은 클래스의 static 메서드와 동일합니다.
- 접근 제어자를 생략하면 컴파일러가 public을 추가해 줍니다.
public class Main implements A {
@Override
public void a() {
System.out.println("A");
}
public static void main(String[] args) {
Main main = new Main();
main.a();
main.aa();
System.out.println();
// static 메서드 aaa() 호출
A.aaa();
}
}
interface A {
void a();
default void aa() {
System.out.println("AA");
}
static void aaa() {
System.out.println("static method");
}
}
다형성
자동 타입 변환
public class Main {
public static void main(String[] args) {
// A 인터페이스에 구현체 B 대입
A a1 = new B();
// A 인터페이스에 구편체 B를 상속받은 C 대입
A a2 = new C();
}
}
interface A { }
class B implements A {}
class C extends B {}
강제 타입 변환
public class Main {
public static void main(String[] args) {
// A 인터페이스에 구현체 B 대입
A a1 = new B();
a1.a();
// a1.b(); // 불가능
System.out.println("\nB 강제 타입변환");
B b = (B) a1;
b.a();
b.b(); // 강제 타입변환으로 사용 가능
System.out.println();
// A 인터페이스에 구편체 B를 상속받은 C 대입
A a2 = new C();
a2.a();
//a2.b(); // 불가능
//a2.c(); // 불가능
System.out.println("\nC 강제 타입변환");
C c = (C) a2;
c.a();
c.b(); // 강제 타입변환으로 사용 가능
c.c(); // 강제 타입변환으로 사용 가능
}
}
interface A {
void a();
}
class B implements A {
@Override
public void a() {
System.out.println("B.a()");
}
public void b() {
System.out.println("B.b()");
}
}
class C extends B {
public void c() {
System.out.println("C.c()");
}
}
인터페이스의 다형성
// LG TV 구현체를 조작
MultiRemoteController mrc = new LgTv("LG");
mrc.turnOnOff();
mrc.volumeUp();
// 조작 대상을 Samsung TV로 교체
mrc = new SamsungTv("Samsung");
mrc.turnOnOff();
mrc.channelUp();
- 멀티 리모컨 인터페이스 변수 = TV 구현 객체;를 선언하여 자동 타입 변환된 인터페이스 변수를 사용하여 TV 구현 객체의 기능을 조작할 수 있습니다.
- TV 구현 객체를 교체해도 멀티 리모컨 인터페이스 변수는 전혀 수정 작업 없이 그대로 기능을 호출할 수 있습니다.
- 다형성은 ‘여러 가지 형태를 가질 수 있는 능력’이라고 배웠습니다.
- 사용 방법은 동일하지만 다양한 특징과 결과를 가질 수 있는 것이 바로 다형성입니다.
- 즉, 멀티 리모컨으로 티비를 사용하는 방법은 동일하지만 어떤 TV 구현 객체가 대입되었느냐에 따라 실행 결과가 다르게 나옴을 통해 다형성이 적용되었음을 확인할 수 있었습니다.
// 매개변수와 반환타입 다형성 확인 메서드
default MultiRemoteController getTV(Tv tv) {
if(tv instanceof SamsungTv) {
return (SamsungTv) tv;
} else if(tv instanceof LgTv){
return (LgTv) tv;
} else {
throw new NullPointerException("일치하는 Tv 없음");
}
}
- 또한 인터페이스도 마찬가지로 매개변수와 반환 타입에서 다형성이 적용될 수 있습니다.
- 위 예제는 반환 타입에는 인터페이스, 매개변수에는 추상 클래스로 다형성이 적용되어있습니다.
- 인터페이스의 default 메서드입니다.
- 전체 예제를 통해 더 자세하게 확인해 보겠습니다.
public abstract class Tv {
private String company; // 티비 회사
private int channel = 1; // 현재 채널 상태
private int volume = 0; // 현재 볼륨 상태
private boolean power = false; // 현재 전원 상태
public Tv(String company) {
this.company = company;
}
public void displayPower(String company, boolean power) {
if(power) {
System.out.println(company + " Tv 전원이 켜졌습니다.");
} else {
System.out.println(company + " Tv 전원이 종료되었습니다.");
}
}
public void displayChannel(int channel) {
System.out.println("현재 채널은 " + channel);
}
public void displayVolume(int volume) {
System.out.println("현재 볼륨은 " + volume);
}
public String getCompany() {
return company;
}
public int getChannel() {
return channel;
}
public int getVolume() {
return volume;
}
public boolean isPower() {
return power;
}
public void setChannel(int channel) {
this.channel = Math.max(channel, 0);
}
public void setVolume(int volume) {
this.volume = Math.max(volume, 0);
}
public void setPower(boolean power) {
this.power = power;
}
}
public class SamsungTv extends Tv implements MultiRemoteController{
public SamsungTv(String company) {
super(company);
}
@Override
public void turnOnOff() {
setPower(!isPower());
displayPower(getCompany(), isPower());
}
@Override
public void channelUp() {
setChannel(getChannel() + 1);
displayChannel(getChannel());
}
@Override
public void channelDown() {
setChannel(getChannel() - 1);
displayChannel(getChannel());
}
@Override
public void volumeUp() {
setVolume(getVolume() + 1);
displayVolume(getVolume());
}
@Override
public void volumeDown() {
setVolume(getVolume() - 1);
displayVolume(getVolume());
}
}
public class LgTv extends Tv implements MultiRemoteController {
public LgTv(String company) {
super(company);
}
@Override
public void turnOnOff() {
setPower(!isPower());
displayPower(getCompany(), isPower());
}
@Override
public void channelUp() {
setChannel(getChannel() + 1);
displayChannel(getChannel());
}
@Override
public void channelDown() {
setChannel(getChannel() - 1);
displayChannel(getChannel());
}
@Override
public void volumeUp() {
setVolume(getVolume() + 1);
displayVolume(getVolume());
}
@Override
public void volumeDown() {
setVolume(getVolume() - 1);
displayVolume(getVolume());
}
}
public interface MultiRemoteController {
void turnOnOff();
void channelUp();
void channelDown();
void volumeUp();
void volumeDown();
// 매개변수와 반환타입 다형성 확인 메서드
default MultiRemoteController getTV(Tv tv) {
if(tv instanceof SamsungTv) {
return (SamsungTv) tv;
} else if(tv instanceof LgTv){
return (LgTv) tv;
} else {
throw new NullPointerException("일치하는 Tv 없음");
}
}
}
public class Main {
public static void main(String[] args) {
// LG TV 구현체를 조작
MultiRemoteController mrc = new LgTv("LG");
mrc.turnOnOff();
mrc.volumeUp();
mrc.channelDown();
mrc.channelUp();
mrc.turnOnOff();
// 조작 대상을 Samsung TV로 교체
System.out.println("\n<Samsung TV로 교체>");
mrc = new SamsungTv("Samsung");
mrc.turnOnOff();
mrc.channelUp();
mrc.volumeDown();
mrc.volumeUp();
mrc.turnOnOff();
// 매개변수, 반환타입 다형성 체크
System.out.println("\n<매개변수, 반환타입 다형성 체크>");
MultiRemoteController samsung = mrc.getTV(new SamsungTv("Samsung"));
samsung.turnOnOff();
SamsungTv samsungTv = (SamsungTv) samsung;
samsungTv.turnOnOff();
System.out.println();
MultiRemoteController lg = mrc.getTV(new LgTv("LG"));
lg.turnOnOff();
LgTv lgTv = (LgTv) lg;
lgTv.turnOnOff();
}
}
'Back-End (Web) > JAVA' 카테고리의 다른 글
[JAVA] Generic (3) | 2024.11.12 |
---|---|
[JAVA] 오류 및 예외에 대한 이해 (1) | 2024.11.12 |
[JAVA] 클래스 간의 관계와 상속 (0) | 2024.11.12 |
[JAVA] package와 import (0) | 2024.11.12 |
[JAVA] 접근 제어자 (1) | 2024.11.12 |