익명 클래스 - 시작
익명 클래스(anonymous class)는 지역 클래스의 특별한 종류의 하나이다.
익명 클래스는 지역 클래스인데, 클래스의 이름이 없다는 특징이 있다.
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` 라는 이름으로 선언한다. 이때 `Printer` 인터페이스도 함께 구현한다.
- 생성: `new LocalPrinter()` 를 사용해서 앞서 선언한 지역 클래스의 인스턴스를 생성한다.
지역 클래스의 선언과 생성
//선언
class LocalPrinter implements Printer{
//body
}
//생성
Printer printer = new LocalPrinter();
익명 클래스를 사용하면 클래스의 이름을 생략하고, 클래스의 선언과 생성을 한번에 처리할 수 있다.
익명 클래스 - 지역 클래스의 선언과 생성을 한번에
Printer printer = new Printer(){
//body
}
예제 코드를 통해 익명 클래스를 만들어보자.
public interface Printer {
void print();
}
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);
}
};
new Printer() {body}
익명 클래스는 클래스의 본문(body)을 정의하면서 동시에 생성한다.
new 다음에 바로 상속 받으면서 구현할 부모 타입을 입력하면 된다.
이 코드는 마치 인터페이스 Printer를 생성하는 것 처럼 보인다.
하지만 자바에서 인터페이스를 생성하는 것을 불가능하다.
이 코드는 인터페이스를 생성하는 것이 아니고, Printer라는 이름의 인터페이스를 구현한 익명 클래스를 생성하는 것이다.
{body} 부분에 Printer 인터페이스를 구현한 코드를 작성하면 된다.
이 부분이 바로 익명 클래스 의 본문이 된다.
쉽게 이야기해서 Printer를 상속(구현) 하면서 바로 생성하는 것이다.
익명 클래스 특징
- 익명 클래스는 이름 없는 지역 클래스를 선언하면서 동시에 생성한다.
- 익명 클래스는 부모 클래스를 상속 받거나, 또는 인터페이스를 구현해야 한다.
- 익명 클래스를 사용할 때는 상위 클래스나 인터페이스가 필요하다.
- 익명 클래스는 말 그대로 이름이 없다. 이름을 가지지 않으므로, 생성자를 가질 수 없다. (기본 생성자만 사용됨)
- 익명 클래스는 `AnonymousOuter$1` 과 같이 자바 내부에서 바깥 클래스 이름 + `$` + 숫자로 정의된다.
- 익명 클래스가 여러개면 `$1` , `$2` , `$3` 으로 숫자가 증가하면서 구분된다.
익명 클래스의 장점
익명 클래스를 사용하면 클래스를 별도로 정의하지 않고도 인터페이스나 추상 클래스를 즉석에서 구현할 수 있어 코드가 더 간결해진다. 하지만, 복잡하거나 재사용이 필요한 경우에는 별도의 클래스를 정의하는 것이 좋다.
익명 클래스를 사용할 수 없을 때
익명 클래스는 단 한 번만 인스턴스를 생생할 수 있다.
다음과 같이 여러 번 생성이 필요하다면 익명 클래스를 사용할 수 없다. 대신에 지역 클래스를 선언하고 사용하면 된다.
Printer printer1 = new LocalPrinter();
printer1.print();
Printer printer2 = new LocalPrinter();
printer2.print();
정리
- 익명 클래스는 이름이 없는 지역 클래스이다.
- 특정 부모 클래스(인터페이스)를 상속 받고 바로 생성하는 경우 사용한다.
- 지역 클래스가 일회성으로 사용되는 경우나 간단한 구현을 제공할 때 사용한다.
익명 클래스 활용1
public static void hello(String str) {
System.out.println("프로그램 시작"); //변하지 않는 부분
System.out.println(str); //str: 변하는 부분
System.out.println("프로그램 종료"); //변하지 않는 부분
}
변하지 않는 부분은 그대로 유지하고, 변하는 문자열은 외부에서 전달 받아서 처리한다.
프로그래밍에서 중복을 제거하고, 좋은 코드를 유지하는 핵심은 변하는 부분과 변하지 않는 부분을 분리하는 것이다.
여기서는 변하지 않는 "프로그램 시작", "프로그램 종료"를 출력하는 부분은 그대로 유지하고, 상황에 따라 변화가 필요한 문자열은 외부에서 전달 받아서 처리했다. 이렇게 변하는 부분과 변하지 않는 부분을 분리하고, 변하는 부분을 외부에서 전달 받으면, 메서드(함수)의 재사용성을 높일 수 있다. 리팩토링 전과 후를 비교해보자. `hello(String str)` 함수의 재사용성은 매우 높아졌다. 여기서 핵심은 변하는 부분을 메서드(함수) 내부에서 가지고 있는 것이 아니라, 외부에서 전달 받는다는 점이다.
익명 클래스 활용2
public class Ex1RefMainV1 {
public static void hello(Process process) {
System.out.println("프로그램 시작");
//코드 조각 시작
process.run();
//코드 조각 종료
System.out.println("프로그램 종료");
}
static class Dice implements Process {
@Override
public void run() {
int randomValue = new Random().nextInt(6) + 1;
System.out.println("주시위 = " + randomValue);
}
}
static class Sum implements Process {
@Override
public void run() {
for (int i = 0; i < 3; i++) {
System.out.println("i = " + i);
}
}
}
public static void main(String[] args) {
Dice dice = new Dice();
Sum sum = new Sum();
System.out.println("Hello 실행");
hello(dice);
hello(sum);
}
}
Dice, Sum 각각의 클래스는 Process 인터페이스를 구현하고 run() 메서드에 필요한 코드 조각을 구현한다.
여기서는 정적 중첩 클래스를 사용했다. 물론 정적 중첩 클래스가 아니라 외부에 클래스를 직접 만들어도 된다.
Process process 매개변수를 통해 인스턴스를 전달할 수 있다.
이 인스턴스의 run() 메서드를 실행하면 필요한 코드 조각을 실행할 수 있다.
이때 다형성을 활용해서 외부에서 전달되는 인스턴스에 따라 각각 다른 코드 조각이 실행된다.
hello()를 호출할 때 어떤 인스턴스를 전달하는 가에 따라서 다른 결과가 실행된다.
hello(dice)를 호출하면 주사위 로직이, hello(sum)을 호출하면 계산 로직이 수행된다.
익명 클래스 활용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);
}
}
익명 클래스 사용1
앞의 지역 클래스는 간단히 한번만 생성해서 사용한다. 이런 경우 익명 클래스로 변경할 수 있다.
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);
}
}
익명 클래스 사용2 - 참조값 직접 전달
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);
}
}
});
}
}
람다(lamba)
자바8 이전까지 메서드에 인수로 전달할 수 있는 것은 크게 2가지였다.
- `int` , `double` 과 같은 기본형 타입
- `Process` `Member` 와 같은 참조형 타입(인스턴스)
결국 메서드에 인수로 전달할 수 있는 것은 간단한 데이터나, 인스턴스의 참조이다.
지금처럼 코드 조각을 전달하기 위해 클래스를 정의하고 메서드를 만들고 또 인스턴스를 꼭 생성해서 전달해야 할까?
생각해보면 클래스나 인스턴스와 관계 없이 다음과 같이 메서드만 전달할 수 있다면 더 간단하지 않을까?
public void runDice() {
int randomValue = new Random().nextInt(6) + 1;
System.out.println("주사위 = " + randomValue);
}
public void runSum() {
for (int i = 1; i <= 3; i++) {
System.out.println("i = " + i);
}
}
hello(메서드 전달: runDice())
hello(메서드 전달: runRun())
자바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 > 중급1' 카테고리의 다른 글
10. 예외 처리2 - 실습 (0) | 2024.09.02 |
---|---|
9. 예외 처리1 - 이론 (0) | 2024.09.01 |
7. 중첩 클래스, 내부 클래스1 (0) | 2024.08.31 |
5. 열거형 - ENUM (0) | 2024.08.28 |
4. 래퍼, Class 클래스 (0) | 2024.08.27 |