본문 바로가기

배운내용 정리

객체지향(2)

1. 클래스 간의 관계와 상속

상속의 사전적 정의는 부모가 자식에게 물려주는 행위를 말합니다.

  • 객체 지향 프로그램에서도 부모 클래스의 필드와 메서드를 자식 클래스에게 물려줄 수 있습니다.
  • 상속을 사용하면 적은 양의 코드로 새로운 클래스를 작성할 수도 있고 공통적인 코드를 관리하여 코드의 추가와 변경이 쉬워질 수도 있습니다.
  • 이러한 특성 때문에 상속을 사용하면 코드의 중복이 제거되고 재사용성이 크게 증가하여 생산성과 유지보수성에 매우 유리해집니다.

정의

public class 자식클래스 extends 부모클래스 {

}

  1. 부모 클래스에 새로운 필드와 메서드가 추가되면 자식 클래스는 이를 상속받아 사용할 수 있다.
  2. 자식 클래스에 새로운 필드와 메서드가 추가되어도 부모 클래스는 어떠한 영향도 받지 않는다.
  3. 따라서 자식 클래스의 멤버 개수는 부모 클래스보다 항상 같거나 많다.
public class Car {
    String company; // 자동차 회사
    private String model; // 자동차 모델
    private String color; // 자동차 색상
    private double price; // 자동차 가격

    double speed;  // 자동차 속도 , km/h
    char gear = 'P'; // 기어의 상태, P,R,N,D
    boolean lights; // 자동차 조명의 상태

    public String getModel() {
        return model;
    }
    public void setModel(String model) {
        this.model = model;
    }
    public double gasPedal(double kmh, char type) {
        changeGear(type);
        speed = kmh;
        return speed;
    }
    public double brakePedal() {
        speed = 0;
        return speed;
    }
    public char changeGear(char type) {
        gear = type;
        return gear;
    }

    public boolean onOffLights() {
        lights = !lights;
        return lights;
    }

    public void horn() {
        System.out.println("빵빵");
    }
}



public class SportsCar extends Car{ // 자식 클래스
    String engine;
    public void booster() {
        System.out.println("엔진 " + engine + " 부앙~\n");
    }
}

public class Main {
    public static void main(String[] args) {
        // 부모 클래스 객체에서 자식 클래스 멤버 사용
        Car car = new Car();
        // car.engine = "Orion"; // 오류
        // car.booster(); // 오류

        // 자식 클래스 객체 생성
        SportsCar sportsCar = new SportsCar();
        sportsCar.engine = "Orion";
        sportsCar.booster();

        // 자식 클래스 객체에서 부모 클래스 멤버 사용
        sportsCar.company = "GENESIS";
        sportsCar.setModel("GV80");
        System.out.println("sportsCar.company = " + sportsCar.company);
        System.out.println("sportsCar.getModel() = " + sportsCar.getModel());
        System.out.println();

        sportsCar.horn();
        System.out.println(sportsCar.changeGear('D'));
    }
}

 

2. final 클래스와 final 메서드

클래스에 final 키워드를 지정하여 선언하면 최종적인 클래스가 됨으로 더 이상 상속할 수 없는 클래스가 된다.

메서드에 final 키워드를 지정하여 선언하면 최종적인 메서드가 됨으로 더 이상 오버라이딩할 수 없는 메서드가 된다.

 

3. 오버라이딩

부모 클래스로부터 상속받은 메서드의 내용을 재정의 하는 것을 오버라이딩이라고 한다.

  • 부모 클래스의 메서드를 그대로 사용 가능하지만 자식 클래스의 상황에 맞게 변경을 해야 하는 경우 오버라이딩을 사용.
  • 오버라이딩을 하기 위해서는 아래 조건들을 만족해야 한다.
  1. 선언부가 부모 클래스의 메서드와 일치해야 한다.
  2. 접근 제어자를 부모 클래스의 메서드 보다 좁은 범위로 변경할 수 없다.
  3. 예외는 부모 클래스의 메서드 보다 많이 선언할 수 없다. 
public class Car { 
// 부모 클래스
    String company; // 자동차 회사
    private String model; // 자동차 모델 
    private String color; // 자동차 색상
    private double price; // 자동차 가격

    double speed;  // 자동차 속도 , km/h
    char gear = 'P'; // 기어의 상태, P,R,N,D
    boolean lights; // 자동차 조명의 상태

