Java - super(),업캐스팅,다운캐스팅

2020. 6. 14. 16:08Java

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
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
public class Ex {
 
    public static void main(String[] args) {
        // Dog 인스턴스 생성
        // 이름 : 멍멍이, 나이 : 2
        // cry() 메서드 호출
        Dog dog = new Dog();
        
        dog.name = "멍멍이";
        dog.age = 2;
        System.out.println("이름 : " + dog.name + ", 나이 : " + dog.age);
        
        dog.cry();
        
        System.out.println("----------------------------");
        
        // Cat 인스턴스 생성
        // 이름 : 야옹이, 나이 : 3
        // cry() 메서드 호출
        Cat cat = new Cat();
        
        cat.name = "야옹이";
        cat.age = 3;
        System.out.println("이름 : " + cat.name + ", 나이 : " + cat.age);
        
        cat.cry();
        
    }
 
}
 
class Animal {
    String name;
    int age;
    
    public void cry() {
        System.out.println("동물 울음 소리!");
    }
}
 
// Dog 클래스 정의 - Animal 클래스를 상속
// => cry() 메서드 오버라이딩 : "강아지는 멍멍!" 출력
class Dog extends Animal {
 
    @Override
    public void cry() {
        System.out.println("강아지는 멍멍!");
    }
    
}
 
// Cat 클래스 정의 - Animal 클래스를 상속
// => cry() 메서드 오버라이딩 : "고양이는 야옹!" 출력
class Cat extends Animal {
 
    @Override
    public void cry() {
        System.out.println("고양이는 야옹!");
    }
    
}
 
public class Ex2 {
 
    public static void main(String[] args) {
        /*
         * 메서드 오버라이딩 뿐만 아니라 멤버변수도 오버라이딩이 가능하다.
         * => 즉, 메서드도 덮어쓸 수 있지만, 멤버변수도 덮어쓸 수 있다!
         * 
         * 레퍼런스 super
         * - 슈퍼클래스의 멤버에 접근하기 위한 키워드(= 레퍼런스 변수)
         * - 레퍼런스 this 와 동일하나 슈퍼클래스 인스턴스 주소가 저장되어 있는 점이 다르다!
         *   (= 레퍼런스 this 는 자신의 인스턴스 주소가 저장되어 있음)
         * - 주로, 오버라이딩에 의해 은닉된 변수 또는 메서드에 접근해야할 경우 사용
         *   (자신의 멤버 : this.XXX  부모의 멤버 : super.XXX)
         */
        Home home = new Home();
        System.out.println("Home 의 TV : " + home.tv);
        home.watchTv();
        
        System.out.println("-----------");
        
        MyHome myHome = new MyHome();
        System.out.println("MyHome 의 TV : " + myHome.tv);
        myHome.watchTv();
        myHome.brokenTv();
        
        System.out.println("=====================");
        
        Child c = new Child();
        c.method();
    }
 
}
 
class Home {
    String tv = "부모님이 구입한 TV";
    
    public void watchTv() {
        System.out.println(tv + " 보기!");
    }
}
 
// Home 클래스를 상속받는 MyHome 클래스 정의
class MyHome extends Home {
    // 만약, 슈퍼클래스의 멤버변수와 동일한 이름의 멤버변수를 서브클래스에서 선언하면
    // 슈퍼클래스의 멤버변수는 은닉됨 = 은닉된 슈퍼클래스의 멤버변수 변수를 은닉 변수라고도 한다.
    String tv = "내가 구입한 TV";
    
    public void watchTv() {
        // MyHome 클래스에서 멤버변수 tv 에 접근하면 은닉된 슈퍼클래스의 tv 가 아닌
        // 서브클래스에서 새로 선언한 멤버변수 tv 에 접근하게됨
        System.out.println(tv + " 보기!");
    }
    
    public void brokenTv() {
        // TV 가 고장났기 때문에 부모님 댁에 있는 TV 를 봐야함
        // => 이 때, 슈퍼클래스인 Home 클래스의 멤버변수 tv 는 은닉되어 보이지 않음
        //    따라서, tv 변수 앞에 레퍼런스 super 를 사용하여 Home 클래스 인스턴스에 접근해야함
        System.out.println(super.tv + " 보기!"); // Home 클래스의 멤버변수 tv 에 접근
        
    }
}
 
