사이먼's 코딩노트
[Java] 인터페이스 본문
[인터페이스]
1. 클래스 다중상속의 장점과 단점은?
- 장점 : 객체에 다형성을 원하는 만큼 부여할 수 있다.
- 단점 : 하나의 자식 클래스에 2명 이상의 부모 클래스에서 똑같은 형태의 메서드를 2개 이상을 물려받을 수 있다. 이 때, 자식 클래스에서 해당 메서드를 오버라이딩하지 않는다면, 모호함이 발생한다. 참고로 자식 클래스에서 해당 메서드를 오버라이딩할 의무는 없다.
2. Java에서 클래스 다중상속을 막은 이유는?
- C++과 달리, Java는 개발자가 고생할 수 있는 여지를 줄이기 위해 해당 기능을 없앴다.
3. 클래스 다중상속에서 나타날 수 있는 문제점을 해결하는 방법은?
- 자식 클래스에서 모호한 메서드를 오버라이딩한다.
4. 인터페이스와 클래스의 차이점은?
- 인터페이스는 100% 추상클래스이다.
- 인터페이스 안에 있는 메서드는 추상 메서드이기 때문에, abstract를 메서드 앞에 붙일 필요가 없다.
5. Java에서 인터페이스 다중상속을 허용한 이유는?
- 객체지향 프로그래밍에서 다형성은 굉장히 중요하다.
- Java에서는 다중상속이 제한되어 자유로운 다형성 부여가 힘든 상황이다.
- 그래서 Java는 인터페이스라는 제한된 형태의 클래스는 다중상속을 허용하였다.
- 인터페이스를 다중상속하여 모호함이 발생해도 자식 클래스에서 해당 메서드를 오버라이딩해야 하는 것이 필수이기 때문에 모호함이 존재할 수 없는 구조이다.
- 다음은 표를 통해 구상(일반) 클래스, 추상 클래스, 인터페이스를 비교해보자
- 표에 작성되진 않았지만, 예외인 경우를 몇 가지 소개하고자 한다.
- 먼저 인터페이스는 다른 인터페이스들을 다중상속할 수 있다.
- 그리고 인터페이스에는 private를 사용할 수 없다.
[문제]
- 이제는 문제를 통해서 인터페이스 역할에 대해서 더 자세히 살펴봅시다.
- 문제는 Main 메서드를 보고 프로그램 실행 시 에러가 발생하지 않도록 클래스와 인터페이스를 추가해봅시다.
public class Main {
public static void main(String[] args) {
사람 a사람 = new 홍길동();
변호사 a변호사 = (변호사)a사람;
치과의사 a치과의사 = (치과의사)a사람;
성화봉송자 a성화봉송자 = (성화봉송자)a사람;
}
}
- 다음은 클래스와 인터페이스를 추가한 코드이다.
public class Main {
public static void main(String[] args) {
사람 a사람 = new 홍길동();
변호사 a변호사 = (변호사)a사람;
치과의사 a치과의사 = (치과의사)a사람;
성화봉송자 a성화봉송자 = (성화봉송자)a사람;
}
}
class 사람 {
}
class 홍길동 extends 사람 implements 변호사, 치과의사, 성화봉송자 {
}
interface 변호사 {
}
interface 치과의사 {
}
interface 성화봉송자 {
}
- 먼저 Main 메서드를 봤을 때 필요한 클래스는 사람, 홍길동, 변호사, 치과의사, 성화봉송자 이다.
- a사람에는 홍길동 객체와 연결되는 리모콘이 포함되어 있다.
- a변호사, a치과의사, a성화봉송자에는 a사람이 가지고 있는 홍길동과 연결되는 리모콘을 넣기 위해 강제 형변환을 하고 있다.
- [사람 a사람 = new 홍길동()] 여기서 홍길동 클래스는 우선 사람 클래스에게 상속받는다는 것을 눈치챌 수 있다.
- 그 다음은 홍길동 객체는 변호사, 치과의사, 성화봉송자 각 클래스와도 상속을 받지만 일반 클래스로는 다중상속이 불가능하다.
- 다중상속이 가능하게 하기위해서 변호사, 치과의사, 성화봉송자를 인터페이스로 생성해주고, implements를 통해 다중상속을 해주면 해당 코드는 에러없이 잘 실행된다.
- 다음은 다중상속과 메서드 오버라이딩 개념이 포함된 문제이다.
- 마찬가지로 Main 메서드를 보고 프로그램 실행 시 에러가 발생하지 않도록 클래스와 인터페이스를 추가해봅시다.
public class Main {
public static void main(String[] args) {
사람 a사람 = new 사람();
변호사 a변호사 = a사람;
a변호사.변호하다();
// 출력 : 사람이 변호 합니다.
변호사 a변호사2 = new 오랑우탄();
a변호사2.변호하다();
// 출력 : 오랑우탄이 변호 합니다.
의사 a의사 = new 오랑우탄();
a의사.진찰하다();
// 출력 : 오랑우탄이 진찰 합니다.
의사 a의사2 = new 사람();
a의사2.진찰하다();
// 출력 : 사람이 진찰 합니다.
}
}
- 다음은 클래스와 인터페이스를 추가한 코드이다.
class 사람 implements 변호사, 의사 {
public void 변호하다() {
System.out.println("사람이 변호 합니다.");
}
public void 진찰하다() {
System.out.println("사람이 진찰 합니다.");
}
}
class 오랑우탄 implements 변호사, 의사 {
public void 변호하다() {
System.out.println("오랑우탄이 변호 합니다.");
}
public void 진찰하다() {
System.out.println("오랑우탄이 진찰 합니다.");
}
}
interface 변호사 {
void 변호하다();
}
interface 의사 {
void 진찰하다();
}
- 먼저 Main 메서드를 봤을 때 필요한 클래스는 사람, 변호사, 의사, 오랑우탄 이다.
- a사람에는 사람 객체와 연결된 리모콘이 포함되어 있다.
- a변호사에는 a사람이 가지고 있는 사람과 연결되는 리모콘을 넣고 있고, 이를 통해 사람은 변호사에 상속받는다는 것을 알 수 있다.
- 또한 a의사2를 보면 new 사람()을 통해 사람 객체와 연결되는 리모콘을 넣고 있고, 이 또한 사람은 의사에게도 상속받는 것을 알 수 있다.
- 일반 클래스에서는 다중상속이 불가능하기 떄문에 인터페이스로 의사와 변호사를 생성해주고 implements를 통해 다중상속을 해줘야한다.
- 각 변호사와 의사는 변호하다(), 진찰하다() 라는 메서드를 가지고 있고 인터페이스의 메서드는 100% 추상 메서드로 구성되기 때문에 void 변호하다(); void 진찰하다(); 라고 선언만 해주고 무조건 자식 클래스인 사람 클래스에서 오버라이딩을 해줘야하고 오버라이딩한 메서드 앞에는 반드시 public를 붙혀줘야한다.
- 오랑우탄 클래스도 사람 클래스와 같은 역할을 하기 때문에 마찬가지로 인터페이스인 변호사와 의사를 상속받고 메서드 오버라이딩을 무조건 해줘야한다.
- 다음은 비슷한 유형이지만 조금 심화된 인터페이스 문제이다.
- 마찬가지로 Main 메서드를 보고 프로그램 실행 시 에러가 발생하지 않도록 코드를 완성해봅시다.
public class Main {
public static void main(String[] args) {
진찰하다(new 사람());
// 출력 : 사람이 진찰합니다.
진찰하다(new 원숭이());
// 출력 : 원숭이가 진찰합니다.
진찰하다(new 치타());
// 출력 : 치타가 진찰합니다.
진찰하다(new 기린());
// 출력 : 기린이 진찰합니다.
진찰하다(new 로봇());
// 출력 : 로봇이 진찰합니다.
진찰하다(new 삼성());
// 출력 : 삼성이 진찰합니다.
}
public static void 진찰하다(의사 a의사) {
a의사.진찰();
}
}
- 다음은 클래스와 인터페이스를 추가한 코드이다.
interface 의사 {
public void 진찰();
}
class 사람 implements 의사 {
public void 진찰() {
진료();
}
void 진료() {
System.out.println("사람이 진찰합니다.");
}
}
class 원숭이 implements 의사 {
public void 진찰() {
진료();
}
void 진료() {
System.out.println("원숭이가 진찰합니다.");
}
}
class 치타 implements 의사 {
public void 진찰() {
진료();
}
void 진료() {
System.out.println("치타가 진찰합니다.");
}
}
class 기린 implements 의사 {
public void 진찰() {
진료();
}
void 진료() {
System.out.println("기린이 진찰합니다.");
}
}
class 로봇 implements 의사 {
public void 진찰() {
진료();
}
void 진료() {
System.out.println("로봇이 진찰합니다.");
}
}
class 삼성 implements 의사 {
public void 진찰() {
진료();
}
void 진료() {
System.out.println("삼성이 진찰합니다.");
}
}
- 먼저 Main 클래스에서 진찰하다() 메서드를 살펴보면 매개변수로 (의사 a의사)을 받고있는 것으로 보아 메서드를 호출할 때 인자로 사람, 원숭이, 치타, 기린, 로봇, 삼성이라는 객체를 생성해서 넘길 때 a의사에 해당 객체의 리모콘을 넣고 있다는 것을 알 수 있다.
- 그렇다면 a의사를 통해 진찰() 메서드를 호출하게 되면 각 연결된 객체에 맞게 출력문이 출력되야 한다.
- 우리가 알 수 있는 것은 모든 클래스는 의사에게 상속을 받는다는 것이고 이 때 인터페이스를 사용한 이유는 다형성 때문이다.
- 인터페이스와 implements를 통한 상속을 구현함으로써 각 클래스가 가진 고유의 메서드를 훼손하지 않고도 의사라는 객체가 다형성을 가질 수 있도록 하는 것이다.
반응형
'Java > Java' 카테고리의 다른 글
[Java] Stream (0) | 2024.05.02 |
---|---|
[Java] 예외처리 / 접근제한자 (0) | 2024.03.19 |
[Java] 제네릭 / HashMap / 정리 (0) | 2024.03.18 |
[Java] ArrayList (2) | 2024.03.18 |
[Java] 배열 / toString / equals / StringBuilder (2) | 2024.03.14 |