목차
- 로컬 클래스 / 내부 클래스
- Java8 이전 익명 클래스 /로컬 클래스에서 변수 캡쳐
- Java8 이후 익명 클래스 /로컬 클래스에서 변수 캡쳐
- 변수 쉐도잉(Shadowing)
- 변수 캡쳐 정리
- 참고 자료
로컬 클래스 / 내부 클래스
로컬 클래스 (Local Class)
메서드 내에서 정의된 클래스를 의미
이 클래스는 메서드의 실행 흐름 안에서만 사용될 수 있다.
public class LocalClassExample {
public void doSomething() {
// 로컬 클래스
class LocalClass {
void printMessage() {
System.out.println("Hello!");
}
}
// 로컬 클래스 인스턴스 생성 및 메서드 호출
LocalClass localClass = new LocalClass();
localClass.printMessage();
}
public static void main(String[] args) {
LocalClassExample example = new LocalClassExample();
example.doSomething(); // Hello 출력
}
}
Static 내부 클래스 (Static Nested Class)
Static 중첩 클래스는 외부 클래스의 인스턴스와는 독립적으로 존재할 수 있다.
Static 내부 클래스는 static 멤버를 가질 수 있으며, 외부 클래스의 static 멤버에만 접근할 수 있다.
Static 내부 클래스는 주로 외부 클래스의 인스턴스와 상관없이 사용될 독립적인 클래스를 만들고자 할 때 사용
public class OuterClass {
static class StaticNestedClass {
void printMessage() {
System.out.println("Hello!");
}
}
public static void main(String[] args) {
// Static 내부 클래스의 인스턴스 생성
StaticNestedClass nested = new StaticNestedClass();
nested.printMessage();
}
}
Non-static 내부 클래스 (Non-static Inner Class)
Non-static 내부 클래스는 외부 클래스의 인스턴스와 연결
이 클래스는 외부 클래스의 인스턴스 변수를 직접 참조할 수 있다.
외부 클래스의 인스턴스 없이는 Non-static 내부 클래스의 인스턴스를 생성할 수 없다. 즉, 내부 클래스 객체는 항상 외부 클래스 객체에 속한다.
public class OuterClass {
private String message = "Hello!";
class InnerClass {
void printMessage() {
// 외부 클래스의 인스턴스 변수에 접근 가능
System.out.println(message);
}
}
public static void main(String[] args) {
// 외부 클래스의 인스턴스 생성
OuterClass outer = new OuterClass();
// Non-static 내부 클래스의 인스턴스 생성
InnerClass inner = outer.new InnerClass();
inner.printMessage();
}
}
Java8 이전 익명 클래스 / 로컬 클래스에서 변수 캡쳐
public class Main {
public static void main(String[] args) {
}
private void run(){
// Java8 이전은 무조건 final
final int baseNumber = 10;
// 로컬 클래스
class LocalClass{
void printBaseNumber(){
System.out.println(baseNumber);
}
}
// 익명 클래스
Consumer<Integer> integerConsumer = new Consumer<Integer>() {
@Override
public void accept(Integer integer) {
System.out.println(baseNumber);
}
};
}
}
Java8 이전에 메서드 내의 로컬 변수는 무조건 final로 선언해야지
익명 클래스나 로컬 클래스(메서드 내에서 정의된 클래스)에서 참조가 가능했다.
Java8 이후 익명 클래스 / 로컬 클래스에서 변수 캡쳐
Java 8 이후에는 effectively final (사실상 final)개념이 도입되었다.
이제 로컬 변수가 final로 명시되지 않더라도, 값이 한 번만 초기화되고 이후에 변경되지 않는다면 해당 변수를 로컬 클래스나 익명 클래스에서 사용할 수 있다.
다시 말해, 변수의 값이 변경되지 않으면 final로 선언하지 않아도 캡쳐가 가능하다.
private void run() {
int baseNumber = 10; // final로 선언하지 않음
// 로컬 클래스
class LocalClass {
void printBaseNumber() {
System.out.println(baseNumber);
}
}
// 익명 클래스
Consumer<Integer> integerConsumer = new Consumer<Integer>() {
@Override
public void accept(Integer integer) {
System.out.println(baseNumber);
}
};
}
하지만 baseNumber의 값을 변경하려고 하면 컴파일 오류가 발생
Java 8 이후에는 변수의 상태만으로도 캡쳐 가능 여부를 결정하는 유연성이 생겼지만, 변수의 값이 변경되면 여전히 익명 클래스나 로컬 클래스에서 참조할 수 없다.
컴파일 오류 케이스
private void run() {
int baseNumber = 10; // final로 선언하지 않음
// 로컬 클래스
class LocalClass {
void printBaseNumber() {
System.out.println(baseNumber); // baseNumber++ 컴파일 오류 발생
}
}
// 익명 클래스
Consumer<Integer> integerConsumer = new Consumer<Integer>() {
@Override
public void accept(Integer integer) {
System.out.println(baseNumber); // baseNumber++ 컴파일 오류 발생
}
};
baseNumber++;
}
변수 쉐도잉(Shadowing)
하위 스코프에서 상위 스코프에 있는 변수와 동일한 이름의 변수를 선언할 때 발생하는 현상
이 경우. 하위 스코프의 변수는 상위 스코프의 변수를 가려서 접근하지 못하게 만든다.
람다 표현식
public class LambdaShadowingExample {
public static void main(String[] args) {
int baseNumber = 10;
// 람다 표현식
Consumer<Integer> consumer = (baseNumber) -> { // 컴파일 오류 발생
System.out.println(baseNumber);
};
}
}
람다 내부에서 baseNumber라는 이름의 변수를 다시 선언하려고 하면, 컴파일 오류가 발생한다.
이는 자바에서 람다 표현식은 상위 스코프의 변수를 가리는 것이 허용되지 않기 때문
로컬 클래스(Local Class) & 익명 클래스(Anonymous Class)
public class LocalClassShadowingExample {
public static void main(String[] args) {
int baseNumber = 10;
// 로컬 클래스
class LocalClass {
void printBaseNumber() {
int baseNumber = 20; // 쉐도잉 발생
System.out.println(baseNumber); // 20이 출력됨
}
}
LocalClass localClass = new LocalClass();
localClass.printBaseNumber(); // 내부 변수인 20 출력
// 익명 클래스
Consumer<Integer> consumer = new Consumer<Integer>() {
@Override
public void accept(Integer integer) {
int baseNumber = 30; // 쉐도잉 발생
System.out.println(baseNumber); // 30이 출력됨
}
};
}
}
로컬 클래스 내부에서 baseNumber라는 동일한 이름의 변수를 선언하여 쉐도잉이 발생
익명 클래스 내부에서 상위 스코프와 동일한 이름의 변수를 선언하면, 그 변수는 상위 스코프의 변수를 가린다.
변수 캡쳐 정리
public class Main {
public static void main(String[] args) {
}
private void run(){
// Java8 이전은 무조건 final
int baseNumber = 10;
// 로컬 클래스
class LocalClass{
void printBaseNumber(){
System.out.println(baseNumber); // 컴파일 에러
}
}
// 익명 클래스
Consumer<Integer> integerConsumer = new Consumer<Integer>() {
@Override
public void accept(Integer integer) {
int baseNumber = 30;
System.out.println(baseNumber);
}
};
baseNumber++;
// 람다
IntConsumer printInt = i -> System.out.println(i + baseNumber); // 컴파일 에러
printInt.accept(10); // 20 출력
}
}
현재 baseNumber는 final이 아니다.
그런데 run() 메서드 익명 클래스와 람다 표현식 사이를 보면 baseNumber가 변경되고 있다.
그래서 사실상 final 즉 effectively final이 아니다.
그래서 로컬 클래스와 람다 표현식에서는 컴파일 에러가 발생하지만
익명 클래스는 내부에 똑같은 이름의 baseNumber가 있기 때문에 쉐도잉을 통해서 컴파일 에러가 발생하지 않는다.
참고 자료
'Computer Sience > Java' 카테고리의 다른 글
[JAVA8] 인터페이스 기본 메소드와 스태틱 메소드 (0) | 2024.10.02 |
---|---|
[JAVA8] 메소드 래퍼런스 (0) | 2024.09.30 |
[JAVA8] 자바에서 제공하는 함수형 인터페이스 (0) | 2024.09.30 |
[JAVA8] 함수형 인터페이스와 람다 표현식 + 자바 함수형 프로그래밍 (2) | 2024.09.30 |
& VS && (0) | 2024.06.04 |