함정2(길긴한데 눈으로,값복사,상속), 꼭 메서드 거치는거 아니더라도 변경 및 변경 안됨.

류한선·2025년 6월 21일

실기연습-2

목록 보기
18/95

좋아요! 정보처리기사 실기 준비를 위해 “기본적이지만 함정이 숨어 있어 기본기가 확실하지 않으면 틀리는 문제”를 드리겠습니다.
이번 문제는 Java로 준비해보았고, 값의 복사와 참조, 배열 초기화, 연산자 우선순위를 잘 이해해야 합니다.


🎯 문제( 체크, 표 정리한거에 의해

배열, int[] arr = {1,2,3};,변경 가능이다.)

다음 코드를 보고 실행 결과를 맞춰보세요.

public class TrickTest {
    public static void main(String[] args) {
        int[] arr1 = new int[3];             // (1)
        int[] arr2 = arr1;                  // (2)

        arr1[0] = 10;                      // (3)
        arr2[1] = 20;                      // (4)

        int x = arr1[2] + arr2[2]++;       // (5)

        System.out.println(arr1[0]);       // (6)
        System.out.println(arr1[1]);       // (7)
        System.out.println(arr1[2]);       // (8)
        System.out.println(arr2[2]);       // (9)
        System.out.println(x);             // (10)
    }
}

🕵️‍♂️ 생각해볼 포인트

arr1arr2가 어떻게 연결되어 있나? (깊은 복사 vs 얕은 복사?)
arr1[2] + arr2[2]++에서 증감 연산자의 동작 시점
✅ Java의 배열 초기값


✏️ 풀이

(1) int[] arr1 = new int[3];

  • 정수형 배열 arr1을 길이 3으로 생성.
  • 값은 [0, 0, 0] (int 배열의 초기값은 0).

상태:

arr1 = [0, 0, 0]

(2) int[] arr2 = arr1;

  • arr2arr1을 참조. 즉 같은 배열을 가리키므로 둘은 동일한 메모리 주소.

상태:

arr2 -> arr1

(3) arr1[0] = 10;

  • arr1[0]에 10 대입. arr2도 동일 참조 중이므로 arr2[0] 역시 10.

상태:

arr1 = [10, 0, 0]
arr2 = arr1 (같은 것)

(4) arr2[1] = 20;

  • arr2[1]에 20 대입 → arr1[1]에도 영향을 미침.

상태:

arr1 = [10, 20, 0]
arr2 = arr1

(5) int x = arr1[2] + arr2[2]++;

여기가 트릭 포인트!

계산 순서:

  • 현재 arr1[2] = 0
  • 현재 arr2[2] = arr1[2] = 0

arr1[2] + arr2[2]++ 에서 arr2[2]++의 의미:

  • arr2[2]++는 후위 증가 연산자이므로 값을 먼저 반환하고, 그 후에 증가.
  • 반환값은 현재 arr2[2]의 값(즉 0)
  • 계산 후 arr2[2]의 값이 1 증가 (0 → 1)

따라서:

  • x = arr1[2] + arr2[2] 반환값 = 0 + 0 = 0
  • 연산 끝나고 arr2[2] = 1 → arr1[2] = 1

상태:

arr1 = [10, 20, 1]
arr2 = arr1
x = 0

출력

(6) arr1[0] → 10
(7) arr1[1] → 20
(8) arr1[2] → 1
(9) arr2[2] → 1
(10) x → 0


✅ 정답

10
20
1
1
0

💡 이 문제의 함정 포인트

  1. 참조 vs 복사
    arr2 = arr1;은 깊은 복사가 아닌 같은 참조를 넘기므로, arr1과 arr2는 같은 배열을 가리킨다.

  2. 후위 연산자 ++ 동작
    arr2[2]++에서 반환값은 증가 전의 값(0)이다. 증가 연산은 반환 이후 일어난다.

  3. 배열 초기화 값
    Java에서 new int[3];[0, 0, 0]으로 초기화됨을 알아야 한다.


좋아요! 이제 단계별 디버깅을 함께해요.
이 문제는 값과 참조를 섞어서 numnumbers의 상태가 어떻게 변하는지를 한 줄 한 줄 디버깅해 보겠습니다.
중간중간 메모리 상태를 도식화하고, 최종 출력값까지 정리해 드릴게요.


🎯 소스코드 복습

public class ValueReferenceTest {
    public static void main(String[] args) {
        int num = 100;
        int[] numbers = {1, 2, 3};

        process(num, numbers);           // (1)

        System.out.println(num);         // (A)
        System.out.println(numbers[0]);  // (B)

        num = changeNum(num);            // (2)
        numbers = changeArray(numbers);  // (3)

        System.out.println(num);         // (E)
        System.out.println(numbers[0]);  // (F)
    }

    static void process(int n, int[] arr) {
        n += 50;
        arr[0] += 50;
    }

    static int changeNum(int n) {
        n = n * 2;
        return n;
    }

    static int[] changeArray(int[] arr) {
        int[] newArr = new int[arr.length];
        for (int i = 0; i < arr.length; i++) {
            newArr[i] = arr[i] + 100;
        }
        return newArr;
    }
}

🧠 한 줄 한 줄 디버깅

초기 상태:

num = 100
numbers -> [1, 2, 3]

(1) process(num, numbers); 호출

이 호출에서 일어나는 일:

호출 시 파라미터

  • n = num의 값 복사 → n = 100
  • arr = numbers의 참조 복사 → arr와 numbers가 같은 배열을 참조

