김영한의 실전 자바 - 중급 1편 강의 | 김영한 - 인프런
김영한 | , 국내 개발 분야 누적 수강생 1위, 제대로 만든 김영한의 실전 자바[사진][임베딩 영상]단순히 자바 문법을 안다? 이걸로는 안됩니다!전 우아한형제들 기술이사, 누적 수강생 40만 명 돌
www.inflearn.com
중첩 클래스, 내부 클래스란?
다음과 같이 클래스 안에 클래스를 중첩해서 정의할 수 있는데, 이것을 중첩 클래스(Nested Class)라 한다.
class Outer {
...
//중첩 클래스
class Nested {
...
}
}
중첩 클래스 분류
중첩 클래스는 클래스를 정의하는 위치에 따라 다음과 같이 분류한다.
중첩 클래스는 총 4가지가 있고, 크게 2가지로 분류할 수 있다.
- 정적 중첩 클래스
- 내부 클래스 종류
- 내부 클래스
- 지역 클래스
- 익명 클래스
중첩 클래스의 선언 위치
- 정적 중첩 클래스 → 정적 변수와 같은 위치
- 내부 클래스 → 인스턴스 변수와 같은 위치
- 지역 클래스 → 지역 변수와 같은 위치
정적 중첩 클래스 vs 내부 클래스
class Outer {
...
//정적 중첩 클래스
static class StaticNested {
...
}
//내부 클래스
class Inner {
...
}
}
- 정적 중첩 클래스는 정적 변수와 같이 앞에 `static`이 붙어있다.
- 내부 클래스는 인스턴스 변수와 같이 앞에 `static`이 붙어있지 않다.
- 중첩(Nested): 어떤 다른 것이 내부에 위치하거나 포함되는 구조적인 관계 (나의 안에 있지만 내 것이 아닌 것)
- 내부(Inner): 나의 내부에 있는 나를 구성하는 요소
정리하자면 정적 중첩 클래스는 바깥 클래스의 안에 있지만 바깥 클래스와 관계 없는 전혀 다른 클래스를 말한다. 내부 클래스는 바깥 클래스의 내부에 있으면서 바깥 클래스를 구성하는 요소를 말한다.
중첩(`Nested`)과 내부(`Inner`)를 분류하는 핵심은 바깥 클래스 입장에서 볼 때 안에 있는 클래스가 나의 인스턴스에 소속이 되는가 되지 않는가의 차이이다.
- 정적 중첩 클래스: 바깥 클래스와 전혀 다른 클래스 → 바깥 클래스의 인스턴스에 소속X
- 내부 클래스: 바깥 클래스를 구성하는 요소 → 바깥 클래스의 인스턴스에 소속O
지역 클래스
public class Outer {
public void process() {
int localVar = 0; // 로컬 변수
// 메서드 내부에 선언된 로컬 클래스
class Local {
void execute() {
System.out.println("localVar = " + localVar);
}
}
Local local = new Local();
local.execute();
}
}
- 지역 클래스는 지역 변수와 같이 코드 블록 안에서 클래스를 정의한다.
- 익명 클래스는 지역 클래스의 특별한 버전이다.
정리
정적 중첩 클래스
- `static`이 붙는다.
- 바깥 클래스의 인스턴스에 소속되지 않는다.
내부 클래스
- `static`이 붙지 않는다.
- 바깥 클래스의 인스턴스에 소속된다.
중첩 클래스 사용 이유
- 논리적 그룹화: 특정 클래스가 다른 하나의 클래스 안에서만 사용되는 경우 해당 클래스 안에 포함되는 것이 논리적으로 더 그룹화된다. 패키지를 열었을 때 다른 곳에서 사용될 필요가 없는 중첩 클래스가 외부에 노출되지 않는 장점도 있다.
- 캡슐화: 중첩 클래스는 바깥 클래스의 `private` 멤버에 접근할 수 있다. 이렇게 해서 둘을 긴밀하게 연결하고 불필요한 `public` 메서드를 제거할 수 있다.
정적 중첩 클래스
public class NestedOuter {
private static int outClassValue = 3;
private int outInstanceValue = 2;
static class Nested {
private int nestedInstanceValue = 1;
public void print() {
// 자신의 멤버에 접근
System.out.println(nestedInstanceValue);
// 바깥 클래스의 인스턴스 멤버에 접근에는 접근할 수 없다.
//System.out.println(outInstanceValue);
// 바깥 클래스의 클래스 멤버에는 접근할 수 있다. private도 접근 가능
System.out.println(NestedOuter.outClassValue);
}
}
}
- 정적 중첩 클래스는 앞에 `static`이 붙는다.
- 정적 중첩 클래스는
- 자신의 멤버에 접근O
- 바깥 클래스의 인스턴스 멤버에 접근X
- 바깥 클래스의 클래스 멤버에 접근O (`private`도 접근 가능)
`NestedOuter.outClassValue`를 `outClassValue`와 같이 줄여서 사용해도 된다. 이 경우 바깥 클래스에 있는 필드를 찾아서 사용한다.
public class NestedOuterMain {
public static void main(String[] args) {
NestedOuter outer = new NestedOuter();
NestedOuter.Nested nested = new NestedOuter.Nested();
nested.print();
System.out.println("nestedClass = " + nested.getClass());
}
}
- 정적 중첩 클래스는 `new 바깥클래스.중첩클래스()`로 생성할 수 있다.
- 중첩 클래스는 `NestedOuter.Nested`와 같이 `바깥클래스.중첩클래스`로 접근할 수 있다.
- `new NestedOuter()`로 만든 바깥 클래스의 인스턴스와 `new NestedOuter.Nested()`로 만든 정적 중첩 클래스의 인스턴스는 서로 아무 관계가 없는 인스턴스이다. 단지 클래스 구조상 중첩해 두었을 뿐이다.
- 둘은 아무런 관련이 없으므로 정적 중첩 클래스의 인스턴스만 따로 생성해도 된다.
실행 결과
1
3
nestedClass = class nested.nested.NestedOuter$Nested
중첩 클래스를 출력해보면 중첩 클래스의 이름은 `NestedOuter$Nested`와 같이 바깥 클래스, `$`, 중첩 클래스의 조합으로 만들어진다.
정적 중첩 클래스의 활용
정적 중첩 클래스로 리팩토링 전
public class NetworkMessage {
private String content;
public NetworkMessage(String content) {
this.content = content;
}
public void print() {
System.out.println(content);
}
}
- `NetworkMessage`는 `Network` 객체 안에서만 사용되는 객체이다.
public class Network {
public void sendMessage(String text) {
NetworkMessage networkMessage = new NetworkMessage(text);
networkMessage.print();
}
}
- `text`를 입력받아서 `NetworkMessage`를 생성하고 출력하는 단순한 기능을 제공한다.
public class NetworkMain {
public static void main(String[] args) {
Network network = new Network();
network.sendMessage("hello java");
}
}
- `Network`를 생성하고 `network.sendMessage()`를 통해 메시지를 전달한다.
- `NetworkMain`은 오직 `Network` 클래스만 사용한다. `NetworkMessage` 클래스는 전혀 사용하지 않는다. `NetworkMessage`는 오직 `Network` 내부에서만 사용된다.
정적 중첩 클래스로 리팩토링 후
public class Network {
public void sendMessage(String text) {
NetworkMessage networkMessage = new NetworkMessage(text);
networkMessage.print();
}
private static class NetworkMessage {
private String content;
public NetworkMessage(String content) {
this.content = content;
}
public void print() {
System.out.println(content);
}
}
}
- `NetworkMessage` 클래스를 `Network` 클래스 안에 중첩해서 만들었다.
- `NetworkMessage`의 접근 제어자를 `private`로 설정했다. 따라서 외부에서 `NetworkMessage`에 접근할 수 없다.
public class NetworkMain {
public static void main(String[] args) {
Network network = new Network();
network.sendMessage("hello java");
}
}
`NetworkMessage`가 중첩 클래스에 `private` 접근 제어자로 되어 있는 것을 보고, `Network` 내부에서만 단독으로 사용하는 클래스라고 바로 인지할 수 있다.
중첩 클래스의 접근
나의 클래스에 포함된 중첩 클래스가 아니라 다른 곳에 있는 중첩 클래스에 접근할 때는 `바깥클래스.중첩클래스`로 접근해야 한다.
NestedOuter.Nested nested = new NestedOuter.Nested();
나의 클래스에 포함된 중첩 클래스에 접근할 때는 바깥 클래스 이름을 적지 않아도 된다.
public class Network {
public void sendMessage(String text) {
NetworkMessage message = new NetworkMessage(text);
}
private static class NetworkMessage {
...
}
}
중첩 클래스(내부 클래스 포함)는 그 용도가 자신이 소속된 바깥 클래스 안에서 사용되는 것이다. 따라서 자신이 소속된 바깥 클래스가 아닌 외부에서 생성하고 사용하고 있다면, 이미 중첩 클래스의 용도에 맞지 않을 수 있다. 이때는 중첩 클래스를 밖으로 빼는 것이 더 나은 선택이다.
내부 클래스
내부 클래스는 바깥 클래스의 인스턴스를 이루는 요소가 된다. 쉽게 이야기해서 내부 클래스는 바깥 클래스의 인스턴스에 소속된다.
public class InnerOuter {
private static int outClassValue = 3;
private int outInstanceValue = 2;
class Inner {
private int innerInstanceValue = 1;
public void print() {
// 자기 자신에 접근
System.out.println(innerInstanceValue);
// 외부 클래스의 인스턴스 멤버에 접근 가능, private도 접근 가능
System.out.println(outInstanceValue);
// 외부 클래스의 클래스 멤버에 접근 가능, private도 접근 가능
System.out.println(outClassValue);
}
}
}
- 내부 클래스는 앞에 `static`이 붙지 않는다. 쉽게 이야기해서 인스턴스 멤버가 된다.
- 내부 클래스는
- 자신의 멤버에 접근O
- 바깥 클래스의 인스턴스 멤버에 접근O (`private`도 접근 가능)
- 바깥 클래스의 클래스 멤버에 접근O (`private`도 접근 가능)
public class InnerOuterMain {
public static void main(String[] args) {
InnerOuter outer = new InnerOuter();
InnerOuter.Inner inner = outer.new Inner();
inner.print();
System.out.println("innerClass = " + inner.getClass());
}
}
- 내부 클래스는 바깥 클래스의 인스턴스에 소속된다. 따라서 바깥 클래스의 인스턴스 정보를 알아야 생성할 수 있다.
- 내부 클래스는 `바깥클래스의 인스턴스 참조.new 내부 클래스()`로 생성할 수 있다.
- 내부 클래스는 바깥 클래스의 인스턴스에 소속되어야 한다. 따라서 내부 클래스를 생성할 때, 바깥 클래스의 인스턴스 참조가 필요하다.
- `outer.new Inner()`에서 `outer`는 바깥 클래스의 인스턴스 참조를 가진다.
- `outer.new Innter()`로 생성한 내부 클래스는 개념상 바깥 클래스의 인스턴스 내부에 생성된다.
- 따라서 바깥 클래스의 인스턴스를 먼저 생성해야 내부 클래스의 인스턴스를 생성할 수 있다.
- 개념상 바깥 클래스의 인스턴스 내부에서 내부 클래스의 인스턴스가 생성된다.
- 따라서 내부 인스턴스는 바깥 인스턴스를 알기 때문에 바깥 인스턴스의 멤버에 접근할 수 있다.
- 실제로 내부 인스턴스가 바깥 인스턴스 안에 생성되는 것은 아니다. 하지만 개념상 인스턴스 안에 생성된다고 이해하면 충분하다.
- 실제로는 내부 인스턴스는 바깥 인스턴스의 참조를 보관한다. 이 참조를 통해 바깥 인스턴스의 멤버에 접근할 수 있다.
내부 클래스의 활용
내부 클래스로 리팩토링 전
public class Engine {
private Car car;
public Engine(Car car) {
this.car = car;
}
public void start() {
System.out.println("충전 레벨 확인: " + car.getChargeLevel());
System.out.println(car.getModel() + "의 엔진을 구동합니다.");
}
}
- 엔진은 `Car` 클래스에서만 사용된다.
- 엔진은 시작하기 위해서는 차의 충전 레벨과 차량의 이름이 필요하다.
- `Car` 인스턴스의 참조를 생성자에서 보관한다.
- 엔진은 충전 레벨을 확인하기 위해 `Car.getChargeLevel()`이 필요하다.
- 엔진은 차량의 이름을 확인하기 위해 `Car.getModel()`이 필요하다.
public class Car {
private String model;
private int chargeLevel;
private Engine engine;
public Car(String model, int chargeLevel) {
this.model = model;
this.chargeLevel = chargeLevel;
this.engine = new Engine(this);
}
//Engine에서만 사용하는 메서드
public String getModel() {
return model;
}
//Engine에서만 사용하는 메서드
public int getChargeLevel() {
return chargeLevel;
}
public void start() {
engine.start();
System.out.println(model + " 시작 완료");
}
}
- `Car` 클래스는 엔진에 필요한 메서드들을 제공해야 한다. 다음 메서드는 엔진에서만 사용하고, 다른 곳에서는 사용하지 않는다.
- `getModel()`
- `getChargeLevel()`
- 결과적으로 `Car` 클래스는 엔진에서만 사용하는 기능을 위해 메서드를 추가해서, 모델 이름과 충전 레벨을 외부에 노출해야 한다.
내부 클래스로 리팩토링 후
엔진은 차의 내부에서만 사용된다. 엔진을 차의 내부 클래스로 만들어보자.
public class Car {
private String model;
private int chargeLevel;
private Engine engine;
public Car(String model, int chargeLevel) {
this.model = model;
this.chargeLevel = chargeLevel;
this.engine = new Engine();
}
public void start() {
engine.start();
System.out.println(model + " 시작 완료");
}
private class Engine {
public void start() {
System.out.println("충전 레벨 확인: " + chargeLevel);
System.out.println(model + "의 엔진을 구동합니다.");
}
}
}
- 엔진을 내부 클래스로 만들었다.
- `Car`의 인스턴스 변수인 `chargeLevel`과 `model`에 직접 접근할 수 있다.
- 바깥 클래스에서 내부 클래스의 인스턴스를 생성할 떄는 바깥 클래스 이름을 생략할 수 있다.
- 예) `new Engine()`
- 바깥 클래스에서 내부 클래스의 인스턴스를 생성할 때 내부 클래스의 인스턴스는 자신을 생성한 바깥 클래스의 인스턴스를 자동으로 참조한다.
지역 클래스
- 지역 클래스(Local class)는 내부 클래스의 특별한 종류 중 하나이다. 따라서 내부 클래스의 특징을 그대로 가진다.
- 지역 클래스는 지역 변수와 같이 코드 블록 안에서 정의된다.
public class LocalOuterV1 {
private int outInstanceVar = 3;
public void process(int paramVar) {
int localVar = 1;
class LocalPrinter {
int value = 0;
public void printData() {
System.out.println("value=" + value);
System.out.println("localVar=" + localVar);
System.out.println("paramVar=" + paramVar);
System.out.println("outInstanceVar=" + outInstanceVar);
}
}
LocalPrinter printer = new LocalPrinter();
printer.printData();
}
public static void main(String[] args) {
LocalOuterV1 localOuter = new LocalOuterV1();
localOuter.process(2);
}
}
지역 클래스의 접근 범위
- 자신의 인스턴스 변수인 `value`에 접근O
- 자신이 속한 코드 블록의 지역 변수인 `localVar`에 접근O
- 자신이 속한 코드 블록의 매개변수인 `paramVar`에 접근O
- 바깥 클래스의 인스턴스 멤버인 `outInstanceVar`에 접근O
지역 클래스는 지역 변수처럼 접근 제어자를 사용할 수 없다.
지역 변수 캡처
- 클래스 변수: 프로그램 종료까지, 가장 길다. (메서드 영역)
- 클래스 변수(static 변수)는 메서드 영역에 존재하고, 자바가 클래스 정보를 읽어 들이는 순간부터 프로그램 종료까지 존재한다.
- 인스턴스 변수: 인스턴스의 생존 기간 (힙 영역)
- 인스턴스 변수는 본인이 소속된 인스턴스가 GC되기 전까지 존재한다. 생존 주기가 긴 편이다.
- 지역 변수: 메서드 호출이 끝나면 사라짐 (스택 영역)
- 지역 변수는 스택 영역의 스택 프레임 안에 존재한다. 따라서 메서드가 호출되면 생성되고, 메서드 호출이 종료되면 스택 프레임이 제거되면서 그 안에 있는 지역 변수도 모두 제거된다. 생존 주기가 아주 짧다. (매개변수도 지역 변수의 한 종류이다.)
public class LocalOuterV3 {
private int outInstanceVar = 3;
public Printer process(int paramVar) {
int localVar = 1; //지역 변수는 스택 프레임이 종료되는 순간 함께 제거된다.
class LocalPrinter implements Printer {
int value = 0;
@Override
public void print() {
System.out.println("value=" + value);
//인스턴스는 지역 변수보다 더 오래 살아남는다.
System.out.println("localVar=" + localVar);
System.out.println("paramVar=" + paramVar);
System.out.println("outInstanceVar=" + outInstanceVar);
}
}
LocalPrinter printer = new LocalPrinter();
//printer.print();를 여기서 실행하지 않고 Printer 인스턴스만 반환한다.
return printer;
}
public static void main(String[] args) {
LocalOuterV3 localOuter = new LocalOuterV3();
Printer printer = localOuter.process(2);
//printer.print()를 나중에 실행한다. process()의 스택 프레임이 사라진 이후에 실행
printer.print();
}
}
- `LocalPrinter.print()` 메서드를 `process()` 안에서 실행하는 것이 아니라 `process()` 메서드가 종료된 이후에 `main()` 메서드에서 실행한다.
원래 시나리오
- 지역 클래스로 만든 객체도 인스턴스이기 때문에 힙 영역에 존재한다. 따라서 GC 전까지 생존한다.
- `LocalPrinter` 인스턴스는 `process()` 메서드 안에서 생성된다. 그리고 `main()`에서 `process()`로 생성한 `LocalPrinter` 인스턴스를 반환하고 `printer` 변수에 참조를 보관한다. → `LocalPrinter` 인스턴스는 `main()`이 종료될 때까지 생존한다.
- `paramVar`, `localVar`와 같은 지역 변수는 `process()` 메서드를 실행하는 동안에만 스택 영역에서 생존한다. → `process()` 메서드가 종료되면 `process()` 스택 프레임이 스택 영역에서 제거되면서 함께 제거된다.
- `LocalPrinter` 인스턴스는 `print()` 메서드를 통해 힙 영역에 존재하는 바깥 인스턴스의 변수인 `outInstanceVar`에 접근한다.
- `LocalPrinter` 인스턴스는 `print()` 메서드를 통해 스택 영역에 존재하는 지역 변수인 `localVar`에 접근하는 것처럼 보인다. 하지만 스택 영역에 존재하는 지역 변수를 힙 영역에 있는 인스턴스가 접근하는 것은 간단하지 않다.
- 지역 변수의 생명 주기는 매우 짧은 반면에 인스턴스의 생명주기는 GC 전까지 생존할 수 있다.
- 지역 변수인 `paramVar`, `localVar`는 `process()` 메서드가 실행되는 동안에만 생존할 수 있다. `process()` 메서드가 종료되면 `process()`의 스택 프레임이 제거되면서 두 지역 변수도 함께 제거된다.
- 여기서 문제는 `process()` 메서드가 종료되어도 `LocalPrinter` 인스턴스는 계속 생존할 수 있다는 점이다.
- 예제를 보면, `LocalPrinter` 인스턴스에 있는 `print()` 메서드는 지역 변수인 `paramVar`, `localVar`에 접근해야 한다. 하지만 `process()` 메서드가 이미 종료되었으므로 해당 지역 변수들도 이미 제거된 상태이다.
그런데 실행 결과를 보면 `localVar`, `paramVar`와 같은 지역 변수의 값들이 모두 정상적으로 출력되는 것을 확인할 수 있다.
지역 변수 캡쳐
위에서처럼, 지역 클래스를 통해 생성한 인스턴스가 지역 변수에 접근해야 하는데, 둘의 생명 주기가 다르기 때문에 인스턴스는 살아있지만, 지역 변수는 이미 제거된 상태일 수 있다.
자바는 이런 문제를 해결하기 위해 지역 클래스의 인스턴스를 생성하는 시점에 필요한 지역 변수를 복사해서 생성한 인스턴스에 함께 넣어둔다. 이런 과정을 변수 캡처(Capture)라 한다.
- `LocalPrinter` 인스턴스에서 `print()` 메서드를 통해 `paramVar`, `localVar`에 접근하면 사실은 스택 영역에 있는 지역 변수에 접근하는 것이 아니다. 대신에 인스턴스에 캡쳐한 변수에 접근한다.
- 캡처한 `paramVar`, `localVar`의 생명주기는 `LocalPrinter`의 생명 주기와 같다.
final
지역 클래스가 접근하는 지역 변수는 절대로 중간에 값이 변하면 안된다. 따라서 `final`로 선언하거나 사실상 `final`이어야 한다.
사실상 final
지역 변수에 `final` 키워드를 사용하지는 않았지만, 값을 변경하지 않는 지역 변수를 뜻한다. `final` 키워드를 넣지 않았을 뿐이지, 실제로는 `final` 키워드를 넣은 것처럼 중간에 값을 변경하지 않은 지역 변수이다.
캡쳐 변수의 값을 변경하지 못하는 이유
- 지역 변수의 값을 변경하면 인스턴스에 캡처한 변수의 값도 변경해야 한다.
- 반대로 인스턴스에 있는 캡처 변수의 값을 변경하면 해당 지역 변수의 값도 다시 변경해야 한다.
- 개발자 입장에서 보면 예상하지 못한 곳에서 값이 변경될 수 있다. 이는 디버깅을 번거롭게 한다.
- 지역 변수의 값과 인스턴스에 있는 캡처 변수의 값을 서로 동기화해야 하는데, 멀티쓰레드 상황에서 이런 동기화는 매우 어렵고, 성능에 나쁜 영향을 줄 수 있다.
익명 클래스
익명 클래스는 지역 클래스인데, 클래스의 이름이 없다는 특징이 있다.
public class LocalOuterV2 {
private int outInstanceVar = 3;
public void process(int paramVar) {
int localVar = 1;
class LocalPrinter implements Printer {
int value = 0;
@Override
public void print() {
System.out.println("value=" + value);
System.out.println("localVar=" + localVar);
System.out.println("paramVar=" + paramVar);
System.out.println("outInstanceVar=" + outInstanceVar);
}
}
LocalPrinter printer = new LocalPrinter();
printer.print();
}
public static void main(String[] args) {
LocalOuterV2 localOuter = new LocalOuterV2();
localOuter.process(2);
}
}
여기서 지역 클래스를 사용하기 위해 선언과 생성이라는 2가지 단계를 거친다
- 선언: 지역 클래스를 `LocalPrinter`라는 이름으로 선언한다.
- 생성: `new LocalPrinter()`를 사용해서 앞서 선언한 지역 클래스의 인스턴스를 생성한다.
// 선언
class LocalPrinter implements Printer {
...
}
// 생성
Printer printer = new LocalPrinter();
익명 클래스를 사용하면 클래스의 이름을 생략하고 클래스의 선언과 생성을 한 번에 처리할 수 있다.
Printer printer = new Printer(){
//body
}
`LocalOuterV2`에서 익명 클래스를 사용한 버전
public class AnonymousOuter {
private int outInstanceVar = 3;
public void process(int paramVar) {
int localVar = 1;
Printer printer = new Printer() {
int value = 0;
@Override
public void print() {
System.out.println("value=" + value);
System.out.println("localVar=" + localVar);
System.out.println("paramVar=" + paramVar);
System.out.println("outInstanceVar=" + outInstanceVar);
}
};
printer.print();
System.out.println("printer.class=" + printer.getClass());
}
public static void main(String[] args) {
AnonymousOuter main = new AnonymousOuter();
main.process(2);
}
}
위 코드는 인터페이스를 생성하는 것이 아니고, `Printer`라는 이름의 인터페이스를 구현한 익명 클래스를 생성하는 것이다.
특징
- 익명 클래스는 이름없는 지역 클래스를 선언하면서 동시에 생성한다.
- 익명 클래스는 부모 클래스를 상속 받거나, 또는 인터페이스를 구현해야 한다. 익명 클래스를 사용할 때는 상위 클래스나 인터페이스가 필요하다.
- 익명 클래스는 말 그대로 이름이 없다. 이름을 가지지 않으므로, 생성자를 가질 수 없다. (기본 생성자만 사용됨)
- 익명 클래스는 `AnonymousOuter$1`과 같이 자바 내부에서 `바깥 클래스 이름` + `$` + `숫자`로 정의된다. 익명 클래스가 여러 개면 `$1`, `$2`, `$3`으로 숫자가 증가하면서 구분된다.
익명 클래스는 단 한 번만 인스턴스를 생성할 수 있다. 여러 번 생성이 필요하다면 지역 클래스를 선언하고 사용하면 된다.
활용
public class Ex1RefMainV2 {
public static void hello(Process process) {
System.out.println("프로그램 시작");
//코드 조각 시작
process.run();
//코드 조각 종료
System.out.println("프로그램 종료");
}
public static void main(String[] args) {
class Dice implements Process {
@Override
public void run() {
int randomValue = new Random().nextInt(6) + 1;
System.out.println("주시위 = " + randomValue);
}
}
class Sum implements Process {
@Override
public void run() {
for (int i = 0; i < 3; i++) {
System.out.println("i = " + i);
}
}
}
Process dice = new Dice();
Process sum = new Sum();
System.out.println("Hello 실행");
hello(dice);
hello(sum);
}
}
앞의 지역 클래스는 간단히 한 번만 생성하기 때문에 익명 클래스로 변경할 수 있다.
public class Ex1RefMainV3 {
public static void hello(Process process) {
System.out.println("프로그램 시작");
//코드 조각 시작
process.run();
//코드 조각 종료
System.out.println("프로그램 종료");
}
public static void main(String[] args) {
Process dice = new Process() {
@Override
public void run() {
int randomValue = new Random().nextInt(6) + 1;
System.out.println("주시위 = " + randomValue);
}
};
Process sum = new Process() {
@Override
public void run() {
for (int i = 0; i < 3; i++) {
System.out.println("i = " + i);
}
}
};
System.out.println("Hello 실행");
hello(dice);
hello(sum);
}
}
이 경우 익명 클래스의 참조값을 변수에 담아둘 필요 없이, 인수로 바로 전달할 수 있다.
public class Ex1RefMainV4 {
public static void hello(Process process) {
System.out.println("프로그램 시작");
//코드 조각 시작
process.run();
//코드 조각 종료
System.out.println("프로그램 종료");
}
public static void main(String[] args) {
System.out.println("Hello 실행");
hello(new Process() {
public void run() {
int randomValue = new Random().nextInt(6) + 1;
System.out.println("주시위 = " + randomValue);
}
});
hello(new Process() {
@Override
public void run() {
for (int i = 0; i < 3; i++) {
System.out.println("i = " + i);
}
}
});
}
}
자바8에 들어서면서 메서드(더 정확하는 함수)를 인수로 전달할 수 있게 되었다. 이것을 간단히 람다(Lambda)라 한다.
public class Ex1RefMainV5 {
public static void hello(Process process) {
System.out.println("프로그램 시작");
//코드 조각 시작
process.run();
//코드 조각 종료
System.out.println("프로그램 종료");
}
public static void main(String[] args) {
System.out.println("Hello 실행");
hello(() -> {
int randomValue = new Random().nextInt(6) + 1;
System.out.println("주시위 = " + randomValue);
});
hello(() -> {
for (int i = 0; i < 3; i++) {
System.out.println("i = " + i);
}
});
}
}
'Java' 카테고리의 다른 글
[Java/김영한] 예외 처리 (0) | 2025.05.04 |
---|---|
[Java/김영한] 날짜와 시간 (0) | 2025.04.24 |
[Java/김영한] 열거형 - ENUM (1) | 2025.04.23 |
[Java/김영한] 래퍼, Class 클래스 (0) | 2025.03.23 |
[Java/김영한] String 클래스 (0) | 2025.03.22 |