    public String getModel() {
        return model;
    }
    public void setModel(String model) {
        this.model = model;
    }
    public double gasPedal(double kmh, char type) {
        changeGear(type);
        speed = kmh;
        return speed;
    }
    public double brakePedal() {
        speed = 0;
        return speed;
    }
    public char changeGear(char type) {
        gear = type;
        return gear;
    }
    public boolean onOffLights() {
        lights = !lights;
        return lights;
    }
    public void horn() {
        System.out.println("빵빵");
    }
}



public class SportsCar extends Car{

// 자식 클래스
    String engine;
    public void booster() {
        System.out.println("엔진 " + engine + " 부앙~\n");
    }
    public SportsCar(String engine) {
        this.engine = engine;
    }
    @Override
    public double brakePedal() {
        speed = 100;
        System.out.println("스포츠카에 브레이크란 없다");
        return speed;
    }
    @Override
    public void horn() {
        booster();
    }
}

 

3. super와 super()

  • super는 부모 클래스의 멤버를 참조할 수 있는 키워드이다.
  • 객체 내부 생성자 및 메서드에서 부모 클래스의 멤버에 접근하기 위해 사용될 수 있다.
  • 자식 클래스 내부에서 선언한 멤버와 부모 클래스에서 상속받은 멤버와 이름이 같을 경우 이를 구분하기 위해 사용
public class Car{
	// 부모 클래스 Car
	String model; // 자동차 모델
	String color; // 자동차 색상
	double price; // 자동차 가격
 
    public String getModel() {
    	return model;
    }
    public String getColor() {
        return color;
    }
}
public class SportsCar extends Car{
	// 자식 클래스 SportsCar
	String model = "Ferrari"; // 자동차 모델
	String color = "Red"; // 자동차 색상
	double price = 300000000; // 자동차 가격

    public SportsCar(String engine) {
        this.engine = engine;
    }
    
    public void setCarInfo(String model, String color, double price) {
    super.model = model; // model은 부모 필드에 set
    super.color = color; // color는 부모 필드에 set
    this.price = price; // price는 자식 필드에 set
	}
}
  • 자식 클래스의 메서드를 호출하면 super 키워드로 접근한 부모 클래스의 model, color 필드에 매개변수의 값이 저장
  • this 키워드로 접근한 자식 클래스의 price 필드에는 매개변수의 값이 저장.
public class Main {
    public static void main(String[] args) {
        // 자식 클래스 스포츠카 객체 생성
        SportsCar sportsCar = new SportsCar("Orion");

        // 자식 객체의 model, color, price 초기값 확인
        System.out.println("sportsCar.model = " + sportsCar.model); // Ferrari
        System.out.println("sportsCar.color = " + sportsCar.color); // Red
        System.out.println("sportsCar.price = " + sportsCar.price); // 3.0E8
        System.out.println();

        // setCarInfo 메서드 호출해서 부모 및 자식 필드 값 저장
        sportsCar.setCarInfo("GV80", "Black", 50000000);

        // this.price = price; 결과 확인
        System.out.println("sportsCar.price = " + sportsCar.price); // 5.0E7
        System.out.println();

        // super.model = model; super.color = color;
        // 결과 확인을 위해 자식 클래스 필드 model, color 확인 & 부모 클래스 메서드인 getModel(), getColor() 호출
        // 자식 클래스 필드 값은 변화 없음.
        System.out.println("sportsCar.model = " + sportsCar.model); // Ferrari
        System.out.println("sportsCar.color = " + sportsCar.color); // Red
        System.out.println();

        // 부모 클래스 필드 값 저장됨.
        System.out.println("sportsCar.getModel() = " + sportsCar.getModel()); // GV80
        System.out.println("sportsCar.getColor() = " + sportsCar.getColor()); // Black

    }
}

 

super(…)는 부모 클래스의 생성자를 호출할 수 있는 키워드입니다.

  • 객체 내부 생성자 및 메서드에서 해당 객체의 부모 클래스의 생성자를 호출하기 위해 사용될 수 있다.
  • 자식 클래스의 객체가 생성될 때 부모 클래스들이 모두 합쳐져서 하나의 인스턴스가 생성된다.
  • 이때 부모 클래스의 멤버들의 초기화 작업이 먼저 수행이 되어야 한다.
    • 따라서 자식 클래스의 생성자에서는 부모 클래스의 생성자가 호출된다.
    • 또한 부모 클래스의 생성자는 가장 첫 줄에서 호출이 되어야 한다. 
public class Car {
// 부모클래스 Car
    String company; // 자동차 회사
    String model; // 자동차 모델
    String color; // 자동차 색상
    double price; // 자동차 가격
    
    public Car(String model, String color, double price) {
        this.model = model;
        this.color = color;
        this.price = price;
    }
    public String getModel() {
        return model;
    }
    public String getColor() {
        return color;
    }
    public double getPrice() {
        return price;
    }

}
public class SportsCar extends Car{
    String engine;

