💡 상속을 통해 유지 보수가 쉽고, 중복을 방지하며, 프로그램의 수정 및 추가가 유연한 프로그램을 만들 수 있다.
부모 클래스
는 추상적
이고 자식 클래스
는 구체적
이어야 한다.class 자식 클래스명 extends 부모 클래스명 { ... }
class A {
...
}
class B extends A {
...
}
💡 클래스 상속의 활용과 이점을 예제를 통해 알아보자.
setSpeed()
, String을 반환하는 drive()
메서드가 포함되어야 한다.시속 100Km로 승용차를 운전합니다.
시속 80Km로 화물차를 운전합니다.
시속 60Km로 버스를 운전합니다.
class Car {
private int speed;
private String type;
public Car () {
setSpeed(100);
type = "승용차";
}
public void setSpeed(int speed) {
this.speed = speed;
}
public String drive() {
return "시속 " + speed + "Km로 " + type + "를 운전합니다.";
}
}
class Truck {
private int speed;
private String type;
public Truck () {
setSpeed(80);
type = "화물차";
}
public void setSpeed(int speed) {
this.speed = speed;
}
public String drive() {
return "시속 " + speed + "Km로 " + type + "를 운전합니다.";
}
}
class Bus {
private int speed;
private String type;
public Bus () {
setSpeed(60);
type = "버스";
}
public void setSpeed(int speed) {
this.speed = speed;
}
public String drive() {
return "시속 " + speed + "Km로 " + type + "를 운전합니다.";
}
}
public class VehicleTest2 {
public static void main(String[] args) {
Car myCar = new Car();
Truck myTruck = new Truck();
Bus myBus = new Bus();
System.out.println(myCar.drive());
System.out.println(myTruck.drive());
System.out.println(myBus.drive());
}
}
class Vehicle{
protected int speed;
protected String type;
public Vehicle () {}
public void setSpeed(int speed) {
this.speed = speed;
}
public String drive() {
return "시속 " + speed + "Km로 " + type + "를 운전합니다.";
}
}
class Car extends Vehicle {
public Car () {
speed = 100;
type = "승용차";
}
}
class Truck extends Vehicle {
public Truck () {
speed = 80;
type = "화물차";
}
}
class Bus extends Vehicle {
public Bus () {
speed = 60;
type = "버스";
}
}
public class VehicleTest {
public static void main(String[] args) {
Car myCar = new Car();
Truck myTruck = new Truck();
Bus myBus = new Bus();
System.out.println(myCar.drive());
System.out.println(myTruck.drive());
System.out.println(myBus.drive());
}
}
💡 상속은 다양한 장점을 갖는다.
super
예약어를 통해 부모 클래스의 필드와 메서드를 쉽게 활용할 수 있다.super();
를 사용public class Parent{
public Parent() { // 기본 생성자: 파라미터가 없는 생성자(NoAurgument Constructor)
System.out.println("부모 생성자");
}
}
public class Child extends Parent{
public Parent(){
super(); //명시적으로 생성자를 호출함
System.out.println("부모 생성자");
}
}
public class Parent{
public Parent() {
System.out.println("부모 생성자");
}
}
public class Child extends Parent{
public Child(){
// super(); 묵시적으로 기본 생성자를 호출함, 컴파일러가 default로 호출
System.out.println("부모 생성자");
}
}
public class Parent{
private int age;
public Parent(int age) {
this.age = age;
System.out.println("부모 생성자, 나이: " + this.age);
}
}
public class Child extends Parent{
public Child(){
/* 컴파일 에러 발생 */
// super(); defatul로 기본생성자 호출되는대 부모클래스에 기본생성자가 없기 때문
System.out.println("부모 생성자");
}
}
따라서 코드를 리팩토링(refactoring)하면 다음과 같습니다.
public class Child extends Parent{
public Child(){
/* 컴파일 에러 해결 */
super(50);
System.out.println("부모 생성자");
}
}
함수의
파라미터를 달리해서
함수를 작성하는 것이고
대표적인 예로System.out.println()
이 있습니다.
→ 즉, 매개변수의 갯수
나 데이터 타입
은 다르고, 함수명은 동일하게
메서드를 정의하는 것
class Calculator {
// 예 1
int sum(int x, int y){
return x + y;
}
// 예2 2
double sum(double x, double, y){
return x + y;
}
// 예 3 → error
int sum(int x, double y){
return x + (int)y;
}
// 예 4 → error
double sum(int x, double y){
return x + y;
}
}
이름
이 같아야 한다.갯수
나 데이터 타입
이 달라야 한다.리턴타입이 다른 경우
, 오버로딩이 성립하지 않는다.성립되지 않는다.
💡 자식클래스가 부모클래스의 메소드를 자신의 필요에 맞추어 재정의한다.
단, "함수 바디"만 달리 하여야한다.
class Shape{ public void draw() { System.out.println("Shape"); } }
class Circle extends Shape {
@Override
public void draw() { System.out.println("Circle을 그립니다."); }
}
class Rectangle extends Shape {
@Override
public void draw() { System.out.println("Rectangle을 그립니다."); }
}
class Triangle extends Shape {
@Override
public void draw() { System.out.println("Triangle을 그립니다."); }
}
Shape
클래스를 부모로 가지는 Circle
, Rectangle
, Triangle
클래스들은 Shape의 draw()
메서드를 활용가능하지만, 위의 표기처럼 같은 이름, 같은 매개변수와 결과 값을 가진 새로운 메소드로 재정의 가능하며 자식을 객체로 불러왔을 때, 오버라이딩
된 자식의 메소드로 사용된다.
다형성 = 상속 + 오버라이딩
객체 지향
원리이다.class Shape{
public void draw() {
System.out.println("도형을 그립니다.");
}
}
class Rectangle extends Shape{
@Override
public void draw() {
System.out.println("사각형을 그립니다.");
}
public void onlyRecFunc(){
System.out.println("오직 사각형클래스의 함수입니다.");
}
}
class Triangle extends Shape {
@Override
public void draw() {
System.out.println("삼각형을 그립니다.");
}
}
public class ShapeTest {
public static void main(String[] args) {
// 1번
Shape shape = new Shape();
shape.draw();
// 2번
Rectangle rectangle = new Rectangle();
rectangle.draw();
// 3번
Triangle triangle = new Triangle();
triangle.draw();
}
}
이와 같이 상속 관계인 상위 클래스의 함수, 변수를 덮어쓰는 것이 다형성이다.
Shape s = new Rectangle(); // (Shape) new Rectangle();
💡 캐스팅 : 형변환을 의미
사전적 의미
- 업캐스팅 (UpCasting) : 자손타입의 참조변수를 조상타입의 참조변수로 변환하는 것
- 다운캐스팅 (DownCasting) : 조상타입의 참조변수를 자손타입의 참조변수로 변환하는 것
일반적 의미
- 업캐스팅: 다형성을 적용한 것
- 다운캐스팅: 다형성 적용으로 잃어버린 특성을 복구시키기 위해 원래 상태로 되돌리는 것
앞선 Shape-Rectangle 관계를 표현한 코드에서
Shape s = new Rectangle();
/* 컴파일 에러 */
s.onlyRecFunc(); // Rectangle클래스에만 선언된 함수인 onlyRecFunc()호출 불가!
// -> 다형성 적용으로 인해 Rectangle클래스의 특성잃어버림
Shape s = new Rectangle();
/* 컴파일 에러해결 */
Reactangle r = (Rectangle) s;
r.onlyRecFunc(); // Rectangle클래스에만 선언된 함수인 onlyRecFunc() 호출 가능!
// -> 다운캐스팅을 통해 잃어버린 Rectangle클래스의 특성 복구 완료!
package Book;
public class Book {
private String name;
private String publisher;
// 기본생성자
public Book(){
this.name = "";
this.publisher = "";
}
// 파라미터 필요로하는 생성자
public Book(String name, String publisher){
this.name = name;
this.publisher = publisher;
}
public void print(){
System.out.println("print : Book");
};
}
class Novel extends Book{
private String name;
private String publisher;
public Novel(String name, String publisher){
super(name, publisher); // Book(String name, String publisher) 호출
}
@Override
public void print(){
System.out.println("print : Novel"); // Body만 변경
}
}
업캐스팅(자식클래스 객체를 부모 클래스로 형변환)하여 객체 선언(부모 = 자식;)
Book b = new Novel("메타버스 소설", "출판사(IT)"); // (Book) new Novel("메타버스 소설", "출판사(IT)")
부모클래스(Book)으로 생성된 객체의 멤버 함수를 호출
Book b = new Novel("소설","소설출판사");
b.print(); // 출력=> print : Novel
부모 클래스를 참조 변수로 하여 자식 클래스 객체 생성한 것을 가리킬 수 있다.
다음 그림은 업캐스팅 시 스택(좌), 힙(우) 메모리 영역을 보여준다.
참조변수 a 가 가리키고 있는 위치에서 부모 클래스(A)에 할당된 영역만큼 접근할 수 있다.
그러니 그 외의 부분 B에 종속된 함수(printB()
)를 사용할 수 없는 것이다.
데이터 타입이 B 클래스이고, 만약, B의 기능(함수)을 사용하려고 하는데
참조변수인 b가 참조하는 곳에는 클래스 A의 멤버(필드, 메서드) 밖에 없기 때문에 추후 b에서 클래스 B에만 할당된 맴버에 접근하는 것을 미연에 방지하고자 컴파일 에러를 발생시킵니다.
현재 힙 영역에 클래스 A의 맴버만 올라가 있는 이유는
new A();
했기 때문이다.
다운캐스팅
이 가능합니다. A a = new B();
B b = (A) a;