TIL-23-02-26
Updated:
추상클래스(abstract class)
추상클래스란?
- 클래스가 설계도라면 추상클래스는 미완성 설계도(미완성 메서드를 포함하고 있다는 의미)
- 완성된 설계도가 아니므로 인스턴스 생성은 불가하고, 새로운 클래스를 작성하는데 도움을 줄 목적으로 작성한다.
- 예를 들어 TV 제품을 구현할 때 서로 다른 설계도를 따로 그리는 것보다, 공통부분만 그린 미완성 설계도를 그려놓고 각각의 제품을 완성하는 것이 효율적이다.
//추상클래스의 선언
abstract class 클래스이름 {
...
}
추상메서드(abstract method)
- 선언부만 작성하고 구현부는 없는 메서드
/*주석을 통해 어떤 기능을 수행할 목적으로 작성하였는지 설명한다.*/
abstract 리턴타입 메서드이름();
- 해당 기능이 꼭 필요하지만 자손마다 다르게 구현될 것으로 예상되는 경우에 사용한다.
- 추상클래스를 상속받는 자손클래스는 오버라이딩을 통해 추상메서드를 모두 구현해야 한다.
- 추상메서드 중 일부만 구현하는 경우, 자손클래스 역시 추상클래스로 지정해야 한다.
abstract class Player { // 추상클래스
abstract void play(int pos) // 추상메서드
abstract void stop(); // 추상메서드
}
class AudioPlayer extends Player {
void play(int pos) { } // 추상메서드를 구현
void stop() { } // 추상메서드를 구현
}
abstract class AbstractPlayer extends Player {
void play(int pos) { } // 추상메서드를 구현
}
- 메서드를 작성할 때 구현부보다 더 중요한 부분이 선언부이다.
- 메서드의 이름과 작업에 필요한 매개변수, 리턴타입을 결정하는 것만으로도 메서드의 절반 이상이 완성된 것이다.
추상클래스의 작성
- 여러 클래스에 공통적으로 사용될 수 있는 추상클래스를 바로 작성하거나, 기존 클래스의 공통 부분을 뽑아서 추상클래스를 만든다.
- 아래는 공통부분인 move 메서드를 추상메서드로 구현했다. 모든 유닛은 이동할 수 있어야하므로 move 메서드가 반드시 필요하다. 따라서 자손 클래스에서 추상메서드를 반드시 구현해야하므로 자신의 클래스에 알맞게 구현이 될 것이다.
/*기존 클래스 작성*/
class Marine {
int x, y;
void move(int x, int y) {/*지정된 위치로 이동*/}
void stop() {/*현재 위치에 정지*/}
void stimPark() {/*스팀팩을 사용한다.*/}
}
class Tank {
int x, y;
void move(int x, int y) {/*지정된 위치로 이동*/}
void stop() {/*현재 위치에 정지*/}
void changeMode() {/*공격모드를 변환한다.*/}
}
class DripShip {
int x, y;
void move(int x, int y) {/*지정된 위치로 이동*/}
void stop() {/*현재 위치에 정지*/}
void unload() {/*선택된 대상을 내린다.*/}
}
/*추상클래스로 구현*/
abstract class Unit {
abstract void move(int x, int y);
void stop() {/*현재 위치에 정지*/}
}
class Marine extends Unit {
void move(int x, int y) {/*지정된 위치로 이동*/}
void stimPark() {/*스팀팩을 사용한다.*/}
}
class Tank extends Unit{
void move(int x, int y) {/*지정된 위치로 이동*/}
void changeMode() {/*공격모드를 변환한다.*/}
}
class DripShip extends Unit{
void move(int x, int y) {/*지정된 위치로 이동*/}
void unload() {/*선택된 대상을 내린다.*/}
}
- 클래스들 간 공통조상이 있으면 아래처럼 조상 클래스타입 배열에 자손 인스턴스를 담을 수 있다.
- 메서드는 실제 인스턴스에 구현된 것이 호출되므로, Unit 클래스의 move 메서드를 호출하면 각 자손클래스의 인스턴스 메서드가 호출된다.
Unit[] group = new Unit[4];
group[0] = new Marine();
group[1] = new Tank();
group[2] = new Marine();
group[3] = new DripShip();
for(int i = 0; i <group.length; i++)
group[i].move(100, 200); //Unit 배열의 모든 유닛을 좌표(100, 200)의 위치로 이동한다.
인터페이스
인터페이스란?
- 추상 메서드의 집합.
- 구현된 것은 아무것도 없고 밑그림만 그려져 있는 ‘기본설계도’, 껍데기이다.(모든 멤버가 public)
- 미리 정해진 규칙에 맞게 구현되도록 표준을 제시하는 데 사용된다.
인터페이스 vs 추상메서드 차이
- 추상메서드는 일반클래스로서 생성자와 iv(멤버변수)를 가질 수 있지만, 인터페이스는 구현된 것이 전혀 없고 추상 메서드만 나열된 것
인터페이스의 작성
- ‘class’ 대신 ‘interface’를 사용한다는 것 외에 클래스 작성과 동일하다.
- 구성요소(멤버)는 추상메서드와 상수만 가능하다.
모든 멤버변수는 public static final이어야 하며, 이를 생략할 수 있다. 모든 메서드는 public abstract 이어야 하며, 이를 생략할 수 있다.
interface PlayingCard {
public static final int SPACE = 4;
final int DIAMOND = 3; // public static final int DIAMOND = 3;
static int HEART = 2; // public static final int HEART = 2;
int CLOVER = 1; // public static final int CLOVER = 1;
public abstract String getCardNumber ();
String getCardKind(); // public abstract String getCardKind();
}
인터페이스의 상속
- 인터페이스도 클래스처럼 상속이 가능하다. (클래스와 달리 다중상속 허용)
interface Moveable {
/* 지정된 위치(x,y)로 이동하는 기능의 메서드*/
void move (int x, int y);
}
interface Attackable {
/* 지정된 대상(u)을 공격하는 기능의 메서드*/
void attack(Unit u);
}
interface Fightable extends Movable, Attackabe { }
- 인터페이스는 Object클래스와 같은 최고 조상이 없다.
인터페이스의 구현
- 인터페이스를 구현하는 것은 클래스를 상속받는 것과 같다.
- 다만, ‘extends’ 대신 ‘implements’를 사용한다.
class 클래스이름 implements 인터페이스이름 {
//인터페이스에 정의된 추상메서드를 구현해야한다.
}
- 인터페이스에 정의된 추상메서드를 완성해야 한다.
class Fighter implements Fightable {
public void move() { /*내용 생략*/ }
public void attack() { /*내용 생략*/ }
}
interface Fightable {
void move(int x, int y);
void attack(Unit u);
}
- 상속과 구현이 동시에 가능하다.
class Fighter extends Unit implements Fightable {
public void move(int x, int y) { /*내용 생략*/ }
public void attack(Unit u) { /*내용 생략*/ }
}
인터페이스를 이용한 다형성
- 인터페이스 타입의 변수로 인터페이스를 구현한 클래스의 인스턴스를 참조할 수 있다.
class Fighter extends Unit implements Fightable {
public void move(int x, int y) { /*내용 생략*/}
public void attack(Fightable f) { /*내용 생략*/}
}
Fighter f = new Fighter();
Fightable f = new Fighter();
- 인터페이스를 메서드의 매개변수 타입으로 지정할 수 있다.
void attack(Fightable f) { //fightable인터페이스를 구현한 클래스의 인스턴스를 매개변수로 받는 메서드
//...
}
- 인터페이스를 메서드의 리턴 타입으로 지정할 수 있다.
Fightable method(){ // Fightable인터페이스를 구현한 클래스의 인스턴스를 반환
//...
return new Fighter();
}
인터페이스의 장점
- 개발시간을 단축시킬 수 있다.
- 인터페이스가 작성되면, 이를 사용해서 프로그램을 작성하는 것이 가능하다. 메서드를 호출하는 쪽에서는 메서드의 내용에 관계없이 선언부만 알면 되기 때문이다.
- 동시에 다른 한 쪽에서는 인터페이스를 구현하는 클래스를 작성하도록 하여, 인터페이스를 구현하는 클래스가 작성될 때까지 기다리지 않고도 양쪽에서 동시에 개발을 할 수 있다.
- 표준화가 가능하다.
- 프로젝트에 사용되는 기본 틀을 인터페이스로 작성한 다음, 개발자들에게 인터페이스를 구현하여 프로그램을 작성하도록 함으로써 보다 일관되고 정형화된 프로그램의 개발이 가능하다.
- 서로 관계없는 클래스들에게 관계를 맺어 줄 수 있다.
- 서로 상속관계에 있지도 않고 같은 조상클래스를 가지고 있지 않은 서로 아무 관계도 없는 클래스들에게 하나의 인터페이스를 공통적으로 구현하도록 하여 관계를 맺어줄 수 있다.
- 독립적인 프로그래밍이 가능하다.
- 인터페이스를 사용하면 클래스의 선언과 구현을 분리시킬 수 있으므로 실제구현에 독립적인 프로그램을 작성하는 것이 가능하다.
- 클래스와 클래스간의 직접적인 관계를 인터페이스를 이용해서 간접적인 관계로 변경하면, 한 클래스의 변경이 관련된 다른 클래스에 영향을 미치지 않는 독립적인 프로그래밍이 가능하다.
인터페이스의 이해
- 두 대상(객체) 간의 ‘연결, 대화, 소통’을 돕는 ‘중간 역할’을 한다.
- 선언(설계)와 구현을 분리시키는 것을 가능하게 한다.
class B { public void method() { System.out.println("methodInB"); } }
인터페이스로 구현
interface I {
public void method();
}
class B implements I {
public void method() {
System.out.println("methodInB");
}
}
디폴트 메서드와 static메서드
- 인터페이스에 디폴트 메서드, static메서드 추가 가능(JDK1.8부터)
디폴트 메서드
- 인터페이스에 메서드를 추가하면, 이 인터페이스를 구현한 모든 클래스들이 새로 추가된 메서드를 구현해야한느 번거로움이 있었다.
- 디폴트 메서드를 사용하면 해당 인터페이스를 구현한 클래스를 변경하지 않아도 된다.
- 디폴트 메서드는 앞에 키워드 default를 붙이며, 일반 메서드처럼 몸통{}이 필요하다.
interface MyInterface {
void method();
void newMethod(); // 추상 메서드
}
/*디폴트 메서드로 메서드 추가*/
interface MyInterface {
void method();
default void newMethod() {}
}
- 새로 추가된 디폴드 메서드가 기존의 메서드와 이름이 중복되어 충돌하는 경우, 규칙은 다음과 같다.
- 여러 인터페이스의 디폴트 메서드 간의 충돌
- 인터페이스를 구현한 클래스에서 디폴트 메서드를 오버라이딩해야 한다.
- 디폴트 메서드와 조상 클래스 메서드 간의 충돌
- 조상 클래스의 메서드가 상속되고, 디폴트 메서드는 무시된다.
- 간단하게, 필요한 쪽의 메서드와 같은 내용으로 오버라이딩하면 된다.
내부 클래스
- 내부 클래스란 클래스 내에 선언된 클래스이다.
- 클래스에 다른 클래스를 선언하는 이유는 두 클래스가 서로 긴밀한 관계가 있기 때문이다.
- 내부 클래스로 선언하면 1. 내부 클래스에서 외부 클래스의 멤버들을 쉽게 접근할 수 있고, 2. 코드의 복잡성을 줄일 수 있다.(캡슐화)
class A { // 외부 클래스
...
class B { // 내부 클래스
...
}
...
}
내부 클래스의 종류와 특징
- 내부 클래스의 종류는 변수의 선언위치에 따른 종류와 같다.
내부 클래스 | 특징 |
---|---|
인스턴스 클래스 | 외부 클래스의 멤버변수 선언위치에 선언하며, 외부 클래스의 인스턴스멤버들과 관련된 작업에 사용된다. |
스태틱 클래스 | 외부 클래스의 멤버변수 선언위치에 선언하며, 외부 클래스의 static 멤버 특히 static 메서드에서 사용될 목적으로 선언된다. |
지역 클래스 | 외부 클래스의 메서드나 초기화블럭에 선언하며, 선언된 영역 안에서만 사용될 수 있다. |
익명 클래스 | 클래스의 선언과 객체의 생성을 동시에 하는 이름없는 클래스(일회용) |
내부 클래스의 제어자와 접근성
- 내부 클래스도 클래스이므로 abstact나 final과 같읕 제어자를 사용할 수 있고, 멤버변수처럼 private, protected 접근제어자도 사용 가능하다.