본문 바로가기

Language/JAVA

[JAVA] 상속

상속의 개요


상속은 객체지향 언어의 장점인 모듈의 재사용과 코드의 간결성을 제공하는 중요한 특성이다.

자바에서는 클래스 선언 시 상위 클래스(super class)를 지정하기 위해 확장을 의미하는 extends라는 예약어를 사용한다. 자바 프로그램에서 클래스의 상속은 상위 클래스의 모든 요소를 상속받고 추가 요소를 더 가지는 확장의 개념이다.

 

 

 

멤버 변수의 상속


클래스가 상속되면 상위 클래스의 멤버 변수들은 접근 한정자에 따라 상속 여부가 결정된다. protected 접근 한정자는 같은 패키지 내의 클래스와 같은 패키지는 아니지만 상속된 클래스에서 사용 가능한 접근 한정자이다.

결론적으로 멤버 변수의 접근 한정자는 다음과 같이 설명될 수 있다.

✔ public : 동일한 패키지인지, 상속관계인지에 상관없이 모든 클래스에서 사용 가능

✔ 한정자 없음 : 동일한 패키지이면 상속여부에 상관없이 사용 가능

✔ protected : 동일한 패키지이면 상속여부에 상관없이 사용 가능, 다른 패키지이면 상속되었을때 사용 가능

✔ private : 어떠한 경우에도 사용 불가능. 클래스 내부에서만 사용 가능

 

 

 

메소드 상속과 오버라이딩 (overriding)


오버로딩(overloading)은 같은 클래스내에서 같은 이름의 생성자나 메소드를 사용하는 경우이다.

오버라이딩(overriding)은 상속관계에 있는 클래스들 간에 같은 이름의 메소드를 정의하는 경우이다. 오버라이딩은 상위클래스의 메소드와 하위클래스의 메소드가 메소드이름, 매개변수 타입과 개수까지도 같아야한다.

자바에서 오버로딩을 사용하는 이유는 다형성을 제공하기 위함이다. 오버로딩은 같은 클래스 내에서 같은 이름의 메소드를 정의하여 다형성을 지원하고, 오버라이딩은 상속관계에 있는 상위 클래스와 하위 클래스에서 같은 이름의 메소드를 정의하여 다형성을 지원한다.

기존의 클래스를 이용하여 새로운 클래스를 만들 때, 기존 클래스의 메소드와 의미적으로는 같지만 구현에서 약간의 변화가 필요하다면 메소드 오버라이딩을 이용한다.

class AAA {
    void show() {
        System.out.println("상위 클래스의 메소드 수행");
    }
}
class BBB extends AAA{
    void show() {
        System.out.println("하위 클래스의 메소드 수행");
    }
}
public class OverridingTest {
    public static void main(String args[]) {
        BBB over = new BBB();
        over.show();
    }
}
/*
출력결과
하위 클래스의 메소드 수행
*/

상속관계의 클래스에서 메소드가 오버라이딩 되었다면 상위 클래스의 메소드가 하위 클래스에 의해 가려지게 된다. 하위 클래스의 객체에서 상위 클래스에서 오버라이딩된 메소드를 사용하려면 예약어 super를 이용해야 한다.

class AAA {
    void show(String str) {
        System.out.println("상위 클래스의 메소드 수행 " + str);
    }
}
class BBB extends AAA{
    void show() {
        System.out.println("하위 클래스의 메소드 수행");
    }
}
public class OverridingTest {
    public static void main(String args[]) {
        BBB over = new BBB();
        over.show("자바");
        over.show();
    }
}
/*
출력결과
상위 클래스의 메소드 수행 자바
하위 클래스의 메소드 수행
*/

오버라이딩이 되지 않는 예시이다. 메소드 이름은 같지만 매개변수의 개수와 타입이 달라서 오버라이딩이 되지 않는다. 하위클래스의 입장에서 보면 메소드가 오버로딩된 것으로 볼 수 있다.

 

 

 

예약어 super


super는 두가지 형태로 사용된다.

✔ 하위 클래스에 의해 가려진 상위 클래스의 멤버 변수나 메소드에 접근할 때 사용한다.

✔ 상위 클래스의 생성자를 호출하기 위해 사용된다.