process 내부:

n += 50;         // n = 100 + 50 = 150 (단, 호출자 num에 영향 없음)
arr[0] += 50;    // arr[0] = 1 + 50 = 51 → numbers[0]도 51로 바뀜

process 끝난 후 상태:

num = 100  (n은 process 안에서만 변했으므로 num에 영향 없음)
numbers -> [51, 2, 3] (arr와 numbers가 같은 참조였으므로 numbers[0]만 바뀜)

(A), (B) 출력

System.out.println(num);        // 100
System.out.println(numbers[0]); // 51

(2) num = changeNum(num); 호출

호출:

  • num 현재 100
  • changeNum(100) 호출
  • n = 100 복사
  • n = n * 2 → n = 200
  • return 200 → num = 200

상태:

num = 200
numbers -> [51, 2, 3]

(3) numbers = changeArray(numbers); 호출

호출:

  • arr = numbers의 참조 복사 → arr -> [51,2,3]

changeArray 내부:

int[] newArr = new int[arr.length]; // newArr = [0,0,0]

i=0 → newArr[0] = arr[0]+100 = 51+100=151
i=1 → newArr[1] = arr[1]+100 = 2+100=102
i=2 → newArr[2] = arr[2]+100 = 3+100=103

반환:

  • return newArr → numbers = newArr
  • 이제 numbers는 새 배열 [151,102,103]를 참조
  • arr, newArr 유효범위 종료. 원래 numbers 참조했던 [51,2,3] 배열은 이제 다른 참조 없음(하지만 process 안에서 수정된 값 남아있었음).

(E), (F) 출력

System.out.println(num);         // 200
System.out.println(numbers[0]);  // 151

✅ 최종 출력 정리

  1. (A) → 100
  2. (B) → 51
  3. (E) → 200
  4. (F) → 151

🔍 메모리 상태 요약

시점numnumbers 참조배열 상태
시작100numbers → arr1arr1 = [1,2,3]
process 후100numbers → arr1arr1 = [51,2,3]
changeNum 후200numbers → arr1arr1 = [51,2,3]
changeArray 후200numbers → arr2arr2 = [151,102,103]

💡 이렇게 한 줄 한 줄 디버깅해 보시면:

  • 값 타입(int) 은 메서드 내부에서 바꿔도 호출자에게 영향 없음.
  • 참조 타입(배열) 은 참조를 공유할 경우 호출자에게 영향 있음.
  • 새로운 배열을 return 해서 참조를 교체할 경우 호출자 참조도 바뀜.

return으로 값을 넣어줘서 그런거임.


🧠 소스 코드 한 번 더 보기

class Parent {
    final int value = 10;
    void print() {
        System.out.println("Parent: " + value);
    }
}

class Child extends Parent {
    int value = 20;
    @Override
    void print() {
        System.out.println("Child: " + value);
    }
    void printParent() {
        System.out.println("Parent's value: " + super.value);
    }
}

public class Test {
    public static void main(String[] args) {
        Parent p = new Child();     // (1)
        Child c = new Child();      // (2)

        p.print();                  // (A)
        c.print();                  // (B)
        c.printParent();            // (C)

        Parent p2 = c;              // (D)
        p2.print();                 // (E)

        System.out.println(p2.value); // (F)
    }
}

📌 한 줄 한 줄 상태 & 출력 해설


(1) Parent p = new Child();

  • Child 객체를 생성해서 Parent 타입의 참조 p에 대입.
  • 실제 객체 타입: Child
  • 참조 타입: Parent

동적 바인딩이 작동하기 위해, print() 호출할 때 Child의 메서드가 호출될 수 있음.


(2) Child c = new Child();

  • Child 객체를 생성해 Child 타입의 참조 c에 대입.
  • c의 참조 타입과 실제 객체 타입 모두 Child.

(A) p.print();

호출되는 메서드?

  • p 참조 타입은 Parent지만, 실제 객체가 Child이므로 Childprint() 오버라이딩된 메서드 호출.

출력:

Child: 20

(B) c.print();

  • c의 타입과 객체 모두 Child이므로 Child의 print() 호출

출력:

Child: 20

(C) c.printParent();

  • Child 클래스 내부에서 정의된 printParent() 호출
  • super.value → Parent의 value(=10)를 출력

출력:

Parent's value: 10

(D) Parent p2 = c;

  • 이미 Child 객체를 참조 중인 c를 Parent 타입 참조 p2에 대입.
  • 이제 p2의 참조 타입은 Parent, 실제 객체 타입은 Child.

(E) p2.print();

  • p2 참조 타입은 Parent이지만 실제 객체 타입은 Child이므로 Child의 print() 호출

출력:

Child: 20

(F) System.out.println(p2.value);

여기가 트릭 포인트!

  • 필드 value정적 바인딩되므로 참조 타입 기준(Parent)으로 찾음.
  • Parent의 value 필드를 출력 → 10

출력:

10

🎯 최종 출력 정리

Child: 20
Child: 20
Parent's value: 10
Child: 20
10

🔄 상태 정리

참조실제 객체호출되는 메서드출력
pChildprint()Child: 20
cChildprint()Child: 20
cChildprintParent()Parent's value: 10
p2Childprint()Child: 20
p2Childvalue 필드10

💡 중요 포인트

메서드 호출은 동적 바인딩 → 실제 객체의 오버라이딩 메서드 호출
필드 접근은 정적 바인딩 → 참조 타입의 필드를 참조
super.value는 부모 클래스의 필드를 가리킴


0개의 댓글