// ------------------------------------
// 슈퍼클래스와 서브클래스, 서브클래스의 메서드 내에 동일한 이름의 변수가 존재할 때 탐색 과정
// => 현재 호출된 곳을 기준으로 가장 가까운 곳부터 점차 탐색 범위를 넓혀가면서 탐색
//    (ex. 로컬변수 -> 멤버변수 -> 슈퍼클래스의 멤버변수)
// => 최종적으로 Object 클래스까지 거슬러 올라가면서 탐색하여 없을 경우 오류 발생!
class Parent {
    String x = "Parent"// 슈퍼클래스의 멤버변수
}
 
class Child extends Parent {
    String x = "Child"// 서브클래스의 멤버변수
    
    public void method() {
        String x = "method"// 서브클래스의 메서드 내의 로컬변수
        
        System.out.println("x = " + x); // 로컬변수 x
        // => 만약, 로컬변수 x 를 선언하지 않으면 this.x 와 동일
        System.out.println("this.x = " + this.x); // 서브클래스의 멤버변수 x
        // => 만약, Child 클래스의 멤버변수 x를 선언하지 않으면 super.x 와 동일
        System.out.println("super.x = " + super.x); // 슈퍼클래스의 멤버변수 x
        // => 만약, Parent 클래스의 멤버변수 x를 선언하지 않으면 오류 발생! 
        //    => Parent 클래스의 슈퍼클래스인 Object 클래스에는 존재하지 않는 변수이기 때문에
    }
}
 
public class Ex3 {
 
    public static void main(String[] args) {
        /*
          * < 상속에서의 생성자 >
         * - 생성자는 상속되지 않는다
         *   => 생성자의 이름은 클래스 이름을 사용하므로, 
         *      상속받은 생성자는 부모클래스의 이름이 되어 생성자 규칙에 위배됨
         * - 서브클래스의 인스턴스를 생성할 때에는, 자동으로 슈퍼클래스의 인스턴스를 
         *   먼저 생성한 후 서브클래스의 인스턴스가 생성됨
         *   => 서브클래스의 인스턴스 생성 시점에서 생성자를 호출하게 되면
         *      슈퍼클래스의 인스턴스를 생성하기 위해 자동으로 슈퍼클래스의 생성자를 호출하게 됨
         *      (기본적으로는 슈퍼클래스의 파라미터가 없는 기본 생성자를 호출)
         *      
         * < 생성자 super() >
         * - 슈퍼클래스의 생성자를 명시적으로 호출
         * - 목적1) 슈퍼클래스의 생성자 중 특정 생성자를 명시적으로 호출해야하는 경우
         *          ex) 슈퍼클래스에 기본생성자 없이 파라미터 생성자만 정의할 경우
         *              서브클래스에서 슈퍼클래스의 기본생성자를 호출하지 못해 오류 발생
         *              이 때, 슈퍼클래스의 파라미터 생성자를 명시적으로 호출할 때 사용
         * - 목적2) 슈퍼클래스의 생성자에서 초기화하는 코드가 존재할 경우
         *          ex) 서브클래스 생성자에서 슈퍼클래스의 멤버변수까지 초기화하면 코드 중복 발생
         *          이 때, 슈퍼클래스의 멤버는 슈퍼클래스의 생성자에서, 서브클래스의 멤버는
         *          서브클래스의 생성자에서 초기화하여 중복을 제거하기 위해 사용 
         * - 주의! 생성자 super() 는 반드시 생성자 내에서 첫번째 문장으로 실행되어야 한다!
         *   => 생성자 내에 super() 와 this() 를 동시에 사용 불가!
         */  
        
        SpiderMan sm = new SpiderMan();
        System.out.println("이름 : " + sm.name);
        System.out.println("나이 : " + sm.age);
        System.out.println("스파이더맨 모드 : " + sm.isSpiderMode);
        
        System.out.println("-------------------------");
        
        SpiderMan sm2 = new SpiderMan("톰홀랜드"16true);
        System.out.println("이름 : " + sm2.name);
        System.out.println("나이 : " + sm2.age);
        System.out.println("스파이더맨 모드 : " + sm2.isSpiderMode);
        
    }
 
}
 
class Person {
    String name;
    int age;
    
    public Person() {}
    
    public Person(String name, int age) {
        super(); // 슈퍼클래스인 Object 클래스의 생성자 Object() 를 호출
        this.name = name;
        this.age = age;
    }
    
}
 
class SpiderMan extends Person {
    boolean isSpiderMode;
    
