[ 혼공자바 ] 4주차 : 상속
본문 바로가기

혼공 스터디/혼자 공부하는 자바

[ 혼공자바 ] 4주차 : 상속

728x90
반응형

 

블로그 쓰다가 한 번 날려서 매우 화가났다.. 저장의 중요성....1

Chap 7 상속

  • 객체 지향 프로그래밍에서 부모 클래스의 멤버를 자식 클래스에게 물려줄 수 있다
  • 부모 클래스를 상위 클래스라고 부름
  • 자식 클래스를 하위 클래스 또는 파생 클래스라고 부름

1. 상속

  • 상속은 이미 잘 개발된 클래스를 재사용해서 새로운 클래스를 만들기 때문에 중복되는 코드를 줄여줌
  • 예를 들어 field1, field2 method1(), method2()를 가지는 클래스를 작성할 때
  • field1과 method1()을 가지고 있는 클래스가 있다면, 4개를 모두 처음부터 작성하는 것보다
    클래스를 상속하고 field2와 method2()만 추가 작성하는 것이 보다 효율적이고 개발 시간을 절약해줌

  • 상속의 장점
    • 상속을 이용하면 부모 클래스의 수정으로 모든 자식 클래스들이 수정됨
    • 따라서 유지보수 시간을 최소화할 수 있다.

 

1) 클래스 상속

  • 자식이 부모를 선택한다.
  • 자식 클래스를 선언할 때 어떤 부모 클래스를 상속받을 것인지 결정하고,
    선택된 부모 클래스는 다음과 같이 extends 뒤에 기술한다.

  • Car 클래스를 상속해서 SportsCar 클래스를 설계하자
class SportsCar extends Car {
}
  • 자바에서 상속의 특징
    1. 여러 개의 부모 클래스를 상속할 수 없다.
      따라서 extends 뒤에는 단 하나의 부모 클래스만 와야한다.
    2. 부모 클래스에서 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 접근 제한을 가질 수 있음
  • 메소드가 재정의되었다면 부모 객체의 메소드는 숨겨지기 때문에,
    자식 객체에서 메소드를 호출하면 재정의된 자식 메소드가 호출됨

부모 클래스 // 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

 

[ 이클립스의 재정의 메소드 자동 생성 ]

이클립스는 부모 메소드 중 하나를 선택해서 재정의 메소드를 자동 생성해주는 기능이 있음

이 기능은 부모 메소드의 시그너처를 정확히 모를 경우 매우 유용하게 사용 가능

  1. 자식 클래스에서 재정의 메소드를 작성할 위치로 입력 커서를 옮김
  2. [Source] - [Override/Implement Methods] 메뉴를 선택함
  3. 부모 클래스에서 재정의될 메소드를 선택하고 [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();
}
}
멍멍
야옹
-----
멍멍
야옹
-----
멍멍
야옹
  1. 가장 일반적인 방식으로 Dog와  Cat 변수로 호출
  2. Animal 변수로 타입 변환해서 sound() 메소드를 호출
    자식은 부모 타입으로 자동 타입 변환이 될 수 있고,
    메소드가 재정의되어 있을 경우 재정의된 자식 메소드가 호출되는 다형성의 특징이 그대로 적용됨
  3. 부모 타입의 매개 변수에 자식 객체를 대입해서 메소드의 다형성을 적용

728x90
반응형