[혼공자] 4주차_상속

jini·2025년 7월 26일
0

혼자공부하는자바

목록 보기
4/6

한빛미디어의 <혼자 공부하는 자바>를 요약 정리했습니다.

 

🧊 4주차 미션

기본: 클래스의 타입 변환에는 어떤 것이 있는지 정리하고 공유하기

업캐스팅(upcasting)

  • 자식 클래스 → 부모 클래스로 변환
  • 자동 변환되며, 명시적인 형변환이 필요 없음
  • 부모 클래스 타입으로 다루기 때문에, 부모 클래스에 정의된 멤버만 접근 가능
  • 주로 다형성(polymorphism)을 활용할 때 사용
Parent p = new Child();  // 업캐스팅 (자동 변환)
p.parentMethod();        // 가능
// p.childMethod();      // 컴파일 오류 (자식 메서드 접근 불가) 

다운캐스팅(downcasting)

  • 부모 클래스 → 자식 클래스로 변환
  • 명시적인 형변환 필요 ((자식타입)으로 캐스팅)
  • 실제로 해당 객체가 자식 타입인 경우에만 안전하게 가능
  • 안전한 다운캐스팅을 위해 instanceof 연산자 사용 권장
if (p instanceof Child) {
    Child c = (Child) p;  // 다운캐스팅 (명시적 변환)
    c.childMethod();      // 자식 클래스의 멤버 사용 가능
}

 

선택: p.389 (07-3) 확인 문제 3번을 풀고 풀이 과정 설명하기

HttpServlet이라는 추상 클래스가 다음과 같이 선언되어 있습니다.

public abstract class HttpServlet {
	public abstract void service();
}

다음 클래스를 실행하면 "로그인 합니다.", "파일 다운로드 합니다."가 차례대로 출력되도록 LoginServlet과 FileDownloadServlet 클래스를 선언해보세요.

public class HttpServletExample {
	public static void main(String[] args) {
    	method(new LoginServlet());  // 로그인합니다.
        method(new FileDownloadServlet());  // 파일 다운로드합니다.
    }
    
    public static void method(HttpServlet servlet) {
    	servlet.service();
    }
}

HttpServlet 추상 클래스를 상속받아 각각의 서브 클래스(LoginServlet, FileDownloadServlet)에서 service() 메서드를 구현

LoginServlet
service()를 오버라이딩하여 "로그인 합니다." 출력

public class LoginServlet extends HttpServlet {
	@Override
    public void service() {
    	System.out.println("로그인 합니다.");
	}
}

FileDownloadServlet
service()를 오버라이딩하여 "파일 다운로드 합니다." 출력

public class FileDownloadServlet extends HttpServlet {
	@Override
    public void service() {
    	System.out.println("파일 다운로드 합니다.");
	}
}

 

🧊 클래스 상속

부모 클래스의 멤버를 자식 클래스에게 물려주는 것

  • 부모 클래스 : 상위 클래스
  • 자식 클래스 : 하위 클래스, 파생 클래스
class 자식클래스 extends 부모클래스 {
    // 필드
    // 생성자
    // 메소드
}
  • 여러 개의 부모 클래스를 상속할 수 없음
  • 부모 클래스에서 private 접근 제한을 갖는 필드와 메소드는 상속 대상에서 제외
  • 부모 클래스와 자식 클래스가 다른 패키지에 존재하면, default 접근 제한을 갖는 필드와 메소드도 상속 대상에서 제외
public class CellPhone {
	// 필드
    String model;
    String color;
    
    // 생성자
    
    // 메소드
    void powerOn { System.out.println("전원을 켭니다."); }
    void powerOff { System.out.println("전원을 끕니다."); }
}

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 방송 수신을 시작합니다.");
}

public class DmbCellPhoneExample {
	public static void main(String[] args) {
    	// DmbCellPhone 객체 생성
        DmbCellPhone dmbCellPhone = new DmbCellPhone("자바폰", "검정", 10);
        
        // CellPhone 클래스로부터 상속받은 필드
        System.out.println("모델: " + dmbCellPhone.model);
        
        // DmbCellPhone 클래스의 필드
        System.out.println("채널: " + dmbCellPhone.channel);
        
        // CellPhone 클래스로부터 상속받은 메소드 호출
        dmbCellPhone.powerOn();
        dmbCellPhone.powerOff();
        
        // DmbCellPhone 클래스의 메소드 호출
        dmbCellPhone.turnOnDmb();
    }
}

 