    // 서브클래스의 기본생성자에서 슈퍼클래스인 Person 클래스의 기본생성자를 자동으로 호출
    // => 만약, 슈퍼클래스의 생성자 중 파라미터 생성자만 존재한다면 오류 발생하게 됨
    //    따라서, 슈퍼클래스에 기본 생성자를 별도로 정의하거나
    //    슈퍼클래스의 파라미터 생성자를 서브클래스 생성자 내에서 명시적으로 호출해야한다!
    public SpiderMan() {
        // 슈퍼클래스인 Person 클래스의 생성자 Person(String, int) 를 명시적으로 호출 필수!
//        super(); // 암묵적으로 생성자내에 포함됨(Person() 생성자가 없을 경우 오류 발생!)
        
//        super("피터파커", 20);
        
        // SpiderMan 클래스의 생성자 내의 초기화 코드가 중복되므로
        // 자신의 다른 생성자 SpiderMan(String, int, boolean) 을 호출하여 대신 초기화 수행 가능
        this("피터파커"20false); // 생성자 this() 사용하여 자신의 다른 생성자 명시적 호출
    }
    
    // 이름(name), 나이(age), 스파이더맨모드(isSpiderMode)를 전달받아 초기화하는 생성자 정의
    public SpiderMan(String name, int age, boolean isSpiderMode) {
        // SpiderMan 클래스의 생성자 내에서 모든 멤버변수를 직접 초기화하는 경우
//        this.name = name; // super.name = name 도 동일한 코드
//        this.age = age; // super.name = name 도 동일한 코드
//        this.isSpiderMode = isSpiderMode;
        // => name 과 age 를 초기화하는 코드는 Person 클래스의 생성자 코드와 중복됨
        //    따라서, 슈퍼클래스로부터 상속받은 멤버변수 초기화는 
        //    슈퍼클래스의 생성자에서 수행하도록 슈퍼클래스의 생성자를 호출하여 변수값 전달
        super(name, age); // name 과 age 는 슈퍼클래스의 생성자에 전달하여 대신 초기화하고
        this.isSpiderMode = isSpiderMode; // 자신의 멤버변수만 직접 초기화
        
    }
}
public class Ex4 {
 