상위클래스의 멤버 변수나 메소드에 사용할 때는 다음과 같이 사용한다.

super.객체변수
super.메소드이름

 

class AAA {
    int x = 1000;
    void display() {
        System.out.println("상위 클래스의 메소드 수행");
    }
}
class BBB extends AAA{
    int x = 2000;
    void display() {
        System.out.println("하위 클래스의 메소드 수행");
    }
    void write() {
        display();
        super.display();
        System.out.println("BBB 클래스의 x값 = " + x);
        System.out.println("AAA 클래스의 x값 = " + super.x);
    }
}
public class SuperTest {
    public static void main(String args[]) {
        BBB sp = new BBB();
        sp.write();
    }
}
/*
출력결과
하위 클래스의 메소드 수행
상위 클래스의 메소드 수행
BBB 클래스의 x값 = 2000
AAA 클래스의 x값 = 1000
*/

예약어 super를 이용하여 상위 클래스의 메소드와 객체변수를 사용한다.

 

 

 

상속과 생성자


클래스에서 생성자는 객체가 생성될 때 초기화 역할을 수행한다고 하였다. 생성자 중에 매개변수가 없는 생성자를 묵시적(default) 생성자라 한다.

클래스가 상속관계에 있을 때 각 클래스들이 묵시적 생성자를 모두 가지고 있다면, 하위 클래스에서 객체가 생성될 때 상위 클래스의 묵시적 생성자가 하위클래스보다 먼저 수행된다.

매개변수가 있는 생성자의 경우에는 예약어 super를 사용해서 상위 클래스의 생성자를 명시적으로 호출해주어야 한다.

 👉 상위 클래스의 특정 생성자를 호출하는 super 문장은 반드시 생성자 부분의 첫번째 라인에 위치해야 하며, 이것은 상위 클래스의 생성자가 하위 클래스 생성자보다 먼저 수행되어야 함을 의미한다.

class A1 {
    double d1;
    A1() {
        System.out.println("클래스 A1의 생성자 수행");
        d1 = 10 * 10;
    }
}
class A2 extends A1 {
    double d2;
    A2() {
        System.out.println("클래스 A2의 생성자 수행");
        d2 = 10 * 10 * 10;
    }
}
class A3 extends A2 {
    double d3;
    A3() {
        System.out.println("클래스 A3의 생성자 수행");
        d3 = 10 * 10 * 10 * 10;
    }
}
class SuperTest2 {
    public static void main(String args[]) {
        A3 t = new A3();
        System.out.println("10의 2제곱 : " + t.d1);
        System.out.println("10의 3제곱 : " + t.d2);
        System.out.println("10의 4제곱 : " + t.d3);
    }
}
/*
출력결과
클래스 A1의 생성자 수행
클래스 A2의 생성자 수행
클래스 A3의 생성자 수행
10의 2제곱 : 100.0
10의 3제곱 : 1000.0
10의 4제곱 : 10000.0
*/

최하위 클래스에서 객체를 생성했고, 상위 클래스의 묵시적 생성자들이 먼저 수행됐다.

 

package ex;

class A1 {
    int d1;
    int s;
    A1(int s1) {
        System.out.println("클래스 A1의 생성자 수행");
        s = s1;
        d1 = s * s;
    }
}
class A2 extends A1 {
    int d2;
    int t;
    A2(int s1, int t1) {
    	super(s1);
        System.out.println("클래스 A2의 생성자 수행");
        t = t1;
        d2 = t * t;
    }
}
class SuperTest2 {
    public static void main(String args[]) {
        A2 t = new A2(10, 20);
        System.out.println("10의 제곱 : " + t.d1);
        System.out.println("20의 제곱 : " + t.d2);
    }
}
/*
출력결과
클래스 A1의 생성자 수행
클래스 A2의 생성자 수행
10의 제곱 : 100
20의 제곱 : 400
*/

매개변수를 가진 생성자를 가진 클래스에서 상위 클래스의 생성자를 호출한다. 상위 클래스의 생성자를 호출하는 super()는 항상 하위 클래스 생성자의 첫번째 라인에 써줘야한다.

 

 

 

객체의 형변환