    public SportsCar(String model, String color, double price, String engine) {
         // this.engine = engine; // 오류 발생
        super(model, color, price);
        this.engine = engine;
    }

}
  • 자식 클래스 객체를 생성할 때 생성자 매개변수에 매개값을 받아와 super(…)를 사용해 부모 생성자의 매개변수에 매개값을 전달하여 호출하면서 부모 클래스의 멤버를 먼저 초기화
  • 오버로딩된 부모 클래스의 생성자가 없다고 하더라도 부모 클래스의 기본 생성자를 호출해야 한다
    • 따라서 눈에 보이지는 않지만 컴파일러가 super(); 를 자식 클래스 생성자 첫 줄에 자동으로 추가해 준다.
public class Main {
    public static void main(String[] args) {
        // 자식 클래스 스포츠카 객체를 생성
        SportsCar sportsCar = new SportsCar("Lamborghini", "Red", 400000000, "V12");
        sportsCar.brakePedal();
        sportsCar.horn();

        // 자식 클래스의 생성자를 통해 부모 클래스의 생성자가 호출되어 필드값이 초기화 되었는지 확인
        System.out.println("sportsCar.getModel() = " + sportsCar.getModel()); // Lamborghini
        System.out.println("sportsCar.getColor() = " + sportsCar.getColor()); // Red
        System.out.println("sportsCar.getPrice() = " + sportsCar.getPrice()); // 4.0E8
    }
}

4. 다형성

자동 타입변환

부모타입 변수 = 자식타입객체; 는 자동으로 부모타입으로 변환이 일어난다.

  • 자식 객체는 부모 객체의 멤버를 상속받기 때문에 부모와 동일하게 취급될 수 있다.
    • 예를 들어 포유류 클래스를 상속받은 고래 클래스가 있다면 포유류 고래 = 고래객체; 가 성립될 수 있다.
    • 왜냐하면 고래 객체는 포유류의 특징인 모유수유 행위를 가지고 있기 때문
    • 다만 주의할 점은 부모타입 변수로 자식객체의 멤버에 접근할 때는 부모 클래스에 선언된 즉, 상속받은 멤버만 접근할 수 있다.
class Mammal {
    // 부모 클래스
    // 포유류는 새끼를 낳고 모유수유를 한다.
    public void feeding() {
        System.out.println("모유수유를 합니다.");
    }
}
class Whale extends Mammal {
    // 자식클래스
    // 고래는 포유류 이면서 바다에 살며 수영이 가능하다.
    public void swimming() {
        System.out.println("수영하다.");
    }

    @Override
    public void feeding() {
        System.out.println("고래는 모유수유를 합니다.");
    }
}
public class Main {
    public static void main(String[] args) {
        // 고래는 포유류이기 때문에 포유류 타입으로 변환될 수 있다
        Mammal mammal = new Whale();

        // 하지만 포유류 전부가 바다에 살고 수영을 할 수 있는 것은 아니기 때문에
        // 수영 하다 메서드는 실행 불가
        // 즉, 부모 클래스에 swimming이 선언되어있지 않아서 사용 불가능
        // mammalia.swimming(); // 오류 발생

        // 반대로 모든 포유류가 전부 고래 처럼 수영이 가능한 것이 아니기 때문에 타입변환이 불가능
        // 즉, 부모타입의 객체는 자식타입의 변수로 변환될 수 없다
        // Whale whale = new Mammal(); // 오류 발생

        mammal.feeding();
    }
}

강제 타입변환

자식타입 변수 = (자식타입) 부모타입객체;

  • 부모타입객체는 자식타입 변수로 자동으로 타입변환되지 않는다.
  • 이럴 때는 (자식타입) 즉, 타입변환 연산자를 사용하여 강제로 자식타입으로 변환할 수 있다. 
// 자식타입객체가 자동 타입변환된 부모타입의 변수
Mammal mammal = new Whale();
mammal.feeding();

// 자식객체 고래의 수영 기능을 사용하고 싶다면
// 다시 자식타입으로 강제 타입변환을 하면된다.
Whale whale = (Whale) mammal;
whale.swimming();
  • 다만 무조건 강제 타입변환을 할 수 있는 것은 아니다.
    • 자식타입객체가 부모타입으로 자동 타입변환된 후 다시 자식타입으로 변환될 때 만 강제 타입변환이 가능하다.
    • 부모타입 변수로는 자식타입객체의 고유한 멤버를 사용할 수 없기 때문에 사용이 필요한 경우가 생겼을 때 강제 타입변환을 사용
