목차
07 추상 클래스, 인터페이스, 중첩 클래스
7.1 추상 클래스
- 완전하게 구현되어 있지 않은 메소드를 가지고 있는 클래스
- 객체를 생성할 수 없다.
- 상속 계층에서 추상적인 개념을 나타내기 위한 용도
- 하나 이상의 추상 메소드를 가지고 있어야 한다.
- 보통의 메소드도 가질 수 있다.
추상 클래스의 용도
- 결과는 같지만 서브 클래스에서 구현을 강제하는 면에서 장점이 있다.
7.2 인터페이스
- 컴퓨터 하드웨어에서 인터페이스는 서로 다른 장치들이 연결되어서 상호 데이터를 주고받는 규격을 의미.
인터페이스의 용도
- 상속 관계가 아닌, 클래스 간의 유사성을 인코딩하는 데 사용.
- 추상화 : 메소드 시그니처만 공개하는 것은 사용자에게 메소드 구현을 숨김으로써 추상화에 도움을 준다.
- 다중 상속 : 기존 방식의 다중 상속은 다이아몬드 문제를 발생. 인터페이스는 이러한 문제를 해결할 수 있다.
- 느슨한 결합 : 메소드와 메소드 시그니처를 따로 정의할 수 있어서 클래스들이 완전히 독립적이다.
인터페이스의 필요성과 예
- 가전 제품을 예를 들어보자
- 가전 제품은 기본적으로 원격 제어할 수 있는 소프트웨어를 내장하여 제공된다.
- 그렇다면 홈 네트워크 서버에서는 모든 가전 제품을 원격 제어할 수 있는 통일된 방법이 필요하다.
- 따라서 원격으로 제어하는 데 필요한 메소드들에 대하여 합의 하여야 하는데,
- 외부에서 메소드를 호출해서 사용할 수 있으면 되고,
- 그것이 인터페이스 개념이다.
인터페이스 정의
- class 대신 interface 사용
- 메소드들은 모두 이름과 매개 변수만 존재.
- 선언되는 메소드들은 묵시적으로 public abstract이다.
인터페이스 구현
- 인터페이스 안에는 구현되지 않은 메소드가 존재하기 때문에 객체를 생성할 수 없다.
- 인터페이스는 다른 클래스에 의하여 구현(implement)될 수 있다.
- 인터페이스를 어떤 클래스가 사용하기 위해서는 인터페이스에 포함된 모든 추상 메소드를 구현하여야 한다.
인터페이스 vs 추상 클래스
- 객체화할 수 없고, 주로 구현이 안 된 메소드들로 이루어져 있는 유사점이 있다.
- 하지만 추상 클래스는 일반적인 필드를 선언할 수 있고, 일반적인 메소드도 정의할 수 있다.
- 인터페이스의 경우 여러 개 구현할 수 있다.
- 추상 클래스를 사용하면 좋은 경우
- 관련된 클래스들 사이에서 코드를 공유할 때
- 공통적인 필드나 메소드의 수가 많은 경우, 또는 public 이외의 접근 지정자를 사용해야 하는 경우
- 정적이 아닌 필드나 상수가 아닌 필드를 선언하기를 원할 때
- 인터페이스를 사용하면 좋은 경우
- 관련 없는 클래스들이 동일한 동작을 구현할 때
- 특정한 자료형의 동작을 지정하고 싶지만 누가 구현하든지 신경쓸 필요가 없을 때
- 다중 상속이 필요할 때
인터페이스와 타입
- 인터페이스를 정의하는 것은 새로운 자료형을 정의하는 것과 마찬가지. 즉, 인터페이스 이름을 자료형처럼 사용가능
- 참조 변수를 정의하는 데 사용할 수 있다. → 이 인터페이스를 구현한 객체라면 어떤 것이라도 참조 변수에 대입가능
7.3 인터페이스를 이용한 다중 상속
인터페이스끼리도 상속이 가능하다
- 기존의 인터페이스에 새로운 메소드를 추가하면 이미 구현된 클래스들이 동작하지 않게된다
- 이런 경우에는 새로운 인터페이스를 만들어서 기존의 인터페이스를 상속 받는다
인터페이스를 이용한 다중 상속
- 다중 상속은 불가능하니 인터페이스 여러개를 동시 구현 해도되고
- 단일 상속과 인터페이스 구현을 해도된다.
상수 정의
- 구현해서 사용 가능
7.4 디폴트 메소드와 정적 메소드
디폴트 메소드
- 인터페이스 개발자가 메소드의 디폴트 구현을 제공할 수 있는 기능
- 인터페이스를 구현한 클래스가 반드시 오버라이딩하지 않아도 된다
- 장점 : 기존에 작성된 인터페이스에 메서드를 추가하고 싶을때 디폴트 메서드를 쓰면 문제를 일으키지 않고 추가가능
정적 메소드
- 인터페이스의 정적 메소드
최근에 인터페이스에서도 팩토리 메소드가 있는 것이 좋다고 간주되고 있다.
팩토리 메소드는 공장처럼 객체를 생성하는 정적 메소드로서 객체를 만드는 부분을 부모 클래스에 위임하는 디자인 패턴이다.
7.5 중첩 클래스
중첩 클래스의 종류
- 정적 중첩 클래스 : 앞에 static이 붙어서 내장되는 클래스
- 비정적 중첩 클래스 : static이 붙지 않은 일반적인 중첩 클래스
- 내부 클래스(inner class) : 클래스의 멤버처럼 선언되는 중첩 클래스
- 지역 클래스(local class) : 메소드의 몸체 안에서 선언되는 중첩 클래스
- 익명 클래스(anonymouse class) : 수식의 중간에서 선언되고 바로 객체화되는 클래스
내부 클래스
- 클래스 안에 클래스를 선언하는 경우
- 내부 클래스는 외부 클래스의 인스턴스 변수와 메소드를 전부 사용할 수 있다.
- private으로 선언되어 있어도 접근 가능함
- 외부 클래스 바깥에서 내부 클래스 객체를 생성하려면 생소한 문법을 사용해야함
...
OuterClass outer = new OuterClass();
OuterClass.InnerClass inner = outer.new InnerClass();
...
지역 클래스
- 메소드 안에 정의되는 클래스
- 이 메소드는 접근 제어 지정자를 가질 수 없다. 지역 변수도 final로 선언해야함
- 지역 클래스는 abstract 또는 final로만 지정할 수 있다.
중첩 클래스를 사용하는 이유
- 중첩 클래스는 외부 클래스의 멤버가 private으로 선언되어 있더라도 접근할 수 있다.
- 중첩 클래스는 외붕서 보이지 않는다. 즉 철저하게 감춰진다.
- 익명 클래스는 콜백 메소드를 작성할 때 아주 편리하다.
7.6 익명 클래스
- 클래스 몸체는 정의되지만 이름이 없는 클래스이다.
- 클래스를 정의하면서 동시에 객체를 생성하게됨
- 이름이 없기 때문에 한 번만 사용가능
부모클래스 참조변수 = new 부모클래스(){
// 클래스 구현
}
- 익명 클래스도 필드와 다른 메소드들을 정의할 수 있다. 단, final 변수만 사용가능
- 주로 GUI 이벤트 처리기를 구현하는 경우에 많이 사용
Mini Project 큐(Queue)
선입선출 방식으로 추가 제거되는 자료구조
정수를 추가 및 제거하는 메소드를 사용하여 Queue 인터페이스 설계
q.dequeue() : Queue에서 하나의 항목을 삭제하고 반환
q.enqueue(item) : Queue에서 하나의 항목을 추가
q.isEmpty() : Queue가 비어 있는지 검사
MyQueue 클래스 작성
배열사용 → 꽉차면 더큰 배열 만들고 복사
package practice.queue;
public interface Queue {
int dequeue();
void enqueue(int data);
boolean isEmpty();
}
package practice.queue;
public class MyQueue implements Queue {
int[] queue;
int size;
final int ADD_SIZE = 10;
public MyQueue() {
size += ADD_SIZE;
queue = new int[size];
}
@Override
public int dequeue() {
if (queue[0] == 0) {
System.out.println("큐가 비어있음");
}
int dequeue = queue[0];
for (int i = 1; i < queue.length; i++) {
queue[i - 1] = queue[i];
}
queue[size - 1] = 0;
return dequeue;
}
@Override
public void enqueue(int data) {
for (int i = 0; i < queue.length; i++) {
if (queue[i] == 0) {
queue[i] = data;
return;
}
}
addQueue();
queue[size - ADD_SIZE] = data;
}
@Override
public boolean isEmpty() {
for (int i = 0; i < queue.length; i++) {
if (queue[i] == 0) {
System.out.println("큐에 빈공간이" + (queue.length - i) + "개 있음");
return true;
}
}
System.out.println("큐가 가득참");
return false;
}
private void addQueue() {
int[] tempQueue = new int[size];
for (int i = 0; i < queue.length; i++) {
tempQueue[i] = queue[i];
}
queue = new int[size + ADD_SIZE];
size += ADD_SIZE;
for (int i = 0; i < tempQueue.length; i++) {
queue[i] = tempQueue[i];
}
}
}
package practice.queue;
public class QueueTest {
public static void main(String[] args) {
MyQueue q = new MyQueue();
q.enqueue(1);
q.enqueue(2);
q.enqueue(3);
q.enqueue(4);
q.enqueue(5);
q.enqueue(6);
q.enqueue(7);
q.enqueue(8);
q.enqueue(9);
q.enqueue(10);
System.out.println(q.queue.length);
q.enqueue(11);
System.out.println(q.queue.length);
MyQueue q2 = new MyQueue();
q2.enqueue(1);
q2.enqueue(2);
q2.enqueue(3);
q2.isEmpty();
System.out.println(q2.dequeue());
q2.isEmpty();
System.out.println(q2.dequeue());
q2.isEmpty();
System.out.println(q2.dequeue());
q2.isEmpty();
}
}
10
20
큐에 빈공간이7개 있음
1
큐에 빈공간이8개 있음
2
큐에 빈공간이9개 있음
3
큐에 빈공간이10개 있음
Summary
- 인터페이스는 추상 메소드만을 담고 있는 특수한 클래스이다.
- 인터페이스는 interface 키워드로 정의한다.
- 인터페이스를 상속받는 클래스는 반드시 인터페이스 안에 정의된 추상 메소드들을 구현하여야 한다.
즉, 메소드의 몸체를 만들어야 한다. - 인터페이스를 이용하면 다중 상속의 효과를 낼 수 있다. 인터페이스 안에는 필드가 없기 때문에 다중 상속에서 발생하는 복잡한 문제가 발생하지 않는다.
- 디폴트 메서드는 인터페이스 개발자가 메소드의 디폴트 구현을 제공할수 있는 기능이다. 디폴트 메소드가 정의되어 있으면 인터페이스를 구현하는 클래스가 메소드의 몸체를 구현하지 않아도 메소드를 호출할 수 있다.
- 중첩 클래스는 클래스 안에 정의되는 클래스를 나타낸다.
- 중첩 클래스에는 내부 클랫, 지역 클래스, 익명 클래스 등이 있다.
- 중첩 클래스를 사용하는 이유는 클래스 안의 멤버들을 자유롭게 접근하기 위해서이다.
- 익명 클래스는 이름이 없이 생성되고 사라지는 클래스이다. 클래스 작성과 객체 생성이 동시에 이루어진다.
'Java > 교재 정리' 카테고리의 다른 글
*16 멀티 스레딩 (1) | 2024.05.02 |
---|---|
08 자바 API 패키지, 예외 처리, 모듈 (0) | 2024.04.27 |
06 상속 (1) | 2024.04.23 |
05 클래스와 객체 II (0) | 2024.04.18 |
04 클래스와 객체 I (0) | 2024.04.15 |