[2024년] 정보처리기사 실기 - Java 언어
2024년도 1회, 2회, 3회의 정보처리기사 실기 기출문제 속 Java 언어 문제를 정리하였다.
2024년 1회 정보처리기사 실기
문제 1. 싱글톤 패턴 (Singleton Pattern)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
class Connection {
private static Connection _inst = null;
private int count = 0;
public static Connection get() {
if(_inst == null) {
_inst = new Connection();
return _inst;
}
return _inst;
}
public void count() {
count++;
}
public int getCount() {
return count;
}
}
public class main {
public static void main(String[] args) {
Connection conn1 = Connection.get();
conn1.count();
Connection conn2 = Connection.get();
conn2.count();
Connection conn3 = Connection.get();
conn3.count();
conn1.count();
System.out.print(conn1.getCount());
}
}
더보기
[정답]
4
[풀이]
1
2
3
4
5
6
public static Connection get() {
if(_inst == null) {
_inst = new Connection();
}
return _inst;
}
객체가 없으면 생성하고, 있으면 기존 객체를 반환한다.
_inst는 static 변수이므로 하나의 객체만 생성된다.
1
2
3
4
5
6
7
8
9
10
11
12
/* conn1, conn2, conn3은 전부 같은 객체이다 */
Connection conn1 = Connection.get();
conn1.count(); // count = 1
Connection conn2 = Connection.get();
conn2.count(); // count = 2
Connection conn3 = Connection.get();
conn3.count(); // count = 3
conn1.count(); // count = 4
모든 변수가 같은 객체를 참조하므로 count 값이 누적된다.
1
System.out.print(conn1.getCount());
현재 conn1의 count 값인 4가 출력된다.
싱글톤 패턴
특정 클래스의 인스턴스를 1개만 생성하는 것을 보장하는 패턴이다.
- 인스턴스 단 하나만 생성
- 여러 번 호출해도 같은 인스턴스를 공유
1
private static Connection _inst = null;
문제 2. 상속과 생성자 호출 순서 및 동적 바인딩
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
class Parent {
int x, y;
Parent(int x, int y) { // ①
this.x = x;
this.y = y;
}
int getX() { // ②
return x * y;
}
}
class Child extends Parent {
int x;
Child(int x) { // ③
super(x + 1, x);
this.x = x;
}
int getX(int n) { // ④
return super.getX() + n;
}
}
public class Main {
public static void main(String[] args) { // ⑤
Parent parent = new Child(10); // ⑥
System.out.println(parent.getX()); // ⑦
}
}
실행 순서 : 5 ➔ ( ) ➔ ( ) ➔ ( ) ➔ ( ) ➔ ( )
더보기
[정답]
5 ➔ 6 ➔ 3 ➔ 1 ➔ 7 ➔ 2
[풀이]
1
2
3
4
5
⑤ main 실행
⑥ 객체 생성
③ Child 생성자
① Parent 생성자
⑦ 출력 (이때 내부적으로 getX() 호출 ➔ ②)
⑥ 객체 생성
1
Parent parent = new Child(10);
부모 타입 변수로 자식 객체를 생성한다. (업캐스팅)
➔ 실제로 생성되는 객체는 Child 객체이다.
③ Child 생성자
1
2
3
4
5
6
7
8
9
10
class Child extends Parent {
int x;
Child(int x) {
super(x + 1, x);
this.x = x;
}
// ...
}
super(x + 1, x); 호출
- super : 상속한 부모 클래스를 가리키는 예약어
① Parent 생성자
1
2
3
4
5
6
7
8
9
10
class Parent {
int x, y;
Parent(int x, int y) {
this.x = x;
this.y = y;
}
// ...
}
x + 1의 값 11을 x가 받고, x의 값 10을 y가 받는다.
- Parent.x = 11
- Parent.y = 10
생성자가 종료되면 다시 Child 생성자에서의 this.x = x;로 이동한다.
- Child.x = 10
⑦ 출력
1
System.out.println(parent.getX());
Child에 getX() (매개변수 없는) 메서드가 오버라이딩 되어 있지 않으므로,
실제 객체는 Child이지만, Parent의 getX()가 호출된다.
② getX() 호출
1
2
3
int getX() {
return x * y;
}
부모 클래스의 x, y 값 사용해서 계산한다.
11 * 10 = 110
따라서 최종 결과는 110이다.
문제 3. super와 동적바인딩
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
class firstArea {
int x, y;
public firstArea(int x, int y) {
this.x = x;
this.y = y;
}
public void print() {
System.out.println(x + y);
}
}
class secondArea extends firstArea {
int bb = 3;
public secondArea(int i) {
super(i, i+1);
}
public void print() {
System.out.println(bb * bb);
}
}
public class Main {
public static void main(String[] args) {
firstArea st = new secondArea(10);
st.print();
}
}
더보기
[정답]
9
[풀이]
1
firstArea st = new secondArea(10);
- 부모 클래스 타입 변수(
firstArea)에 자식 객체(secondArea)를 할당 ➔ 업캐스팅 - 실제 객체는 secondArea이다.
1
2
3
4
5
class secondArea extends firstArea {
public secondArea(int i) {
super(i, i+1); // firstArea 생성자 호출
}
}
super(10, 11)➔ 부모 클래스firstArea의 생성자가 실행됨
1
2
3
4
5
6
7
8
9
10
// super(10, 11);
class firstArea {
int x, y;
public firstArea(int x, int y) {
this.x = x;
this.y = y;
}
}
- 부모 필드 초기화
- firstArea.x = 10
- firstArea.y = 11
1
st.print();
- 변수 타입은
firstArea이지만, 실제 객체는 secondArea이다. - 자바의 동적 바인딩(실제 객체 기준) 때문에
secondArea에서 오버라이딩한print()가 호출된다.
1
2
3
4
5
6
7
8
class secondArea extends firstArea {
int bb = 3;
public void print() {
System.out.println(bb * bb); // 3 * 3
}
}
따라서 출력값은 9이다.
2024년 2회 정보처리기사 실기
문제 4. 문자열 분할 split()
1
2
3
4
5
6
7
public class Test {
public static void main(String[] args) {
String str = "ITISTESTSTRING";
String[] result = str.split("T");
System.out.print(result[3]);
}
}
더보기
[정답]
S
[풀이]
1
String[] result = str.split("T");
문자열 "ITISTESTSTRING"을 "T"를 기준으로 분할한다.
split()은 지정한 문자를 기준으로 문자열을 나누어 배열로 반환한다.
1
result = ["I", "IS", "ES", "S", "RING"]
이때, result[3]은 "S"이므로 최종 출력 결과는 S이다.
문제 5. 참조 비교 (== 연산자)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class Test {
public static void check(int[] a, int[] b) {
if (a == b) {
System.out.print("O");
} else {
System.out.print("N");
}
}
public static void main(String[] args) {
int[] a = new int[]{1, 2, 3, 4};
int[] b = new int[]{1, 2, 3, 4};
int[] c = new int[]{1, 2, 3};
check(a, b);
check(b, c);
check(a, c);
}
}
더보기
[정답]
NNN
[풀이]
== 연산자
== 연산자는 값이 아닌 참조값(주소)을 비교한다.
1
2
3
int[] a = new int[]{1, 2, 3, 4};
int[] b = new int[]{1, 2, 3, 4};
int[] c = new int[]{1, 2, 3};
check(a, b);- 값은 같지만, 참조값이 다르다. ➔ N 출력
check(b, c);- 값과 참조값 모두 다르다. ➔ N 출력
check(a, c);- 값과 참조값 모두 다르다. ➔ N 출력
따라서 모든 비교 결과는 false가 되어
최종 출력값은 NNN이다.
문제 6. 인터페이스 구현과 조건 분기
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class Main {
public static void main(String[] args) {
int a[] = {1, 2, 3, 4, 5, 6, 7, 8, 9};
ODDNumber OE = new ODDNumber();
System.out.print(OE.sum(a, true) + ", " + OE.sum(a, false));
}
}
interface Number {
int sum(int[] a, boolean odd);
}
class ODDNumber implements Number {
public int sum(int[] a, boolean odd) {
int result = 0;
for (int i = 0; i < a.length; i++) {
if ((odd && a[i] % 2 != 0) || (!odd && a[i] % 2 == 0)) {
result += a[i];
}
}
return result;
}
}
더보기
[정답]
25, 20
[풀이]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
/* ODDNumber 클래스는 Number 인터페이스를 구현하여 sum() 메서드를 정의한다. */
class ODDNumber implements Number {
public int sum(int[] a, boolean odd) {
int result = 0;
for (int i = 0; i < a.length; i++) {
// odd가 true이면 홀수 값을 더하고,
// odd가 false이면 짝수 값을 더한다.
if ((odd && a[i] % 2 != 0) || (!odd && a[i] % 2 == 0)) {
result += a[i];
}
}
return result;
}
}
1
2
3
4
5
6
7
8
public class Main {
public static void main(String[] args) {
int a[] = {1, 2, 3, 4, 5, 6, 7, 8, 9};
ODDNumber OE = new ODDNumber();
System.out.print(OE.sum(a, true) + ", " + OE.sum(a, false));
}
}
OE.sum(a, true)➔ 홀수 값의 합
1
2
3
4
5
6
7
8
9
10
11
i = 0 -> true, result = 1
i = 1 -> false
i = 2 -> true, result = 4
i = 3 -> false
i = 4 -> true, result = 9
i = 5 -> false
i = 6 -> true, result = 16
i = 7 -> false
i = 8 -> true, result = 25
result 값 25 반환
OE.sum(a, false)➔ 짝수 값의 합
1
2
3
4
5
6
7
8
9
10
11
i = 0 -> false
i = 1 -> true, result = 2
i = 2 -> false
i = 3 -> true, result = 6
i = 4 -> false
i = 5 -> true, result = 12
i = 6 -> false
i = 7 -> true, result = 20
i = 8 -> false
result 값 20 반환
1
2
3
System.out.print(OE.sum(a, true) + ", " + OE.sum(a, false));
// 25 + ", " + 20
// 25, 20 출력
문제 7. 재귀를 이용한 문자열 처리
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class Main {
public static String calculFn(String str, int index, boolean[] seen) {
if (index < 0) return "";
char c = str.charAt(index);
String result = calculFn(str, index-1, seen);
if (!seen[c]) {
seen[c] = true;
return c + result;
}
return result;
}
public static void main(String[] args) {
String str = "abacabcd";
boolean[] seen = new boolean[256];
System.out.print(calculFn(str, str.length() - 1, seen));
}
}
더보기
[정답]
dcba
[풀이]
calculFn(str, 7, seen) 호출
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
* calculFn(str, 7, seen)
char c = str.charAt(7);
= 'd'
result = calculFn(str, 6, seen)
* calculFn(str, 6, seen)
char c = str.charAt(6);
= 'c'
result = calculFn(str, 5, seen);
* calculFn(str, 5, seen)
char c = str.charAt(5);
= 'b'
result = calculFn(str, 4, seen);
* calculFn(str, 4, seen)
char c = str.charAt(4);
= 'a'
result = calculFn(str, 3, seen);
* calculFn(str, 3, seen)
char c = str.charAt(3);
= 'c'
result = calculFn(str, 2, seen);
* calculFn(str, 2, seen)
char c = str.charAt(2);
= 'a'
result = calculFn(str, 1, seen);
* calculFn(str, 1, seen)
char c = str.charAt(1);
= 'b'
result = calculFn(str, 0, seen);
* calculFn(str, 0, seen)
char c = str.charAt(0);
= 'a'
result = calculFn(str, -1, seen);
* calculFn(str, -1, seen)
index = -1이므로, "빈문자열" 반환 (재귀 종료)
동작 원리
1
2
3
4
5
if (!seen[c]) {
seen[c] = true;
return c + result; // 현재 문자를 앞에 붙인다.
}
return result;
문자열을 뒤에서부터 재귀적으로 탐색하면서, 이미 나온 문자인지
seen[]배열로 체크한다.
seen[c]는 문자 c의 아스키 값을 인덱스로 사용한다.
(예: ‘a’ = 97, ‘b’ = 98, ‘c’ = 99, ‘d’ = 100)
| index | 문자 | seen 상태 | 결과 |
|---|---|---|---|
| 0 | ‘a’ | 처음 등장 | “a” |
| 1 | ‘b’ | 처음 등장 | “ba” |
| 2 | ‘a’ | 이미 있음 | “ba” |
| 3 | ‘c’ | 처음 등장 | “cba” |
| 4 | ‘a’ | 이미 있음 | “cba” |
| 5 | ‘b’ | 이미 있음 | “cba” |
| 6 | ‘c’ | 이미 있음 | “cba” |
| 7 | ‘d’ | 처음 등장 | “dcba” |
1
System.out.print(calculFn(str, 7, seen));
최종 출력값은 dcba이다.
2024년 3회 정보처리기사 실기
문제 8. 문자열(String) 비교 방법
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public class Main {
static String[] x = new String[3];
static void func(String[] x, int y) {
for(int i= 1; i < y; i++) {
if(x[i-1].equals(x[i])) {
System.out.print("O");
}
else {
System.out.print("N");
}
}
for (String z : x) {
System.out.print(z);
}
}
public static void main(String[] args) {
x[0] = "A";
x[1] = "A";
x[2] = new String("A");
func(x, 3);
}
}
더보기
[정답]
OOAAA
[풀이]
1
static String[] x = new String[3];
클래스 변수(static 변수) : 3개의 요소를 갖는 String 배열 x 선언
- 선언한 문자열 배열
x에 값 넣기
1
2
3
x[0] = "A";
x[1] = "A";
x[2] = new String("A"); // 새로운 객체를 생성하여 x[2]에 저장함
1
func(x, 3);
equals()
두 문자열 객체의 내용(값)이 동일하면true를 반환
(참조값이 아닌 문자열의 실제 내용을 비교함)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
static void func(String[] x, int y) {
for(int i= 1; i < y; i++) {
if(x[i-1].equals(x[i])) {
System.out.print("O");
}
else {
System.out.print("N");
}
}
for (String z : x) {
System.out.print(z);
}
}
1
2
3
4
5
i = 1인 경우,
x[0].equals(x[1]) ➔ "A".equals("A") ➔ true ➔ "O" 출력
i = 2인 경우,
x[1].equals(x[2]) ➔ "A".equals("A") ➔ true ➔ "O" 출력
x[2]는 new String()으로 생성된 별도의 객체이지만,
equals()는 문자열의 내용을 비교하므로 true가 된다.
1
2
3
for (String z : x) {
System.out.print(z);
}
x = ["A", "A", "A"] 이므로
향상된 for문을 사용하여 배열의 모든 요소를 순서대로 출력한다.
첫 번째 반복문에서 "OO"가 출력되고,
두 번째 반복문에서 "AAA"가 출력된다.
최종 출력은 "OOAAA"이다.
java의 문자열(String) 비교 방법
1
2
3
String str1 = "candy"; // 리터럴로 선언
String ggum = new String("ggum"); // new 연산자로 선언
- 문자열 리터럴을 사용하는 경우
- 같은 값을 가지는 문자열은
String Pool에 저장되어 동일한 참조를 사용할 수 있다.
- 같은 값을 가지는 문자열은
new연산자를 사용하는 경우- 각각 다른 참조값을 갖는다.
== 연산자
두 문자열의 주소(참조)값이 같은지 비교한다.
메모리에서 동일한 위치를 가리키는지를 확인한다.
equals() 메서드
두 문자열의 내용을 비교한다.
동일한 값을 가지고 있는지를 확인한다.
문제 9. 변수와 메서드의 바인딩 차이
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class Main {
public static void main(String[] args) {
B a = new D();
D b = new D();
System.out.print(a.getX() + a.x + b.getX() + b.x);
}
}
class B {
int x = 3;
int getX() {
return x * 2;
}
}
class D extends B {
int x = 7;
int getX() {
return x * 3;
}
}
더보기
[정답]
52
[풀이]
1
2
3
4
5
6
7
8
public class Main {
public static void main(String[] args) {
B a = new D();
D b = new D();
System.out.print(a.getX() + a.x + b.getX() + b.x);
}
}
a.getX()a는B타입이지만, 실제 객체는 D이다.- 메서드는 오버라이딩이 적용되므로 D의
getX()가 실행된다.- x = 7 ➔
7 * 3= 21
- x = 7 ➔
a.x- 변수는 오버라이딩이 되지 않는다.
- 참조 타입 B 기준으로 접근한다.
- B의
x값인 3이 사용된다.
b.getX()b는 D타입이며, 객체도 D이다.- 메서드는 오버라이딩이 적용되므로 D의
getX()가 실행된다.- x = 7 ➔
7 * 3= 21
- x = 7 ➔
b.xb는 D타입이다.- D의
x값인 7이 사용된다.
21 + 3 + 21 + 7 = 52 따라서 최종 출력 결과는 52이다.
1
Car car = new Bus();
부모 타입으로 자식 객체를 참조하면 접근 가능한 멤버 변수는 부모 타입 기준이지만,
메서드는 오버라이딩된 경우, 실제 객체(자식 클래스)의 메서드가 실행된다.
“실제 실행되는 메서드는 객체의 실제 객체 타입에 따라 결정된다.”
메서드는 동적 바인딩(실행 시점, Runtime),
변수(필드)는 정적 바인딩(컴파일 시점)이 적용된다.
문제 10. try ~ catch ~ finally
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class ExceptionHandling {
public static void main(String[] args) {
int sum = 0;
try {
func();
} catch (NullPointerException e) {
sum = sum + 1;
} catch (Exception e) {
sum = sum + 10;
} finally {
sum = sum + 100;
}
System.out.print(sum);
}
static void func() throws Exception {
throw new NullPointerException();
}
}
더보기
[정답]
101
[풀이]
1
2
3
static void func() throws Exception {
throw new NullPointerException();
}
func() 메서드에서 NullPointerException이 발생한다.
이 예외는 Exception의 하위 클래스이므로, try ~ catch문에서 처리된다.
catch 블록은 위에서부터 순서대로 검사되며,
NullPointerException을 처리하는 catch (NullPointerException e)가 먼저 선언되어 있으므로 해당 블록이 실행된다.
따라서 sum = sum + 1;이 실행되어 sum = 1이 된다.
이후, 예외 발생 여부와 관계없이 항상 실행되는 finally 블록이 실행된다.
sum = sum + 100;이 실행되어 최종적으로 sum = 101이 된다.
따라서 출력값은 101이다.
try ~ catch ~ finally
catch블록은 위에서부터 순서대로 검사된다.finally는 예외 발생 여부와 관계없이 항상 실행된다.
문제 11. 제네릭과 오버로딩
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
class Printer {
void print(Integer a) {
System.out.print("A" + a);
}
void print(Object a) {
System.out.print("B" + a);
}
void print(Number a) {
System.out.print("C" + a);
}
}
public class Generic {
public static void main(String[] args) {
new Container<>(10).print();
}
public static class Container<T> {
T value;
public Container(T t) {
value = t;
}
public void print() {
new Printer().print(value);
}
}
}
더보기
[정답]
B10
[풀이]
1
new Container<>(10).print();
10은 기본형 int이다.
하지만 자바 제네릭은 기본형을 사용할 수 없고, 내부적으로 Object 기반으로 동작한다.
따라서 int는 자동으로 오토박싱이 되어, Integer로 변환된다.
1
Container<Integer>
즉, 제네릭 타입 T는 Integer로 결정된다.
1
2
3
4
5
6
7
8
9
10
11
public static class Container<T> {
T value;
public Container(T t) {
value = t;
}
public void print() {
new Printer().print(value);
}
}
T = Integer이지만, 컴파일 시점에서value의 타입은Integer가 아니라T라는 것이다.
따라서print()메서드를 호출할 때, 컴파일 시점의 타입을 기준으로 결정된다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Printer {
// Integer 타입을 처리한다.
void print(Integer a) {
System.out.print("A" + a);
}
// 모든 Object 타입을 처리한다.
void print(Object a) {
System.out.print("B" + a);
}
// Number 타입을 처리한다.
void print(Number a) {
System.out.print("C" + a);
}
}
자바에서 오버로딩은 컴파일 시점의 타입을 기준으로 결정된다.
value의 컴파일 타입은 Integer가 아니라, 타입 변수 T이다.
그리고 제네릭은 타입 소거(type erasure) 이후 T는 Object처럼 취급된다.
따라서 컴파일러는 print(Object a)를 선택한다.
최종 실행
1
2
3
void print(Object a) {
System.out.print("B" + a);
}
출력 결과 : B10
제네릭
컴파일 시점에 타입을 지정하지 않고, 런타임에 구체적인 타입이 확정되는 방식으로 타입 안정성을 보장한다.
제네릭 + 오버로딩
- C10이 되는 경우 ➔
T extends Number
1
public static class Container<T extends Number> {}
T ➔ Number의 하위 타입
즉, 컴파일러가 이제 이렇게 인식하게 된다. ➔ value : Number
value의 컴파일 타입 = Number
따라서 출력은 print(Number a)가 출력된다.
- A10이 되는 경우 ➔ 캐스팅
1
2
3
public void print() {
new Printer().print((Integer) value);
}
1
(Integer) value ➔ 컴파일 타입이 Integer로 명확해짐
이제 컴파일러는 print(Integer)로 본다.
따라서 print(Integer a)가 출력된다.
명시적 캐스팅 = 컴파일 타입을 강제로 바꾸는 것
1
2
3
기본 (T) → Object 취급 → B10
T extends Number → Number 취급 → C10
(Integer) 캐스팅 → Integer 취급 → A10
오버로딩은 “실제 값”이 아니라, “컴파일 타입”으로 결정된다.
제네릭에 기본형은 넣을 수 없다!
자바 제네릭은 내부적으로 Object 기반으로 데이터를 처리한다.
int, double, boolean 등의 기본형은 Object가 아니다.
- 타입 소거 후 모든
T는Object로 변환된다. Object에는 기본형을 직접 담을 수 없으며, 기본형은 제네릭에 사용이 불가능하다.Object는 참조형의 최상위 타입이므로, 참조형만이 대상이 될 수 있다.- 기본형은
Object의 하위가 아니므로 허용되지 않는다.
- 기본형은
| 기본형 | 레퍼(wrapper) 클래스 |
|---|---|
| int | Integer |
| double | Double |
| boolean | Boolean |
정리
싱글톤 패턴
특정 클래스의 인스턴스를 오직 하나만 생성되도록 보장하는 패턴
- 인스턴스를 단 하나만 생성
- 여러 번 호출해도 항상 같은 인스턴스를 사용
- 핵심은 동시성 환경에서도 1개만 생성되도록 보장하는 것
업캐스팅, super
- 업캐스팅
- 부모 타입의 변수에 자식 객체를 담는 것 (자동 형변환)
1
Car car = new Tesla("tesla");
- 업캐스팅이 되면 자식 고유의 멤버에는 접근할 수 없음
- Tip. 다운캐스팅은 명시적으로 해야 하며, 안전하게
instanceof확인이 필요
- super
- 상속받은 부모 클래스를 가리키는 키워드
- 부모의 생성자, 필드, 메서드에 접근할 때 사용
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
class Car { String name; Car(String name) { this.name = name; } void move() { System.out.println("Car moves"); } } class Tesla extends Car { Tesla(String name) { super(name); // 부모 생성자 호출 } @Override void move() { super.move(); // 부모 메서드 호출 System.out.println("Tesla moves"); } }
문자열(String) 비교 방법
1
2
String str1 = "candy"; // 리터럴로 생성
String ggum = new String("ggum"); // new 연산자로 생성
- 문자열 리터럴을 사용하는 경우
- 같은 값을 가지는 문자열은
String Pool에 저장되어 같은 참조를 공유할 수 있음
- 같은 값을 가지는 문자열은
new연산자를 사용하는 경우- 항상 새로운 객체가 생성되어 서로 다른 참조값을 가짐
문자열 비교에서 중요한 기준은 두 가지이다.
언제 생성되었는가? (컴파일 시점 / 런타임 시점)
String Pool에 들어갔는가?
== 연산자
- 두 문자열의 주소(참조)값이 같은지 비교
- 메모리에 동일한 위치를 가리키는지를 확인
equals() 메서드
- 두 문자열의 내용을 비교
변수와 메서드의 바인딩
- 변수(필드)는 정적 바인딩 (컴파일 시점)
- 메서드는 동적 바인딩 (실행 시점)
변수는 오버라이딩되지 않는다. (가려진다, hiding)
1
2
3
4
5
class A { int x = 10; }
class B extends A { int x = 20; }
A a = new B();
System.out.println(a.x); // 10
부모 타입으로 자식 객체를 참조하면 접근 가능한 변수는 부모 타입 기준으로 결정된다.
하지만 메서드는 오버라이딩이 되어 있다면 실제 객체(자식 클래스)의 메서드가 실행된다.
제네릭과 오버로딩
오버로딩은 실제 값이 아니라, 컴파일 시점의 타입을 기준으로 결정된다.
그리고 제네릭은 타입 소거 이후 판단된다.
오버로딩은 타입 소거 이후의 시그니처를 기준으로 결정된다.
- B10이 되는 경우 ➔
Container<T>
1
public static class Container<T> {}
타입 소거 이후 T는 Object처럼 취급된다.
따라서 컴파일러는 value를 Object로 인식하고,
print(Object a)가 호출된다.
- C10이 되는 경우 ➔
T extends Number
1
public static class Container<T extends Number> {}
T는 Number의 하위 타입으로 제한된다.
타입 소거 이후 T는 Number로 취급되므로, 컴파일러는 value를 Number로 인식한다.
따라서 print(Number a)가 호출된다.
- A10이 되는 경우 ➔ 명시적 캐스팅
1
2
3
public void print() {
new Printer().print((Integer) value);
}
1
(Integer) value ➔ 컴파일 타입이 Integer로 명확해짐
명시적으로 (Integer)로 캐스팅하면
컴파일러는 value를 Integer 타입으로 인식한다.
따라서 print(Integer a)가 호출된다.
1
2
3
기본 (T) → Object 취급 → B10
T extends Number → Number 취급 → C10
(Integer) 캐스팅 → Integer 취급 → A10
명시적 캐스팅은 컴파일 타입을 강제로 바꾸는 것이다.