이전 글 확인하기 : https://challenging.tistory.com/99
6. 추상클래스
6.1 추상클래스란?
추상클래스는 추상메서드를 포함하고 있다는 것을 제외하고는 일반클래스와 다르지 않다. 동일하게 생성자와 멤버변수, 메서드를 가질 수 있다. 추상클래스는 상속을 통해서 구현해줘야 하고 그 자체로는 클래스로서의 역할을 다 하지 못한다.
6.2 추상메서드
추상메서드 : 선언부만 작성하고 구현부는 작성하지 않은 채로 남겨 둔 것. 미완성 메서드
/* 주석을 통해 어떤 기능을 수행할 목적으로 작성하였는지 설명 */
abstract 리턴타입 메서드이름();
메서드를 사용하는 쪽에서는 메서드가 실제로 어떻게 구현되어 있는지 몰라도 메서드의 이름과 매개변수, 리턴타입 (= 선언부)만 알고 있으면 되므로 내용이 없을 지라도 추상메서드를 사용하는 코드를 작성할 수 있다. 실제로는 자손클래스에 구현된 완성된 메서드가 호출되도록 할 수 있다.
6.3 추상클래스의 작성
- 추상화 : 클래스 간의 공통점을 찾아내서 공통의 조상을 만드는 작업
- 구체화 : 상속을 통해 클래스를 구현, 확장하는 작업
자손클래스에서 알아서 오버라이딩하여 자신의 클래스에 맞게 구현할 수도 있는데 추상메서드를 선언하는 이유는 자손클래스에서 추상메서드를 반드시 구현하도록 강요하기 위해서이다. 아래 사진처럼 IDE 에서도 추상클래스를 상속받으면서 추상메서드를 구체화하지 않으면 오류를 보여준다.
7. 인터페이스
7.1 인터페이스란?
인터페이스는 일종의 추상클래스이다. 추상클래스처럼 추상메서드를 갖지만 추상클래스보다 추상화 정도가 높아서 일반 메서드나 멤버변수를 구성원으로 가질 수 없다. 오로지 추상메서드와 상수만을 멤버로 가질 수 있다. 추상클래스는 미완성 설계도였다면 인터페이스는 기본 설계도라고 할 수 있다. 역시 완성되지 않은 불완전한 것이기 때문에 다른 클래스를 작성하는데 도움 줄 목적으로 작성된다.
7.2 인터페이스의 작성
// 작성방법
interface 인터페이스이름{
public static final 타입 상수이름 = 값;
public abstract 메서드이름 (매개변수 목록);
}
제약 사항
- 모든 멤버변수는 public static final이어야 하며 이를 생략할 수 있다.
- 모든 메서드는 public abstract 이어야 하며 이를 생략할 수 있다. (단, JDK1.8부터는 static 메서드와 디폴트 메서드를 추가할 수 있다)
7.3 인터페이스의 상속
인터페이스는 인터페이스로부터만 상속을 받을 수 있다. 클래스와는 달리 다중상속이 가능하다
interface Movable {
/** 지정된 위치 (x, y)로 이동하는 기능의 메서드 */
void move(int x, int y);
}
interface Attackable {
/** 지정된 대상 (u)를 공격하는 기능의 메서드 */
void attack(Unit u);
}
interface Fightable extends Movable, Attackable { }
7.4 인터페이스의 구현
인터페이스의 구현은 다음과 같이 진행한다. 아래의 경우 'Fighter 클래스는 Fightable 인터페이스를 구현한다'라고 표현한다.
class Fighter implements Fightable {
public void move(int x, int y) { /*내용 생략*/ }
public void attack(Unit u) { /*내용 생략*/ }
}
만일 인터페이스의 메서드 중 일부만 구현한다면 클래스에 abstract를 붙여 추상클래스를 선언해줘야 한다.
그리고 다음과 같이 상속과 구현을 동시에 할 수도 있다.
abstract class Fighter extends Unit implements Fightable {
public void move(int x, int y) { /*내용 생략*/ }
}
※ 인터페이스의 이름에는 주로 Fightable과 같이 able로 끝나는 경우가 많다. 어떠한 기능 또는 행위를 하는데 필요한 메서드를 제공한다는 의미를 강조하기 위해서이다. 또한 그 인터페이스를 구현한 클래스는 '~를 할 수 있는' 능력을 갖추었다 라는 의미로 이해할 수도 있다.
오버라이딩을 할 때는 조상의 메서드보다 넓은 범위의 접근 제어자를 지정해야 한다. Movable 인터페이스에 void move(...) 앞에는 사실 public abstract가 생략되어 있기 때문에 구현체인 Fighter 클래스에서는 public void move(...)이 되는 것이다.
7.5 인터페이스를 이용한 다중상속
인터페이스가 다중상속을 허용하기는 하지만 C++에서는 다중상속을 허용하는데 자바는 하지 않는다는 단점을 극복하기 위한 대응으로 제공하는 수단일 뿐이고 실제로 다중상속을 구현하는 경우는 거의 없다. 따라서 이 부분은 가능하다는 점만을 알고 인터페이스를 이용하면 다형적 특성을 이용할 수 있다는 점을 짚으며 넘어가면 될 듯 하다. (인터페이스를 새로 작성하지 않고도 클래스의 멤버 변수로 지정하는 것으로도 해결이 가능하기 때문이다)
7.6 인터페이스를 이용한 다형성
다형성 부분에서 자손클래스의 인스턴스를 조상타입의 참조변수로 참조하는 것이 가능하다는 것을 배웠다. 인터페이스 역시도 조상 클래스라고 할 수 있으므로 인터페이스 타입의 참조변수로 이를 구현한 클래스의 인스턴스를 참조할 수 있으며 인터페이스 타입으로 형변환도 가능하다.
Fightable f = (Fightable) new Fighter();
Fightable f = new Fighter();
따라서 인터페이스는 메서드의 매개변수 타입으로도 사용될 수 있다.
void attack (Fightable f){
...
}
매개변수가 인터페이스인 것의 의미는 메서드 호출 시 해당 인터페이스를 구현한 클래스의 인스턴스를 매개변수로 제공해야 한다는 것이다. 그리고 다음과 같이 메서드의 리턴타입으로 인터페이스의 타입을 지정하는 것도 역시 가능하다.
Fightable method(){
...
Fighter f = new Fighter();
return f;
}
리턴타입이 인터페이스라는 것은 메서드가 해당 인터페이스를 구현한 클래스의 인스턴스를 반환한다는 것을 의미한다. 이러한 장점은 특히 분산환경 프로그래밍에서 그 위력을 발휘한다. 사용자 컴퓨터에 설치된 프로그램을 변경하지 않고 서버측의 변경만으로도 사용자가 새로 개정된 프로그램을 사용하는 것이 가능하다.
7.7 인터페이스의 장점
- 개발 시간을 단축시킬 수 있다 : 인터페이스를 구현하는 클래스를 작성하게 되면, 인터페이스를 구현하는 클래스가 작성될 때까지 기다리지 않고도 양쪽(구현체 개발, 구현체 클래스 응용하는 부분 개발)에서 동시에 개발을 진행할 수 있다.
- 표준화가 가능하다
- 서로 관계없는 클래스들에게 관계를 맺어줄 수 있다
- 독립적인 프로그래밍이 가능하다 : 클래스의 선언과 구현을 분리시킬 수 있기 때문에 독립적인 프로그램을 작성하는 것이 가능하다. 클래스 - 클래스 간의 직접적인 관계를 인터페이스를 이용한 간접적인 관계로 변경하면, 한 클래스의 변경이 관련된 다른 클래스에 영향을 미치지 않는 독립적인 프로그래밍이 가능하다.
7.8 인터페이스의 이해
인터페이스를 이해하기 전 알아야 할 두 가지 사항
- 클래스를 사용하는 쪽(User)과 클래스를 제공하는 쪽(Provider)이 있다.
- 메서드를 사용(호출)하는 쪽(User)에서는 사용하려는 메서드(Provider)의 선언부분만 알면 된다. (로직 처리는 몰라도 된다)
class A {
void autoPlay(I i){
i.play();
}
}
interface I {
void play();
}
class B implements I {
public void play() {
System.out.println("play in B class");
}
}
class InterfaceTest {
public static void main(String[] args) {
A a = new A();
a.autoPlay(new B());
}
}
이런 코드로 작성하게 되면 A는 매개변수를 통해서 인터페이스 I를 구현한 클래스의 인스턴스를 동적으로 제공받아야 한다.
혹은 제 3의 클래스를 통해서 제공받을 수도 있다.
class InterfaceTest2 {
public static void main(String[] args) {
A a = new A();
a.methodA(InstanceManager.getInstance());}}
class A {
void methodA(){
InstanceManager.getInstance().methodB();}}
interface I {
void methodB();}
class B implements I {
public void methodB() {
System.out.println("methodB in B class");}}
class InstanceManager{
public static I getInstance(){
return new B();}} // 다른 인스턴스로 교환 시 여기만 교체
인스턴스를 직접 생성하지 말고 getInstance() 메서드를 통해 제공받는다. 이렇게 하면 나중에 다른 클래스의 인스턴스로 변경되어도 A 클래스의 변경 없이 getInstance()만 변경하면 된다.
7.9 디폴트 메서드와 static 메서드
디폴트 메서드
인터페이스에 메서드를 추가하는 일은 추상 메서드를 추가하는 것이고, 이 인터페이스를 구현한 기존의 모든 클래스들이 새로 추가된 메서드를 구현해야 한다는 말이다. 따라서 인터페이스의 변경은 최소화하는 것이 좋다. 하지만 설계를 잘했다고 해도 변경이 발생할 수 있다. 이럴 때 디폴트 메서드를 사용할 수 있다. 이는 추상 메서드의 기본적인 구현을 제공하는 메서드로, 추상 메서드가 아니기 때문에 디폴트 메서드가 새로 추가되어도 모든 구현 클래스에 메서드를 추가하지 않아도 된다.
interface I {
void method();
default void newMethod() {};
}
디폴트 메서드 역시 접근 제어자가 public이다.
새로 추가된 디폴트 메서드가 기존의 메서드와 이름이 중복되어 충돌하는 경우가 발생할 수 있는데, 이 충돌을 해결하는 규칙은 다음과 같다.
- 여러 인터페이스의 디폴트 메서드 간 충돌 : 인터페이스를 구현한 클래스에서 디폴트 메서드를 오버라이딩 해야 한다.
- 디폴트 메서드와 조상 클래스의 메서드 간의 충돌 : 조상 클래스의 메서드가 상속되고 디폴트 메서드는 무시된다.
혹은 그냥 필요한 쪽의 메서드와 같은 내용으로 오버라이딩 해버리면 그만이다. 즉 구현 클래스에서 선택한 인터페이스의 default 메서드에 적힌 내용을 그대로 갖다 적는 것이다.
8. 내부 클래스
8.1 내부 클래스란?
내부 클래스는 클래스 내부에 선언된 클래스이다. 두 클래스가 서로 긴밀한 관계에 있기 때문에 클래스 안에 클래스를 선언하게 된다.
내부 클래스의 장점은 다음과 같다.
- 내부 클래스에서의 외부 클래스의 멤버들을 쉽게 접근할 수 있다.
- 코드의 복잡성을 줄일 수 있다. (캡슐화 - 외부에는 불필요한 클래스를 감춤)
8.2 내부 클래스의 종류와 특징
내부 클래스 | 특징 |
인스턴스 클래스 | 외부 클래스의 멤버변수 선언위치에 선언하며, 외부 클래스의 인스턴스 멤버처럼 다뤄진다. 주로 외부 클래스의 인스턴스 멤버들과 관련된 작업에 사용될 목적으로 선언된다. |
static 클래스 | 외부 클래스의 멤버변수 선언위치에 선언하며 외부 클래스의 static 멤버처럼 다루어진다. 주로 외부 클래스의 static 멤버, 특히 static 메서드에서 사용될 목적으로 선언된다. |
지역 클래스 | 외부 클래스의 메서드나 초기화 블럭 안에 선언하며 선언된 영역 내부에서만 사용될 수 있다. |
익명 클래스 | 클래스의 선언과 객체의 생성을 동시에 하는 이름없는 일회용 클래스 |
8.3 내부 클래스의 선언
내부 클래스의 선언 방법은 다음과 같다.
class Outer {
class InstanceInner{}
static class StaticInner {}
void myMethod(){
class LocalInner {}
}
}
8.4 내부 클래스의 제어자와 접근성
내부 클래스도 클래스이기 때문에 abstract나 final과 같은 제어자를 사용할 수 있고 멤버변수와 마찬가지로 private, protected의 접근제어자도 사용이 가능하다.
내부 클래스 중에서 스태틱클래스 (StaticInner)만 static 멤버를 가질 수 있다. 내부 클래스에서 static 변수를 선언해야 한다면 스태틱클래스로 선언해야 한다. (잘 사용하지 않는다) 다만 final과 static이 동시에 붙은 변수는 상수이므로 모든 내부 클래스에서 정의가 가능하다.
8.5 익명 클래스
익명 클래스는 이름이 없다. 이름이 없어서 생성자도 가질 수 없고 조상클래스의 이름이나 인터페이스의 이름을 사용해서 정의하기 때문에 클래스 상속받는 동시에 인터페이스를 구현하거나 둘 이상의 인터페이스 구현이 불가하다.
class InnerEx {
public static void main(String[] args) {
Button b = new Button("Start");
b.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
System.out.println("ActionEvent occurred");
}
}); // 익명 클래스의 끝 -> 인터페이스의 구현클래스로 먄들어 준 것
}
}
'TIL > Java | Spring Boot' 카테고리의 다른 글
[Java] 스터디 4주차_java.lang 패키지와 날짜, 시간, 형식화 (0) | 2022.05.15 |
---|---|
[Java] 스터디 3주차_예외 처리 (exception handling) (0) | 2022.05.06 |
[Java] 스터디 2주차_객체지향프로그래밍2 (1) - 상속, 오버라이딩, 제어자, 다형성 (0) | 2022.05.01 |
[Spring boot] Jenkins를 이용한 spring boot 자동 배포 CI (0) | 2022.04.29 |
[Java] 스터디 1주차_객체지향프로그래밍1 (0) | 2022.04.10 |
댓글