    public static void main(String[] args) {
        /*
         * 레퍼런스(참조형) 형변환
         * - 어떤 객체를 다른 타입으로 변환하는 것(슈퍼클래스타입 <-> 서브클래스타입)
         * - 업캐스팅(Up Cating) 과 다운캐스팅(Down Casting) 으로 구분됨
         * 
         * 1. 업캐스팅(Up Casting)
         *    - 슈퍼클래스 타입 레퍼런스로 서브클래스의 인스턴스를 참조하는 것
         *      = 서브클래스의 인스턴스를 슈퍼클래스 타입으로 변환하는 것
         *    - 컴파일러에 의해 자동 형변환 일어남(= 묵시적 형변환)
         *    - 참조 가능한 영역이 축소됨
         *    - 서브클래스 인스턴스의 멤버 중 공통 항목을 제외한 나머지에 대한 포기 선언을 하는 것
         *      => 대신, 하나의 슈퍼클래스 타입으로 여러 서브클래스 인스턴스를 참조할 수 있다!
         *      
         * 2. 다운캐스팅(Down Casting)
         *    - 서브클래스 타입 레퍼런스로 슈퍼클래스의 인스턴스를 참조하는 것
         *      = 슈퍼클래스의 인스턴스를 서브클래스 타입으로 변환하는 것
         *    - 컴파일러에 의해 자동 형변환이 일어나지 않음
         *      => 강제 형변환(명시적 형변환) 필수
         *    - 참조 가능한 영역이 확대됨
         *      => 존재하지 않는 영역에 대한 참조 위험성 때문에 
         *         명시적 형변환 후에도 오류가 발생할 수 있다!
         *      => 대부분의 다운캐스팅은 허용되지 않는다!
         *    - 이전에 이미 업캐스팅 된 레퍼런스를 다시 다운캐스팅 하는 경우에만 안전하므로
         *      객체간의 상속 관계를 고려하여 캐스팅 해야한다!
         */
        
//        Parent4 p = new Parent4();
        // Parent4 타입으로 접근 가능한 메서드 : 1개
//        p.parentPrn(); // 슈퍼클래스 자신이 직접 정의한 메서드
        
        System.out.println("-------------------");
        
        Child4 c = new Child4();
        // Child4 타입으로 접근 가능한 메서드 : 2개
        c.childPrn(); // 서브클래스 자신이 직접 정의한 메서드
        c.parentPrn(); // 슈퍼클래스로부터 상속받은 메서드
        
        System.out.println("======================================");
//        int i = 10;
//        long l = i; // int형 변수 i 의 값을 long 타입 변수에 전달 시 자동 형변환 발생
        
        Parent4 p; // 슈퍼클래스 타입 레퍼런스 변수 선언
        p = c; // 묵시적 업캐스팅(서브클래스 Child 타입 -> 슈퍼클래스 Parent 타입으로 형변환)
//        p = new Child4(); // Child4 타입 인스턴스를 생성하여 바로 Parent4 타입으로 전달
        // Child 타입 인스턴스를 Parent 타입 레퍼런스 변수에 전달
        
        // Parent4 타입 레퍼런스 변수 p 로 접근 가능한 메서드 : 1개
        p.parentPrn(); // 상속된 메서드
//        p.childPrn(); // 서브클래스에서 정의한 메서드 => 오류 발생!
        // => 슈퍼클래스 타입으로 업캐스팅 후에는 상속된 메서드만 호출 가능
        // 슈퍼클래스 타입으로 서브클래스 인스턴스를 참조하게 되면
        // 참조 영역에 대한 제한(축소)으로 인해 서브클래스의 메서드가 보이지 않게 됨
        
        System.out.println("======================================");
        // ---------- 다운캐스팅 ------------
        Parent4 p2 = new Parent4();
        
        Child4 c2; // 서브클래스 타입 레퍼런스 변수 선언
//        c2 = new Parent4(); // 슈퍼클래스 인스턴스 -> 서브클래스 타입 변수에 전달 => 오류 발생!
//        c2 = p2;
        // 슈퍼클래스 인스턴스를 서브클래스 타입 변수에 저장하려면 강제 형변환(다운캐스팅) 필수!
        
        // 명시적 형변환을 통한 다운캐스팅
//        c2 = (Child4)p2; // 형변환 연산자를 사용하여 Child4 타입으로 명시적 형변환 수행
        // => 형변환 후에도 실행 시점에서 ClassCastException 이라는 오류 발생!
        
//        c2.parentPrn(); // 실제 존재하는 Parent4 인스턴스의 메서드
//        c2.childPrn(); // 실제 존재하지 않는 메서드이지만, Child4 타입 변수로 접근 가능
        // => 존재하지 않는 참조 위험성 때문에 다운캐스팅 시점에서 오류가 발생함
        // => 기본적으로 대부분의 다운캐스팅은 허용되지 않는다!
        System.out.println("===================================");
        
        // 다운캐스팅이 안전하게 수행되는 경우
        // => 이전에 이미 업캐스팅 된 인스턴스를 다시 다운캐스팅 하는 경우에만 안전하다!
        Parent4 p3 = new Child4(); // Child4 -> Parent4 업캐스팅(묵시적 = 자동형변환)
        
        // Parent4 타입으로 접근 가능한 메서드 : 1개(참조 영역이 축소되었기 때문)
        p3.parentPrn();
//        p3.childPrn(); // Parent4 타입으로 참조 불가능한 메서드
        
//        Child4 c3 = p3; // Parent4 타입 인스턴스를 Child4 타입 변수에 전달 => 강제형변환 필수!
        Child4 c3 = (Child4)p3; // Parent4 -> Child4 다운캐스팅
        
        // Child4 타입으로 접근 가능한 메서드 : 2개(참조 영역이 확대되었기 때문)
        // 다운캐스팅 후에도 원래 Child4 타입이 참조하는 영역만큼의 인스턴스가 실제 존재하므로
        // 아무런 문제가 일어나지 않는다!
        c3.parentPrn();
        c3.childPrn();
        
        
    }
 
}
 
class Parent4 {
    // 사용 가능한 멤버 : 1개
    public void parentPrn() {
        System.out.println("슈퍼클래스의 parentPrn()");
    }
    
}
 
class Child4 extends Parent4 {
    // 사용 가능한 멤버 : 2개(자신의 클래스에서 정의한 메서드, 상속받은 메서드)
    public void childPrn() {
        System.out.println("서브클래스의 childPrn()");
    }
    
}
public class Test4 {
 
