5주차 내용은 혼공자의 8장 인터페이스와 9장 중첩 클래스와 중첩 인터페이스이다. 8장의 인터페이스와 7장의 상속이 내가 혼공학습단에 지원하게 된 목표인데, 자바에 대한 기초 지식 없이 스프링부트 공부를 시작했다가 앞서 두 가지의 벽에 부딛혔기 때문이다. 이번 주차에는 벽 중에 하나였던 인터페이스에 대해 자세히 공부해보았다.
8장 인터페이스
자바에서 인터페이스는 객체의 사용 방법을 정의한 타입
이다.
코드에서 인터페이스의 메소드를 호출하면 인터페이스는 객체의 메소드를 호출하는데, 이 때문에 객체의 구조를 알 필요 없이 인터페이스의 메소드만 알면 원하는 작업을 수행할 수 있다.
이는 마치 API를 사용할 때, 내부 구조를 모르는 상태로 기능을 호출하는 것과 같다.
01 인터페이스
내부 구조를 알 필요 없도록 하는 인터페이스가 왜 필요할까? 바로 코드를 수정하지 않고 프로그램에서 사용하는 객체를 변경할 수 있기 때문이다. 인터페이스를 통해 하나의 정해진 객체가 아니라 여러가지 객체를 사용
할 수 있는데, 이를 활용하는 것이다.
인터페이스 선언
클래스와 물리적인 형태(.java)는 같지만 선언 시 class 대신 interface
키워드를 사용한다.
[public] interface 이름 {...}
클래스는 멤버로 필드, 생성자, 메소드를 가지나, 인터페이스는 상수 필드와 추상 메소드
만을 멤버로 같는다. 생성자의 경우 인터페이스를 객체로 생성할 수 없기 때문에 존재하지 않는다.
상수 인터페이스 선언
인스턴스를 생성하지 않는 인터페이스 특성 상, 값이 변하지 않는 상수 필드는 가질 수 있다. 상수 필드는 아래와 같이 선언한다. public static final는 붙이지 않아도 자동으로 붙는다.
[public static final] 타입 이름 = 값;
추상 메소드 선언
인터페이스를 통해 객체의 메소드를 실행하므로 인터페이스는 추상 메소드만을 갖는다. 추상 메소드는 중괄호 블럭이 없는 메소드이다. public abstract는 자동으로 붙는다.
[public abstract] 리턴타입 메소드 이름(매개변수);
인터페이스 구현
개발 코드가 인터페이스의 메소드를 호출 → 인터페이스가 객체의 메소드 호출
위의 흐름대로 프로그램이 진행되므로 객체에는 인터페이스에 정의된 추상 메소드를 정의한 실체 메소드가 있어야 한다. 이 객체를 구현 객체
라고 하고 구현 객체를 생성하는 클래스를 구현 클래스
라고 한다.
구현 클래스
구현 클래스는 구현 객체를 생성하는 클래스로, 인터페이스의 추상 메소드를 실체 메소드로 정의해야 한다.
구현 클래스 선언은 인터페이스 타입으로 사용할 수 있음을 알리기 위해 implements
키워드를 사용한다.
public class 클래스이름 implements 인터페이스이름 {...}
예를 들어 아래 Television 클래스는 RemoteControl의 구현 클래스이다.
Television 클래스는 RemoteControl 인터페이스로 사용이 가능한데, 그렇기 위해서는 인터페이스의 추상 메소드를 정의한 실체 메소드가 있어야 한다.
public class Television implements RemoteControl
인터페이스로 구현 클래스를 사용하기 위해서는 아래 방법으로 구현 객체를 생성한다. 이 때, 구현 객체의 타입은 인터페이스 이름이다.
RemoteControl rc = new Television();
클래스이름 변수 = new 클래스이름();
과 같이 일반 객체를 생성하면 인터페이스에서 사용할 수 있는 구현 객체가 되지 못한다. 구현 객체는 인터페이스이름 변수 = new 클래스이름();
과 같이 선언해야 한다.
다중 인터페이스 구현 클래스
하나의 객체는 여러 인터페이스에서 사용할 수 있다. 인터페이스 A에서 객체에 대한 메소드를 접근할 수도 있지만 인터페이스 B에서도 접근할 수 있다는 뜻이다.
여러 인터페이스에서 사용하기 위한 구현 객체를 생성하기 위해서는, 구현 클래스에서 이를 명시해야 한다.
public class 구현클래스이름 implements 인터페이스A, 인터페이스B {
// 인터페이스 A에 있는 추상 메소드의 구현 메소드 선언
// 인터페이스 B에 있는 추상 메소드의 구현 메소드 선언
}
다른 인터페이스에서 하나의 객체에 접근하기 위해 아래처럼 코드를 작성할 수 있다.
Television tv = new Television();
RemoteControl rc = tv; // 인터페이스 A
Searchable searchable = tv; // 인터페이스 B
인터페이스 사용
클래스 선언 시, 인터페이스는 아래와 같은 상황에서 사용될 수 있다.
- 필드
- 생성자/메소드의 매개 변수
- 생성자/메소드의 로컬 변수
아래 코드는 RemoteControl 인터페이스를 사용하는 여러 예시를 보여준다.
public class MyClass {
// 1. 필드
RemoteControl rc = new Television();
// 2. 생성자의 매개 변수
MyClass(RemoteClass rc) {
this.rc = rc;
}
// 3. 메소드의 로컬 변수
void methodA() {
RemoteControl rc = new Audio();
}
// 4. 메소드의 매개 변수
void methodB(RemoteControl rc) {...}
}
- 인터페이스가 필드로 사용된 경우
new Television();
처럼 구현 객체를 대입할 수 있다.
MyClass myClass = new MyClass();
myClass.rc.turnOn(); // Television의 turnOn() 메서드 실행
- 인터페이스가 생성자의 매개 변수로 사용된 경우 new 연산자로 객체 생성 시
구현 객체를 대입
한다.
MyClass myClass = new MyClass(new Television());
- 인터페이스가 로컬 변수로 사용된 경우 변수에 구현 객체를 대입한다.
void methodA() {
RemoteControl rc = new Audio();
rc.turnOn();
}
- 인터페이스가 메소드의 매개 변수로 사용된 경우 메소드 호출 시 구현 객체를 대입한다.
MyClass myClass = new MyClass();
myClass.methodB(new Television());
02 타입 변환과 다형성
다형성을 구현하기 위해서는 메소드 재정의와 타입 변환이 필요하다. 이 두 가지 기능 모두 인터페이스에서 지원하므로, 인터페이스는 다형성을 구현할 수 있다
.
이전 7장에서 배운 상속의 다형성은 아래와 같다.
필드의 다형성은 필드의 타입을 부모타입으로 선언해 다양한 자식 객체를 저장할 수 있도록 하는 것이다. 매개 변수의 다형성은 매개 변수가 클래스인 경우, 자식 객체까지 매개값으로 사용할 수 있는 것이다. 두가지 모두 사용 결과를 다양하게 만들어 준다는데 의미가 있다.
인터페이스는 소스 코드의 변함 없이, 여러 객체를 사용
할 수 있으므로 프로그램의 결과가 다양해진다. 이것이 인터페이스의 다형성이다.
자동 타입 변환
구현 객체가 인터페이스로 변환되는 것은 자동 타입 변환이다.
인터페이스 변수 = 구현객체;
RemoteControl rc = new Television();
인터페이스 구현 클래스의 하위 클래스가 있다면, 하위 클래스로 생성한 자식 객체도 인터페이스 타입으로 자동 타입 변환될 수 있다. (물론 인터페이스에서 사용할 수 있다는 뜻이기도 하다.)
필드의 다형성
7장의 상속에서 필드의 다형성을 배울 때, 타이어를 사용해 예시를 들었는데, 이때 상위 클래스는 타이어 하위 클래스는 좋은 타이어 나쁜 타이어로 두고 타이어를 두고 다른 하위 클래스를 대입해 교체
할 수 있었다.
인터페이스에서 필드의 다형성은 그 구현을 인터페이스로 하고, 자식 클래스 대신 구현 객체
로 둔다는 차이가 있다. 예를 들어 타이어라는 인터페이스가 있을 때, 그 구현 객체에 좋은 타이어와 나쁜 타이어가 있으면 각 객체가 인터페이스에서 접근할 수 있다. 구현 객체이기 때문에 인터페이스의 추상 메소드를 실체 메소드로 구현했다.
매개 변수의 다형성
상속에서 매개 변수의 다형성은 자식 클래스까지 매개 변수로 사용할 수 있는 것이다.
인터페이스에서 매개 변수의 다형성은 매개 변수가 인터페이스인 경우, 구현 객체를 매개 변수로 사용할 수 있다.
public class Driver{
public void drive(Vehicle vehicle){
vehicle.run();
}
}
// Vehicle.java 인터페이스
public interface Vehicle {
public void run(); // 추상 메소드
}
// Bus라는 구현 객체가 있는 경우 아래와 같이 사용할 있다.
Driver driver = new Driver();
driver.drive(new Bus());
강제 타입 변환
자동 타입 변환의 경우 인터페이스의 메소드만 사용 가능한 단점이 있다.
인터페이스에 정의되어 있지 않은 구현 클래스의 메소드를 사용하기 위해서, 인터페이스 타입으로 정의된 구현 객체를 다시 구현 클래스 타입으로 변환하는 것
을 강제 타입 변환이라고 한다.
구현클래스 변수 = (구현클래스) 인터페이스변수;
Vehicle vehicle = new Bus(); // 인터페이스 타입으로 선언된 구현 객체
Bus bus = (Bus) vehicle; // 구현 클래스 타입으로 강제 타입 변환
객체 타입 확인
강제 타입 변환은 구현 객체가 인터페이스 타입인 경우에만 가능하며, 그외에는 예외로 인한 에러가 발생한다. 이를 위해 객체가 인터페이스 타입인지 확인해야 하는데, 이때 상속에서도 사용했던 instanceof
연산자를 사용한다.
if(vehicle instanceof Bus){ // vehicle이 Bus 클래스로 생성한 구현 객체인 경우
Bus bus = (Bus) vehicle;
}
인터페이스 상속
인터페이스는 클래스와 달리 다중 상속이 가능하다.
public interface 하위인터페이스 extends 상위인터페이스1, 상위인터페이스2 {...}
인터페이스는 추상 메소드를 가지고 있는데, 하위 인터페이스를 구현하는 클래스는 상속받은 모든 상위 인터페이스의 추상 메소드에 대한 실체 메소드
를 가지고 있어야 한다.
9장 중첩 클래스와 인터페이스
03 중첩 클래스
클래스가 여러 클래스와 관계를 맺으면 클래스를 독릭접으로 선언하는 것이 좋다. 하지만 특정 클래스와 관계를 맺는 경우 해당 클래스 내부에 클래스를 선언하는 것이 좋다.
중첩 클래스란, 클래스 내부에 선언한 클래스
를 말한다.
class ClassA{
class ClassB{...}
}
또한, 중첩 인터페이스란, 클래스 내부에 선언한 인터페이스
를 말한다.
class Class{
interface interfaceA{...}
}
중첩 클래스는 선언 위치에 따라 멤버 클래스
와 로컬 클래스
로 나뉜다.
멤버 클래스
클래스 멤버로서 선언
되는 중첩 클래스
멤버 클래스는 객체를 생성해야만 사용할 수 있는 인스턴스 멤버 클래스
와 클래스에 바로 접근할 수 있는 정적 멤버 클래스
로 나뉜다.
인스턴스 멤버 클래스
static
키워드 없이 선언된 중첩 클래스- 인스턴스 필드와 인스턴스 메소드만 선언 가능 (static-정적- 필드/메소드 선언 불가)
- 외부에서 B 객체를 사용하려면 객체 A를 생성해야만 B 클래스를 생성할 수 있다
class A{
class B{}
}
// A 클래스 외부에서 B 객체를 생성하기
A a = new A();
A.B b = a.new B();
b.field = 3;
b.method();
// A 클래스 내부에서 B를 사용할 때는 제약 없음
class A{
class B{...}
void methodA(){
B b = new B();
}
}
정적 멤버 클래스
static
키워드로 선언된 중첩 클래스모든 필드, 메소드 선언 가능
(static 필드/메소드 선언 가능)- 객체 A를 생성하지 않아도 객체 C에 접근할 수 있다
class A{
static class C{}
}
// 외부에서 C에 접근하기 위해 C만 생성 가능
A.C c new A.C();
c.methodA();
A.C.methodB(); // 정적 메소드
로컬 클래스
생성자/메소드 내부에서 선언
되는 중첩 클래스- 접근 제한자를 붙일 수 없음 + static 필드와 메소드 선언 불가
- methodA를 실행할 때만 B 클래스에 접근할 수 있다
- 또한 메소드가 종료되면 없어진다
- 따라서
메소드 안에서 정의하고 사용
한다
class A{
void methodA(){
class B{...}
B b = new B(); // 메소드 안에서 정의하고 사용
}
}
중첩 클래스 접근 제한
바깥 필드와 메소드에서(외부) 사용 제한
바깥 클래스에서 멤버 클래스에 접근할 때
인스턴스 멤버 클래스에 접근할 때는 인스턴스 필드, 인스턴스 메서드에서만 사용 가능
- 정적 멤버 클래스에 접근할 때는 모든 곳에서 사용 가능
public class A{
// B는 인스턴스 멤버 클래스, C는 정적 멤버 클래스
// 인스턴스 필드
B field1 = new B(); // C도 가능
// 인스턴스 메소드
void methodA(){
B var1 = new B(); // C도 가능
}
// 정적 필드 초기화
static C field2 = new C(); // B는 불가
// 정적 메소드
static void methodB(){
C var2 = new C();
}
class B{}
static class C{}
}
멤버 클래스(내부)에서 사용 제한
내부 멤버 클래스에서 바깥 클래스의 필드와 메소드에 접근할 때
- 인스턴스 멤버 클래스 안에서는 바깥 클래스의 모든 필드, 메소드에 접근 가능
정적 멤버 클래스 안에서는 바깥 클래스의 정적 필드와 정적 메소드만 접근 가능
- 더 자유로운 바깥 클래스의 필드와 메소드를 사용하기에는 정적 멤버 클래스가 더 강한 제한이 있음
로컬 클래스에서 사용 제한
로컬 클래스에서 메소드의 매개 변수나 로컬 변수를 사용할 때, 메소드가 종료되어도 계속 실행될 수 있다. 이를 위해 매개 변수와 로컬 변수를 final로 선언해야 한다
.(값이 실행도중 변하지 않도록) 선언하지 않더라도 final 특성이 부여된다.
중첩 클래스에서 바깥 클래스 참조 얻기
중첩 클래스 내부에서 this
는 중첩 클래스의 객체를 참조한다. 중첩 클래스(내부)에서 바깥 클래스(외부)를 참조하려면 바깥 클래스의 이름.this
을 사용한다.
바깥클래스.this.필드
바깥클래스.this.메소드();
중첩 인터페이스
중첩 인터페이스란, 클래스 내부에 선언한 인터페이스
를 말한다.
중첩 인터페이스도 중첩 클래스와 마찬가지로 인스턴스 멤버와 정적 멤버로 나뉜다.
인스턴스 멤버 인터페이스
- 바깥 클래스의 객체가 있어야만 사용 가능
정적 멤버 인터페이스
- 바깥 클래스 객체가 없어도 사용 가능
04 익명 객체
익명 개체란, 클래스 이름이 없는 객체
이다.
익명 개체를 생성하기 위한 조건은 1) 클래스를 상속
하거나 2) 인터페이스를 구현
해야한다.
// 1. 클래스 상속
부모클래스 변수 = new 부모클래스() {...};
// 2. 인터페이스 구현
인터페이스 변수 = new 인터페이스() {...};
익명 자식 객체 생성
일반적으로 자식 객체를 new를 사용해 명시적으로 사용한다.
Parent var = new Child();
위와 같이 명시적으로 자식 클래스로 객체를 생성하면 다른 곳에서도 객체를 사용할 수 있는데, 이것을 재사용성이 높다
고 한다.
하지만, 자식 클래스가 특정 위치에서 재사용 없이 사용된다면 자식 클래스 명시 없이 익명 자식 객체를 생성한다. 선언하는 실행문이므로 세미콜론이 필요하다.
부모클래스 [필드|변수] = new 부모클래스(매개값) { //필드, 메소드};
익명 구현 객체 생성
일반적으로 인터페이스 타입 필드나 변수에 구현 객체를 대입한다.
RemoteControl rc = new Television();
구현 객체도 마찬가지로 재사용성이 높은데, 특정 상황에서만 사용하는 경우 익명 구현 객체를 생성한다. 익명 구현 객체의 경우 실체 메소드를 정의
하는 것에 주의한다.
인터페이스 [필드|변수] = new 인터페이스() {
// 실체 메소드
// 필드, 메소드
};
익명 객체의 로컬 변수 사용
익명 객체도 로컬 클래스의 사용 제한처럼 익명 객체 내부에서 메소드 매개 변수나 로컬 변수를 사용할 때, 메소드가 종료되어도 동일한 값을 계속 가져야하므로 매개 변수와 로컬 변수를 final로 선언
해야 한다. 선언하지 않더라도 final 특성이 부여된다.
숙제 01
클래스를 선언할 때 인터페이스는 어떻게 선언될 수 있는지 정리하기
인터페이스를 선언하는 기본적인 방법은 다음과 같다
[public] interface 인터페이스이름 {
// 필드 (상수로 선언됨)
// 추상메소드
}
그리고 인터페이스를 구현하는 구현 객체는 구현 클래스로 만드는데, 아래는 그 구현 클래스이다.
class 클래스명 implements 인터페이스이름 {
// 추상 메소드를 정의한 실체 메소드
@Override
public void 추상메소드이름() {
// 메소드 구현
}
// 필드와 메소드
}
구현 클래스를 통해 구현 객체는 다음과 같이 생성한다.
인터페이스이름 변수 = new 구현클래스이름();
클래스 선언 시, 인터페이스는 아래와 같은 상황에서 사용될 수 있다.
- 필드
- 생성자/메소드의 매개 변수
- 생성자/메소드의 로컬 변수
public class MyClass {
// 1. 필드
RemoteControl rc = new Television();
// 2. 생성자의 매개 변수
MyClass(RemoteClass rc) {
this.rc = rc;
}
// 3. 메소드의 로컬 변수
void methodA() {
RemoteControl rc = new Audio();
}
// 4. 메소드의 매개 변수
void methodB(RemoteControl rc) {...}
}
- 인터페이스가 필드로 사용된 경우
new Television();
처럼 구현 객체를 대입할 수 있다.
MyClass myClass = new MyClass();
myClass.rc.turnOn(); // Television의 turnOn() 메서드 실행
- 인터페이스가 생성자의 매개 변수로 사용된 경우 new 연산자로 객체 생성 시
구현 객체를 대입
한다.
MyClass myClass = new MyClass(new Television());
- 인터페이스가 로컬 변수로 사용된 경우 변수에 구현 객체를 대입한다.
void methodA() {
RemoteControl rc = new Audio();
rc.turnOn();
}
- 인터페이스가 메소드의 매개 변수로 사용된 경우 메소드 호출 시 구현 객체를 대입한다.
MyClass myClass = new MyClass();
myClass.methodB(new Television());