자바는 클래스 계층 구조에서 상속관계의 클래스로부터 생성된 객체 사이의 형변환을 허용한다.

class Parent {
	String name = "부모";
	void print(){
		System.out.println("Parent method 호출");
	}
}
class Child extends Parent{
	String name = "자식";
	void print(){
		System.out.println("Child method 호출");
	}
}
public class CastingTest {
    public static void main(String[] args) {
    	Parent p = new Child();
    	p.print();
    	System.out.println(p.name);
    }
}
/*
출력결과
Child method 호출
부모
*/

상위 클래스 객체변수를 하위클래스의 객체로 형변환했다. 객체 p의 print메소드는 오버라이딩 되어서 하위 클래스를 호출하지만 name 변수는 하위 클래스에 접근 불가능하다.

✔ 상위 클래스 = 하위 클래스 ⭕형변환가능

✔ 하위 클래스 = 상위 클래스 ❌형변환불가능

하위 클래스는 상위 클래스의 확장개념이므로 하위 클래스를 상위 클래스로 변환하는 것은 불가능하다.

 

 

 

추상 클래스와 추상 메소드


추상 클래스는 하위 클래스에서 구현되는 추상적인 기능만을 정의하는 클래스이다. 추상 클래스에서 정의된 추상적인 기능은 하위 클래스에서 구현된다.

추상 클래스는 기능이 무엇(what)인지만 정의하고 어떻게(how) 구현되는지는 정의하지 않는다. 추상 클래스에서 선언된 기능이 하위 클래스에서 구현되므로, 하나의 추상 클래스에 정의된 여러개의 하위 클래스에서 서로 다른 형태로 구현하여 사용할 수 있다.

추상 메소드는 추상 클래스 내에 정의되는 메소드로써 선언 부분만 있고 구현 부분이 없는 메소드이다.({ }로 된 바디가 없는 경우) 추상 클래스를 상위 클래스로 하는 하위 클래스에서는 상위 클래스의 추상 메소드를 서로 다른 방법으로 구현하여 사용할 수 있다. 즉 하위 클래스에서는 상위 클래스의 추상 메소드를 오버라이딩하여 사용한다.

추상 클래스는 최소한 하나 이상의 추상 메소드를 가져야 하며, 추상 클래스로부터는 직접 객체가 생성될 수 없다. 왜냐하면 추상 메소드는 구현부분이 없는 메소드이기 때문이다.

abstract class Shape {
	abstract void draw();
	abstract void computeArea(double a, double b);
}
class Circle extends Shape {
	void draw() {
		System.out.println("원을 그리는 기능");
	}
	void computeArea(double r1, double r2) {
		System.out.println("원의 넓이 : " + (3.14 * r1 * r2));
	}
}
class Rectangle extends Shape {
	void draw() {
		System.out.println("사각형을 그리는 기능 ");
	}
	void computeArea(double h, double v) {
		System.out.println("사각형의 넓이 : " + (h * v));
	}
}
public class AbstractTest {
	public static void main(String[] args) {
		Circle c = new Circle();
		c.draw();
		c.computeArea(5.0,  5.0);
		Rectangle r = new Rectangle();
		r.draw();
		r.computeArea(5.0, 10.0);
		System.out.println("==객체 형변환과 오버라이딩을 이용==");
		Shape s = new Circle();
		s.draw();
		s.computeArea(5.0, 5.0);
		s = new Rectangle();
		s.draw();
		s.computeArea(5.0, 10.0);
	}
}
/*
출력결과
원을 그리는 기능
원의 넓이 : 78.5
사각형을 그리는 기능 
사각형의 넓이 : 50.0
==객체 형변환과 오버라이딩을 이용==
원을 그리는 기능
원의 넓이 : 78.5
사각형을 그리는 기능 
사각형의 넓이 : 50.0
*/

서로 다른 객체에 같은 이름의 메소드를 사용했다.

객체의 형변환을 이용하여 동일한 메소드에 대해 서로 다른 결과를 출력했다.

 

'Language > JAVA' 카테고리의 다른 글

자바언어의 구조와 기본문법  (0) 2021.01.02
[JAVA] 클래스의 기능  (0) 2020.06.15
[JAVA] 객체지향 개념  (0) 2020.06.15