벌써 50%나 달성했다. 2025년 2월이기도 하니까 1월을 알차게 보냈다는 뜻이기도 하겠지…!
학교 동아리에서 스프링부트 스터디를 진행하면서 코멘트를 달아주는데, 이 수많은 사람들의 글을 하나하나 다 읽고 코멘트를 다는 일이 얼마나 대단한 일인지 다시 한 번 느꼈다. (존경합니다 족장님 괜히 “족장”이라는 이름이 아니에요 정말)
그리고 다음 혼공학습단에 지원하시는 분이 참고하면 좋은 점인데, 가끔 아래와 같은 이벤트를 한다. 이번에는 돌려돌려 돌림판 이벤트로, 도서를 추첨하여 증정해준다! 한빛미디어 sns를 팔로우하며 새로 나온 책들을 자주 보는 편인데, 이번 이벤트로 증정하는 책은 바로바로 CS 지식의 정수이자 개발자들의 친구(?) 알고리즘이다.
당첨이 되지 않아도 알고리즘 책은 하나 구매할 예정이다. 지난해 자료구조와 알고리즘 수업을 학습하고 아직 정리가 덜 된 느낌이다. 물론 이번 학기에 듣는 운영체제도 마찬가지로 구매해야겠지만..!
아래는 이번 4주차에 해당하는 상속에 관한 내용과 숙제이다.
01 상속
자바에서는 부모 클래스에서 멤버를 자식 클래스로 물려줄 수 있다.
멤버를 상속한다면 기존의 코드를 재사용하므로 중복을 줄일 수 있다.
클래스 상속
extends
이후 부모 클래스를 작성해 상속받는다.
class 자식클래스 extends 부모클래스 {
}
특징
- 하나의 부모 클래스에서만 상속받을 수 있음 (단일 상속)
- 부모 클래스에서 private 접근 제한자가 붙은 멤버는 상속받을 수 없음 - 다른 패키지에 존재하는 경우, default 접근 제한자가 붙은 멤버를 상속받을 수 없음
부모 생성자 호출
부모 객체가 생성된 후 자식 객체를 생성할 수 있다.
따라서 자식 객체의 생성자를 정의하지 않는다면, 아래 코드와 같은 기본 생성자가 자동으로 만들어진다.
public 자식클래스() {
super();
}
super()
는 부모의 기본 생성자를 호출한다. 따라서 부모 객체를 생성하고 자식 클래스가 만들지는 것을 알 수 있다. 자동으로 만들어지는 super()는 부모 클래스의 기본 생성자를 호출하므로, 부모 클래스의 기본 생성자가 없는 경우
에는 super()에 알맞은 매개 변수를 넣어 명시적으로 호출해야한다.
또한, super()을 사용할 때에는 자식 클래스의 가장 첫 줄에 작성한다.
메소드 overriding
메소드 오버라이딩은 메소드를 재정의하는 것으로, 부모 클래스의 메소드를 자식 클래스에 맞게 수정하는 것이다.
다만, 다음과 같은 규칙을 지켜야한다.
- 부모 메소드와 동일한 시그니처(리턴 타입, 메소드 이름, 매개 변수 목록)
- 접근 제한을 더 강하게 정할 수 없음
- 새로운 예외를 throw할 수 없음
접근 제한자를 강하게/약하게 설정하기
만약 접근 제한자가 public에서 private으로 변경되면 강하게 설정된 것이고, 그 반대라면 약하게 설정된 것이다.
접근 제한자의 순서는 public < protected < default < private
재정의 방법
메소드 위에 @Override
를 적어 메소드 재정의임을 명시하고, 아래 메소드를 작성한다.
부모 클래스 메소드 호출
메소드 재정의 이후 부모 클래스의 메소드를 호출하려면, super.부모메소드()
를 사용한다.
final 클래스와 final 메소드
필드에 final 키워드가 붙으면 수정이 불가능함
을,
클래스에 final 키워드가 붙으면 상속할 수 없음
을,
메소드에 final 키워드가 붙으면 재정의할 수 없음
을 나타낸다.
예를 들어
public final class Car{}
라는 클래스가 있다면, 해당 클래스는 부모 클래스가 될 수 없다.
public final int add(매개변수)
라는 메소드가 있다면, 해당 메소드는 자식 클래스에서 재정의될 수 없다.
protected 접근 제한자
protected 접근 제한자는 public과 default 사이에 위치한다. 다른 패키지일 때, 자식 클래스만 접근 가능하다.
다른 접근 제한자와 같이 필드, 메소드, 생성자에 붙여 사용할 수 있다.
02 타입 변환과 다형성
자바의 클래스는 타입 변환이 가능하다. 앞의 재정의와 타입 변환으로 다형성을 구현
할 수 있다.
다형성이란, 사용 방법은 동일하지만 다양한 객체를 이용해서 다양한 실행결과가 나오도록 하는 성질이다. 쉽게 설명하면, 다양한 형태를 가질 수 있는 특성이다. 이를 구현하기 위해 부모 타입으로의 자동 타입 변환을 학습해야 한다.
자동 타입 변환
클래스의 타입 변환은 상속 관계에 있는 클래스 사이에서 발생한다.
자식 클래스는 부모 클래스로 자동 타입 변환이 가능하다. 자식 클래스 객체를 부모 클래스 타입의 변수에 대입할 수 있는데. 이때, 자식 객체는 부모 객체의 타입으로 변환된다.
자동 타입 변환이 일어나는 상황은 다음과 같다.
부모타입 변수 = 자식타입;
Animal animal = new Cat(); // Cat 객체가 Animal 타입으로 자동 변환
/* 아래 코드도 가능하다
Cat cat = new Cat();
Animal animal = cat;
*/
직속 부모 객체가 아니더라도 상속 관계라면 상위 타입으로 자동 형변환이 일어날 수 있다.
만약 A-B-C 순으로 상속이 되어있다면, A a = new C();
가 가능하다.
부모 타입으로 자동 형변환이 되면, 부모 클래스의 멤버만 접근
이 가능하다. 다만, 자식 클래스에서 재정의된 메소드의 경우 자식 클래스에서 호출
된다.
필드의 다형성
자동 형변환이(부모 타입으로의 변환이) 필요한 이유는 다형성을 구현하기 위함이다.
필드의 타입을 부모타입으로 선언하면 다양한 자식 객체를 저장할 수 있다.
이렇게 하면 필드 사용의 결과가 달라질 수 있는데, 이것이 ‘필드의 다형성’이다.
예를 들어, 자동차의 타이어를 생각해보면, 낡은 타이어를 빼고 성능이 좋은 새 타이어를 갈아 끼울 수 있는데, 여러 타이어를 넣을 수 있게 자동차를 설계했기 때문에 타이어를 갈아 끼우기 좋다.
OOP에서도 마찬가지로 객체를 다른 객체로 쉽게 교체할 수 있도록 다형성을 구현한다.
class Car {
// 필드
Tire tire = new Tire();
// 메소드
void run() {
tire.roll();
}
}
//main
Car myCar = new Car();
myCar.tire = new GoodTire(); // 자동 형변환
myCar.run();
GoodTire로 교체되면, 자동 형변환이 되어 Tire의 멤버만 접근 가능하지만 메소드를 재정의한 경우 재정의한 메소드를 호출할 수 있다.
매개 변수의 다형성
메소드를 호출할 때, 매개값을 다양화하기 위해 매개 변수에 자식 객체를 지정할 수 있다.
//Driver.java
class Driver {
void drive(Vehicle vehicle) {
vehicle.run();
}
}
// main
Driver driver = new Driver();
Vehicle vehicle = new Vehicle();
driver.drive(vehicle); // 정상 호출
// Vehicle을 상속받은 Bus 객체의 변수 bus를 넣는다면 Vehicle로 자동 형변환된다.
driver.drive(bus);
위의 코드에서 bus를 매개변수로 bus를 넣으면 Vehicle로 형변환된다.
매개 변수가 클래스인 경우, 자식 객체까지 매개값으로 사용
할 수 있는 것이다. 또한, 매개값으로 받은 객체에서 메소드를 재정의한 경우, 재정의한 메소드를 호출할 수 있으므로 실행 결과가 다양해진다.(다형성 구현)
강제 타입 변환
강제 타입 변환은 부모 타입을 자식 타입으로 변환
하는 것이다. 모든 부모 타입이 자식 타입이 될 수 있는 것은 아니다. 자식 타입 → 부모 타입으로 자동 형변환 후 다시 자식 타입으로 형변환할 때 강제 형변환을 사용할 수 있다.
한마디로 자동 형변환된 것으로 되돌릴 때 사용하는 것이다.
Parent parent = new Child(); // 자동 타입 변환
Child child = (Child) parent; // 강제 타입 변환
위 코드에서, Parent parent = new Child();
는 Child가 Parent형으로 자동 타입 변환되는 과정이다. 이를 다시 Parent형으로 변한 parent를 다시 Child형으로 변환하기 위해서 (자식타입)
으로 타입을 명시한다.
자동 타입 변환이 되면, 부모 타입의 멤버만 사용가능한데, 다시 자식 타입의 멤버를 사용하기 위해서 강제 형변환을 거친다.
객체 타입 확인
객체가 어떤 클래스의 인스턴스인지 확인하기 위해 (예를 들면 부모 변수가 참조하는 객체가 부모 객체인지 자식 객체인지 확인하기 위해) instanceof
연산자를 사용한다.
객체 instanceof 타입
myCar instanceof Car
객체가 해당 타입이면 true를 아니면 false를 반환한다.
03 추상 클래스
객체를 직접 생성할 수 있는 클래스를 실체 클래스라고 한다. 추상 클래스는 클래스들의 공통점으로 선언한 클래스
다. 추상 클래스와 실체 클래스는 상속 관계를 가진다.
추상 클래스의 용도
추상 클래스는 두 가지 목적이 있다.
- 공통된 필드와 메소드 이름을 통일
-
실체 클래스를 작성할 때 시간 절약
공통된 점을 추상 클래스에 선언하고 다른 점을 실체 클래스에 선언해 시간을 절약할 수 있다. 또한, 추상 클래스를 상속받기 때문에 코드의 중복을 줄일 수 있는 점도 있다.
추상 클래스 선언
추상 클래스에는 abstract
키워드를 붙여야 한다. 추상 클래스는 new로 객체를 생성할 수 없고 오직 상속으로 자식 클래스를 가질 수만 있다.
public abstract class 클래스 {
}
추상 클래스도 자식 클래스에서 super()을 호출하므로 생성자가 필요하다.
추상 메소드와 재정의
자식 클래스에서 특정 메소드를 강제로 선언하도록 만들때, 추상 메소드를 정의한다. 추상 메소드는 abstract 키워드와 함께 선언부만 있고 실행 내용인 {}는 없는 메소드이다.
[public/protected] abstract 리턴타입 메소드이름(매개변수);
예를 들어, 동물은 소리를 내므로 sound라는 추상 메소드를 정의하고, 어떤 소리를 내는지는 하위 클래스에서 정의하도록 만들 수 있다.
숙제 01 클래스 타입 변환
클래스의 타입 변환에는 어떤 것이 있는지 정리하고 공유하기
클래스의 타입 변환에는 2가지가 있다.
- 자동 타입 변환(promotion)
- 강제 타입 변환(casting)
먼저, 자동 타입 변환은 자식 클래스 객체를 부모 클래스 타입의 변수에 대입해서, 부모 객체의 타입으로 변환하는 것이다.
부모타입 변수 = 자식타입;
Animal animal = new Cat();
자동 타입 변환은 다형성을 구현할 수 있도록 하는데, 필드의 다형성과 매개 변수의 다형성 두 가지가 있다. 필드의 다형성은 필드의 타입을 부모타입으로 선언해 다양한 자식 객체를 저장할 수 있도록 하는 것이다. 매개 변수의 다형성은 매개 변수가 클래스인 경우, 자식 객체까지 매개값으로 사용할 수 있는 것이다. 두가지 모두 사용 결과를 다양하게 만들어 준다는데 의미가 있다.
두 번째로, 강제 타입 변환은 자동 타입 변환으로 부모 타입이 된 자식 객체를 다시 자식 타입으로 되돌리는 것이다. 이렇게 하면 자동 타입 변환으로 인해 사용할 수 없던 자식 클래스의 멤버를 사용할 수 있도록 할 수 있다.
Parent parent = new Child(); // 자동 타입 변환
Child child = (Child) parent; // 강제 타입 변환
숙제 02 추상 클래스 문제 풀이
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();
}
}
풀이
HttpServletExample에서, method()는 매개 변수에 자식 클래스를 넣어 HttpServlet으로 자동 타입 변환이 된다. 따라서 LoginServlet과 FileDownloadServlet는 service() 메소드를 갖는 HttpServlet를 상속받아 service() 메소드를 재정의해야 한다.
HttpServlet 클래스는 추상 클래스로, 상속을 받기 위해 extends를 사용한다. 또한, 메소드인 service도 추상 메소드이므로 @Override로 메소드를 재정의한다. 아래는 코드와 실행 결과이다.
먼저 LoginServlet.java에서 클래스를 선언한다.
public class LoginServlet extends HttpServlet {
@Override
public void service(){
System.out.println("로그인합니다.");
}
}
마찬가지로 FileDownloadServlet.java에서 클래스를 선언한다.
public class FileDownloadServlet extends HttpServlet {
@Override
public void service() {
System.out.println("파일 다운로드합니다.");
}
}
마지막으로 두 클래스의 객체를 선언해 메소드의 매개값으로 넣는 HttpServletExample를 수정한다.
public class HttpServletExample {
public static void main(String[] args) {
// 매개 변수에 자식 클래스를 넣어 HttpServlet으로 자동 타입 변환이 된다.
method(new LoginServlet()); // 자식 클래스에서 service()를 재정의해 로그인합니다라고 출력한다.
method(new FileDownloadServlet()); // 자식 클래스에서 service()를 재정의해 파일 다운로드합니다라고 출력한다.
}
public static void method(HttpServlet servlet) {
servlet.service();
}
}
실행 결과는 다음과 같다.