Mammal newMammal = new Mammal();
Whale newWhale = (Whale) newMammal; // ClassCastException 발생
  • 이렇게 자동 타입변환된 부모타입 변수가 아닌 부모객체를 자식타입의 변수로 강제 타입변환하려고 하면 오류가 발생한다.

다형성

다형성이란 ‘여러 가지 형태를 가질 수 있는 능력’을 의미한다.

  • 예를 들어 자동차의 핸들을 교체하면 핸들링이 부드러워지고 바퀴를 교체하면 승차감이 좋아진다.
  • 소프트웨어 또한 구성하고 있는 객체를 바꿨을 때 소프트웨어의 실행 성능 및 결과물이 다르게 나올 수 있다.
  • 일전에 배운 참조변수 타입변환을 활용해서 다형성을 구현할 수 있다. 
Tire tire = new HankookTire("HANKOOK");
Tire tire = new KiaTire("KIA");

부모타이어 변수 = 자식타이어객체; 를 선언하여 자동 타입변환된 변수를 사용하여 각각의 자식타이어 객체에 재정의 된 메서드를 통해 다양한 승차감을 가진 자동차를 생성할 수 있다.

public class Tire {
    //부모클래스
    String company; // 타이어 회사

    public Tire(String company) {
        this.company = company;
    }

    public void rideComfort() {
        System.out.println(company + " 타이어 승차감은?");
    }
}
public class KiaTire extends Tire{
    //자식클래스
    public KiaTire(String company) {
        super(company);
    }

    @Override
    public void rideComfort() {
        System.out.println(super.company + " 타이어 승차감은 " + 60);
    }
}
public class HankookTire extends Tire {
    //자식클래스
    public HankookTire(String company) {
        super(company);
    }

    @Override
    public void rideComfort() {
        System.out.println(super.company + " 타이어 승차감은 " + 100);
    }
}
public class Car {
    Tire tire;

    public Car(Tire tire) {
        this.tire = tire;
    }

    Tire getHankookTire() {
        return new HankookTire("HANKOOK");
    }

    Tire getKiaTire() {
        return new KiaTire("KIA");
    }
}
public class Main {
    public static void main(String[] args) {
        // 매개변수 다형성 확인!
        Car car1 = new Car(new KiaTire("KIA"));
        Car car2 = new Car(new HankookTire("HANKOOK"));

        // 반환타입 다형성 확인!
        Tire hankookTire = car1.getHankookTire();
        KiaTire kiaTire = (KiaTire) car2.getKiaTire();

        // 오버라이딩된 메서드 호출
        car1.tire.rideComfort(); // KIA 타이어 승차감은 60
        car2.tire.rideComfort(); // HANKOOK 타이어 승차감은 100
    }
}

 

  • 매개변수에도 다형성이 적용될 수 있습니다.
  • Car 생성자에서 매개변수의 타입이 부모 타이어 이기 때문에 자식 타이어 객체들을 매개값으로 전달할 수 있습니다.
  • 반환타입에도 다형성이 적용될 수 있습니다.
  • 반환타입이 부모 타이어 이기 때문에 자식 타이어 객체들을 반환값으로 지정할 수 있습니다.
  • 또한 자동 타입변환이 된 반환값인 자식 타이어 객체를 강제 타입변환할 수도 있습니다.

5. instanceof

다형성 기능으로 인해 해당 클래스 객체의 원래 클래스명을 체크하는 것이 필요한데 이때 사용할 수 있는 명령어가 instance of이다.

  • 이 명령어를 통해서 해당 객체가 내가 의도하는 클래스의 객체인지 확인할 수 있다.
  • {대상 객체} instance of {클래스 이름}와 같은 형태로 사용하면 응답값은 boolean이다.
// 다형성

class Parent { }
class Child extends Parent { }
class Brother extends Parent { }


public class Main {
    public static void main(String[] args) {

				Parent pc = new Child();  // 다형성 허용 (자식 -> 부모)

        Parent p = new Parent();

        System.out.println(p instanceof Object); // true 출력
        System.out.println(p instanceof Parent); // true 출력
        System.out.println(p instanceof Child);  // false 출력

        Parent c = new Child();

        System.out.println(c instanceof Object); // true 출력
        System.out.println(c instanceof Parent); // true 출력
        System.out.println(c instanceof Child);  // true 출력

    }
}

 

'배운내용 정리' 카테고리의 다른 글

자바의 구조체  (0) 2023.10.18
객체지향(3)  (1) 2023.10.17
객체지향(1)  (1) 2023.10.16
Java 문법 Array, collection  (0) 2023.10.13
Java 문법  (0) 2023.10.13