반드시 코드를 끝까지 한 줄 한 줄 트레이스(디버깅) 하며 읽어야 틀리지 않아요!
class Test {
static int num = 10;
public static void main(String[] args) {
int num = 5;
num += num++ + --num;
System.out.println(num + " " + Test.num);
}
}
class Test { ... }Test.num이라는 정적 변수(num)가 10으로 초기화됨.Test.num을 참조해야 함.static int num = 10;Test.num = 10 저장.main 시작int num = 5; → main 메서드의 지역변수 num이 5로 초기화됨.Test.num과 다른, 별도의 num.num은 5, Test.num은 10num += num++ + --num; 부분의 연산 순서이 부분이 핵심 트릭이에요. 우선 수식을 단계별로 분해:
num = 5 상태수식: num += num++ + --num;
연산 우선순위를 보면 num++ + --num 먼저 계산되고, 나중에 += 대입됨.
num++후위 증가 연산자이므로:
num++ 값 자체 = 현재 num 값(5), 이후 num을 1 증가--num전위 감소 연산자이므로:
--num 값 = 5num++ + --num = 5 + 5 = 105 상태(--num이 한번 빼버렸으니까 6→5)num += 10;num = num + 10;System.out.println(num + " " + Test.num);15 10
✅ 변수 섀도잉: 지역 num과 Test.num은 다르다.
✅ 후위/전위 연산자 순서 꼼꼼히 계산.
✅ 한 수식 안에서 num++와 --num이 섞일 때 반드시 중간 상태를 추적.
이런 문제를 만났을 때:
좋아요! 😄 이번엔 더 트리키(tricky) 하고, **스코프(scope)**와 static 초기화 블록, 그리고 메서드 호출 순서까지 포함한 문제를 내드릴게요.
이 문제를 풀어보면 정말 정보처리기사 시험에서 나오는 혼동 포인트를 잡을 수 있을 거예요.
class TrickTest {
static int a = initA();
int b = initB();
{
System.out.println("인스턴스 초기화 블록: a=" + a + ", b=" + b);
}
static {
System.out.println("static 초기화 블록: a=" + a);
a += 5;
}
TrickTest() {
System.out.println("생성자 호출됨: a=" + a + ", b=" + b);
b += 10;
}
static int initA() {
System.out.println("initA() 호출됨");
return 3;
}
int initB() {
System.out.println("initB() 호출됨");
return 7;
}
public static void main(String[] args) {
TrickTest t1 = new TrickTest();
System.out.println("마지막 출력: a=" + a + ", b=" + t1.b);
}
}
이 코드를 한 줄 한 줄 디버깅하면서 출력값을 맞춰보세요.
풀이는 아래에서 이어집니다.
우선 한 번 생각해 보신 뒤 아래를 펼쳐 주세요.
static int a = initA(); → initA() 호출 → initA() 호출됨 출력 → a = 3
static { ... } → static 초기화 블록
→ 현재 a = 3 상태
→ static 초기화 블록: a=3 출력
→ 이후 a += 5; → a = 3 + 5 = 8
TrickTest t1 = new TrickTest(); 호출인스턴스 멤버 초기화 시작: int b = initB(); → initB() 호출됨
→ initB() 호출됨 출력
→ b = 7 반환
인스턴스 초기화 블록({ ... })
→ 현재 a = 8, b = 7
→ 인스턴스 초기화 블록: a=8, b=7 출력
생성자 호출
→ 현재 a = 8, b = 7
→ 생성자 호출됨: a=8, b=7 출력
→ 이후 b += 10; → b = 17
System.out.println("마지막 출력: a=" + a + ", b=" + t1.b);마지막 출력: a=8, b=17initA() 호출됨
static 초기화 블록: a=3
initB() 호출됨
인스턴스 초기화 블록: a=8, b=7
생성자 호출됨: a=8, b=7
마지막 출력: a=8, b=17
✅ static 초기화 → 인스턴스 초기화 → 생성자 호출 순서
✅ static 초기화 블록과 static 필드 초기화의 호출 순서
✅ 인스턴스 필드 초기화(initB() 호출) → 인스턴스 초기화 블록 → 생성자 호출 순서
✅ static 필드(a)와 인스턴스 필드(b) 값 변화 꼼꼼히 추적하기!
좋아요! 🎯 이번에는 인터페이스의 default 메서드 트릭 문제를 드릴게요.
이건 인터페이스 상속, 다중 인터페이스, 오버라이딩의 트릭까지 섞여 있어서 정보처리기사 시험에서 헷갈리기 좋은 부분이에요.
interface A {
default void hello() {
System.out.println("A의 hello()");
}
}
interface B {
default void hello() {
System.out.println("B의 hello()");
}
}
class C implements A, B {
// 트릭: 여기서 hello()를 어떻게 처리해야 컴파일될까?
@Override
public void hello() {
B.super.hello(); // 이 부분 트릭의 핵심!
}
}
public class Test {
public static void main(String[] args) {
C c = new C();
c.hello();
}
}
✅ A와 B가 동일한 default 메서드를 가지고 있음 → 충돌
✅ C에서 반드시 hello()를 **재정의(오버라이드)**해야 함
✅ B.super.hello()와 같이 어떤 인터페이스의 default 메서드를 호출할지 지정할 수 있음
A와 B 정의interface A {
default void hello() {
System.out.println("A의 hello()");
}
}
→ A의 default 메서드 hello(): "A의 hello()" 출력
interface B {
default void hello() {
System.out.println("B의 hello()");
}
}
→ B의 default 메서드 hello(): "B의 hello()" 출력
class C implements A, B {
@Override
public void hello() {
B.super.hello();
}
}
→ 인터페이스 A와 B 모두 hello()를 가지고 있어서 C에서 오버라이드 안 하면 컴파일 오류
→ B.super.hello() 호출 → B 인터페이스의 hello() 호출
→ 호출 결과: "B의 hello()" 출력
C c = new C();
c.hello(); // C에서 오버라이드한 hello() 호출
→ "B의 hello()" 출력
B의 hello()
✅ 다중 인터페이스가 동일한 default 메서드를 가질 때 반드시 오버라이드해야 함.
✅ InterfaceName.super.메서드() 형태로 특정 인터페이스의 default 메서드를 호출할 수 있음.
✅ 오버라이드 안 하면 컴파일 오류 발생!