[1] 부모 생성자 호출

자식 객체를 생성하면, 부모 객체가 먼저 생성되고 그 다음에 자식 객체가 생성

DmbCellPhone dmbCellPhone = new DmbCellPhone();
  • 부모 객체를 생성하기 위한 부모 생성자는 자식 생성자 안에 들어있음
  • 부모 생성자는 자식 생성자의 맨 첫 줄에서 호출
  • DmbCellPhone의 생성자가 명시적으로 선언되지 않았다면 컴파일러는 기본 생성자를 생성
public DmbCellPhone() {
	super();  // 부모의 기본 생성자를 호출
}

명시적으로 부모 생성자를 호출 가능

자식클래스(매개변수선언, ...) {
	super(매개값, ...);
    ...
}
  • super는 매개값의 타입과 일치하는 부모 생성자를 호출(없을 경우 에러 발생)
public class People {
	public String name;
    public String ssn;
    
    public People(String name, String ssn) {
    	this.name = name;
        this.ssn = ssn;
    }
}

public class Student extends People {
	public int studentNo;
    
    public Students(String name, String ssn, int studentNo) {
    	super(name, ssn);
        this.studentNo = studentNo;
    }
}

재정의된 부모 클래스의 메소드 호출

super.부모 메소드();

 

[2] 메소드 재정의

부모 클래스의 메소드가 자식 클래스가 사용하기에 적합하지 않은 경우, 상속된 일부 메소드를 자식 클래스에서 수정해서 사용
-> 메소드 재정의(오버라이딩: Overriding)

  • 부모의 메소드와 동일한 시그니처(리턴 타입, 메소드 이름, 매개 변수 목록)을 가져야 함
  • 접근 제한을 더 강하게 재정의 불가능
  • 새로운 예외(Exception)를 throws 불가능
public class Calculator {
	double areaCircle(double r) {
    	return 3.14159 * r * r;
 	}       
}

public class Computer extends Calculator {
	@Override  // 생략 가능
    double areaCircle(double r) {
    	return Math.PI * r * r;
 	}   
}

 

[3] final 클래스와 final 메소드

final 키워드는 클래스, 필드, 메소드를 선언할 때 해당 선언이 최종 상태이고 수정될 수 없음을 뜻함

  • 필드 : 초기값 설정 후 더 이상 값을 변경할 수 없음
  • 클래스 : 최종적인 클래스이므로 상속할 수 없음
    • public final class 클래스 { ... }
  • 메소드 : 최종적인 메소드이므로 재정의할 수 없음
    • public final 리턴타입 메소드([매개변수, ...]) { ... }

 

🧊 타입 변환과 다형성

다형성은 사용 방법은 동일하지만 다양한 객체를 이용해서 다양한 실행결과가 나오도록 하는 성질

  • 메소드 재정의 + 타입 변환 -> 다형성

 

[1] 클래스의 자동 타입 변환

타입 변환은 타입을 다른 타입으로 변환하는 행위

  • 클래스의 변환은 상속 관계에 있는 클래스 사이에서 발생
  • 자식은 부모 타입으로 자동 타입 변환 가능

자동 타입 변환(promotion)은 프로그램 실행 도중에 자동적으로 타입 변환이 일어나는 것

  • 부모타입 변수 = 자식타입;
class Animal { ... }

class Cat extends Animal { ... }

Cat cat = new Cat();
Animal animal = cat;

// 가능
Animal animal = new Cat();
  • Cat 클래스로부터 Cat 객체를 생성하고 이것을 Animal 변수에 대입하면 자동 타입 변환
  • 바로 위의 부모가 아니더라도 상속 계층에서 상위 타입이라면 자동 타입 변환이 가능
  • 부모 타입으로 자동 타입 변환된 이후에는 부모 클래스에 선언된 필드와 메소드만 접근 가능

하지만 메소드가 자식 클래스에서 재정의되었다면 자식 클래스의 메소드가 대신 호출

 

[2] 필드의 다형성

필드의 타입을 부모 타입으로 선언하면 다양한 자식 객체들이 저장될 수 있기 때문에 필드 사용 결과가 달라질 수 있음

  • 자식 클래스는 부모가 가지고 있는 필드와 메소드를 가지고 있으니 사용 방법이 동일
  • 부모의 메소드를 재정의해서 메소드의 실행 내용을 변경함으로써 더 우수한 실행 결과가 나오게 할수 있음
  • 자식 타입을 부모 타입으로 변환 가능
