메서드는 객체를 따르고 필드는 참조를 따른다. 생성자는 부모부터. 오버로딩은 컴파일 때, 오버라이딩은 실행 때. 네 문장이 17문항이다
왜 이걸 하나 — Java 34문항 중 17문항이 이 장이고 11회 중 10회에 나왔다. 2026년 2회에는 2문항이었다.
11회분 220문항 중 이 장의 기출은 17문항이다. 2023년 1회부터 2026년 2회까지 2024년 2회만 빼고 매 회 나왔고, 2023년 1회 · 3회 · 2024년 3회 · 2025년 3회 · 2026년 2회에는 2문항씩이었다. 훈련 Java 10문항(1~3 · 5 · 16~19 · 27 · 30)이 붙는다.
| 꼴 | 몇 문항 | 무엇을 적나 | 보기 |
|---|---|---|---|
| 오버라이딩 출력 | 7 | 부모 참조로 자식 메서드 호출 | 2023-3회 1번 · 14번 · 2024-1회 16번 · 2024-3회 17번 · 2025-1회 13번 · 2026-2회 9번 · 16번 |
| 오버로딩 · 정적 바인딩 | 4 | 매개변수 타입으로 고르기, static 메서드 | 2024-3회 18번 · 2025-1회 20번 · 2025-2회 10번 · 2026-1회 1번 |
| 생성자 순서 | 3 | 부모 생성자 먼저, this() · super() | 2023-1회 17번 · 20번 · 2024-1회 10번 |
| 인터페이스 · 키워드 | 3 | implements 구현, super, 인터페이스 타입 호출 | 2024-2회 12번 · 2025-3회 13번 · 14번 |
→ 17문항 중 11문항이 참조 타입 vs 객체 타입 하나로 갈린다. 2024년 3회 17번(a.x 는 부모 필드, a.getX() 는 자식 메서드)과 2025년 2회 10번(static 메서드는 참조 타입)이 이 규칙의 양쪽 끝이다.
E01 진단의 Java 3문항 중 9번(super.print → 10a20b)과 16번(재정의된 func → 509)이 이 장이다. Java 를 4년 써 온 개발자에게도 시험의 함정은 실무와 다른 자리에 있다 — 필드는 오버라이딩되지 않는다는 것과 오버로딩은 컴파일 때 정해진다는 것이다.
문항 수는 이 노트의 문제 데이터에서 ch=E05 인 것을 센 값이다(2026-09-14). 기출 코드는 grandlife.co.kr 복원에서 옮겼고, 17문항 전부 JDK 17 로 실행하거나(키워드 빈칸 2개는 채워서) 컴파일해 정답을 확인했다. 실행 순서 문제(2024-1회 10번)는 코드 추적으로 확인했다.
왜 이걸 하나 — 시험 전날 규칙 8개를 다시 본다. 전부 「누구를 따르나」다.
Parent p = new Child(); 에서 p.method() 는 Child 의 것. 부모 코드 안에서 자기를 부르는 draw() 도 자식 것이 실행된다(동적 바인딩). 재귀 호출도 마찬가지.p.x 는 Parent 의 x. 자식에 같은 이름의 x 가 있어도 참조 타입이 Parent 면 Parent 것. 메서드 안의 x 는 그 메서드가 속한 클래스의 x.ref.tag() 는 참조 타입의 것. 인스턴스 메서드만 객체를 따른다.f("a") 는 부모가 아는 시그니처 f(Object) 로 묶인다.super()). this(…) 는 같은 클래스의 다른 생성자를 먼저 부른다. 생성자 안에서 오버라이딩된 메서드를 부르면 자식 것이 실행된다.super.method() 는 부모 것을 명시적으로. super(a) 는 부모 생성자, 생성자의 첫 줄에만. this.x 는 내 필드, this(…) 는 내 다른 생성자.abstract 메서드는 몸통 없음, 자식이 구현. 인터페이스는 implements 로 구현하고 모든 추상 메서드를 구현해야 한다. 인터페이스 타입 변수로 불러도 구현체의 코드가 돈다. 상속은 extends.(Child) p 캐스팅, instanceof 로 확인.왜 이걸 하나 — 규칙 8개는 「언제 정해지나」 하나로 묶인다. 컴파일 때 정해지면 참조, 실행 때 정해지면 객체.
Java 는 인스턴스 메서드 호출을 실행 때 객체의 실제 클래스에서 찾는다(동적 바인딩). 그래서 NumberBox box = new SquareBox(); box.calculate(3) 은 SquareBox 의 것이 돌아 9 다(2024년 1회 16번). 부모 코드 안에서 부른 것도 같다 — 2023년 3회 1번의 Base.paint 가 부르는 draw() 는 Sub 의 draw 라 D 가 찍힌다.
필드는 다르다. 필드 접근은 컴파일 때 참조 변수의 타입으로 정해지므로 Base a = new Derived(); a.x 는 Base 의 3 이다. 2024년 3회 17번은 이 둘을 한 식에 넣었다 — a.getX() 는 Derived 것(7 × 3 = 21), a.x 는 Base 것(3), b.getX() 21, b.x 7, 합 52. 메서드는 객체 · 필드는 참조라고 두 번 갈라 적으면 된다.
2023년 3회 1번을 끝까지 따라가면 규칙이 다 보인다. value.paint() 는 Sub 의 paint → super.draw() 는 명시적이라 Base 의 draw 로 B, 그 안의 draw() 는 다시 동적 바인딩이라 Sub 의 D, 돌아와 C, this.draw() 로 D, 마지막 value.draw() 로 D → BDCDD. super 가 붙은 호출만 부모로 고정된다.
오버로딩은 같은 이름의 메서드를 매개변수 타입으로 고르는 것이고, 고르는 시점이 컴파일 때다. 2026년 1회 1번에서 A.g() 안의 f("a") 는 A 가 아는 메서드 f(Object) 로 묶이고, 실행 때 B 가 그 f(Object) 를 오버라이딩했으니 "2" 다. B 의 f(String) 은 A 가 모르는 시그니처라 후보에 없다.
2024년 3회 18번은 제네릭이 끼었다. Collection<T> 의 value 는 컴파일 때 Object 로 지워지므로 print(value) 는 print(Object) 가 선택돼 B0 다. 실제 값이 Integer 라도 선택은 선언 타입으로 한다. 2025년 1회 20번은 calc("5") 가 String 판을 고르고 그 안에서 int 판을 부르는 꼴이라 두 정의를 오가며 4 가 된다.
static 메서드도 참조를 따른다. 2025년 2회 10번의 ref.tag() 는 Parent 참조라 "P", 인스턴스 메서드 ref.value(2) 는 Child 것이라 5 → 5P. static 은 오버라이딩이 아니라 숨김이라는 말이 이 뜻이다.
자식 객체를 만들면 부모 생성자가 먼저 돈다. 명시하지 않아도 super() 가 첫 줄에 있는 셈이고, 2024년 1회 10번의 순서(main → new Child → Child 생성자 → Parent 생성자 → p.getT() → Parent 의 getT)가 그것이다 — Child 의 getT(int) 는 매개변수가 달라 오버로딩이라 Parent 의 getT() 가 실행된다.
2025년 1회 13번은 생성자 안에서 오버라이딩된 메서드를 부른다. Base() 가 update() 를 부르면 객체는 이미 Derived 라 Derived.update 가 돌고, 그 시점의 static total 에 곱이 쌓인다. total 을 줄마다 표로 적어야 54 가 나온다. this(500) 은 같은 클래스의 다른 생성자를 먼저 부르는 것이라 2023년 1회 20번의 x 가 500 이 된다.
2026년 2회 9번의 super.print() 는 부모 print 를 먼저 돌려 10a 를 찍고 이어서 20b 를 찍는다. 2026년 2회 16번은 오버라이딩된 func 이 a × c 라 500, 부모 func 은 1 + 5 + 3 = 9, 합 509. 두 문항이 이 장의 규칙 둘(super 명시 · 오버라이딩)을 한 회차에 담았다.
인터페이스는 메서드 이름만 정한 계약이라 구현 클래스가 implements 로 받아 전부 구현해야 한다(2025년 3회 13번). 인터페이스 타입 변수로 불러도 구현체의 코드가 도는 것은 오버라이딩과 같은 원리다 — 2024년 2회 12번의 Odd · Even 구현체가 각각 25 · 20 을 돌려준다. 부모 생성자를 부르는 예약어는 super 다(2025년 3회 14번).
Base a = new Derived(); a.x 는 Base 의 x 다. 2024년 3회 17번의 52 와 2025년 1회 13번의 54 가 이 규칙 위에 있다. 메서드만 객체를 따른다.왜 이걸 하나 — 호출마다 객체 · 참조를 표시하며 푸는 연습이다.
코드의 호출 줄마다 옆에 「객」 또는 「참」을 적고, 생성자가 있으면 부모 → 자식 순서를 번호로 적는다. 정답을 연 뒤 어느 호출에서 갈렸는지 보면 규칙 8개 중 무엇이 빠졌는지 나온다.
다음 Java 프로그램의 실행 결과를 쓰시오.
답 — Vehicle name: Spark. 인자 없는 getName() 은 부모에만 있으므로 부모 것이 돌고, name 에는 생성자가 넣은 Spark 가 들어 있다.
원리 — 이름이 같은 메서드가 둘 있어도 매개변수의 꼴이 다르면 서로 다른 메서드다(오버로딩). Car 의 getName(String value) 는 인자를 하나 받는 메서드이고, Vehicle 의 getName() 은 인자가 없는 메서드라 둘은 남남이다. 자식이 부모 메서드를 갈아끼우는 것(오버라이딩)은 이름과 매개변수 꼴이 완전히 같을 때만 일어난다. vehicle.getName() 은 인자가 없으니 갈아끼워진 적 없는 부모의 것이 그대로 돈다. 추상 클래스는 직접 만들 수 없지만 자식 객체를 부모 이름으로 담는 것은 된다.
따라가기
new Car("Spark") → Car 생성자에서 부모 필드 name = "Spark"vehicle.getName() — 인자 없음 → Vehicle.getName()헷갈리는 자리 — Car 에 getName 이 있으니 "Car name: Spark" 라고 적는 것. 그것은 인자를 받는 다른 메서드라 이 호출로는 불리지 않는다. 오버로딩(같은 이름 · 다른 매개변수)과 오버라이딩(같은 이름 · 같은 매개변수)을 나누는 문제이고, 2024년 1회 10번의 getT() 도 같은 함정이다.
복원 코드를 JDK 17 로 컴파일 · 실행해 같은 출력을 확인했다. 복원 출처 grandlife.co.kr(2026-09-14) — 공식 원문이 아니라 문장 · 변수명이 실제와 다를 수 있다.
다음 Java 프로그램의 실행 결과를 쓰시오.
답 — 500. Child 를 만들 때 부모 생성자 사슬이 Parent 의 x 를 500 으로 만들고, getX() 는 부모 메서드라 부모의 x 를 돌려준다.
원리 — this(500) 은 같은 클래스의 다른 생성자를 부르는 것이고, 자식 생성자는 첫 줄에 super() 를 안 적어도 부모의 인자 없는 생성자가 몰래 먼저 불린다. 그래서 new Child() 는 Child() → Child(5000) → (암묵) Parent() → Parent(500) 순으로 이어진다. 부모와 자식이 같은 이름 x 를 각각 가지면 두 칸이 따로 있다(필드는 갈아끼워지지 않는다). getX() 는 Parent 에만 있으므로 그 안의 x 는 Parent 의 x 다.
따라가기
헷갈리는 자리 — 5000(자식 x)이나 4000(자식 초기값)을 적는 것. getX 는 자식 칸을 볼 수 없다. 100 을 적는 것은 this(500) 을 못 본 것이다. 필드는 참조형 · 소속 클래스에 따라, 메서드는 실제 객체에 따라 정해진다는 규칙을 이 문제로 외운다.
복원 코드를 JDK 17 로 컴파일 · 실행해 같은 출력을 확인했다. 복원 출처 grandlife.co.kr(2026-09-14) — 공식 원문이 아니라 문장 · 변수명이 실제와 다를 수 있다.
다음 Java 프로그램의 실행 결과를 쓰시오.
답 — BDCDD. super.draw() 만 부모 것이고, 그 안에서 부르는 draw() 와 this.draw() · value.draw() 는 전부 자식 것이다.
원리 — 객체가 Sub 이면 draw() 를 어디서 부르든 Sub 의 draw 가 돈다(오버라이딩, 실제 객체 기준). 딱 하나 예외가 super.draw() 로, "부모 판을 직접 불러라"는 지시다. 그런데 그 부모 판 안에서 다시 draw() 를 부르면 그것은 super 가 아니라 보통 호출이라 다시 자식 것으로 간다. 즉 super 는 그 한 줄에만 효력이 있고 안쪽까지 번지지 않는다. value.paint() 도 Sub 의 paint 다.
따라가기
헷갈리는 자리 — Base.draw 안의 draw() 를 부모 것으로 보고 B 가 계속 찍힌다고(무한 반복) 여기는 것. 자식이 draw 를 덮어썼으니 D 한 번으로 끝난다. Base.paint 의 A 는 한 번도 안 찍힌다 — paint 도 갈아끼워졌다.
복원 코드를 JDK 17 로 컴파일 · 실행해 같은 출력을 확인했다. 복원 출처 grandlife.co.kr(2026-09-14) — 공식 원문이 아니라 문장 · 변수명이 실제와 다를 수 있다.
다음 Java 프로그램의 실행 결과를 쓰시오.
답 — 2. 부모 타입 변수에 자식 객체가 들어 있으므로 Child 의 compute(n − 1 과 n − 3 규칙)가 돌고, 그 재귀 안의 호출도 전부 Child 것이다.
원리 — Parent value = new Child() 는 겉은 Parent, 속은 Child 다. 메서드는 속(실제 객체)을 따르므로 value.compute(7) 은 Child.compute 다. 그 안에서 자기를 다시 부르는 compute(number - 1) 도 같은 객체의 메서드라 계속 Child 규칙이다. 멈춤 조건 number <= 1 은 그 수를 그대로 돌려주므로 0 과 음수도 값으로 나온다 — 여기서 −1 이 나오는 것이 계산의 핵심이다. 작은 수부터 표를 만들어 올라간다.
따라가기 — c(n) = c(n − 1) + c(n − 3), n ≤ 1 이면 n.
헷갈리는 자리 — Parent 규칙(n − 1, n − 2)으로 풀어 피보나치 13 을 적는 것. 그리고 c(−1) 을 0 으로 두는 것 — 조건이 <= 1 이라 −1 을 그대로 돌려준다. 이 두 자리에서 답이 갈린다.
복원 코드를 JDK 17 로 컴파일 · 실행해 같은 출력을 확인했다. 복원 출처 grandlife.co.kr(2026-09-14) — 공식 원문이 아니라 문장 · 변수명이 실제와 다를 수 있다.
다음 Java 코드에서 번호가 붙은 부분의 실행 순서를 중복 번호 없이 쓰시오.
답 — ⑤ → ⑥ → ③ → ① → ⑦ → ②. main 이 시작되고, 자식 생성자가 부모 생성자를 부른 뒤, getT() 는 부모의 것이 불린다.
원리 — 객체를 만들 때는 자식 생성자에 먼저 들어가지만, 그 첫 줄의 super(…) 가 부모 생성자를 부르므로 실제로 몸통이 먼저 끝나는 것은 부모 쪽이다. 그래서 순서는 "자식 진입 → 부모 완료 → 자식 완료"다. Child 의 getT(int value) 는 인자를 받으니 Parent 의 getT() 와 다른 메서드(오버로딩)이고, p.getT() 는 인자가 없으니 부모 것이다. 번호를 "실행이 그 줄에 도달한 순서"로 적는다.
따라가기
헷갈리는 자리 — ① 을 ③ 보다 앞에 두는 것. 부모 생성자가 먼저 "끝나지만" 진입은 자식이 먼저라, 문제가 "실행 순서"를 물으면 ③ → ① 이다. ④ 를 넣어 ⑦ → ④ 로 적는 것 — getT() 와 getT(int) 는 다른 메서드다. 2023년 1회 17번과 같은 함정이다.
각 자리에 출력문을 넣어 JDK 17 로 실행해 순서를 확인했다. 복원 출처 grandlife.co.kr(2026-09-14) — 공식 원문이 아니라 문장 · 변수명이 실제와 다를 수 있다.
다음 Java 프로그램의 실행 결과를 쓰시오.
답 — 9. 부모 타입으로 담았어도 실제 객체가 SquareBox 이므로 갈아끼운 calculate(n × n)가 돌아 3 × 3 이다.
원리 — 자식이 부모와 이름 · 매개변수가 같은 메서드를 다시 정의하면(오버라이딩) 그 객체에서는 언제나 자식 판이 돈다. 변수의 겉모양(NumberBox box)은 "무엇을 부를 수 있는가"만 정하고, "어느 판이 도는가"는 new SquareBox() 가 정한다. @Override 는 컴파일러에게 "이건 갈아끼우기다, 부모에 같은 메서드가 없으면 오류를 내라"고 알리는 표시일 뿐 동작을 바꾸지는 않는다.
따라가기
헷갈리는 자리 — 겉이 NumberBox 니까 n + 1 = 4 라고 적는 것. 필드라면 겉을 따르지만 메서드는 속을 따른다. 이 규칙 하나가 자바 문제 절반의 뼈대라 "필드는 겉, 메서드는 속"으로 외운다. 만약 SquareBox 가 calculate 를 안 만들었다면 부모 것이 돌아 4 가 됐을 것이다.
복원 코드를 JDK 17 로 컴파일 · 실행해 같은 출력을 확인했다. 복원 출처 grandlife.co.kr(2026-09-14) — 공식 원문이 아니라 문장 · 변수명이 실제와 다를 수 있다.
다음 Java 프로그램의 실행 결과를 쓰시오.
답 — 25, 20. 1 부터 9 까지 홀수의 합이 25, 짝수의 합이 20 이고 printf 의 서식대로 쉼표와 띄어쓰기가 붙는다.
원리 — i % 2 == 0 은 2 로 나눠 나머지가 0, 즉 짝수라는 뜻이다. 반복이 1 부터 9 까지 돌며 짝수는 even 에, 나머지(홀수)는 odd 에 더한다. printf("%d, %d", odd, even) 은 서식 문자열의 %d 자리에 차례로 값을 끼우고 나머지 글자(쉼표 · 공백)는 그대로 찍는다. 자바의 printf 는 C 와 같은 꼴이며 줄바꿈은 안 넣는다.
따라가기
헷갈리는 자리 — 순서를 바꿔 20, 25 로 적는 것(첫 %d 가 odd). 쉼표 뒤 공백을 빼먹는 것 — 서식 문자열의 글자가 답의 일부다. 9 를 짝수에 넣거나 0 을 세는 것도 흔한 실수인데, 반복은 1 부터라 0 은 없다. 조건이 i < 9 였다면 9 가 빠져 16, 20 이 된다 — 등호를 본다.
복원 코드를 JDK 17 로 컴파일 · 실행해 같은 출력을 확인했다. 복원 출처 grandlife.co.kr(2026-09-14) — 공식 원문이 아니라 문장 · 변수명이 실제와 다를 수 있다.
다음 Java 프로그램의 실행 결과를 쓰시오.
답 — 52. 메서드는 실제 객체(Derived)를 따라 둘 다 21 이고, 필드 x 는 변수의 겉모양을 따라 a.x 는 3, b.x 는 7 이다.
원리 — 자바에서 메서드는 갈아끼워지지만(오버라이딩) 필드는 갈아끼워지지 않는다. 부모와 자식이 같은 이름의 필드를 가지면 객체 안에 두 칸이 나란히 있고, 어느 칸을 읽을지는 변수의 선언 타입이 정한다. Base a = new Derived() 에서 a.x 는 Base 의 칸(3), a.getX() 는 Derived 의 메서드(그 안의 x 는 Derived 칸 7 → 21). Derived b 는 겉도 Derived 라 b.x 가 7 이다. 한 문제 안에 겉 · 속 규칙을 두 번 넣은 것이다.
따라가기
헷갈리는 자리 — a.x 를 7 로 읽어 56 을 적는 것. 필드는 겉을 따른다. a.getX() 를 Base 판(3 × 2 = 6)으로 읽어 37 로 적는 것 — 메서드는 속을 따른다. 두 규칙을 반대로 적용하면 정확히 틀린다.
복원 코드를 JDK 17 로 컴파일 · 실행해 같은 출력을 확인했다. 복원 출처 grandlife.co.kr(2026-09-14) — 공식 원문이 아니라 문장 · 변수명이 실제와 다를 수 있다.
다음 Java 프로그램의 실행 결과를 쓰시오.
답 — B0. 제네릭 T 는 컴파일 때 Object 로 지워지므로 print(value) 는 print(Object) 로 묶이고, 값 0 이 뒤에 붙는다.
원리 — 오버로딩(같은 이름 · 다른 매개변수 타입) 중 어느 것을 부를지는 실행 때가 아니라 컴파일 때 "변수의 선언 타입"으로 정해진다. Collection<T> 안의 value 는 타입이 T 인데, 자바의 제네릭은 컴파일이 끝나면 T 를 그 상한인 Object 로 바꿔 버린다(타입 소거). 그래서 new Printer().print(value) 는 실제로 Integer 0 이 들어 있어도 print(Object) 로 묶인다. 실행 때 "더 잘 맞는 Integer 판"으로 바꿔 타는 일은 없다 — 오버로딩은 정적이다.
따라가기
new Collection<>(0) → T 는 Integer 로 추론되지만 코드 안에서는 Object 취급헷갈리는 자리 — 값이 정수니 print(Integer) 의 A0, 혹은 Number 라서 C0 을 적는 것. 오버로딩 선택은 값이 아니라 선언 타입으로, 그것도 컴파일 때 끝난다. 2026년 1회 1번(f(Object) 와 f(String))이 같은 규칙의 다른 얼굴이다.
복원 코드를 JDK 17 로 컴파일 · 실행해 같은 출력을 확인했다. 복원 출처 grandlife.co.kr(2026-09-14) — 공식 원문이 아니라 문장 · 변수명이 실제와 다를 수 있다.
다음 Java 프로그램의 실행 결과를 쓰시오.
답 — 54. 부모 생성자 안에서 부른 update() 도 자식 판이 돌아 total 이 2 → 6 이 되고, 자식 생성자에서 12 를 더해 18, 다시 자식 update 로 54 가 된다.
원리 — new Derived() 는 부모 생성자 Base() 를 먼저 돌린다. 그 안에서 update() 를 부르면 만들어지는 객체가 Derived 이므로 갈아끼운 Derived.update 가 돈다 — 생성자 안이라도 오버라이딩은 그대로다. ++value 는 먼저 1 을 올린 값을 쓰고, value++ 는 지금 값을 쓴 뒤 올린다. Base 의 value 와 Derived 의 value 는 이름만 같은 다른 칸이다. static 인 total 은 클래스에 하나뿐이라 부모 · 자식이 같은 것을 고친다.
따라가기 — total = 0.
헷갈리는 자리 — 부모 생성자 안의 update 를 부모 판(total += total → 4)으로 계산해 다른 값을 얻는 것. 그리고 ++value 를 1 로 쓰는 것 — 앞에 붙은 ++ 는 올린 뒤의 값 2 다. 6 → 18 → 54 의 세 배 규칙이 보이면 맞게 간 것이다.
복원 코드를 JDK 17 로 컴파일 · 실행해 같은 출력을 확인했다. 복원 출처 grandlife.co.kr(2026-09-14) — 공식 원문이 아니라 문장 · 변수명이 실제와 다를 수 있다.
다음 Java 프로그램의 실행 결과를 쓰시오.
답 — 4. calc("5") 는 String 판에서 시작해 calc(4) 와 calc(2) 를 부르는데, 인자가 int 라 그 뒤로는 전부 int 판(n − 1, n − 2)이다.
원리 — 같은 이름 calc 가 둘인데 하나는 String 을, 하나는 int 를 받는다(오버로딩). 어느 것을 부를지는 넘기는 값의 타입으로 정해진다. calc("5") 는 String 판이고, 그 안의 calc(value - 1) 은 int 를 넘기니 int 판이다. String 판은 딱 한 번만 돌고 규칙(n − 1, n − 3)도 그 한 번에만 쓰인다. 나머지는 int 판의 피보나치 규칙이다. 두 규칙을 언제 바꿔 타는지가 전부다.
따라가기 — int 판: c(0) = 0, c(1) = 1, c(2) = 1, c(3) = 2, c(4) = 3.
헷갈리는 자리 — String 판 규칙(n − 1, n − 3)을 끝까지 쓰는 것. 첫 호출 뒤에는 인자가 int 라 String 판으로 돌아오지 않는다. 반대로 처음부터 int 판으로 보고 c(5) = 5 를 적는 것 — 첫 갈림이 (4, 2) 다.
복원 코드를 JDK 17 로 컴파일 · 실행해 같은 출력을 확인했다. 복원 출처 grandlife.co.kr(2026-09-14) — 공식 원문이 아니라 문장 · 변수명이 실제와 다를 수 있다.
다음 Java 프로그램의 실행 결과를 쓰시오.
답 — 5P. 인스턴스 메서드 value 는 자식 판(n + 3)이 돌아 5 이고, static 메서드 tag 는 변수의 겉모양 Parent 를 따라 P 다.
원리 — 오버라이딩은 인스턴스 메서드에만 일어난다. static 메서드는 객체가 아니라 클래스에 붙어 있어서 ref.tag() 라고 써도 실제로는 Parent.tag() 로 컴파일된다(변수의 선언 타입). 자식이 같은 이름의 static 메서드를 만들어도 그것은 "숨기기"일 뿐 갈아끼우기가 아니다. 그래서 같은 객체에서 value 는 속(Child)을, tag 는 겉(Parent)을 따른다. 필드가 겉을 따르는 것과 같은 편이다.
따라가기
헷갈리는 자리 — 5C 로 적는 것. static 은 오버라이딩되지 않는다. 겉을 따르는 것 두 가지(필드 · static 메서드)와 속을 따르는 것 한 가지(인스턴스 메서드)로 정리해 둔다. 숫자와 문자열의 + 는 결합이라 5P 이지 덧셈이 아니다.
복원 코드를 JDK 17 로 컴파일 · 실행해 같은 출력을 확인했다. 복원 출처 grandlife.co.kr(2026-09-14) — 공식 원문이 아니라 문장 · 변수명이 실제와 다를 수 있다.
다음 Java 프로그램의 빈칸에 들어갈 키워드를 쓰시오.
답 — implements. 클래스가 인터페이스를 따를 때 쓰는 예약어다.
원리 — 인터페이스는 "이런 메서드를 반드시 갖춰라"는 약속 목록이다. 클래스가 그 약속을 지키겠다고 선언하는 말이 implements(구현하다)이고, 그 클래스는 목록의 메서드를 전부 몸통과 함께 만들어야 한다. 반면 클래스가 다른 클래스를 물려받을 때는 extends(확장하다)를 쓴다. 인터페이스가 다른 인터페이스를 물려받을 때도 extends 다. Machine 이 interface 로 선언됐으니 Washer 쪽은 implements 다.
따라가기
헷갈리는 자리 — extends 를 적는 것. 클래스 → 인터페이스는 implements, 클래스 → 클래스와 인터페이스 → 인터페이스는 extends 로 짝을 외운다. 인터페이스 메서드는 기본이 public 이라 구현할 때 public 을 빼면 오류가 나는 것도 같은 묶음의 함정이다.
빈칸에 답을 넣어 JDK 17 로 컴파일해 확인했다. 복원 출처 grandlife.co.kr(2026-09-14) — 공식 원문이 아니라 문장 · 변수명이 실제와 다를 수 있다.
다음 Java 코드의 ①에 들어갈 예약어를 쓰시오.
답 — super. 자식 생성자에서 부모 생성자를 부를 때 쓰는 예약어다.
원리 — Rectangle 에는 인자 없는 생성자가 없고 (width, height) 를 받는 생성자만 있다. 자식 Square 를 만들려면 부모 부분도 만들어져야 하는데, 자바는 부모의 인자 없는 생성자를 몰래 부르므로 그런 생성자가 없으면 컴파일 오류가 난다. 그래서 자식 생성자 첫 줄에 super(side, side) 로 "부모의 저 생성자를 이 값으로 불러라"고 직접 적어야 한다. 정사각형은 가로와 세로가 같으니 side 를 두 번 넘긴다. super(...) 는 반드시 첫 줄이어야 한다.
따라가기
헷갈리는 자리 — this(side, side) 를 적는 것. this(…) 는 같은 클래스의 다른 생성자를 부르는 것인데 Square 에는 두 인자 생성자가 없다. Rectangle(side, side) 처럼 클래스 이름을 적는 것도 오류다 — 생성자 호출은 super 나 this 뿐이다.
빈칸에 답을 넣어 JDK 17 로 컴파일해 확인했다. 복원 출처 grandlife.co.kr(2026-09-14) — 공식 원문이 아니라 문장 · 변수명이 실제와 다를 수 있다.
다음 Java 프로그램을 실행했을 때 출력되는 값을 쓰시오.
답 — 2. a.g() 는 A 에 적힌 g() 를 돌리고, 그 안의 f("a") 는 컴파일 때 f(Object) 로 정해진 뒤 실행 때 B 가 덮어쓴 f(Object) 로 간다.
원리 — 자바는 메서드를 두 번 고른다. 첫째, 컴파일 때 "어느 이름 · 어느 매개변수 꼴인가"를 고른다(오버로딩 선택). 둘째, 실행 때 "그 꼴을 가진 메서드 중 실제 객체의 것"을 고른다(오버라이딩). 편지에 비유하면 봉투의 주소(시그니처)는 부칠 때 정해지고, 그 집에 누가 사는지(실제 객체)는 배달할 때 본다. 봉투를 쓰는 곳은 A 안이라 A 가 아는 f(Object) 라고만 적힌다. B 의 f(String) 은 이름만 같을 뿐 다른 봉투라 후보에도 못 든다.
따라가기
A a = new B(); — 겉은 A, 속은 Ba.g() — g 는 A 에만 있으니 A.g 가 돈다f("a") — A 클래스 안에서 컴파일됐고 A 가 아는 f 는 f(Object) 하나. 봉투 = f(Object)헷갈리는 자리 — "a" 는 String 이니 B.f(String) 이 더 잘 맞지 않나? 아니다. 오버로딩 선택은 A 안에서 끝났고 그때 f(String) 은 없었다. 답을 3 으로 적는 것이 이 문제의 함정이다. 2024년 3회 18번(제네릭 T 가 Object 로 지워져 print(Object) 가 골라짐)과 같은 규칙이다.
복원 코드를 JDK 17 로 컴파일 · 실행해 같은 출력을 확인했다. 복원 출처 grandlife.co.kr(2026-09-14) — 공식 원문이 아니라 문장 · 변수명이 실제와 다를 수 있다.
다음 Java 프로그램의 실행 결과를 쓰시오.
답 — 10a20b. 자식의 print 가 먼저 부모 print 를 부르고(10a), 이어서 자기 것을 찍는다(20b).
원리 — super(a) 는 부모 생성자에 값을 넘겨 부모 필드 a 를 채우는 것이고, super.print() 는 갈아끼우기 전의 부모 판 print 를 그 자리에서 한 번 부르는 것이다. 자식이 print 를 덮어썼어도 super. 을 붙이면 부모 판이 돈다. 문자열과 숫자를 + 로 이으면 덧셈이 아니라 붙이기라 a + "a" 는 "10a" 다. 두 print 는 print(줄바꿈 없음)라 한 줄에 이어진다.
따라가기
헷갈리는 자리 — 20b 만 적는 것(super.print 를 빼먹음)이나 순서를 바꿔 20b10a 로 적는 것. super.print() 가 먼저 오는 줄이라 10a 가 앞이다. 10 + "a" 를 계산하려 드는 것 — 한쪽이 문자열이면 붙이기다.
복원 코드를 JDK 17 로 컴파일 · 실행해 같은 출력을 확인했다. 복원 출처 grandlife.co.kr(2026-09-14) — 공식 원문이 아니라 문장 · 변수명이 실제와 다를 수 있다.
다음 Java 프로그램의 실행 결과를 쓰시오.
답 — 509. a.func() 는 부모 판이라 1 + 5 + 3 = 9, b.func() 는 갈아끼운 판이라 10 × 50 = 500 이다.
원리 — a 는 A 로 만든 객체이므로 A.func 가 그대로 돌고, b 는 B 로 만들었으니 B 가 덮어쓴 func 가 돈다. B.func 안에서 a 와 c 를 쓸 수 있는 이유는 접근 범위 때문이다 — int a(기본, 같은 패키지)와 protected int c 는 자식이 볼 수 있지만 private int b 는 A 안에서만 보인다. 그래서 B.func 가 b 를 안 쓴 것이고, 만약 썼다면 컴파일 오류다. super(a, b, c) 로 값은 전부 부모 칸에 저장돼 있다.
따라가기
헷갈리는 자리 — b.func() 도 부모 판으로 계산해 90 을 더하는 것(99). 오버라이딩된 객체는 자기 판이다. 답이 정수 덧셈인 것도 확인한다 — 둘 다 int 라 붙이기가 아니라 더하기다(9509 가 아니다).
복원 코드를 JDK 17 로 컴파일 · 실행해 같은 출력을 확인했다. 복원 출처 grandlife.co.kr(2026-09-14) — 공식 원문이 아니라 문장 · 변수명이 실제와 다를 수 있다.
다음 코드의 출력을 쓰시오.
답 — DAnimalAnimal. 메서드는 자식 것이 불리지만(D), 필드는 참조 타입인 Animal 것이 읽힌다(Animal · Animal).
원리 — 자바에서 메서드 호출은 "실제 객체"를 보고 결정된다(동적 바인딩). a 는 Animal 타입 변수지만 안에 든 것은 Dog 이므로 cry() 는 Dog 의 것이 불려 D 가 찍힌다. 그런데 필드는 다르다 — 필드는 오버라이딩되지 않고 "가려질(hiding)" 뿐이며, 어느 필드를 읽을지는 컴파일 시점의 타입으로 정해진다. sound() 는 Animal 클래스 안에 적힌 코드라 거기서 name 은 Animal 의 name 이고, a.name 은 a 의 타입이 Animal 이라 역시 Animal 의 name 이다. Dog 의 name = "Dog" 는 Dog 타입 변수로 봐야만 보인다.
따라가기
헷갈리는 자리 — 필드도 오버라이딩된다고 생각해 DDogDog 로 적는 것. "메서드는 객체를 보고, 필드는 타입을 본다" 한 줄로 잡는다. 필드에 자식 값을 쓰게 하려면 getName() 같은 메서드로 감싸야 한다 — 그것이 캡슐화가 권장되는 이유 중 하나다.
이전 자료의 예상 문제다. 코드를 손으로 따라가고 JDK 17 실행 결과와 대조했다.
다음 코드의 출력을 쓰시오.
답 — 11. super.g() 안에서 불리는 f() 도 B 의 f 라서 5 + 1 = 6 이고, 거기에 B 의 f() 5 를 더해 11 이다.
원리 — super.g() 는 "부모 클래스의 g 코드를 실행하라"이지 "부모 객체로 바꿔라"가 아니다. 실행되는 객체는 여전히 B 인스턴스다. 그래서 A 의 g 안에 있는 f() 호출은 동적 바인딩으로 B 의 f 를 부른다 — A 의 f 가 아니다. 결과 super.g() = 5 + 1 = 6. B 의 g 는 거기에 자기 f() = 5 를 더해 11 을 돌려준다. super 는 "코드 선택"만 바꾸고 "객체"는 못 바꾼다는 것이 핵심이다.
따라가기
헷갈리는 자리 — super.g() 안의 f() 를 A 의 f 로 보고 3 + 1 + 5 = 9 로 적는 것. super 로 부른 메서드 안에서도 this 는 B 다. 부모의 f 를 명시적으로 부르려면 A 의 g 코드가 아니라 B 안에서 super.f() 라고 써야 한다.
이전 자료의 예상 문제다. 코드를 손으로 따라가고 JDK 17 실행 결과와 대조했다.
왜 이걸 하나 — Java 는 원리를 알아도 한 호출에서 갈려 통째로 깎인다.
| 자리 | 새는 꼴 | 적는 법 | 기출 |
|---|---|---|---|
| 필드 | a.x 를 자식 값으로 읽는다 | 필드는 참조 타입. 메서드만 객체 타입 | 2024-3회 17번 · 2025-1회 13번 |
| static | ref.tag() 를 자식 것으로 | static 메서드는 참조 타입(숨김) | 2025-2회 10번 |
| 오버로딩 후보 | 부모가 모르는 f(String) 을 고른다 | 부모 코드 안의 호출은 부모의 시그니처로 묶인다 | 2026-1회 1번 |
| 제네릭 | T 를 Integer 로 보고 print(Integer) | T 는 Object 로 지워진다 | 2024-3회 18번 |
| super 범위 | super.draw() 안의 draw() 도 부모로 | super 가 붙은 그 호출만 부모. 안에서 부르는 것은 다시 객체 | 2023-3회 1번 |
| 생성자 순서 | 자식 생성자 본문을 부모보다 먼저 | super() 가 첫 줄. this() 도 먼저 | 2024-1회 10번 · 2023-1회 20번 |
| 오버로딩 · 오버라이딩 | getT(int) 를 getT() 의 오버라이딩으로 | 매개변수가 다르면 오버로딩 — 부모 것이 그대로 남는다 | 2024-1회 10번 |
| 표기 | 10a20b 를 「10a 20b」로, 순서를 번호 없이 | print 는 줄바꿈 없음. 순서 문제는 문제의 번호 표기 그대로 | 2026-2회 9번 · 2024-1회 10번 |
Java 출력 문제는 print 와 println 의 차이가 줄바꿈을 정한다. 코드에서 println 을 동그라미 치고, 답의 줄 수를 그 개수에 맞춘다.
왜 이걸 하나 — 규칙 8개를 짧은 코드 10개로 돌린다. 매일 5문항씩 곁들이는 자리다.
훈련 10문항은 동적 바인딩, 필드 비오버라이딩, 생성자 순서와 super, 오버로딩 선택, instanceof 와 캐스팅, 인터페이스, 추상 클래스, static 숨김, 2026년 1회 1번 원리 재훈련, this() 체인이다. 정답은 전부 JDK 17 로 실행해 확인했다.
답 — CP. 변수 타입은 둘 다 A 지만 실제 객체가 B 면 B 의 show, A 면 A 의 show 가 불린다.
원리 — 오버라이딩된 메서드는 "변수의 타입"이 아니라 "변수가 가리키는 실제 객체"의 것이 실행된다. A obj = new B() 에서 obj 의 타입은 A 지만 객체는 B 라 show() 는 C 를 찍는다. obj = new A() 로 갈아 끼우면 이제 객체가 A 라 P 를 찍는다. 같은 변수, 같은 호출문인데 결과가 다른 것이 다형성이다.
따라가기
이 노트에서 만든 변형 문제다. 코드를 손으로 따라가고 JDK 17 실행 결과와 대조했다.
답 — 10 20. 필드 o.x 는 타입(A)을 따라 10, 메서드 o.get() 은 객체(B)를 따라 20 이다.
원리 — 필드는 오버라이딩되지 않는다. 자식이 같은 이름의 필드를 선언하면 부모 필드를 "가릴" 뿐 둘 다 존재하고, 어느 것을 읽을지는 변수의 컴파일 타입으로 정해진다 — o 는 A 타입이라 A 의 x = 10. 반면 메서드 get() 은 오버라이딩되어 실제 객체 B 의 get 이 불리고, B 의 get 안에서 x 는 B 의 x = 20 이다. 한 줄에 "필드는 타입, 메서드는 객체"가 나란히 나오는 문제다.
따라가기
이 노트에서 만든 변형 문제다. 코드를 손으로 따라가고 JDK 17 실행 결과와 대조했다.
답 — ABA5B5. 자식 생성자는 반드시 부모 생성자를 먼저 부른다 — 안 적으면 super() 가, 적으면 그 super(n) 이.
원리 — 자바는 자식 객체를 만들 때 부모 부분을 먼저 만든다. 그래서 자식 생성자의 첫 줄에는 항상 부모 생성자 호출이 있다. new B() 의 B() 에는 super 호출이 안 적혀 있지만 컴파일러가 super() 를 넣어 A() 가 먼저 돌아 A, 그다음 B. new B(5) 의 B(int) 는 super(n) 을 명시했으니 A(int) 가 돌아 A5, 그다음 B5 다. "부모 먼저, 그다음 자식"이 생성 순서의 철칙이다.
따라가기
이 노트에서 만든 변형 문제다. 코드를 손으로 따라가고 JDK 17 실행 결과와 대조했다.
답 — int3 dbl3.0. 인자 타입에 따라 같은 이름의 다른 메서드가 골라진다(오버로딩).
원리 — 오버로딩은 이름이 같고 매개변수 타입 · 개수가 다른 메서드를 여러 개 두는 것이다. 컴파일러가 호출 인자의 타입을 보고 가장 잘 맞는 것을 고른다. f(3) 의 3 은 int 리터럴이라 f(int) 가, f(3.0) 의 3.0 은 double 리터럴이라 f(double) 이 불린다. "dbl" + n 에서 double 3.0 은 문자열로 바뀔 때 "3.0" 이 된다 — 소수점이 보존된다.
따라가기
이 노트에서 만든 변형 문제다. 코드를 손으로 따라가고 JDK 17 실행 결과와 대조했다.
답 — 4. o 가 실제로 String 이라 instanceof 가 참이고, 형 변환 뒤 "text" 의 길이 4 를 찍는다.
원리 — Object 타입 변수는 무엇이든 담을 수 있지만 그 타입으로는 String 메서드를 못 부른다. instanceof 는 "이 변수에 든 객체가 실제로 그 타입(또는 자식)인가"를 실행 시점에 검사한다. 참이면 (String) o 로 내려 형 변환(다운캐스팅)해 String 메서드를 쓴다. 검사 없이 캐스팅했다가 실제 타입이 다르면 ClassCastException 이 나므로 이 짝(instanceof → 캐스팅)이 관용구다.
따라가기
이 노트에서 만든 변형 문제다. 코드를 손으로 따라가고 JDK 17 실행 결과와 대조했다.
답 — 25. 1 부터 9 까지 홀수 1 + 3 + 5 + 7 + 9 다.
원리 — 인터페이스 Calc 는 calc(int) 라는 약속만 있고 몸통이 없다. Odd 가 implements 로 그 약속을 구현했고, 구현 메서드는 반드시 public 이어야 한다(인터페이스 메서드가 public 이라 좁힐 수 없다). Calc c = new Odd() 로 인터페이스 타입 변수에 담아도 c.calc(9) 는 Odd 의 구현이 실행된다. 반복은 i = 1 에서 2 씩 올라 1 · 3 · 5 · 7 · 9 를 더한다 — 홀수 n 개의 합은 n² 이라 5² = 25 로 검산된다.
따라가기
이 노트에서 만든 변형 문제다. 코드를 손으로 따라가고 JDK 17 실행 결과와 대조했다.
답 — area:12. 추상 클래스의 print 가 자식이 구현한 area() 를 불러 3 × 4 를 찍는다.
원리 — abstract 메서드 area() 는 몸통이 없어 Shape 자체로는 객체를 못 만들고, 자식 Rect 가 반드시 구현해야 한다. print() 는 Shape 에 몸통이 있고 그 안에서 area() 를 부른다 — 부모 코드가 "자식이 채울 빈칸"을 호출하는 구조다(템플릿 메서드 패턴의 축소판). Shape s = new Rect() 로 담아 s.print() 를 부르면 Shape 의 print 가 돌고, 그 안의 area() 는 실제 객체 Rect 의 것이라 12 다.
따라가기
이 노트에서 만든 변형 문제다. 코드를 손으로 따라가고 JDK 17 실행 결과와 대조했다.
답 — SAIB. static 메서드는 타입(A)을 따라 SA, 인스턴스 메서드는 객체(B)를 따라 IB 다.
원리 — static 메서드는 객체가 아니라 클래스에 붙어 있어 오버라이딩되지 않고 "가려질(hiding)" 뿐이다. 어느 것을 부를지는 변수의 컴파일 타입으로 정해진다 — x 는 A 타입이라 x.f() 는 A 의 f 다(원래 A.f() 라고 쓰는 것이 맞고 x.f() 는 경고 대상). 인스턴스 메서드 g() 는 오버라이딩되어 실제 객체 B 의 g 가 불린다. 필드와 static 메서드는 "타입", 인스턴스 메서드는 "객체" — 이 한 줄이 다형성 문제 전부를 가른다.
따라가기
이 노트에서 만든 변형 문제다. 코드를 손으로 따라가고 JDK 17 실행 결과와 대조했다.
답 — BO. 어느 오버로드를 부를지는 A 의 코드에서 컴파일 시점에 f(Object) 로 정해지고, 그 f(Object) 의 실제 구현은 B 의 것이 실행된다.
원리 — 오버로딩(어느 시그니처인가)은 컴파일 시점에, 오버라이딩(누구의 구현인가)은 실행 시점에 결정된다. g() 는 A 안에 있고 A 에는 f(Object) 하나뿐이라 f("a") 는 f(Object) 로 묶인다 — B 에 f(String) 이 있어도 A 의 코드는 그것을 모른다. 실행 시점에 객체가 B 이므로 f(Object) 의 구현은 B 가 오버라이딩한 것이 불려 BO 다. f(String) 은 오버라이딩이 아니라 B 만의 새 오버로드라 여기서는 닿지 않는다.
따라가기
이 노트에서 만든 변형 문제다. 코드를 손으로 따라가고 JDK 17 실행 결과와 대조했다.
답 — LK7. this(7) 이 먼저 X(int) 를 실행해 L 을 찍고, 돌아와 K 를 찍은 뒤 x 는 7 이다.
원리 — this(...) 는 같은 클래스의 다른 생성자를 부르는 것이고 생성자의 첫 줄에만 올 수 있다. new X() 는 X() 로 들어가자마자 this(7) 로 X(int) 를 실행한다 — x = 7, "L" 출력. 그것이 끝나면 X() 의 나머지 줄 "K" 가 실행된다. 그래서 순서가 L 다음 K 다. o.x 는 7 이다. 생성자 체이닝은 초기화 코드를 한 곳에 모으는 관용구다.
따라가기
이 노트에서 만든 변형 문제다. 코드를 손으로 따라가고 JDK 17 실행 결과와 대조했다.
JDK 17 이 깔려 있으니 코드를 Main.java 로 저장하고 파워셸에서 아래를 치면 된다. 클래스 이름이 파일 이름과 같아야 하고, public class 가 하나여야 한다.
이 장의 기출 17개 · 훈련 10개는 위 명령(JDK 17.0.12)으로 실행해 답을 확인했다. 클래스 선언과 문장이 섞인 훈련 코드는 클래스는 밖으로, 문장은 main 안으로 옮겨 컴파일했다.
왜 이걸 하나 — 아래를 안 보고 적을 수 있어야 이 장이 끝난다.