본문 바로가기
TIL/Java | Spring Boot

2021.8.3 TIL : [Java] 기초 문법2 - 객체지향 프로그래밍 응용(1)

by yeon_zoo 2021. 8. 3.

객체지향 프로그래밍 응용에서는 세 가지 주요 내용을 다룬다. 클래스의 상속, 추상 클래스와 인터페이스이다.

1. 클래스의 상속

클래스 상속은 파이썬에서도 해본 적이 있다. A와 B 객체가 있을 때 B의 속성들이 A와 공통된다면 B에서 A의 속성들을 상속하고 A가 가지고 있는 필드와 메소드를 그대로 물려받은 것이다. 필요한 경우 B에서 추가로 필드나 메소드를 만들어서 사용해도 된다. 이 때 상위 클래스인 A클래스를 '슈퍼 클래스' 혹은 '부모 클래스'라고 하고 하위 클래스인 B는 '서브 클래스'나 '자식 클래스'라고 한다. 예를 들어 부모 클래스로 자동차 클래스가 있고 자식 클래스로 트럭과 세단이 있다고 해보면 코드는 다음과 같다. 

class Car{
	String color;
	int speed;
	
	void upSpeed(int value) {
		speed = speed + value;
	}
	
	void downSpeed(int value) {
		speed -= value;
	}
}
class Sedan extends Car{
	int seatNum;
	
	int getSeatNum() {
		return seatNum;
	}
}
class Truck extends Car{
	int capacity;
	
	int getCapacity() {
		return capacity;
	}
}
public class Ex12_01 {

	public static void main(String[] args) {
		
		Sedan sedan1 = new Sedan();
		Truck truck1 = new Truck();
		
		sedan1.upSpeed(300);
		truck1.upSpeed(100);
		sedan1.seatNum = 5;
		truck1.capacity = 50;
		
		System.out.println("승용차 속도는 "+sedan1.speed + "km, 좌석 수는 "+sedan1.getSeatNum() + "개입니다. ");
		System.out.println("트럭 속도는 "+truck1.speed + "km, 적재량은 " + truck1.getCapacity()+"톤 입니다.");
	}

}

Sedan과 Truck에서는 별도로 지정해주지 않은 upSpeed()메소드를 Car로부터 상속받았기 때문에 사용할 수 있다. 


생성자의 상속

슈퍼 클래스와 서브 클래스에서 생성자의 관계를 살펴보면 서브 클래스의 인스턴스를 생성하면 먼저 슈퍼 클래스의 생성자를 호출한 다음 서브 클래스의 생성자를 호출한다.

class Animals{
	Animals(){
		System.out.println("슈퍼클래스 생성자 호출!");
	}
}
class Rabbit extends Animals{
	Rabbit(){
		System.out.println("서브클래스 생성자 호출!");
	}
}
public class Animal {

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		Rabbit rabbit1 = new Rabbit();
	}

}

위와 같은 코드를 실행하면 다음과 같은 결과를 얻는 것을 통해 확인할 수 있다.

여러 개의 생성자가 있을 때 슈퍼 클래스의 생성자 호출

하나의 클래스 안에서도 파라미터만 다르다면 생성자를 여러 개 만들 수 있다. 다음 코드에서 슈퍼 클래스에 여러 개의 생성자가 있을 때 호출되는 순서를 살펴보고 강제로 슈퍼 클래스의 생성자를 호출하는 super()메소드도 확인 해 볼 수 있다. 

class Car3{
	Car3(){
		System.out.println("슈퍼 클래스 생성자 호출(파라미터 없음) ");
	}
	Car3(String str){
		System.out.println("슈퍼 클래스 생성자 호출 ==> " + str);
	}
}

class Sedan1 extends Car3{
	Sedan1(String str){
		
		System.out.println("서브 클래스 생성자 호출 ==> " + str);
	}
}
public class Ex12_03 {

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		Sedan1 sedan1 = new Sedan1("여기요~"); 
	}

}

이렇게 하면 다음과 같은 결과가 나온다.

이 때 파라미터가 있는 슈퍼 클래스 생성자를 호출하고 싶다면 Sedan1(String str){} 여기에서 super(str);을 넣어주면 된다. 

class Sedan1 extends Car3{
	Sedan1(String str){
		super(str);
		System.out.println("서브 클래스 생성자 호출 ==> " + str);
	}
}

super()를 사용할 때 주의할 점은 서브 클래스의 생성자 코드 중에서 첫 번째 행에 나와야 한다는 것이다. 


상속의 제한과 오버라이딩

만약 Sedan 클래스에서 Car의 클래스 중의 일부를 상속받고 싶지 않다면 private 접근 제어 수식어를 사용하면 된다.

class Car{
    private String color;
    int speed;
}
class Sedan extends Car {
    void setColor(String color){
        this.color = color;
    }
}

위처럼 접근하게 되면 에러가 난다. Sedan은 Car의 color 속성을 상속받지 못했기 때문이다. 혹은 protected 수식어를 사용해서 상속은 되지만 클래스나 패키지 외에는 접근하지 못하도록 설정할 수도 있다. 

 

메소드 오버라이딩

메소드는 상위 클래스의 메소드를 하위 클래스에서 재정의하는 것을 의미한다. 만약 트럭은 속도에 제한이 없지만 승용차는 속도에 제약이 있는 경우 upSpeed()메소드에서 차이를 보인다. 이 때는 그냥 해당 하위 클래스 내부에서 메소드를 같은 이름으로 재정의해주면 된다. 

만약 메소드 오버라이딩을 막고 싶다면 메소드 앞에 'final' 키워드를 붙이면 된다. 이렇게 하면 이후로 해당 메소드는 서브 클래스에서 오버 라이딩 할 수 없고 슈퍼 클래스에서 정의한 대로 사용해야 한다. 보통 final은 상수처럼 고정된 형태에서 붙이게 되며 주로 static과 함께 클래스 변수를 상수처럼 지정하는데 사용된다.  메소드에 final을 붙이면 마지막 메소드가 되므로 상속을 할 수 없다. 또한 필드에 final을 붙이면 마지막 변수 값을 갖기 때문에 변경할 수 없다고 이해하면 된다.

class Car4{
	int speed = 0;
	
	final void upSpeend(int speed) {
		this.speed += speed;
	}
}
class Sedan4 extends Car4{
	final static String CAR_TYPE = "승용차";
	
	//void upSpeed(int speed){
	//재정의한 메소드 내
	//}
}
public class Ex12_07 {

	public static void main(String[] args) {
		System.out.println("Sedan 클래스 타입 -->" +Sedan4.CAR_TYPE);
	}

}

만약 중간에 주석 처리한 부분을 실제 코드로 돌리면 슈퍼 클래스의 upSpeed()를 오버라이딩 하면 오류가 발생한다. 또한 클래스 변수 CAR_TYPE을 final로 지정하여 변경하지 못하도록 한다. (혹은 static을 써도 같은 결과를 얻는다) 이렇게 하면 별도의 인스턴스 없이 Sedan4.CAR_TYPE으로 클래스 변수를 사용할 수 있다. 

댓글