Do it! 자바 프로그래밍 입문 / 8장 상속
8.1 상속이란?
클래스 B가 클래스 A를 상속 받는 것은 A의 내용들을 B가 상속 받는 다는 것을 의미한다.
아래처럼 클래스를 선언할 때 extends를 이용해서 상속받는다.
class B extends A
{
}
protected 예약어 : 외부에선 접근이 불가능하면서 상속받은 클래스에서는 접근이 가능하게 하는 예약어
|public|외부에서 접근 가능|
|protected|자기 자신, 상속 클래스에서만 접근 가능|
|private|자기 자신만 접근 가능|
8.2 상속에서 클래스 생성과 형 변환
하위 클래스가 생성되는 과정
하위 클래스 생성 : 상위 클래스 디폴트 생성자 호출 -> 하위 클래스 생성자 호출
super() : 상위 클래스에 접근할 때 사용하는 예약어(매개변수가 있는 생성자를 호출할 수도 있음)
super 예약어를 작성하지 않는다면 자동으로 super()이 실행돼서 디폴트 생성자가 호출되는 것
class VIP()
{
public VIP()
{
super(int ID, String Name) // 상위 클래스의 디폴트 생성자가 아닌 매개변수가 있는 생성자를 호출
}
}
아래처럼 super 예약어를 이용해서 상위 클래스의 멤버에도 접근할 수 있음
class VIP()
{
public String showVIPInfo()
{
return super.showCustomerInfo() // 상위 클래스인 Customer 클래스의 멤버 함수 showCustomerInfo() 함수를 호출
}
}
상위 클래스로 묵시적 클래스 형 변환
"하위 클래스인 VIP 클래스를 상위 클래스인 Customer 클래스"로 형 변환할 수 있음
형 변환 하게 되면 상위 클래스의 멤버 변수와 메소드만 사용 가능하다.
(힙 메모리에 상위, 하위 클래스의 변수 모두 생성은 되지만 사용은 상위 클래스 멤버 변수만 가능함)
But, 그 반대인 상위 "
클래스 -> 하위 클래스
"는 불가능하다
Customer vc = new VIP() // VIP 클래스를 생성했지만 Customer 클래스로 형변환 했기 때문에 Customer 멤버 변수와 메소드에 접근 가능
8.3 메서드 오버라이딩
상위 클래스 메소드(함수) 재정의
메소드 오버라이딩 : 상위 클래스에 정의된 메소드를 하위 클래스에 재정의하는 것
하위 클래스에 재정의 할 때 반환형, 메소드 이름, 매개변수 개수, 매개변수 자료형이 상위 클래스의 메소드와 같아야 한다. 만약 넷 중 하나라도 다르면 오버라이딩이 아닌 아예 다른 함수로 본다.
묵시적 클래스 형 변환과 메소드(함수) 재정의(가상 메소드)
만약 형 변환을 한 인스턴스의 함수를 호출할 때 호출하는 함수가 재정의된 함수인 경우 어떤 함수가 호출될까?
-> 재정의한 함수를 호출할 경우 생성된 인스턴스의 메소드를 호출하게 된다.(new 클래스 <- 얘의 메소드 호출한다는 의미)
Customer vc = new VIP("10030", "나몰라", 2000)
vc.calcPrice(10000); // VIP의 calcPrice() 메소드를 호출
형 변환한 객체의 경우
멤버 변수는 힙 메모리에 특정 객체만의 멤버 변수가 생성되지만
멤버 메소드는 특정 객체의 멤버 메소드가 생성되는 것이 아닌 메소드가 호출될 때 메소드 테이블의 메소드 주소를 참조해서 해당 메소드를 호출한다.
즉, 인스턴스를 생성할 때 변수는 특정 인스턴스 전용 변수 생성, 메소드는 따로 생성하지 X
8.4 다형성
다형성 : 가상 메소드를 이용하여 하나의 코드에서 여러 실행 결과나 나오는 것
class Animal{
public void move()
{
System.out.println("동물이 움직입니다.");
}
}
class Human extends Animal{
public void move()
{
System.out.println("사람이 두 발로 걷습니다. ");
}
public void readBook()
{
System.out.println("사람이 책을 읽습니다. ");
}
}
class Tiger extends Animal{
public void move()
{
System.out.println("호랑이가 네 발로 뜁니다. ");
}
public void hunting()
{
System.out.println("호랑이가 사냥을 합니다. ");
}
}
class Eagle extends Animal{
public void move()
{
System.out.println("독수리가 하늘을 납니다 ");
}
public void flying()
{
System.out.print("독수리가 날개를 쭉 펴고 멀리 날아갑니다");
}
}
public class AnimalTest1 {
public static void main(String[] args) {
AnimalTest1 aTest = new AnimalTest1();
aTest.moveAnimal(new Human());
aTest.moveAnimal(new Tiger());
aTest.moveAnimal(new Eagle());
}
public void moveAnimal(Animal animal) { // 매개 변수의 자료형이 상위 클래스
animal.move(); } // 재정의 된 메서드 호출
}
위의 animal.move()에서 각 클래스의 재정의된 move 메소드가 호출된다.
결국 여러 클래스 자료형을 사용하지 않고 Animal 자료형 하나로 여러 클래스의 재정의된 메소드를 간편하게 사용할 수 있다는 것이 장점이다.
8.5 다형성 활용하기
배열에 상위, 하위 클래스 인스턴스를 넣어서 관리
상위 클래스 자료형으로 배열을 선언한다.
-> 배열에 인스턴스를 추가하면 자동으로 상위 클래스 자료형으로 묵시적 형 변환이 된다.
public class CustomerTest {
public static void main(String[] args) {
// 상위 클래스인 Customer 자료형으로 배열을 선언한다.
ArrayList<Customer> customerList = new ArrayList<Customer>();
// 상위, 하위 클래스의 인스턴스를 배열에 추가하면 상위 클래스 자료형으로 형 변환이 된다.
Customer customerLee = new Customer(10010, "이순신");
Customer customerShin = new Customer(10020, "신사임당");
Customer customerHong = new GoldCustomer(10030, "홍길동");
Customer customerYul = new GoldCustomer(10040, "이율곡");
Customer customerKim = new VIPCustomer(10050, "김유신", 12345);
// 여기선 미리 형 변환을 하고 add() 했지만 형 변환을 안하고 하위 클래스 인스턴스를 넣어도 자동으로 묵시적 형 변환이 된다.
customerList.add(customerLee);
customerList.add(customerShin);
customerList.add(customerHong);
customerList.add(customerYul);
customerList.add(customerKim);
System.out.println("====== 고객 정보 출력 =======");
for( Customer customer : customerList){
System.out.println(customer.showCustomerInfo());
}
System.out.println("====== 할인율과 보너스 포인트 계산 =======");
int price = 10000;
for( Customer customer : customerList){
int cost = customer.calcPrice(price);
System.out.println(customer.getCustomerName() +" 님이 " + + cost + "원 지불하셨습니다.");
System.out.println(customer.getCustomerName() +" 님의 현재 보너스 포인트는 " + customer.bonusPoint + "점입니다.");
}
}
}
상속은 왜 사용할까?
상속을 사용하지 않고 상위 클래스(Customer 클래스)에 Gold, VIP 내용을 추가해서 코드를 작성할 수도 있다.
if(customerGrade == "VIP") {}
else if(customerGrade == "GOLD") {}
else if(customerGrade == "SILVER") {}
이런 조건문을 필요한 함수마다 집어 넣으면 가능하다.
하지만 이런식으로 하면 코드가 너무 복잡하고 가독성이 안좋아져서 상속을 이용해 가독성 좋은 코드를 만드는 것이다.
상속은 언제 사용할까?
상속은 'IS-A 관계'일 때 사용한다.
IS-A 관계 : '사람은 포유류다'와 같이 상위 클래스가 하위 클래스를 포괄하는 경우(특정 개념(포유류)을 구체화(사람) 하는 경우)
단순히 코드 재활용을 목적으로 사용하지 않는다.('HAS-A 관계'일 때는 사용하지 않는다)
HAS-A 관계 : '학생이 과목을 듣는다.'와 같이 (특정 클래스(학생)이 다른 클래스(과목)을 소유하고 있는 경우)
8.6 다운 캐스팅과 instanceof
다운 캐스팅(하위 클래스로 형 변환)
다운 캐스팅 : 상위 클래스로 형 변환을 한 인스턴스를 다시 하위 클래스 자료형으로 형 변환하는 경우
(상위 클래스 자료형 -> 하위 클래스 자료형)
.
instanceof
다운 캐스팅을 하기 전에 상위 클래스를 상속받은 여러 하위 클래스 중 어떤 하위 클래스인지 확인하는 예약어
'상위 클래스 -> 하위 클래스' 형 변환은 자료형을 명시적으로 써줘야 형 변환이 된다.
class Shape{
void draw()
{
System.out.println("draw Shape");
}
}
class Circle extends Shape{
void draw()
{
System.out.println("draw Circle");
}
void circle()
{
System.out.println("원 입니다.");
}
}
class Rectangle extends Shape{
void draw()
{
System.out.println("draw Rectangle");
}
void rectangle()
{
System.out.println("사각형 입니다.");
}
}
class Triangle extends Shape{
void draw()
{
System.out.println("draw Triangle");
}
void triangle()
{
System.out.println("삼각형 입니다.");
}
}
public class ShapeTest {
ArrayList<Shape> sList = new ArrayList<Shape>();
public static void main(String[] args) {
ShapeTest sTest = new ShapeTest();
sTest.addShape();
System.out.println("원래 타입으로 다운 캐스팅 ");
sTest.testCasting();
}
public void addShape()
{
sList.add(new Circle()); //ArrayList 에 추가되면서 Shape 타입으로 형 변환
sList.add(new Rectangle());
sList.add(new Triangle());
for( Shape s : sList){ // 모두 Shape 타입으로 꺼내서 draw 호출하면
s.draw(); // 오버라이딩된 함수가 호출 됨
}
}
public void testCasting()
{
for(int i=0; i<sList.size(); i++){ //모든 배열 항목들을 하나씩 돌면서
Shape s = sList.get(i); // 일단 Shape 타입으로 가져옴
if(s instanceof Circle){ //Circle 이면
Circle c = (Circle)s; //Circle 로 형변환
c.circle();
}
else if(s instanceof Rectangle){
Rectangle r = (Rectangle)s;
r.rectangle();
}
else if(s instanceof Triangle){
Triangle t = (Triangle)s;
t.triangle();
}
else{
System.out.println("지원되지 않는 타입입니다.");
}
}
}
}
public void testCasting() 메소드처럼 if-else 조건문을 이용하여 명시적으로 형 변환을 하고 하위 클래스에만 있는 메소드를 실행한다.