좋아! 스코프(Scope, 변수의 유효 범위)는 자바에서 매우 중요한 개념이고,
정보처리기사 실기에서 자주 함정 문제로 출제되는 부분이야.
변수가 선언된 위치에 따라, 어디까지 그 변수를 사용할 수 있는지를 말해.
| 종류 | 설명 | 예시 사용 가능 위치 |
|---|---|---|
| 클래스 스코프 | 클래스 전체에서 접근 가능 (static 제외) | 모든 메서드 내부 |
| 메서드 스코프 | 해당 메서드 내부에서만 사용 가능 | 지역 변수 등 |
| 블록 스코프 | {} 블록 안에서 선언된 변수 (for, if 등) | 해당 블록 내부 |
public class Main {
public static void main(String[] args) {
if (true) {
int a = 10;
System.out.println(a); // OK
}
System.out.println(a); // ❌ 컴파일 오류 (a는 if블록 안에서만 유효)
}
}
int a = 10; → if 블록 내부 변수System.out.println(a); (if 밖) → a는 이 위치에서 없음public class Main {
public static void main(String[] args) {
for (int i = 0; i < 3; i++) {
System.out.println(i); // OK
}
System.out.println(i); // ❌ 오류: i는 for문 안에서만 존재
}
}
int i = 0은 for문의 초기식에서 선언됨 → for문의 블록 안에서만 유효public class Main {
static int x = 5;
public static void main(String[] args) {
int x = 10;
System.out.println(x); // 출력값은?
}
}
10
main 안에서 새로 선언된 int x = 10;이static int x = 5;를 가려버림(shadowing)public class Main {
public static void printSum(int a) {
int b = 5;
int a = 3; // ❌ 오류: 변수 a는 이미 파라미터로 존재
System.out.println(a + b);
}
}
a는 이미 파라미터로 선언됨 → 같은 이름의 로컬 변수 선언 불가public class Main {
public static void main(String[] args) {
int sum = 0;
for (int i = 0; i < 3; i++) {
int temp = i * 2;
sum += temp;
}
System.out.println(temp); // ❌ temp는 for문 안에서만 사용 가능
}
}
public class Main {
public static void main(String[] args) {
int a = 5;
{
int a = 10; // ❌ 같은 블록 내에서 변수 이름 중복 선언 불가
}
}
}
public class Main {
public static void main(String[] args) {
int outer = 100;
{
int inner = outer + 50; // OK: 외부 스코프 변수 사용 가능
System.out.println(inner); // 150
}
System.out.println(inner); // ❌ inner는 블록 밖에서 사용 불가
}
}
| 사례 | 변수 접근 가능 여부 |
|---|---|
| 바깥 변수 → 안쪽 블록 | 가능 ✅ |
| 안쪽 변수 → 바깥 블록 | 불가능 ❌ |
| 중첩 블록 안에서 이름 중복 | 불가능 ❌ (같은 스코프 내) |
| 메서드 파라미터와 중복 | 불가능 ❌ |
좋아! 그럼 지금부터 위의 스코프 연습문제 10문제에 대해
하나하나 디버깅 수준으로 상세하게 해설해줄게.
각 줄에서 무슨 일이 일어나는지, 어떤 변수들이 어떤 범위에서 유효한지 모두 설명할게!
public class Main {
public static void main(String[] args) {
int x = 5;
{
int x = 10;
System.out.println(x);
}
}
}
int x = 5; → main() 안에서 x라는 이름의 변수를 선언함 (메서드 스코프)
{ int x = 10; ... } → 같은 스코프 내에 다시 x를 선언하려 함
x가 유효한 상태이므로, 컴파일 에러 발생!❗ 이유:
variable x is already defined in method main(String[])
public class Main {
public static void main(String[] args) {
{
int a = 3;
}
System.out.println(a);
}
}
{ int a = 3; } → a는 이 블록 안에서만 살아있는 블록 스코프 변수System.out.println(a); → 이 줄은 a가 선언된 블록 바깥 → 접근 불가❗ 이유:
a cannot be resolved to a variable
public class Main {
static int value = 100;
public static void main(String[] args) {
int value = 50;
System.out.println(value);
}
}
static int value = 100;main() 안에서 int value = 50;을 새로 선언함50public class Main {
public static void main(String[] args) {
int x = 1;
if (x < 10) {
int y = 5;
}
System.out.println(y);
}
}
int y = 5;는 if 블록 안에서 선언됨 → 블록 스코프System.out.println(y);는 if 밖 → y 접근 불가❗ 이유:
y cannot be resolved to a variable
public class Main {
public static void main(String[] args) {
for (int i = 0; i < 3; i++) {
int sum = i * 2;
System.out.println(sum);
}
System.out.println(sum);
}
}
int sum = ... 은 for 블록 안에서 선언됨 → for문 안에서만 유효sum을 출력하려고 하므로 → 접근 불가❗ 이유:
sum cannot be resolved to a variable
public class Main {
public static void main(String[] args) {
int a = 1;
{
int b = 2;
{
int c = 3;
System.out.println(a + b + c);
}
System.out.println(c);
}
}
}
c는 가장 안쪽 블록에서만 유효함System.out.println(c);는 c가 선언된 블록 바깥 → 접근 불가❗ 오류 줄:
System.out.println(c);
❗ 이유:c cannot be resolved to a variable
public class Main {
public static void print(int num) {
int num = 10;
System.out.println(num);
}
}
int num이 이미 선언되어 있음❗ 이유:
variable num is already defined in method print(int)
public class Main {
static int x = 20;
public static void test() {
System.out.println(x);
}
public static void main(String[] args) {
int x = 10;
test();
}
}
main 안의 x = 10은 지역 변수test()는 자신의 지역 x가 없으므로 클래스의 static int x = 20 사용20public class Main {
public static void main(String[] args) {
int a = 1;
int a = 2;
System.out.println(a);
}
}
❗ 이유:
variable a is already defined in method main(String[])
public class Main {
static int x = 10;
public static void main(String[] args) {
x = x + 5; // 클래스 변수 x = 15
{
int x = 3; // 이 x는 지역 변수, 클래스 x를 가림
x = x + 1; // 지역 변수 x = 4
}
System.out.println(x); // 출력되는 것은 클래스 변수 x = 15
}
}
15인스턴스와 상관없이 클래스가 소유하는 공유 변수-static 변수
final + static 진짜 "변경 불가한 상수"를 의미 (static final int A = 100;)
좋아! 이번엔 기본 개념을 아는 사람도 헷갈릴 수 있는
“꼬아낸” 자바 스코프 문제를 준비했어.
헷갈리는 이유는 shadowing, static/instance 섞기, 클래스 안 중첩, 메서드 내부 클래스 등으로 섞여 있어.
❗ 초보자는 당연히 틀리고, 중급자도 실수할 수 있게 일부러 헷갈리게 꼬았음
❗ 일부 문제는 컴파일 되지만, 일부는 "되긴 하는데 예상과 다른 결과"가 나옴
public class Main {
static int x = 10;
public static void main(String[] args) {
int x = x + 1;
System.out.println(x);
}
}
❓ 출력 결과는?
public class Main {
int a = 1;
public void test() {
int a = a + 1;
System.out.println(a);
}
public static void main(String[] args) {
new Main().test();
}
}
❓ 실행되나? 된다면 출력값은?
public class Main {
public static void main(String[] args) {
int num = 3;
{
int num = num + 1;
System.out.println(num);
}
}
}
❓ 컴파일 될까? 된다면 출력은?
public class Main {
public static void main(String[] args) {
int i = 0;
for (int i = 0; i < 3; i++) {
System.out.println(i);
}
}
}
❓ 컴파일 가능? 왜?
public class Main {
public static void main(String[] args) {
int val = 10;
class Local {
void print() {
System.out.println(val);
}
}
new Local().print();
}
}
❓ 이 코드는 컴파일 되는가? 된다면 출력은?
public class Main {
public static void main(String[] args) {
int val = 10;
class Local {
void modify() {
val = 20;
}
}
new Local().modify();
System.out.println(val);
}
}
❓ 컴파일 되는가? 된다면 출력은?
public class Main {
static {
System.out.println("x = " + x);
}
static int x = 5;
public static void main(String[] args) {
System.out.println("main x = " + x);
}
}
❓ 출력 순서와 출력 내용은?
좋아! 그럼 지금부터 위의 꼬아놓은 스코프 문제 7개를 하나씩,
각 줄에 어떤 일이 일어나는지 디버깅 수준으로 상세하게 해설해줄게.
public class Main {
static int x = 10;
public static void main(String[] args) {
int x = x + 1;
System.out.println(x);
}
}
static int x = 10;
int x = x + 1;
variable x might not have been initialized
int a = 1;
int a = a + 1;
a 선언하려는데 오른쪽 a는 지역 스코프의 a를 참조하려 함this.a를 써야 했음variable a might not have been initialized
int num = 3;
{
int num = num + 1;
System.out.println(num);
}
int num = num + 1; → 선언 중인 변수 num을 자기 자신으로 초기화하려는 코드variable num might not have been initialized
int i = 0;
for (int i = 0; i < 3; i++) {
System.out.println(i);
}
int i = 0; → 먼저 선언됨 (main 내부)for (int i = ... → 같은 이름으로 또 선언하려 함 (블록 안이지만 같은 스코프로 인식됨)variable i is already defined in method main(String[])
int val = 10;
class Local {
void print() {
System.out.println(val);
}
}
val은 이후 변경되지 않기 때문에 "사실상 final"로 간주됨10int val = 10;
class Local {
void modify() {
val = 20;
}
}
Local variable val defined in an enclosing scope must be final or effectively final
static {
System.out.println("x = " + x);
}
static int x = 5;
x가 아직 선언 전 → 기본값 사용int는 초기화 전에는 0이 기본값임x = 0
main x = 5
| 문제 | 정답 | 설명 요약 |
|---|---|---|
| 1 | 컴파일 오류 | 지역 변수 선언 전에 자기 자신 참조 → 초기화 불가 |
| 2 | 컴파일 오류 | 지역 변수 선언 시 인스턴스 변수 가림 → this 필요 |
| 3 | 컴파일 오류 | 선언 중인 변수로 초기화 시도 |
| 4 | 컴파일 오류 | 같은 스코프에 i 중복 선언 |
| 5 | 10 | 지역 클래스가 사실상 final인 val을 참조 |
| 6 | 컴파일 오류 | 지역 클래스 내부에서 지역 변수 val 수정 불가 |
| 7 | x = 0, main x = 5 | static 초기화 순서 주의 |
좋아, 그럼 문제 5, 6, 7번에 대해 "각 줄마다 디버깅 수준으로",
"왜 이 시점에서 그렇게 되는가?"를 시각적으로 해설해줄게.
자바의 지역 클래스(Local Class), 변수 캡처, 그리고 static 초기화 순서에 대한 심화 설명도 포함할게.
public class Main {
public static void main(String[] args) {
int val = 10;
class Local {
void print() {
System.out.println(val);
}
}
new Local().print();
}
}
public class Main {
public static void main(String[] args) {
main() 함수는 프로그램 실행의 시작점. int val = 10;
val이라는 지역 변수 선언됨.main() 메서드 지역 스코프에 속함. class Local {
void print() {
System.out.println(val);
}
val 사용 → 읽기만 함.val은 수정되지 않았으므로 "사실상 final" 상태. new Local().print();
Local 클래스의 객체를 생성하고 print() 호출.val의 값인 10.10
public class Main {
public static void main(String[] args) {
int val = 10;
class Local {
void modify() {
val = 20;
}
}
new Local().modify();
System.out.println(val);
}
}
int val = 10;
val을 선언하고 10으로 초기화.class Local {
void modify() {
val = 20;
}
}
val을 참조할 수 있지만,val은 modify() 메서드에서 값을 변경하려고 함 → 더 이상 final이 아님new Local().modify(); // 호출 안 됨 (컴파일도 안됨)
컴파일 에러:
Local variable val defined in an enclosing scope must be final or effectively final
| 지역 변수 사용 조건 | 읽기 | 쓰기 |
|---|---|---|
사실상 final (val = 10) | ✅ 가능 | ❌ 불가능 |
명시적 final (final val = 10) | ✅ 가능 | ❌ 불가능 |
public class Main {
static {
System.out.println("x = " + x);
}
static int x = 5;
public static void main(String[] args) {
System.out.println("main x = " + x);
}
}
자바 클래스가 최초 로드될 때 다음 순서로 실행됨:
static {
System.out.println("x = " + x);
}
x는 아직 초기화 전 → 자동으로 int의 기본값인 0✔ 출력:
x = 0
static int x = 5;
System.out.println("main x = " + x);
main x = 5
x = 0
main x = 5
| 주제 | 개념 요약 |
|---|---|
| 지역 클래스 변수 접근 | 메서드 안의 지역 변수는 사실상 final일 때만 접근 가능 (Java 8+) |
| 지역 클래스 변수 수정 | 절대 불가능 – 읽기만 가능 |
| static 초기화 순서 | 선언 순서대로 초기화됨. static 블록은 위에 있으면 변수 초기화 전 실행됨 |
좋아, 방금 제시한 이 코드를 디버깅 하듯 한 줄씩 해설해볼게.
초보자가 가장 많이 틀리는 "지역 변수와 final, 지역 클래스(Local Class)" 스코프 문제야.
public class Main {
public static void main(String[] args) {
int x = 5;
int y = 10;
x = 7;
class Local {
void print() {
System.out.println(x + y);
}
}
new Local().print();
}
}
❌ 이 코드는 컴파일 오류 발생함
오류 메시지는 다음과 같아:
Local variable x defined in an enclosing scope must be final or effectively final
public class Main {Mainmain() 메서드 하나를 가짐public static void main(String[] args) {main() 안에서 모든 코드가 실행됨int x = 5;x는 main()의 지역 변수5int y = 10;y도 지역 변수사실상 finalx = 7;📌 이 줄 때문에 x는 더 이상 "사실상 final"이 아님
→ 왜냐면, 초기값 이후에 다시 대입되었기 때문
class Local { ... }지역 클래스 Local 선언
지역 클래스는 감싸고 있는 main()의 지역 변수에 접근할 수 있음
단, 접근 가능한 조건:
final 또는 사실상 final 일 것void print() { System.out.println(x + y); }print() 메서드는 x와 y를 더해서 출력하려고 함
여기서 문제 발생:
x는 수정되었기 때문에 → ❌ 접근 불가 (컴파일 에러)y는 값이 변경되지 않았기 때문에 → ✅ 접근 가능new Local().print();Local 객체를 만들어서 print() 호출 시도하지만 앞에서 x는 접근 자체가 불가능하므로 컴파일 에러 발생
| 변수 | 변경 여부 | 사실상 final? | 지역 클래스 접근 가능? |
|---|---|---|---|
x | 변경됨 | ❌ | ❌ 접근 불가 |
y | 변경 안됨 | ✅ | ✅ 접근 가능 |
방법 1: x를 final로 만들기
final int x = 5; // 값 변경 금지
방법 2: x = 7; 줄을 제거해서 값 안 바꾸게 하기
// x = 7; 주석 처리 또는 삭제
이렇게 하면 x가 사실상 final이 되어 정상 작동함
int x = 5; // 또는 final int x = 5;
int y = 10;
// x = 7; 없애기
System.out.println(x + y); // 5 + 10 = 15
출력 결과: 15