    public static void main(String[] args) {
        SmartPhone 스마트폰 = new SmartPhone("갤럭시""010-1234-5678""안드로이드");
        // HandPhone 으로 상속받은 메서드 호출 가능
        스마트폰.call();
        스마트폰.sms();
        // SmartPhone 클래스에서 정의한 메서드 호출 가능
        스마트폰.kakaoTalk();
        스마트폰.youtube();
        
        System.out.println("-------------------------");
        
        // HandPhone 타입 변수(어머니폰)를 사용하여 SmartPhone 인스턴스(스마트폰)를 참조 = 업캐스팅
        HandPhone 어머니폰 = 스마트폰;
        // 실제 인스턴스가 SmartPhone 이지만 HandPhone 타입 변수로 참조하면 참조 영역 추소 발생
        // HandPhone 타입 변수(어머니폰)로 접근 가능한 메서드 : 2개(상속된 메서드만 접근 가능)
//        어머니폰.call();
//        어머니폰.sms();
//        어머니폰.kakaoTalk(); // 오류 발생!
//        어머니폰.youtube(); // 오류 발생!
        
        System.out.println("=============================");
        
        HandPhone 어머니의피처폰 = new HandPhone("애니콜""010-1111-2222");
//        SmartPhone 내폰 = 어머니의피처폰; // 명시적 형변환 필요
//        SmartPhone 내폰 = (SmartPhone)어머니의피처폰; // 일반적인 다운캐스팅은 오류 발생!
        
//        내폰.call();
//        내폰.sms();
//        내폰.kakaoTalk(); // 컴파일에러 발생하지 않음(실제로는 인스턴스에 존재하지 않는 메서드)
//        내폰.youtube(); // 컴파일에러 발생하지 않음(실제로는 인스턴스에 존재하지 않는 메서드)
        // => 존재하지 않는 메서드에 대한 참조 위험성 때문에 일반적인 다운캐스팅은 허용X
        
        System.out.println("=============================");
        
        // 다운캐스팅이 가능한 경우
        HandPhone 어머니폰2 = new SmartPhone("갤럭시""010-1234-5678""안드로이드"); // 업캐스팅
        SmartPhone 내폰2 = (SmartPhone)어머니폰2;
        내폰2.call();
        내폰2.sms();
        내폰2.kakaoTalk();
        내폰2.youtube();
        
    }
 
}
 
class HandPhone {
    String modelName;
    String number;
    
    public HandPhone(String modelName, String number) {
        super();
        this.modelName = modelName;
        this.number = number;
    }
 
    public void call() {
        System.out.println("HandPhone - 전화 기능!");
    }
    
    public void sms() {
        System.out.println("HandPhone - 문자 기능!");
    }
}
 
class SmartPhone extends HandPhone {
    String osName;
 
    public SmartPhone(String modelName, String number, String osName) {
        super(modelName, number);
        this.osName = osName;
    }
    
    public void kakaoTalk() {
        System.out.println("SmartPhone - 카톡 기능!");
    }
    
    public void youtube() {
        System.out.println("SmartPhone - 유튜브 기능!");
    }
    
}
cs

상속을 한 번 더 복습해보고 다른 진도를 나가보았는데 복습을 해보니 확실히 좀 더 이해가 되었고 super()를 배워 보았는데 this. 와 별 다를게 없이 조금만 다른 개념이라 쉽게 이해가 되었다. 오버 로딩의 단축키를 사용하였을 때 super()가 자동으로 나왔는데 왜 나오게 되었는지 이해가 되었다. 오버 로딩을 할 때 기본 생성자를 참조하게 되는데 기본 생성자를 자동으로 참조할 수 있게 해주는 역할이었다. 그리고 항상 사용될 때 제일 첫 줄에 위치해야 하고 this와 같은 곳에서 사용할 수 없는 문법이었다. 왜냐하면 this 도 마찬가지로 사용될 때 제일 첫 줄에 사용되어야 하기 때문이다. 코드의 중복을 피하기 위해서 super()를 사용하여 중복되게 출력이나 값을 초기화할 때 참조하여 사용할 수 있기 때문에 이런 용도로도 사용이 가능하다. 그리고 업 캐스팅, 다운 캐스팅은 개념 자체는 어렵지 않은데 굉장히 헷갈릴 수 있다고 생각이 들었다. 슈퍼클래스로 올라갓다 서브클래스로 내려갔다 하면서 사용 가능한 메서드의 개수와 원래는 사용 가능했지만 사용이 불가능하게도 되고 복잡하게 될 수 있기 때문이다. 복습을 좀 더 철저히 해서 개념을 확실히 잡아 놔야겠다고 생각이 들었다.