class Car {
    // 필드
    Tire frontLeftTire = new Tire();
    Tire frontRightTire = new Tire();
    Tire backLeftTire = new Tire();
    Tire backRightTire = new Tire();
    
    // 메소드
    void run() { ... }
}

Car myCar = new Car();
myCar.frontRightTire = new HankookTire();
myCar.backLeftTire = new KumhoTire();
myCar.run();

 

[3] 매개변수의 다형성

자동 타입 변환은 필드의 값을 대입할 때에도 발생하지만, 주로 메소들르 호출할 때 많이 발생

  • 메소드를 호출할 때에는 매개 변수의 타입과 동일한 매개값을 지정하는 것이 정석이지만
  • 매개값을 다양화하기 위해 매개 변수에 자식 객체 지정 가능
class Driver {
	void drive(Vehicle vehicle) {
    	vehicle.run();
    }
}

Driver driver = new Driver();
Vehicle vehicle = new Vehicle();
driver.drive(vehicle);

// Vehicle의 자식 클래스인 Bus 객체를 drive() 메소드의 매개값으로 넘겨줌
Driver driver = new Driver();
Bus bus = new Bus();
driver.drive(bus);  // 자동 타입 변환 발생 -> Vehicle vehicle = bus;

 

[4] 강제 타입 변환(casting)

부모 타입을 자식 타입으로 변환하는 것

  • 자식 타입이 부모 타입으로 자동 타입 변환한 후 다시 자식 타입으로 변환할 때 강제 타입 변환 사용 가능
자식타입 변수 = (자식타입) 부모타입;

// example
Parent parent = new child();  // 자동 타입 변환
Child child = (Child) parent;  // 강제 타입 변환

자식 타입이 부모 타입으로 자동 타입 변환하면, 부모에 선언된 필드와 메소드만 사용 가능하기 때문에, 자식에 선언된 필드와 메소드를 사용하기 위해 강제 타입 변환

 

[5] 객체 타입 확인

객체가 어떤 인스턴스인지 확인하기 위해 instanceof 연산자 사용

  • 좌항의 객체가 우항의 인스턴스이면 true, 그렇지 않으면 false 리턴
public void method(Parent parent) {
	if(parent instanceof Child) {
   		Child child = (Child) parent;
    }
}
  • 타입을 확인하지 않고 강 타입 변환을 시도하면 ClassCastException 발생

 

🧊 추상 클래스

추상 클래스는 실체 클래스들의 공통적인 특성을 추출해서 선언한 클래스

  • 실체 클래스 : 객체를 직접 생성할 수 있는 클래스
  • 추상 클래스와 실체 클래스는 상속의 관계(추상 클래스 - 부모, 실체 클래스 - 자식)

 

[1] 추상 클래스의 용도

  • 공통된 필드와 메소드의 이름을 통일할 목적
    • 실체 클래스를 설계한느 사람에 따라, 필드와 메소드의 이름이 다를 수 있음
  • 실체 클래스를 작성할 때 시간 절약
    • 공통적인 필드와 메소드는 추상 클래스에 모두 선언하고, 다른 점만 실체 클래스에 선언

 

[2] 추상 클래스 선언

클래스 선언에 abstract 키워드를 붙여서 선언하고, new 연산자를 이용해서 객체를 만들지 못함

  • 직접 생성자를 호출할 수는 없지만, 자식 객체가 생성될 때 super(...)를 호출해서 추상 클래스 객체를 생성하므로 추상 클래스도 생성자가 반드시 있어야 함
public abstract class 클래스 {
  // 필드
  // 생성자
  // 메소드
}

객체를 직접 생성 불가능

Animal animal = new Animal();

 

[3] 추상 메소드와 재정의

메소드의 선언만 통일하고, 실행 내용은 실체 클래스마다 달라야 하는 경우, 추상 메소드 선언

  • 메소드의 선언부만 있고, 실행 내용인 중괄호 {} 가 없는 메소드
  • 자식 클래스가 추상 메소드를 재정의하지 않으면, 컴파일 에러 발생
[public | protected] abstract 리턴타입 메소드이름(매개변수, ...);

sound() 메소드를 추상 메소드로 선언

public abstract class Animal {
	public String kind;
   
    public abstract void sound();
}

public class Dog extends Animal {
	public Dog() {
    	this.kind = "포유류";
    }
    
    @Override 
    public void sound() {
    	System.out.println("멍멍");
    }
}

0개의 댓글