한빛미디어의 <혼자 공부하는 자바>를 요약 정리했습니다.
-> 인터페이스 > 인터페이스 사용
에 정리
다음과 같이 Car 클래스 내부에 Tire와 Engine이 멤버 클래스로 선언되어 있습니다. 바깥 클래스(NestedClassExample)에서 멤버 클래스의 객체를 생성하는 코드를 빈칸에 작성해 보세요.
Car.java
public class Car {
class Tire { }
static class Engin { }
}
NestedClassExample.java
public class NestedClassExample {
public static void main(String[] args) {
Car myCar = new Car();
// 인스턴스 멤버 클래스는 바깥 객체를 통해 생성
Car.Tire tire = myCar.new Tire();
// static 멤버 클래스는 바깥 객체 없이 생성 가능
Car.Engine engine = new Car.Engine();
}
}
바깥객체.new 내부클래스()
new 바깥클래스.내부클래스()
개발 코드와 객체가 서로 통신하는 접점 역할
[public] interface 인터페이스이름 {
// 상수
[public static final] 타입 상수이름 = 값;
public int MAX_VOLUME = 10;
// 추상 메소드
[public abstract] 리턴타입 메소드이름(매개변수, ...);
public void setColume(int volume);
}
상수 필드 선언(constant field)
객체 사용 방법을 정의한 것이므로 실행 시 데이터를 저장할 수 있는 인스턴스 또는 정적 필드 선언 불가능
추상 메소드 선언(abstract method)
인터페이스를 통해 호출된 메소드는 객체에서 실행되므로 실행 블록이 필요없는 추상 메소드로 선언
IntelliJ에서 인터페이스 생성
객체는 인터페이스에서 정의된 추상 메소드와 동일한 메소드 이름, 매개 타입, 리턴 타입을 가진 실체 메소드를 가지고 있어야 함
-> 인터페이스의 구현(implement) 객체, 구현 객체를 생성하는 클래스를 구현 클래스
구현 클래스
public class 구현클래스이름 implements 인터페이스이름 {
// 인터페이스에 선언된 추상 메소드의 실체 메소드 선언
}
// example
public class Television implements RemoteControl {
// 필드
private int volume;
// setVolume() 추상 메소드의 실체 메소드
public void setVolume(int volume) {
if (volume > RemoteControl.MAX_VOLUME) {
this.volume = RemoteControl.MAX_VOLUME;
} else if (volume < RemoteControl.MIN_VOLUME) {
....
}
구현 클래스가 작성되면 new 연산자로 객체 생성 가능
public class RemoteControlExample {
public static void main(String[] args) {
RemoteControl rc;
rc = new Television();
rc = new Television();
}
}
다중 인터페이스 구현 클래스
객체는 다수의 인터페이스 타입으로 사용 가능
public class 구현클래스이름 implements 인터페이스A, 인터페이스B {
// 인터페이스 A에 선언된 추상 메소드의 실체 메소드 선언
// 인터페이스 B에 선언된 추상 메소드의 실체 메소드 선언
}
클래스를 선언할 때 인터페이스는 필드, 생성자 또는 메소드의 매개 변수, 생성자 또는 메소드의 로컬 변수로 선언될 수 있음
example: Remote Control 인터페이스를 필드, 생성자 또는 메소드의 매개 변수 그리고 메소드의 로컬 변수로 선언
public class MyClass {
// 필드
RemoteControl rc = new Television();
// 생성자
MyClass(RemoteControl rc) { // 생성자의 매개값으로 구현 객체 대입
this.rc = rc;
}
// 메소드
void methodA() {
// 로컬 변수
RemoteControl rc = new Audio();
}
void methodB(RemoteCointrol rc) { ... } // 생성자의 매개값으로 구현 객체 대입
}
MyClass myClass = new MyClass();
myClass.rc.turnOn(); // Television의 turnOn()이 실행
myClass.rc.setVolume(5); // Television의 setVolume(5)가 실행
MyClass( RemoteControl rc ) {
this.rc = rc;
rc.turnOn();
rc.setVolume(5);
}
Myclass myclass = new MyClass(new Audio());
void methodA() {
RemoteControl rc = new Audio();
rc.turnOn(); // Audio의 turnOn()이 실행
rc.setVolume(5); // Audio의 setVolume(5)가 실행
}
void methodB() {
rc.turnOn();
rc.setVolume(5);
}
Myclass myclass = new MyClass();
myClass.methodB(new Television());
인터페이스를 사용해서 메소드를 호출하도록 구현했다면, 구현 객체를 빠르게 고체 가능
-> 인터페이스의 다형성
// A에서 B로 수정
I i = new A();
I i = new B();
// 수정 필요 없음
i.method1();
i.method2();
프로그램 실행 도중에 자동적으로 타입 변환이 일어나는 것
인터페이스 변수 = 구현객체;
한국 타이어와 금호 타이어는 공통적으로 타이어 인터페이스를 구현했기 때문에 모두 타이어 인터페이스에 있는 메소드를 가지고 있음
Tire.java
public interface Tire {
public void roll();
}
HankookTire.java
puclic class HankookTire implements Tire {
@Override
public void roll() {
System.out.println("한국 타이어가 굴러갑니다.");
}
}
KumhoTire.java
public class KumhoTire implememts Tire {
@Override
public void roll() {
System.out.println("금호 타이어가 굴러갑니다.");
}
}
Car.java
public class Car {
Tire frontLeftTire = new Hankooktire();
Tire frontRightTire = new HankookTire();
Tire backLeftTire = new HankookTire();
Tire backRightTire = new HankookTire();
void run() {
frontLeftTire.roll();
frontRightTire.roll();
backLeftTire.roll();
backRightTire.roll();
}
}
CarExample.java
public class CarExample {
public static void main(String[] args) {
Car myCar = new Car();
myCar.run();
myCar.frontLeftTire = new KumhoTire();
myCar.frontRightTire = new KumhoTire();
myCar.run();
}
}
자동 타입 변환은 필드의 값을 대입할 때에도 발생하지만, 주로 메소드를 호출할 때 발생
example: 매개 변수를 인터페이스 타입으로 선언하고 호출할 때에는 구현 객체를 대입
public class Driver {
public void drive(Vehicle vehicle) { // 구현 객체
vehicle.run(); // 구현 객체의 run() 메소드가 실행됨
}
}
public interface Vehicle {
public void run();
}
public class Bus implements Vehicle {
@Override
public void run() {
System.out.println("버스가 달립니다.");
}
}
Bus가 구현 클래스라면 Driver의 drive() 메소드르 호출할 때 Bus 객체를 생성해서 매개값으로 줌
Vehicle vehicle = bus // 자동 타입 변환
-> 매개 변수의 타입이 인터페이스일 경우 어떠한 구현 객체도 매개값으로 사용할 수 있음
구현 객체가 인터페이스 타입으로 자동 타입 변환하면, 이터페이스에 선언된 메소드만 사용 가능하다는 제약
구현클래스 변수 = (구현클래스) 인터페이스변수;
// example
interface Vehicle {
void run();
}
class Bus implements Vehicle {
void run() { ... }
void checkFare() { ... };
}
Vehicle vehicle = new Bus();
vehicle.run(); // 가능
vehicle.checkFare(); // 불가능
Bus bus = (Bus) vehicle; // 강제 타입 변환
bus.run(); // 가능
bus.checkFare(); // 가능
강제 타입 변환은 구현 객체가 인터페이스 타입으로 변환되어 있는 상태에서 가능
ClassCastExveption
발생if (vehicle instanceof Bus) {
Bus bus = (Bus) vehicle;
}
// example
public class Driver {
public void drive(Vehicle vehicle) {
if(vehicle instanceof Bus) { // vehicle 매개 변수가 참조하는 객체가 Bus인지 조사
Bus bus = (Bus) vehicle; // Bus 객체일 경우 안전하게 강제 타입 변환
bus.checkFare(); // Bus 타입으로 강제 타입 변환을 하는 이유
}
vehicle.run();
인터페이스도 다른 인터페이스를 상속 가능
public interface 하위인터페이스 extends 상위인터페이스1, 상위인터페이스2 { ... }
하위 인터페이스를 구현하는 클래스는 하위 인터페이스의 메소드뿐만 아니라 상위 인터페이스의 모든 추상 메소드에 대한 실체 메소드를 가지고 있어야 함
하위인터페이스 변수 = new 구현클래스(...);
상위인터페이스1 변수 = new 구현클래스(...);
상위인터페이스2 변수 = new 구현클래스(...);
public interface InterfaceA {
public void methodA();
}
public interface InterfaceB {
public void methodB();
}
public interface InterfaceC extends InterfaceA, InterfaceB {
public void methodC();
}
public class ImplementionC implements InterfaceC {
public void methodA() {
System.out.println("ImplementationC-methodA() 실행");
}
public void methodB() {
System.out.println("ImplementationC-methodB() 실행");
}
public void methodC() {
System.out.println("ImplementationC-methodA() 실행");
}
}
public class Example {
public static void main(Stirng[] args) {
Implementation impl = new ImplementationC();
InterfaceA ia = impl; // InterfaceA 변수는 methodA()만 호출 가능
ia.methodA();
Interface ib = impl; // InterfaceB 변수는 methodB()만 호출 가능
ib.methodB();
Interface ic = impl; // InterfaceC 변수는 methodA(), methodB(), methodC() 모두 호출 가능
ic.methodA();
ic.methodB();
ic.methodC();
}
}
클래스 내부에 선언한 클래스
A $ B .class
A $1 B .class
class ClassName {
class NestedClassName {
}
}
멤버 클래스
클래스의 멤버로서 선언되는 중첩 클래스
클래스나 객체가 사용 중이라면 언제든지 재사용 가능
class A {
class B { ... }
}
class A {
static class B { ... }
}
로컬 클래스
생성자 또는 메소드 내부에서 선언되는 중첩 클래스
메소드를 실행할 때만 사용되고 메소드가 종료되면 없어짐
class A {
void method() {
class B { ... }
}
}
인스턴스 멤버 클래스
static 키워드 없이 중첩 선언된 클래스
class A {
// 인스턴스 멤버 클래스
class B {
B() { } // 생성자
int field1; // 인스턴스 필드
// static int field2; // 정적 필드(x)
void method() { } // 인스턴스 메소드
// static void method2() { } // 정적 메소드(x)
}
}
A a = new A();
A.B b = a.new B();
b.field1 = 3;
b.method1();
class A {
class B { .. }
void methodA() {
B b = new B();
b.field1 = 3;
b.method1();
}
}
정적 멤버 클래스
static 키워드로 선언된 클래스(모든 종류의 필드와 메소드 선언 가능)
class A {
// 정적 멤버 클래스
static class C {
C() { } // 생성자
int field1; // 인스턴스 필드
static int field2; // 정적 필드
void method1() { } // 인스턴스 메소드
static void method2() { } // 정적 메소드
A 클래스 외부에서 정적 멤버 클래스 C를 생성하기 위해서는 다음과 같이 선언
A.C c = new A.C();
c.field1 = 3; // 인스턴스 필드 사용
c.method1(); // 인스턴스 메소드 호출
A.C.field2 = 3; // 정적 필드 사용
A.C.method2(); // 정적 메소드 호출
로컬 클래스
중첩 클래스는 메소드 내에서도 선언 가능 = 로컬 클래스
void method() {
// 로컬 클래스
class D {
D() { } // 생성자
int field1; // 인스턴스 필드
// static int field2; 정적 필드 (x)
void method1() { }; 인스턴스 메소드
// static void method2{ }; 정적 메소드 (x)
}
D.d = new D();
d.field1 = 3;
d.method1();
바깥 필드와 메소드에서 사용 제한
바깥 클래스에서 인스턴스 멤버 클래스를 사용할 때 제한이 있음
public class A {
// 인스턴스 필드
B field1 = new B();
C field2 = new C();
// 인스턴스 메소드
B var1 = new B();
C var2 = new C();
// 정적 필드 초기화
// static B field3 = new B();
static C field4 = new C();
// 정적 메소드
static void method2() {
// B var1 = new B(); (x)
C car2 = new C();
// 인스턴스 멤버 클래스
class B { }
// 정적 멤버 클래스
static class C { }
}
멤버 클래스에서 사용 제한
멤버 클래스가 인스턴스 또는 정적으로 선언됨에 따라 멤버 클래스 내부에서 바깥 클래스의 필드와 메소드에 접근할 때에도 제한
class A {
int field1;
void method() { ... }
static int field2;
static void method2() { ... }
class B {
void method() {
field1 = 10;
method1();
field2 = 10;
method2();
}
static class C {
void method() {
field1 = 10; // (x)
method1(); // (x)
field2 = 10;
method2();
}
}
로컬 클래스에서 사용 제한
메소드의 매개 변수나 로컬 변수를 로컬 클래스에서 사용할 때 제한
컴파일 시 로컬 클래스에서 사용하는 매개 변수나 로컬 변수의 값을 로컬 클래스 내부에 복사해두고 사용
public class Outter {
public void method1([final] int arg) {
[final] int localVariable = 1;
arg = 100; // (x)
localVariable = 100; // (x)
class Inner {
public void method() {
int result = arg + localVariable;
}
}
}
중첩 클래스에서 바깥 클래스 참조 얻기
클래스 내부에서 this는 객체 자신의 참조
바깥클래스.this.필드
바깥클래스.this.메소드();
// example
public class Outter {
String field = "Outter-field";
void method() {
System.out.println("Outter-method");
}
class Nested {
String field = "Nested-field";
void method() {
System.out.println("Nested-method");
}
}
void print() {
System.out.println(this.field); // Nested-field
this.method(); // Nested-method
System.out.println(Outter.this.field); // Outter-field
Outter.this.method(); // // Outter-method
클래스 내부에 선언한 인터페이스
class A {
[static] interface I {
void method();
}
}
// example
public class Button {
OnClickListener listener;
void setOnClickListener(OnClickListener listener) {
this.listener = listener;
}
void touch() {
listener.onClick();
}
static interface OnClickListener {
void onClick();
}
}
public class CallListener implements Button.OnClickListener {
@Override
public void onClick() {
System.out.println("전화를 겁니다.");
}
}
public class ButtonExample {
public static void main(String[] args) {
Button btn = new Button();
btn.setOnClickListener(new CallListener());
btn.touch();
}
}
익명(anonymous) 객체는 이름이 없는 객체
일반적인 경우에는 다음과 같이 명시적으로 클래스 이름을 주고 선언
[상속]
class 클래스이름1 extends 부모클래스 { ... }
부모클래스 변수 = new 클래스이름1();
[구현]
class 클래스이름2 implements 인터페이스 { ... }
인터페이스 변수 = new 클래스이름2();
익명 객체 생성
[상속]
부모클래스 변수 = new 부모클래스() { ... };
[구현]
인터페이스 변수 = new 인터페이스() { ... };
자식 클래스를 명시적으로 선언
재사용되지 않고, 오로지 특정 위치에서 사용할 경우 자식 클래스를 명시적으로 선언하지 않아도 됨
부모클래스 [필드|변수] = new 부모클래스(매개값, ...) {
// 필드
// 메소드
};
필드를 선언할 때 초기값으로 익명 자식 객체를 생성해서 대입하는 예
class A {
Parent field = new Parent() { // A 클래스의 필드 선언
int childField;
void childMethod() { }
@Override // parent의 메소드를 재정의
void parentMethod() { }
};
}
메소드 내에서 로컬 변수를 선언할 때 초기값으로 익명 자식 객체를 생성해서 대입하는 예
class A {
Parent localVar = new Parent() { // 로컬 변수 선언
int childField;
void childMethod() { }
@Override // Parent의 메소드를 재정의
void parentMethod() { }
};
}
메소드의 매개 변수가 부모 타입일 경우 메소드를 호출하는 코드에서 익명 자식 객체를 생성해서 매개값으로 대입하는 예
class A {
void method1(Parent parent) { }
void method2() {
method1( // method1 메소드 호출
new Parent() { // method1의 매개값으로 익명 자식 객체를 대입
int childField;
void childMethod() { }
@Override
void parentMethod() { }
}
);
}
}
인터페이스 타입의 필드 또는 변수를 선언하고, 구현 객체를 초기값으로 대입하는 경우
// 일반적인 방법
class TV implements RemoteControl { }
class A {
RemoteControl field = new TV(); // 필드에 구현 객체를 대입
void method() {
RemoteControl localVar = new TV(); // 로컬 변수에 구현 객체를 대입
}
}
// 익명 구현 객체 생성
인터페이스 [필드|변수] = new 인터페이스() {
// 인터페이스에 선언된 추상 메소드의 실체 메소드 선언
// 필드
// 메소드
}
필드를 선언할 때 초기값으로 익명 구현 객체를 생성해서 대입하는 예
class A {
RemoteControl field = new RemoteControl() { // 클래스 A의 필드 선언
@Override // RemoteControl 인터페이스의 추상 메소드에 대한 실체 메소드
void turnOn() { }
};
}
메소드 내에서 로컬 변수를 선언할 때 초기값으로 익명 구현 객체를 생성해서 대입하는 예
void mtehod() {
RemoteControl localVar = new RemoteControl() { // 로컬 변수 선언
@Override // RemoteControl 인터페이스의 추상 메소드에 대한 실체 메소드
void turnOn() { }
};
}
메소드의 매개변수가 인터페이스 타입일 경우 메소드를 호출하는 코드에서 익명 구현 객체를 생성해서 매개값으로 대입하는 예
class A {
void method1(ReoteControl rc) { }
void mtehod2() {
method1( // method1() 메소드 호출
new RemoteControl() { // method1()의 매개값으로 익명 구현 객체를 대입
@Override
void turnOn() { }
};
}
}
메소드의 매개 변수나 로컬 변수를 익명 객체 내부에서 사용할 때도 제한
public interface Calculatable {
public int sum();
}
public class Anonymous {
private int field;
public void method(final int arg1, int arg2) {
final int var1 = 0;
int var2 = 0;
arg1 = 20; // (x)
arg2 = 20; // (x)
var1 = 30; // (x)
var2 = 30; // (x)
}
}