728x90
반응형
블로그 쓰다가 한 번 날려서 매우 화가났다.. 저장의 중요성....1
Chap 7 상속
- 객체 지향 프로그래밍에서 부모 클래스의 멤버를 자식 클래스에게 물려줄 수 있다
- 부모 클래스를 상위 클래스라고 부름
- 자식 클래스를 하위 클래스 또는 파생 클래스라고 부름
1. 상속
- 상속은 이미 잘 개발된 클래스를 재사용해서 새로운 클래스를 만들기 때문에 중복되는 코드를 줄여줌
- 예를 들어 field1, field2 method1(), method2()를 가지는 클래스를 작성할 때
- field1과 method1()을 가지고 있는 클래스가 있다면, 4개를 모두 처음부터 작성하는 것보다
클래스를 상속하고 field2와 method2()만 추가 작성하는 것이 보다 효율적이고 개발 시간을 절약해줌
- 상속의 장점
- 상속을 이용하면 부모 클래스의 수정으로 모든 자식 클래스들이 수정됨
- 따라서 유지보수 시간을 최소화할 수 있다.
1) 클래스 상속
- 자식이 부모를 선택한다.
- 자식 클래스를 선언할 때 어떤 부모 클래스를 상속받을 것인지 결정하고,
선택된 부모 클래스는 다음과 같이 extends 뒤에 기술한다.
- Car 클래스를 상속해서 SportsCar 클래스를 설계하자
class SportsCar extends Car { } |
- 자바에서 상속의 특징
- 여러 개의 부모 클래스를 상속할 수 없다.
따라서 extends 뒤에는 단 하나의 부모 클래스만 와야한다. - 부모 클래스에서 private 접근 제한을 갖는 필드와 메소드는 상속 대상에서 제외됨.
그리고 부모 클래스와 자식 클래스가 다른 패키지에 존재한다면
default 접근 제한을 갖는 필드와 메소드도 상속 대상에서 제외됨
- 여러 개의 부모 클래스를 상속할 수 없다.
부모 클래스 // CellPhone.java |
package sec01.exam01;
public class CellPhone { //필드 String model; String color; //생성자 //메소드 void powerOn() { System.out.println("전원을 켭니다."); } void powerOff() { System.out.println("전원을 끕니다."); } void bell() { System.out.println("벨이 울립니다."); } void sendVoice(String message) { System.out.println("자기: " + message); } void receiveVoice(String message) { System.out.println("상대방: " + message); } void hangUp() { System.out.println("전화를 끊습니다."); } } |
자식 클래스 // DmbCellPhone |
package sec01.exam01;
public class DmbCellPhone extends CellPhone { //필드 int channel; //생성자 DmbCellPhone(String model, String color, int channel) { this.model = model; this.color = color; this.channel = channel; } //메소드 void turnOnDmb() { System.out.println("채널 " + channel + "번 DMB 방송 수신을 시작합니다."); } void changeChannelDmb(int channel) { this.channel = channel; System.out.println("채널 " + channel + "번으로 바꿉니다."); } void turnOffDmb() { System.out.println("DMB 방송 수신을 멈춥니다."); } } |
자식 클래스 사용 // DmbCellPhoneExample.java |
package sec01.exam01;
public class DmbCellPhoneExample { public static void main(String[] args) { //DmbCellPhone 객체 생성 DmbCellPhone dmbCellPhone = new DmbCellPhone("자바폰", "검정", 10); //CellPhone 클래스로부터 상속받은 필드 System.out.println("모델: " + dmbCellPhone.model); System.out.println("색상: " + dmbCellPhone.color); //DmbCellPhone 클래스의 필드 System.out.println("채널: " + dmbCellPhone.channel); //CellPhone 클래스로부터 상속받은 메소드 호출 dmbCellPhone.powerOn(); dmbCellPhone.bell(); dmbCellPhone.sendVoice("여보세요."); dmbCellPhone.receiveVoice("안녕하세요! 저는 홍길동인데요."); dmbCellPhone.sendVoice("아~ 예 반갑습니다."); dmbCellPhone.hangUp(); //DmbCellPhone 클래스의 메소드 호출 dmbCellPhone.turnOnDmb(); dmbCellPhone.changeChannelDmb(12); dmbCellPhone.turnOffDmb(); } } |
모델: 자바폰
색상: 검정 채널: 10 전원을 켭니다. 벨이 울립니다. 자기: 여보세요. 상대방: 안녕하세요! 저는 홍길동인데요. 자기: 아~ 예 반갑습니다. 전화를 끊습니다. 채널 10번 DMB 방송 수신을 시작합니다. 채널 12번으로 바꿉니다. DMB 방송 수신을 멈춥니다. |
2) 부모 생성자 호출
- DmbCellPhone dmbCellPhone = new DmbCellPhone();
- 위의 코드는 DmbCellPhone 객체만 생성하는 것처럼 보이지만,
사실은 내부적으로 부모인 CellPhone 객체가 먼저 생성되고 자식인 DmbCellPhone 객체가 생성됨
- 모든 객체는 클래스의 생성자를 호출해야 생성된다.
- 부모 생성자는 자식 생성자의 맨 첫 줄에서 호출된다.
- 만약, DmbCellPhone의 생성자가 명시적으로 선언되지 않았다면 컴파일러는 다음과 같은 기본 생성자를 생성함
public DmbCellPhone() { supper(); } |
- 첫 줄에 super();가 추가된 것을 볼 수 있음, super()는 부모의 기본 생성자를 호출한다.
- 즉, CellPhone 클래스의 다음 생성자를 호출한다.
- CellPhone.java 소스 코드에서도 CellPhone의 생성자가 선언되지 않았지만
컴파일러에 의해 기본 생성자가 만들어지므로 문제없이 실행된다.
public CellPhone() { } |
- 직접 자식 생성자를 선언하고 명시적으로 부모 생성자를 호출하고 싶다면 아래와 같이 작성하면 된다.
- super(매개값, ... )
- 매개값의 타입과 일치하는 부모 생성자를 호출
- 매개값의 타입과 일치하는 부모 생성자가 없을 경우 컴파일 에러가 발생
- 생략되면 super()가 자동으로 추가되기 때문에 부모의 기본 생성자가 존재해야함.
- 부모 클래스에 기본 생성자가 없고 매개 변수가 있는 생성자만 있다면
자식 생성자에서 반드시 부모 생성자 호출을 위해 super(매개값, ...)를 명시적으로 호출해야함 - 반드시 자식 생성자 첫 줄에 위치해야하며 그렇지 않으면 컴파일 에러 발생
부모 클래스 // People.java |
package sec01.exam02;
public class People { public String name; public String ssn; public People(String name, String ssn) { this.name = name; this.ssn = ssn; } } |
- People 클래스는 기본 생성자가 없고 name과 ssn을 매개값으로 받아 객체를 생성시키는 생성자만 있음
- People을 상속하는 Student 클래스는 생성자에서 super(name, ssn)으로 People 클래스의 생성자를 호출해야함
자식 클래스 // Student.java |
01 package sec01.exam02;
02 03 public class Student extends People{ 04 public int studentNo; 05 06 public Student(String name, String ssn, int studentNo) { 07 super(name, ssn); 08 this.studentNo = studentNo; 09 } 10 } |
- Student 클래스이 생성자는 name, ssn, studentNo를 매개값으로 받아서
name과 ssn은 다시 부모 생성자를 호출하기 위해 매개값으로 넘겨준다. - 7라인의 super(name, ssn)은 People 생성자인 People(String name, String ssn)을 호출한다.
- 7라인을 주석처리하면 컴파일 에러 발생
=> 부모의 기본 생성자가 없으니 다른 생성자를 명시적으로 호출하라는 뜻
자식 객체 이용 // StudentExample.java |
package sec01.exam02;
public class StudentExample { public static void main(String[] args) { Student student = new Student("홍길동", "123456-1234567", 1); System.out.println("name : " + student.name); System.out.println("ssn : " + student.ssn); System.out.println("studentNo : " + student.studentNo); } } |
name : 홍길동
ssn : 123456-1234567 studentNo : 1 |
3) 메소드 재정의
- 어떤 메소드는 자식 클래스가 사용하기에 적합하지 않을 수도 있음
- 이 경우 상속된 일부 메소드는 자식 클래스에서 다시 수정해서 사용해야함.
- 이런 경우에 메소드 재정의(Overriding) 기능을 제공함.
(1) 메소드 재정의 방법
- 메소드 재정의는 자식 클래스에서 부모 클래스의 메소드를 다시 정의하는 것을 말한다.
- 메소드를 재정의할 때는 다음과 같은 규칙에 주의해서 작성해야한다.
- 부모의 메소드와 동일한 시그니처(리턴 타입, 메소드 이름, 매개 변수 목록)을 가져야 한다.
- 접근 제한을 더 강하게 재정의할 수 없다.
- 새로운 예외를 throws할 수 없다.
- 접근 제한을 더 강하게 재정의할 수 없다.
- 부모 메소드가 public 접근 제한을 가지고 있을 경우 재정의하는 자식 메소드는
default나 private 접근 제한으로 수정할 수 없다는 뜻임 - 하지만 부모 메소드가 default 접근 제한을 가지만 재정의하는 자식 메소드는 dafault 또는 public 접근 제한을 가질 수 있음
- 부모 메소드가 public 접근 제한을 가지고 있을 경우 재정의하는 자식 메소드는
- 메소드가 재정의되었다면 부모 객체의 메소드는 숨겨지기 때문에,
자식 객체에서 메소드를 호출하면 재정의된 자식 메소드가 호출됨
부모 클래스 // Calculator.java |
package sec01.exam03;
public class Calculator { double areaCircle(double r) { System.out.println("Calculator 객체의 areaCircle() 실행"); return 3.14159 * r * r; } } |
자식 클래스 // Computer.java |
package sec01.exam03;
public class Computer extends Calculator { @Override double areaCircle(double r) { System.out.println("Computer 객체의 areaCircle() 실행"); return Math.PI * r * r; } } |
메소드 재정의 테스트 // ComputerExample.java |
package sec01.exam03;
public class ComputerExample { public static void main(String[] args) { int r = 10; Calculator calculator = new Calculator(); System.out.println("원면적 : " + calculator.areaCircle(r)); System.out.println(); Computer computer = new Computer(); System.out.println("원면적 : " + computer.areaCircle(r)); } } |
Calculator 객체의 areaCircle() 실행
원면적 : 314.159 Computer 객체의 areaCircle() 실행 원면적 : 314.1592653589793 |
[ 이클립스의 재정의 메소드 자동 생성 ]
이클립스는 부모 메소드 중 하나를 선택해서 재정의 메소드를 자동 생성해주는 기능이 있음
이 기능은 부모 메소드의 시그너처를 정확히 모를 경우 매우 유용하게 사용 가능
- 자식 클래스에서 재정의 메소드를 작성할 위치로 입력 커서를 옮김
- [Source] - [Override/Implement Methods] 메뉴를 선택함
- 부모 클래스에서 재정의될 메소드를 선택하고 [OK] 버튼을 클릭함
(2) 부모 메소드 호출
- 자식 클레스에서 부모 클래스의 메소드를 재정의하게 되면, 부모 클래스의 메소드는 숨겨지고 재정의된 자식 메소드만 사용됨
- 자식 클래스 내부에서 재정의된 부모 클래스의 메소드를 호출해야 하는 상황이 발생한다면 명시적으로 super 키워드를 붙여서 부모 메소드를 호출할 수 있다.
super 변수 // Airplane.java |
package sec01.exam04;
public class Airplane { public void land() { System.out.println("착륙합니다."); } public void fly() { System.out.println("일반비행합니다."); } public void takeOff() { System.out.println("이륙합니다."); } } |
super 변수 // SupersonicAirplane.java |
package sec01.exam04;
public class SupersonicAirplane extends Airplane { public static final int NORMAL = 1; public static final int SUPERSONIC = 2; public int flyMode = NORMAL; @Override public void fly() { if(flyMode == SUPERSONIC) { System.out.println("초음속비행합니다."); } else { super.fly(); } } } |
super 변수 // SupersonicAirplaneExample.java |
package sec01.exam04;
public class SupersonicAirplaneExample { public static void main(String[] args) { SupersonicAirplane sa = new SupersonicAirplane(); sa.takeOff(); sa.fly(); sa.flyMode = SupersonicAirplane.SUPERSONIC; sa.fly(); sa.flyMode = SupersonicAirplane.NORMAL; sa.fly(); sa.land(); } } |
이륙합니다.
일반비행합니다. 초음속비행합니다. 일반비행합니다. 착륙합니다. |
4) final 클래스와 final 메소드
- final 키워드는 클래스, 필드, 메소드를 선언할 때 사용할 수 있음
- 해당 선언이 최종 상태이고 결코 수정될 수 없음을 의미
- 필드를 선언할 때 final이 지정되면 초기값 설정 후 더 이상 값을 변경할 수 없음
- 클래스와 메소드를 선언할 때 final 키워드가 지정되면 상속과 관련이 있다는 뜻임
(1) 상속할 수 없는 final 클래스
- 클래스를 선언할 때 final 키워드를 class 앞에 붙이면 이 클래스는 최종적인 클래스이므로 상속할 수 없는 클래스가 됨
- 즉 final 클래스는 부모 클래스가 될 수 없어 자식 클래스를 만들 수 없음
- final 클래스의 대표적인 예는 자바 표준 API에서 제공하는 String 클래스이다.
상속할 수 없는 final 클래스 // Member.java |
package sec01.exam05;
public final class Member { } |
상속할 수 없는 final 클래스 // VeryImportantPerson.java |
package sec01.exam05;
//public class VeryVeryImportantPerson extends Member { public class VeryImportantPerson { } |
(2) 재정의할 수 없는 final 메소드
- 메소드를 선언할 때 final 키워드를 붙이면 이 메소드는 최종적인 메소드이므로 재정의할 수 없는 메소드가 됨
- 부모 클래스를 상속해서 자식 클래스를 선언할 때 부모 클래스에 선언된 final 메소드는 자식 클래스에서 재정의할 수 없다
재정의할 수 없는 final 메소드 // Car.java |
package sec01.exam06;
public class Car { //필드 public int speed; //메소드 public void speedUp() { speed += 1; } //final 메소드 public final void stop() { System.out.println("차를 멈춤"); speed = 0; } } |
재정의할 수 없는 final 메소드 // SportCar.java |
package sec01.exam06;
public class SportsCar extends Car { @Override public void speedUp() { speed += 10; } //재정의할 수 없음 /* @Override public void stop() { System.out.println("스포츠카를 멈춤"); speed = 0; } */ } |
5) protected 접근 제한자
- 접근 제한자는 public, protected, default, private 네 종류가 있음
- protected는 public과 default 접근 제한의 중간쯤에 해당함
- 같은 패키지에서는 default와 같이 접근 제한이 없지만 다른 패키지에서는 자식 클래스만 접근을 허용함
- protected는 필드와 생성자, 메소드 선언에 사용될 수 있음
protected 접근 제한자 // A.java |
package sec01.exam07.pack1;
public class A { protected String field; protected A() { } protected void method() { } } |
protected 접근 제한자 테스트 // B.java |
package sec01.exam07.pack1;
public class B { public void method() { A a = new A(); a.field = "value"; a.method(); } } |
- B 클래스는 A 클래스와 동일한 패키지에 있다.
- default 접근 제한과 마찬가지로 B 클래스의 생성자와 메소드에서는
A 클래스의 protected 필드, 생성자, 메소드에 얼마든지 접근이 가능함
protected 접근 제한자 테스트 // C.java |
package sec01.exam07.pack2;
import sec01.exam07.pack1.A; public class C { public void method() { /* 접근 불가능 A a = new A(); a.field = "value"; a.method(); */ } } |
- C 클래스는 A 클래스와 다른 패키지에 있다
- default 접근 제한과 마찬가지로 C 클래스의 생성자와 메소드에서는
A 클래스의 protected 필드, 생성자, 메소드에 접근 가능
protected 접근 제한자 테스트 // D.java |
package sec01.exam07.pack2;
import sec01.exam07.pack1.A; public class D extends A { public D() { super(); this.field = "value"; this.method(); } } |
- D 클래스는 A 클래스와 다른 패키지에 있다
- C 클래스와는 달리 D는 A의 자식 클래스임
- 따라서 A 클래스의 protected 필드, 생성자, 메소드에 접근이 가능함
- 하지만 new 연산자를 사용해서 생성자를 직접 호출할 수는 없고,
자식 생성자에서 super()로 A 생성자를 호출할 수 있다.
2. 타입 변환과 다형성
- 다형성: 사용 방법은 동일하지만 다양한 객체를 이용해서 다양한 실행결과가 나오도록 하는 성질
- 예) 자동차가 타이어를 사용하는 방법은 동일하지만
어떤 타이어를 사용하느냐에 따라 주행 성능이 달라질 수 있음
- 다형성을 구현하려면 메소드 재정의와 타입 변환이 필요함
1) 자동 타입 변환(promotion)
- 타입을 다른 타입으로 변환하는 행위를 말함
- 클래스의 변환은 상속 관계에 있는 클래스 사이에서 발생함
- 자식은 부모 타입으로 자동 타입 변환이 가능
- 개념: 자식은 부모의 특징과 기능을 상속받기 때문에 부모와 동일하게 취급될 수 있다
- 예시: 고양이가 동물의 특징과 기능을 상속받았다면 '고양이는 동물이다.'가 성립
- Animal과 Cat 클래스가 다음과 같이 상속 관계에 있다고 가정해보자
- Cat 클래스로부터 Cat 객체를 생성하고 이것을 Animal 변수에 대입하면 자동 타입 변환이 일어남.
- 위 코드로 생성되는 메모리 상태를 그림으로 묘사하면 다음과 같다.
- cat과 animal 변수는 타입만 다를 뿐, 동일한 Cat 객체를 참조한다.
- 위 그림에서 animal 변수가 Animal 타입이므로 당연히 부모인 Animal 객체를 참조하는 것이 맞지 않냐고 생각할 수 있지만 아님
- 다음과 같이 cat과 animal 변수를 == 연산해보면 true가 나오는데, 참조 변수의 == 연산은 참조 번지가 같을 경우 true를 산출하므로 두 변수가 동일한 객체를 참조하고 있다는 뜻임
- 바로 위의 부모가 아니더라도 상속 계층에서 상위 타입이라면 자동 타입 변환이 일어날 수 있다.
- D 객체는 B와 A 타입으로 자동 타입 변환이 될 수 있음
- E 객체는 C와 A 타입으로 자동 타입 변환이 될 수 있음
- 하지만 D 객체는 C 타입으로 변환될 수 없음
- E 객체도 B 타입으로 변환될 수 없음
자동 타입 변환 // PromotionExample.java |
package sec02.exam01;
public class PromotionExample { public static void main(String[] args) { B b = new B(); C c = new C(); D d = new D(); E e = new E(); A a1 = b; A a2 = c; A a3 = d; A a4 = e; B b1 = d; C c1 = e; //B b3 = e; //C c2 = d; } } |
자동 타입 변환 후의 멤버 접근 // Parent.java |
package sec02.exam02;
public class Parent { public void method1() { System.out.println("Parent-method1()"); } public void method2() { System.out.println("Parent-method2()"); } } |
자동 타입 변환 후의 멤버 접근 // Child.java |
package sec02.exam02;
public class Child extends Parent { @Override public void method2() { System.out.println("Child-method2()"); } public void method3() { System.out.println("Child-method3()"); } } |
자동 타입 변환 후의 멤버 접근 // ChildExample.java |
package sec02.exam02;
public class ChildExample { public static void main(String[] args) { Child child = new Child(); Parent parent = child; // 자동 타입 변환 parent.method1(); parent.method2(); // 재정의된 메소드가 호출 //parent.method3(); (호출 불가능) } } |
Parent-method1()
Child-method2() |
2) 필드의 다형성
- 필드의 타입을 부모 타입으로 선언하면 다양한 자식 객체들이 저장될 수 있기 때문에 필드 사용 결과가 달라질 수 있다
- 필드의 다형성을 구현하기 위한 3가지
- 상속
- 재정의
- 타입 변환
(1) Tire 클래스
- 필드
- 최대 회전수(maxRotation): 타이어의 수명, 최대 회전수에 도달하면 타이어에 펑크가 남
- 누적 회전수(accumulatedRotation): 타이어가 1번 회전할 때마다 1씩 증가되는 필드
- 타이어의 위치(location): 앞왼쪽, 앞오른쪽, 뒤왼쪽, 뒤오른쪽
- Tire 클래스의 생성자
- 타이어의 위치와 최대 회전수를 매개값으로 받아 각각의 필드에 저장.
- roll() 메소드
- 타이어를 1회전시키는 메소드, 1번 실행 시 누적 회전수 1씩 증가
- 누적 회전수가 최대 회전수보다 작을 때는 남은 회전수를 출력
- 최대 회전수가 되면 펑크를 출력
- 리턴 타입은 boolean, 정상 회전하면 true를, 펑크가 나면 false를 리턴
Tire 클래스 // Tire.java |
package sec02.exam03;
public class Tire { //필드 public int maxRotation; //최대 회전수(타이어 수) public int accumulatedRotation; //누적 회전수 public String location; //타이어의 위치 //생성자 public Tire(String location, int maxRotation) { this.location = location; this.maxRotation = maxRotation; } //메소드 public boolean roll() { ++accumulatedRotation; if(accumulatedRotation<maxRotation) { System.out.println(location + " Tire 수명: " + (maxRotation-accumulatedRotation) + "회"); return true; } else { System.out.println("*** " + location + " Tire 펑크 ***"); return false; } } } |
(2) Car 클래스
- 필드
- 4개의 타이어가 있음
- Tire 객체를 생성할 때 타이어의 위치와 최대 회전수를 생성자의 매개값으로 지정
- run() 메소드
- 4개의 타이어를 한 번씩 1회전시키는 메소드
- 각각의 Tire 객체의 roll() 메소드를 호출해서 리턴값이 false가 되면 stop() 메소드를 호출하고
해당 타이어 번호를 리턴
- stop() 메소드
- 타이어에 펑크가 날 때 자동차를 멈추는 메소드
Tire를 부품으로 가지는 클래스 // Car.java |
package sec02.exam03;
public class Car { //필드 Tire frontLeftTire = new Tire("앞왼쪽", 6); Tire frontRightTire = new Tire("앞오른쪽", 2); Tire backLeftTire = new Tire("뒤왼쪽", 3); Tire backRightTire = new Tire("뒤오른쪽", 4); //생성자 //메소드 int run() { System.out.println("[자동차가 달립니다.]"); if(frontLeftTire.roll()==false) { stop(); return 1; }; if(frontRightTire.roll()==false) { stop(); return 2; }; if(backLeftTire.roll()==false) { stop(); return 3; }; if(backRightTire.roll()==false) { stop(); return 4; }; return 0; } void stop() { System.out.println("[자동차가 멈춥니다.]"); } } |
(3) HankookTire, KumhoTire 클래스
- 두 클래스
- Tire 클래스를 상속받음
- 생성자는 타이어의 위치, 최대 회전수를 매개값으로 받아서
부모인 Tire 클래스의 생성자를 호출할 때 넘겨줌
- roll() 메소드
- 재정의되어 정상 회전과 펑크가 났을 때 출력하는 내용이 Tire 클래스의 roll() 메소드랑 다름
Tire의 자식 클래스 // HankookTire.java |
package sec02.exam03;
public class HankookTire extends Tire { //필드 //생성자 public HankookTire(String location, int maxRotation) { super(location, maxRotation); } //메소드 @Override public boolean roll() { ++accumulatedRotation; if(accumulatedRotation<maxRotation) { System.out.println(location + " HankookTire 수명: " + (maxRotation-accumulatedRotation) + "회"); return true; } else { System.out.println("*** " + location + " HankookTire ��ũ ***"); return false; } } } |
Tire의 자식 클래스 // KumhoTire.java |
package sec02.exam03;
public class KumhoTire extends Tire { //필드 //생성자 public KumhoTire(String location, int maxRotation) { super(location, maxRotation); } //메소드 @Override public boolean roll() { ++accumulatedRotation; if(accumulatedRotation<maxRotation) { System.out.println(location + " KumhoTire 수명: " + (maxRotation-accumulatedRotation) + "회"); return true; } else { System.out.println("*** " + location + " KumhoTire 펑크 ***"); return false; } } } |
(4) CarExample 클래스
- 지금까지 작성한 Tire, Car HankookTire, KumhoTire 클래스를 이용하는 실행 클래스
실행 클래스 // CarExample.java |
package sec02.exam03;
public class CarExample { public static void main(String[] args) { Car car = new Car(); for(int i=1; i<=5; i++) { int problemLocation = car.run(); switch(problemLocation) { case 1: System.out.println("앞왼쪽 HankookTire로 교체"); car.frontLeftTire = new HankookTire("앞왼쪽", 15); break; case 2: System.out.println("앞오른쪽 KumhoTire로 교체"); car.frontRightTire = new KumhoTire("앞오른쪽", 13); break; case 3: System.out.println("뒤왼쪽 HankookTire로 교체"); car.backLeftTire = new HankookTire("뒤왼쪽", 14); break; case 4: System.out.println("뒤오른쪽 KumhoTire로 교체"); car.backRightTire = new KumhoTire("뒤오른쪽", 17); break; } System.out.println("----------------------------------------"); } } } |
[자동차가 달립니다.]
앞왼쪽 Tire 수명: 5회 앞오른쪽 Tire 수명: 1회 뒤왼쪽 Tire 수명: 2회 뒤오른쪽 Tire 수명: 3회 ---------------------------------------- [자동차가 달립니다.] 앞왼쪽 Tire 수명: 4회 *** 앞오른쪽 Tire 펑크 *** [자동차가 멈춥니다.] 앞오른쪽 KumhoTire로 교체 ---------------------------------------- [자동차가 달립니다.] 앞왼쪽 Tire 수명: 3회 앞오른쪽 KumhoTire 수명: 12회 뒤왼쪽 Tire 수명: 1회 뒤오른쪽 Tire 수명: 2회 ---------------------------------------- [자동차가 달립니다.] 앞왼쪽 Tire 수명: 2회 앞오른쪽 KumhoTire 수명: 11회 *** 뒤왼쪽 Tire 펑크 *** [자동차가 멈춥니다.] 뒤왼쪽 HankookTire로 교체 ---------------------------------------- [자동차가 달립니다.] 앞왼쪽 Tire 수명: 1회 앞오른쪽 KumhoTire 수명: 10회 뒤왼쪽 HankookTire 수명: 13회 뒤오른쪽 Tire 수명: 1회 ---------------------------------------- |
3) 매개 변수의 다형성
- 자동 타입 변환은 필드의 값을 대입할 때에도 발생하지만, 주로 메소드를 호출할 때 많이 발생
- 메소드를 호출할 때에는 매개 변수 타입과 동일한 매개값을 지정하는 것이 정석이지만,
매개값을 다양화하기 위해 매개 변수에 자식 객체를 지정할 수도 있다
부모 클래스 // Vehicle.java |
package sec02.exam04;
public class Vehicle { public void run() { System.out.println("차량이 달립니다."); } } |
- Driver 클래스는 drive() 메소드에서 Vehicle 타입의 매개값을 받아서 run() 메소드를 호출
Vehicle을 이용하는 클래스 // Driver.java |
package sec02.exam04;
public class Driver { public void drive(Vehicle vehicle) { vehicle.run(); } } |
- Bus와 Taxi 클래스는 Vehicle 클래스를 상속받아 run() 메소드를 재정의 함
자식 클래스 // Bus.java |
package sec02.exam04;
public class Bus extends Vehicle { @Override public void run() { System.out.println("버스가 달립니다."); } } |
자식 클래스 // Taxi.java |
package sec02.exam04;
public class Taxi extends Vehicle { @Override public void run() { System.out.println("택시가 달립니다."); } } |
- 위의 클래스를 이용해서 실행하는 DriverExample 클래스
실행 클래스 // DriverExample.java |
package sec02.exam04;
public class DriverExample { public static void main(String[] args) { Driver driver = new Driver(); Bus bus = new Bus(); Taxi taxi = new Taxi(); driver.drive(bus); driver.drive(taxi); } } |
4) 강제 타입 변환(casting)
- 부모 타입을 자식 타입으로 변환하는 것
- 모든 부모 타입을 자식 타입으로 강제 변환할 수 있는 것은 아님
- 자식 타입이 부모 타입으로 자동 타입 변환한 후 다시 자식 타입으로 변환할 때 사용 가능
- 다음 코드와 같이 Child 객체가 Parent 타입으로 자동 변환된 상태에서 원래 Child로 강제 변환 가능
- 자식 타입이 부모 타입으로 자동 타입 변환하면, 부모에 선언된 필드와 메소드만 사용 가능하다는 제약 사항이 따름
- 자식에 선언된 필드와 메소드를 꼭 사용해야 한다면 강제 타입 변환을 해서 다시 자식 타입으로 변환한 다음 자식 필드와 메소드를 사용하면 된다.
부모 클래스 // Parent.java |
package sec02.exam05;
public class Parent { public String field1; public void method1() { System.out.println("Parent-method1()"); } public void method2() { System.out.println("Parent-method2()"); } } |
자식 클래스 // Child.java |
package sec02.exam05;
public class Child extends Parent { public String field2; public void method3() { System.out.println("Child-method3()"); } } |
강제 타입 변환 // ChildExample.java |
package sec02.exam05;
public class ChildExample { public static void main(String[] args) { Parent parent = new Child(); parent.field1 = "data1"; parent.method1(); parent.method2(); /* parent.field2 = "data2"; parent.method3(); */ Child child = (Child) parent; child.field2 = "yyy"; child.method3(); } |
Parent-method1()
Parent-method2() Child-method3() |
5) 객체 타입 확인
- 강제 타입 변환은 자식 타입이 부모 타입으로 변환되어 있는 상태에서만 가능하기 때문에
다음과 같이 처음부터 부모 타입으로 생성된 객체는 자식 타입으로 변환할 수 없다. - instanceof 연산자
- 부모 변수가 참조하는 객체가 부모 객체인지 자식 객체인지 확인하는 방법
- 어떤 객체가 어떤 클래스의 인스턴스인지 확인하기 위해 사용
- 좌항에는 객체가 우항에는 타입이 옴
- 좌항의 객체가 우항의 인스턴스이면, 우항의 타입으로 객체가 생성되었다면
true를 리턴하고 그렇지 않으면 false를 리턴한다.
- 주로 매개값의 타입을 조사할 때 사용
- 메소드 내에서 강제 타입 변환이 필요할 경우 반드시 매개값이 어떤 객체인지 instanceof 연산자로 확인하고
안전하게 강제 타입 변환을 해야한다.
부모 클래스 // Parent.java |
package sec02.exam06;
public class Parent { } |
자식 클래스 // Child.java |
package sec02.exam06;
public class Child extends Parent { } |
객체 타입 확인 // InstanceofExample.java |
package sec02.exam06;
public class InstanceofExample { public static void method1(Parent parent) { if(parent instanceof Child) { Child child = (Child) parent; System.out.println("method1 - Child로 변환 성공"); } else { System.out.println("method1 - Child로 변환되지 않음"); } } public static void method2(Parent parent) { Child child = (Child) parent; System.out.println("method2 - Child 로 변환 성공 "); } public static void main(String[] args) { Parent parentA = new Child(); method1(parentA); method2(parentA); Parent parentB = new Parent(); method1(parentB); method2(parentB); //예외 발생 } } |
method1 - Child로 변환 성공
method2 - Child 로 변환 성공 method1 - Child로 변환되지 않음 Exception in thread "main" java.lang.ClassCastException: class sec02.exam06.Parent cannot be cast to class sec02.exam06.Child (sec02.exam06.Parent and sec02.exam06.Child are in module chap07 of loader 'app') at chap07/sec02.exam06.InstanceofExample.method2(InstanceofExample.java:15) at chap07/sec02.exam06.InstanceofExample.main(InstanceofExample.java:26) |
3. 추상 클래스
- 실체 간에 공통되는 특성을 추출한 것
- 특성: 필드 그리고 메소드
- 추상 클래스: 실체 클래스에서 공통되는 필드와 메소드를 따로 선언한 Animal.class 클래스
1) 추상 클래스의 용도
(1) 공통된 필드와 메소드의 이름을 통일할 목적
- 실체 클래스를 설계하는 사람이 여러 사람일 경우, 실체 클래스마다 필드와 메소드가 제각기 다른 이름을 가질 수 있음
- 하나의 추상 클래스에 필드와 메소드를 선언하고, 하루 클래스를 상속함으로써 필드와 메소드 이름을 통일
(2) 실체 클래스를 작성할 때 시간 절약
- 공통적인 필드와 메소드를 추상 클래스에 모두 선언하고
- 다른 점만 실체 클래스에 선언하면 실체 클래스를 작성하는 데 시간을 절약할 수 있음
- 일반적으로 개발 프로젝트에서 설계자와 코더는 다른 일을 수행함
- 설계자는 코더에게 클래스는 어떤 구조로 작성해야 한다는 것을 알려줘야함
2) 추상 클래스 선언
- 추상 클래스를 선언할 때 클래스 선언에 abstarct 키워드를 붙여야 함
- abstract를 붙이면 new 연산자를 이용해서 객체를 만들지 못하고, 상속을 통해서 자식 클래스만 만들 수 있음
- 추상 클래스도 일반 클래스와 마찬가지로 필드, 생성자, 메소드 선언을 할 수 있음
- new 연산자로 직접 생성자를 호출할 수는 없지만 자식 객체가 생성될 때 super(...)를 호출해서
추상 클래스 객체를 생성하므로 추상 클래스도 생성자가 반드시 있어야 함.
추상 클래스 // Phone.java |
package sec03.exam01;
public abstract class Phone { //필드 public String owner; //생성자 public Phone(String owner) { this.owner = owner; } //메소드 public void turnOn() { System.out.println("폰 전원을 켭니다."); } public void turnOff() { System.out.println("폰 전원을 끕니다."); } } |
실체 클래스 // SmartPhone.java |
package sec03.exam01;
public class SmartPhone extends Phone { //생성자 public SmartPhone(String owner) { super(owner); } //메소드 public void internetSearch() { System.out.println("인터넷 검색을 합니다."); } } |
실행 클래스 // PhoneExample.java |
package sec03.exam01;
public class PhoneExample { public static void main(String[] args) { //Phone phone = new Phone(); (x) SmartPhone smartPhone = new SmartPhone("홍길동"); smartPhone.turnOn(); smartPhone.internetSearch(); smartPhone.turnOff(); } } |
폰 전원을 켭니다.
인터넷 검색을 합니다. 폰 전원을 끕니다. |
- 추상 클래스는 실체 클래스의 공통되는 필드와 메소드를 추출해서 만들었기 때문에
객체를 직접 생성해서 사용할 수 없다 - 추상 클래스는 새로운 실체 클래스를 만들기 위해 부모 클래스로만 사용됨
- 추상 클래스는 extends 뒤에만 올 수 있는 클래스
3) 추상 메소드와 재정의
추상 메소드 선언 // Animal.java |
package sec03.exam02;
public abstract class Animal { public String kind; public void breathe() { System.out.println("숨을 쉽니다."); } public abstract void sound(); } |
추상 메소드 재정의 // Dog.java |
package sec03.exam02;
public class Dog extends Animal { public Dog() { this.kind = "포유류"; } @Override public void sound() { System.out.println("멍멍"); } } |
추상 메소드 재정의 // Cat.java |
package sec03.exam02;
public class Cat extends Animal { public Cat() { this.kind = "포유류"; } @Override public void sound() { System.out.println("야옹"); } } |
실행 클래스 // AnimalExample.java |
package sec03.exam02;
public class AnimalExample { public static void main(String[] args) { Dog dog = new Dog(); Cat cat = new Cat(); dog.sound(); cat.sound(); System.out.println("-----"); //변수의 자동 타입 변환 Animal animal = null; animal = new Dog(); animal.sound(); animal = new Cat(); animal.sound(); System.out.println("-----"); //메소드의 다형성 animalSound(new Dog()); animalSound(new Cat()); } public static void animalSound(Animal animal) { animal.sound(); } } |
멍멍
야옹 ----- 멍멍 야옹 ----- 멍멍 야옹 |
- 가장 일반적인 방식으로 Dog와 Cat 변수로 호출
- Animal 변수로 타입 변환해서 sound() 메소드를 호출
자식은 부모 타입으로 자동 타입 변환이 될 수 있고,
메소드가 재정의되어 있을 경우 재정의된 자식 메소드가 호출되는 다형성의 특징이 그대로 적용됨 - 부모 타입의 매개 변수에 자식 객체를 대입해서 메소드의 다형성을 적용
728x90
반응형
'혼공 스터디 > 혼자 공부하는 자바' 카테고리의 다른 글
[혼공자바] 6주차: 예외와 API 클래스 (0) | 2024.02.06 |
---|---|
[혼공자바] 5주차 : 인터페이스 (0) | 2024.02.04 |
[ 혼공자바 ] 3주차 : 클래스 (0) | 2024.01.19 |
[ 혼공자바 ] 2주차 : 조건문, 반복문, 참조 타입 (1) | 2024.01.14 |
[ 혼공자바 ] 1주차 (1) | 2024.01.07 |