지역 클래스는 내부 클래스의 특별한 종류, 내부 클래스의 특징을 그대로 살림
지역 클래스는 지역 변수와 같이 코드 블럭 안에서 정의
class Outer {
public void process() {
//지역 변수
int localVar = 0;
//지역 클래스
class Local {...}
Local local = new Local();
}
}
지역 클래스 특징
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 localOuterV1 = new LocalOuterV1();
localOuterV1.process(2);
}
}
지역 클래스의 접근 범위
public interface Printer {
void print();
}
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 localOuterV1 = new LocalOuterV2();
localOuterV1.process(2);
}
}

public class LocalOuterV3 {
private int outInstanceVar = 3;
//매개변수도 지역 변수중 하나로 볼 수 있다.
public Printer process(int paramVar){
//지역 변수 (지역변수는 스텍 영역이 종료되는 순간 함께 제거된다.)
int localVar = 1;
//지역 클래스
class LocalPrinter implements Printer {
int value = 0;
//paramVar
//localVar => caputure 한 내용에 접근
@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();
return printer;
}
public static void main(String[] args) {
LocalOuterV3 localOuter = new LocalOuterV3();
Printer printer = localOuter.process(2);
//printer.print()를 나중에 실행, process 스택 프레임이 사라진 이후에 실행
printer.print();
System.out.println("필드 확인");
Field[] fields = printer.getClass().getDeclaredFields();
for (Field field : fields) {
System.out.println("field = " + field);
}
}
}

지역 클래스 인스턴스 생존 범위
지역 클래스로 만든 객체도 인스턴스 -> 힙 영역에 존재, GC전까지 생존
-> LocalPrinter 인스턴스를 반환 printer 변수에 참조를 보관, main이 종료 될때까지 생존
paramVar, localVar와 같은 지역 변수는 process 메서드를 실행하는 동안, 스택 영역에서 생존, process 스택 프레임이 스택 영역에서 제거 되면서 함께 제거

LocalPrinter 인스턴스는 print 메서드를 통해 힙 영역에 존재하는 바깥 인스턴스의 변수 outInstanceVar 접근
LocalPrinter 인스턴스는 print 메서드를 통해 스택 영역에 존재하는 지역 변수도 접근하는 것 처럼 보임. 스택 영역에 존재하는 지역 변수를 힙 영역에 있는 인스턴스가 접근하는 것은 쉽지 않음


그런데 실행 결과를 보면 localVar , paramVar 와 같은 지역 변수의 값들이 모두 정상적으로 출력
지역 변수 캡처
클래스의 인스턴스를 생성하는 시점에 필요한 지역 변수를 복사해서 생성한 인스턴스에 함께 넣어둔다. = Capture
인스턴스를 생성할 때 필요한 지역 변수를 복사해서 보관

LocalPrinter 인스턴스 생성 시도
인스턴스를 생성할 때 지역 클래스가 접근하는 지역 변수 확인
사용하는 지역 변수 복사
지역 클래스가 사용하는 지역 변수 복사

지역 변수 복사 완료
복사한 지역 변수를 인스턴스에 포함
인스턴스 생성 완료
복사한 지역 변수를 포함해 인스턴스 생성이 완료

LocalPrinter 인스턴스에서 print() 메서드를 통해 paramVar , localVar 에 접근하면 사실은 스택영역에 있는 지역 변수에 접근하는 것이 아니다. 대신에 인스턴스에 있는 캡처한 변수에 접근
지역 변수의 생명주기와 무관하게 언제든지 paramVar , localVar 캡처 변수
에 접근할 수 있다.
지역 클래스가 접근하는 지역변수는 절대로 중간에 값 변경 X -> final 사용
public class LocalOuterV4 {
private int outInstanceVar = 3;
//매개변수도 지역 변수중 하나로 볼 수 있다.
public Printer process(int paramVar){
//지역 변수 (지역변수는 스텍 영역이 종료되는 순간 함께 제거된다.)
int localVar = 1; //사실상 final, 값 변경되면 안된다.
//지역 클래스
class LocalPrinter implements Printer {
int value = 0;
//paramVar
//localVar => capture 한 내용에 접근
@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();
// 만약 localVar 값을 변경?? -> 다시 capture 해야 하나??
// localVar = 10; (값을 변경하지 못하게 한다)
return printer;
}
public static void main(String[] args) {
LocalOuterV4 localOuter = new LocalOuterV4();
Printer printer = localOuter.process(2);
//printer.print()를 나중에 실행, process 스택 프레임이 사라진 이후에 실행
printer.print();
System.out.println("필드 확인");
Field[] fields = printer.getClass().getDeclaredFields();
for (Field field : fields) {
System.out.println("field = " + field);
}
}
}
Printer printer = localOuter.process(2);
localVar, paramVar를 캡처
-> 스택 영역에 존재하는 지역 변수의 값과 인스턴스에 캡처한 캡처 변수의 값이 서로 달라지는 문제 = 동기화 문제
캡처 변수의 값을 변경하지 못하는 이유
익명 클래스 - 지역 클래스의 선언과 생성을 한번에
Printer printer = new Printer(){
//body
}
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.getClass() = " + printer.getClass());
}
public static void main(String[] args) {
AnonymousOuter main = new AnonymousOuter();
main.process(2);
}
}
new Printer() {body}
익명 클래스는 클래스의 본문을 정의하면서 동시에 생성
new 다음에 바로 상속 받으면서 구현 할 부모 타입을 입력
인터페이스를 생성하는 것 처럼 보임. 자바는 인터페이스를 생성하는 것은 불가능. 인터페이스를 구현한 익명 클래스를 생성하는 것 -> body 부분에 구현한 코드를 작성
익명 클래스 특징
익명 클래스의 장접
익명 클래스를 사용하면 클래스를 별도로 정의하지 않고도 인터페이스나 추상 클래스를 즉석에서 구현 가능. 재사용은 불가능
익명 클래스는 단 한번만 인터페이스를 생성 가능
Printer printer1 = new LocalPrinter();
printer1.print();
Printer printer2 = new LocalPrinter();
printer2.print();
public class Ex0Main {
public static void hello(String str){
System.out.println("프로그램 시작"); //변하지 않는 부분
System.out.println(str); //변하는 부분
System.out.println("프로그램 종료");//변하지 않는 부분
}
public static void main(String[] args) {
hello("hello Java");
hello("spring");
}
}
public class Ex0 {
public static void helloJava() {
System.out.println("프로그램 시작 ");
System.out.println("Hello Java ");
System.out.println("프로그램 종료 ");
}
public static void helloSpring(){
System.out.println("프로그램 시작");
System.out.println("hello spring");
System.out.println("프로그램 종료");
}
public static void main(String[] args) {
helloJava();
helloSpring();
}
}
public class Ex1Main {
public static void hello(RollDice rollDice){
System.out.println("프로그램 시작");
rollDice.run();
System.out.println("프로그램 종료");
}
static class Dice implements RollDice {
@Override
public void run() {
System.out.println("프로그램 시작");
int random = new Random().nextInt(6) + 1;
System.out.println("random = " + random);
}
}
static class Sum implements RollDice{
@Override
public void run() {
for (int i = 0; i <=3 ; i++) {
System.out.println("i = " + i);
}
}
}
public static void main(String[] args) {
hello(new Dice());
hello(new Sum());
}
}
public interface RollDice {
void run();
}
public class Ex1 {
public static void hellDice(){
System.out.println("프로그램 시작");
int randomVal = new Random().nextInt(6) + 1;
System.out.println("randomVal = " + randomVal);
System.out.println("프로그램 종료");
}
public static void hellSum() {
System.out.println("프로그램 시작");
for (int i = 0; i <= 3; i++) {
System.out.println("i = " + i);
}
System.out.println("프로그램 종료");
}
public static void main(String[] args) {
hellDice();
hellSum();
}
}
코드 조각을 시작하고 종료하는 부분은 변하는 부분이다.
결국 코드 조각을 시작하고 종료하는 부분을 외부에서 전달 받아야 한다. 이것은 단순히 문자열 같은 데이터를 전달 받는 것과는 차원이 다른 문제이다.
코드 조각을 전달하기 위해서는 메서드가 필요. 인스턴스에 있는 메서드 호출
정적 중첩 클래스를 사용했다. 물론 정적 중첩 클래스가 아니라 외부에 클래스를 직접 만들어도 된다.
Process process 매개변수를 통해 인스턴스를 전달할 수 있다. 이 인스턴스의 run() 메서드를 실행하면 필요한 코드 조각을 실행할 수 있다.
이때 다형성을 활용해서 외부에서 전달되는 인스턴스에 따라 각각 다른 코드 조각이 실행된다
public class Ex2Main {
public static void hello(RollDice rollDice){
System.out.println("프로그램 시작");
rollDice.run();
System.out.println("프로그램 종료");
}
public static void main(String[] args) {
class Dice implements RollDice {
@Override
public void run() {
System.out.println("프로그램 시작");
int random = new Random().nextInt(6) + 1;
System.out.println("random = " + random);
}
}
class Sum implements RollDice{
@Override
public void run() {
for (int i = 0; i <=3 ; i++) {
System.out.println("i = " + i);
}
}
}
hello(new Dice());
hello(new Sum());
}
}
public class Ex2 {
public static void hellDice(){
System.out.println("프로그램 시작");
int randomVal = new Random().nextInt(6) + 1;
System.out.println("randomVal = " + randomVal);
System.out.println("프로그램 종료");
}
public static void hellSum() {
System.out.println("프로그램 시작");
for (int i = 0; i <= 3; i++) {
System.out.println("i = " + i);
}
System.out.println("프로그램 종료");
}
public static void main(String[] args) {
hellDice();
hellSum();
}
}
코드 조각을 전달하기 위해 클래스를 정의하고 메서드를 만들고 또 인스턴스를 생성해야 전달??
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);
}
}
public class Ex4 {
public static void hellDice(){
System.out.println("프로그램 시작");
int randomVal = new Random().nextInt(6) + 1;
System.out.println("randomVal = " + randomVal);
System.out.println("프로그램 종료");
}
public static void hellSum() {
System.out.println("프로그램 시작");
for (int i = 0; i <= 3; i++) {
System.out.println("i = " + i);
}
System.out.println("프로그램 종료");
}
public static void main(String[] args) {
hellDice();
hellSum();
}
}
public class Ex5Main {
public static void hello(RollDice rollDice) {
System.out.println("프로그램 시작");
rollDice.run();
System.out.println("프로그램 종료");
}
public static void main(String[] args) {
hello(() -> {
int random = new Random().nextInt(6) + 1;
System.out.println("random = " + random);
});
hello(() -> {
for (int i = 0; i <= 3; i++) {
System.out.println("i = " + i);
}
});
}
}