0. 학습할 것
- 자바 상속의 특징
- super 키워드
- 메소드 오버라이딩
- 다이나믹 메소드 디스패치 (Dynamic Method Dispatch)
- 추상 클래스
- final 키워드
- Object 클래스
1. 자바 상속의 특징
1-1. 상속
기존의 클래스를 재사용하여 새로운 클래스를 작성하는 것 이다.
즉, 부모 클래스의 멤버(변수,메서드)를 자식 클래스에게 이관하는 것(물려주는 것) 이다.
이런 상속은 코드의 재사용성을 통해 코드의 간결성을 확보해 준다.
상속 받는 클래스 = 하위클래스 = 자식클래스 = 서브클래스(sub class)
상속 해주는 클래스 = 상위클래스 = 부모클래스 = 슈퍼클래스(super class)
1-2. 상속을 하는방법
extends 키워드를 사용하여 상속한다.
class 자식클래스명 extends 부모클래스 {
// 필드
// 생성자
// 메소드
}
Ex)
// super 부모 클래스
public class Vehicle{
}
// Vehicle 클래스를 상속받은 자식 클래스 Cycle
public class Cycle extends Vehicle{
}
// Vehicle 클래스를 상속받은 자식 클래스 MotorCycle
public class MotorCycle extends Vehicle{
}
// Vehicle 클래스를 상속받은 자식 클래스 Car
public class Car extends Vehicle{
}
1-3. 상속을 사용하는 이유 (장점)
- 유지보수가 쉬워짐
- 확장성이 용이해짐
- 모듈화를 통해 재사용이 가능
- 코드가 간결해짐
- 1,2,3,4의 장점으로 개발시간 단축
아래 예제를 통해 상속을 쓰는 이유를 생각해보자.
EX
(예시는 여기를 참고했습니다. https://jhnyang.tistory.com/73)
예를들어 RPG 온라인게임에 직업 클래스를 만든다고 생각해보자.
class 마법사{ };
class 궁수{ };
class 전사{ };
각 직업은 방향키로 움직이고 점프하고 공격하는 기능이 있다고 생각해보자.
class 마법사{
void move(){}
void jump(){}
void attack() {}
}
이렇게 마법사 직업에 움직이는기능, 점프하는기능, 공격하는 기능을 추가했다고 하자..
그런데 rpg게임은 대부분 직업이 3개뿐이겠는가?
몇십개 직업에 저 기능들을 언제 다 추가할 것인가?
만약 move에 조작키 변경 요청이 들어오거나, 새로운 직업을 추가한다면 위의 기능을 한땀한땀 전부 구현해야 할 것이다..
(아래 접은글은 로스트아크 게임의 직업 종류이다. 이 직업들에 기능을 추가하고 수정한다고 생각해보자.. 답이 없다)
이 비효율적인 방법을 개선해 주는 것이 상속이며 상속을 통해 유지보수를 쉽게 할 수 있다.
class Character{
void move(){ ... }
void jump(){ ... }
void attack(){ ... }
}
class 전사 extends Character { ... }
class 궁수 extends Character { ... }
class 마법사 extends Character { ... }
위 1-2에서 상속의 장점을 언급하였는데 설명하자면
캐릭터의 공통기능을 부모클래스에 정의하고 직업별로 부모클래스 메서드를 상속받았다.
(= move, jump, attack 이란 캐릭터들의 공통 기능들을 상속받음)
여기서 아까말한 모든 직업의 move메서드의 조작키를 변경한다던가, jump메서드의 높이를 변경하고 싶을 때 부모 클래스에서 수정하면 간단하기 때문에 "유지보수가 쉽다" 라고 한 것이다.
각 직업마다 고유 스킬이 존재할 것이다. 추가하려면 다음과 같이 자식클래스에서 메서드만 추가해 주면된다.
class 전사 extends Character {
void 강한베기(){...}
}
class 궁수 extends Character {
void 더블애로우(){...}
}
class 마법사 extends Character {
void 파이어볼() {...}
}
이렇기 때문에 확장성이 용이하다 라고 하는 것이다.
또한 새로운 직업을 추가할 때 부모클래스를 상속받기만 하면 되기 때문에 부모클래스 재사용, 코드가 간결해진다 라고 하는 것이다.
class 전사 extends Character { ... }
class 궁수 extends Character { ... }
class 마법사 extends Character { ... }
class 도적 extends Character { ... }
class 해적 extends Character { ... }
1-4. 상속의 특징
1. 다중상속 불가
자바는 다중 상속을 허용하지 않는다.
다중상속을 허용하면 여러 클래스로부터 상속받아 복합적인 기능을 가는 클래스를 작성할 수 있지만, 클래스 간의 관계가 매우 복잡해진다.
또한 클래스간 서로 같은 이름의 메서드라면 구별할 수 없다는 단점이 있다.
예시를 보자
class CharacterHp1{
void hp() {
int hp = 3000;
}
}
class CharacterHp2{
void hp() {
int hp = 5000;
}
}
class 전사 extends CharacterHp1, CharacterHp2 { ... }
class 궁수 extends CharacterHp1, CharacterHp2 { ... }
class 마법사 extends CharacterHp1, CharacterHp2 { ... }
이런 상황이라면 어느 hp를 상속받는것인지 알 수 없다.
static메서드라면 메서드 이름 앞에 클래스의 이름을 붙여서 구별할 수 있지만, 인스턴스 메서드의 경우 선언부가 같은 두 메서드를 구별할 수 있는 방법은 없다.
자바에서는 다중상속의 이러한 문제점을 해결하기 위해 다중상속의 장점을 포기하고 단일상속만을 허용한다.
2. 부모의 생성자는 상속되지 않는다.
3. 자식 클래스는 부모 클래스가 가진 멤버변수와 메소드를 모두 상속받는다.
4. 부모 클래스 내에서 멤버 변수 또는 메소드가 private 접근 제한자 를 사용하면 멤버변수는 상속받으나 → 바로 접근이 불가능하다. 메소드는 상속되지 않는다.
5. static 메서드 또는 변수도 상속이 된다.
6. 동일한 이름의 변수가 부모 클래스와 자식 클래스에 둘 다 존재할 경우 부모 클래스의 변수는 가려진다.
7. 상속에 대한 횟수를 제한하지 않는다.
8. final 클래스는 상속할 수 없고, final 메소드는 오버라이딩 불가능(final 키워드는 해당 선언이 최종 상태이고, 결코 수정될 수 없음을 의미하여 상속과 오버라이딩이 불가능)
9. 자바의 모든 클래스는 최상위 클래스 Object의 서브클래스이다. (모든 클래스들은 Object 클래스의 자식 클래스이다.) Object 클래스를 상속하도록 코딩하지 않아도 자바에서 만들어지는 모든 클래스는 Object 클래스를 자동으로 상속받게끔 되어 있다.
public class Animal extends Object {
String name;
public void setName(String name) {
this.name = name;
}
}
Object animal = new Animal();
Object dog = new Dog();
2. Super
2-1. super 키워드
자식이 부모 메소드를 호출하거나 인스턴스를 참조할 때 super 키워드를 사용한다.
부모 객체를 참조하고 있기 때문에 부모에 직접 접근 가능하다.
public class Parent {
int num = 10;
public void print() {
System.out.println("부모");
}
}
class Child extends Parent {
int num = 20;
public void print() {
System.out.println("자식");
System.out.println(num); // Child 클래스의 멤버변수 num:10
System.out.println(this.num); // Child 클래스의 멤버변수 num:10
System.out.println(super.num); // Parent 클래스의 멤버변수 num:20
super.print(); // Parent 클래스의 멤버메서드 print()
}
}
2-2. super() 메서드
부모의 기본 생성자를 호출하는 역할 (반드시 자식 생성자의 첫 줄에 위치)
자바는 자식 객체를 생성하면 부모 객체가 먼저 생성되고 자식 객체는 그 다음에 생성되는 구조
반드시 자식 클래스의 생성자 첫 라인에서 부모 생성자를 호출해야 한다.
명시적으로 자식 클래스에서 부모 생성자를 호출하지 않아도 super 키워드를 이용한 생성자 호출이 자동 삽입되어 부모 클래스의 생성자를 호출하게 된다.
public class People { }
public class Student extends People {
public int studentNo;
public Student(int studentNo) {
//super();가 숨겨져 있는 것
this.studentNo = studentNo;
}
}
→ 만약 별도 매개변수를 가진 생성자를 자식 클래스에서 생성했다면 부모 클래스의 생성자를 호출하는 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 Student(String name, String ssn, int studentNo) {
super(name, ssn);
this.studentNo = studentNo;
}
}
3. 메서드 오버라이딩
부모 클래스로부터 상속받은 메서드를 재정의 하는 것을 말한다.
= 상속관계에 있는 클래스간에 같은 이름의 메서드를 정의하는 문법을 오버라이딩이라고 한다.
오버라이딩 어노테이션은 생략할 수도 있다.
자식 클래스는 부모 클래스의 private 멤버를 제외한 모든 메소드를 상속받는다.
3-1. 메서드 오버라이딩의 조건
- 메서드의 이름은 같아야 한다.
- 메서드의 매개변수가 같아야 한다.
- 메서드의 반환타입이 같아야 한다.
- 부모 클래스의 메소드보다 접근 제어자를 더 좁은 범위로 변경할 수 없다.
- 부모 클래스의 메소드보다 더 큰 범위의 예외를 선언할 수 없다.
Ex)
package overriding;
public class ParentClass {
public ParentClass(){
System.out.println("ParentClass Constructor");
}
public void printName(){
System.out.println("ParentClass Name");
}
}
public class ChildClass extends ParentClass{
public ChildClass(){
System.out.println("ChildClass Constructor");
}
public void printName(){
System.out.println("ChildClass Name");
}
}
package overriding;
public class MainClass {
public static void main(String[] args) {
ChildClass childClass = new ChildClass();
childClass.printName();
}
}
/* output
ParentClass Constructor
ChildClass Constructor
ChildClass Name
*/
3-2. @Override애노테이션
컴파일 시점에 정말 오버라이드가 되는 것이 맞는지 check 해주는 역할을 해준다.
개발자의 실수를 줄일 수 있고, 명시적으로 Override를 표현함으로써 가독성에 이점을 가져갈 수 있다.
ex)
public class Parent {
protected int method1(int num1, int num2) {
return num1 / num2;
}
}
class Child extends Parent {
@Override // 오버라이딩 검사
public int method1(int num1, int num2) { // 접근제어자 넓은 범위로 변경
return super.method1(num1*num1, num2*num2); // 부모클래스의 메소드 재정의
}
}
// [출처] 6주차 과제: 상속|작성자 SeJongDeveloper
// https://blog.naver.com/sejonghumble/222183439699
4. 메소드 디스패치 (Method Dispatch)
메소드 디스패치란 어떤 메소드를 호출할지 결정하여 실제로 실행시키는 과정이다.
자바는 런타임 시 객체를 생성하고, 컴파일 시에는 생성할 객체 타입에 대한 정보만 보유한다.
메소드 디스패치에는 정적 메소드 디스패치(Static Method Dispatch), 동적 메소드 디스패치(Dynamic Method Dispatch), 더블 디스패치(Double Dispatch) 세 가지가 존재한다.
Runtime, compile Time?
- Compile Time (컴파일)
Java와 같은 프로그래밍 언어로 작성된 소스코드를
컴퓨터가 인식 할 수 있는 기계어로 변환되어 실행 가능한 프로그램이 되는 과정
ex) java 파일을 javac Main.java 명령어를 사용하면 Main.class 파일이 생성되는 과정
- RunTime (런타임)
컴파일 과정을 마친 응용프로그램이 사용자에 의해 실행되는 때
ex) class 파일을 java Main 명령어를 사용할 때
- Compile Time Error
대표적으로 세미콜론을 쓰지않거나, 키워드의 맞춤법이 틀린 등 문법이 잘못됬을 때 발생하는 Syntax Error가 있다.
IDE를 사용 할 경우 실행 전부터 IDE가 빨간줄을 그어버린다.
- RunTime Error
이미 컴파일이 완료되었으나 의도치 않은 예외 상황으로 발생하는 에러이다.
대표적으로 객체를 생성하지 않고 Null Object를 참조하려고해서 발생하는 NPE (NullPointException),
정수를 0으로 나눗셈을 하면 발생하는 ArithmeticException 등이 있다.
https://leegicheol.github.io/whiteship-live-study/whiteship-live-study-06-Inheritance/
4-1. 스태틱 메소드 디스패치 (Static Method Dispatch)
컴파일 시점에서 메소드를 결정하는 과정이다.
자바의 객체는 Runtime시에 호출되어 생성된다.
즉, 컴파일 시점에 타입에 대한 정보를 알 수 있다.
public class A{
public void print(){
System.out.println("A");
}
}
public class B extends A {
//메소드 오버라이딩 - A를 상속받았으나 함수를 재정의
public void print(){
System.out.println("B");
}
}
public class Test{
public static void main(String[] args){
B b = new B();
b.print(); //B를 출력
}
}
메인 함수에서 b.print()를 호출했을 때 우리는 클래스B의 오버라이딩 된 함수가 불릴 것이라는 것을 알고 있다.
이미 알고 있는 것과 같이 컴파일러 역시도 이 메소드를 호출하고 실행시켜야되는 것을 명확하게 알고 있다.
우리는 이를 정적 메소드 디스패치라 부른다.
대표적으로, 메소드를 오버로딩(Overloading)하면 매개변수 타입과 개수에 따라 어떤 메소드를 호출할지 알 수 있는 경우(메소드 시그니처 변경)
상위 클래스가 있더라도 하위 클래스(구현 클래스)로 선언을 하고 하위 클래스의 인스턴스를 생성
4-2. 다이나믹 메소드 디스패치 (Dynamic Method Dispatch)
컴파일타임에는 알 수 없는 메서드의 의존성을 런타임에 늦게 바인딩 하는것이다.
= 컴파일 시점이 아닌 실행시점(runtime)에서 메소드 호출을 결정하는 경우이다. (컴파일러가 어떤 메서드를 호출해야 되는지 모른다.)
인터페이스를 이용해 호출되는 메서드가 동적으로 정해지는 것
class Character {
public void attack() {
System.out.println("캐릭터가 공격한다.");
}
}
class Wizard extends Character{
@Override
public void attack() {
System.out.println("마법사가 공격한다");
}
}
class Archer extends Character{
@Override
public void attack() {
System.out.println("궁수가 공격한다");
}
}
class Warrior extends Character{
@Override
public void attack() {
System.out.println("전사가 공격한다.");
}
}
public class Main {
public static void main(String[] args) {
Character character1 = new Wizard();
character1.attack();
Character character2 = new Archer();
character2.attack();
Character character3 = new Warrior();
character3.attack();
}
}
/* output
마법사가 공격한다
궁수가 공격한다
전사가 공격한다.
*/
subclass를 superclass타입으로 표현하여도 재정의된 메서드를 호출할 수 있게된다.
만약 Dynamic Method Dispatch를 지원하지 않는다면, 모두 "캐릭터가 공격한다." 라는 문구가 출력될 것이다.
컴파일러가 알고 있는 타입에 대한 정보를 토대로 런타임 시 해당 타입의 객체를 생성하고 메소드를 호출한다.
즉 다시말하자면,
런타임 전에는 객체 생성이 되지 않기 때문에 Character character = new Wizard();를 해도, 컴파일러는 Wizard가 생성됨을 알 수 없으므로 Character 정의한 attack() 메소드만 접근 가능
Example 클래스의 생성자 인자로 넘어오는 매개값의 타입이 Wizard, Archer, Warrior인지 알 수 있으려면 실행 시 확인 가능하다.
실행 시 동적으로 확인해서 다이나믹 디스패치 라고 부른다.
많은 블로그 글을 둘러보면서 확인한 결과, 대부분의 블로그의 글들이 구현체가 없는 인터페이스, 혹은 추상클래스의 메서드를 호출할 때 클래스 타입에 따라 동적으로 메서드가 결정되는것이라고 설명해놓았다. 그런데 해 본 결과 굳이 추상클래스나 인터페이스가 아니어도 동적 디스패치는 발생할 수 있다. 수퍼클래스의 메서드를 오버라이딩해도 동적 디스패치가 가능하다.
4-3. 더블 디스패치 (Double dispatch)
다이나믹 메소드 디스패치가 2번 발생하는 것
호출하고자 하는 메서드가 런타임 시점에 호출하는 객체의 타입과 argument의 타입으로 인해 정해지는 것을 의미한다.
(호출하는 객체의 타입과 arugment의 타입 모두 런타임 시점에 결정됨)
자바에서는 현재 Double Dispatch를 지원하고 있지 않다. 하지만 Visitor 패턴을 통해 유사한 행동을 구현할 수 있다.
... 추후 디자인 패턴을 이해하고 추가예정.
5. 추상 클래스
- 구제적이지 않은 클래스이다. (메서드의 본체가 완성되지 않은 미완성 메서드)
- 클래스들의 공통되는 필드와 메소드를 정의한 클래스
- 추상클래스는 클래스 앞에 "abstarct" 키워드를 이용해서 정의한다.
- 추상클래스는 미완성인 추상 메소드를 포함 할 수 있다.
- 추상메소드는 리턴타입앞에 "abstarct" 키워드를 붙여야한다.
- 인스턴스를 생성할 수 없다. (상속을 통해 서브클래스에서 생성)
- 추상클래스는 다른 클래스를 확장하고 여러 인터페이스를 구현 할 수 있음
선언방법
abstract class class-name{
//body of class
}
5-1. 추상 메서드 오버라이딩
추상 클래스는 자식 클래스의 공통 기능을 추출해 구현한 클래스이지만, 경우에 따라 자식 클래스마다 구현 내용이 달라질 수 있기 때문에 필요 시 추상 메소드를 작성
추상 메소드는 추상 클래스에서만 선언 가능하며, 메소드 선언부만 있는 형태
자식 클래스가 반드시 실행 내용을 구현하게 하기 위해 추상 메소드 선언
ex)
public abstract class Animal {
abstract void cry();
}
public class Dog extends Animal {
void cry() {
System.out.println("멍멍!");
}
}
public class Cat extends Animal {
void cry() {
System.out.println("야옹~");
}
}
6.final
클래스, 필드, 메소드 선언 시 사용 가능한 키워드 해당 선언이 최종 상태이고, 더 이상 수정될 수 없음을 의미
6-1. final 클래스
변경될 수 없는 클래스, 확장될 수 없는 클래스이다.
final 키워드가 붙어 있다면 다른 클래스의 조상이 될 수 없다.
public final class 클래스명 { . . . }
ex)
대표적인 final 클래스로 String이 있다. String을 만약 상속한다면 다음 에러가 출력된다.
public class Example extends String { . . . }
/*output
Cannot inherit from final 'java.lang.String'
*/
6-2. final 메서드
변경될 수 없는 메서드.
final 키워드를 사용하면, 이 메소드는 오버라이딩할 수 없는 메소드임을 의미
상속받은 부모 클래스의 final 메소드는 자식 클래스에서 재정의 불가능
public final return-type method-name (매개변수,..) { . . . }
ex)
public class Car {
public final void stop() {}
}
public class SportsCar extends Car {
@Override
public void stop() {}
}
/* output
'stop()' cannot override 'stop()' in 'Car'; overridden method is final
*/
6-3. final 변수
변수 앞에 final이 붙으면, 값을 변경할 수 없는 상수가 된다.
변수명은 일반적으로 상수라는 것을 알기 쉽게 하기 위해 모두 대문자로 작성한다.
public class Main {
final int IMMUTABLE = 10;
}
7. Object 클래스
- 자바의 최상위 클래스
- 따로 어디서 상속받지 않더라도 Obejct는 모든 클래스의 최상위 클래스이기 때문에 클래스를 생성하면 그 클래스에는 자동으로 object의 기본메서드가 포함되어있다. (클래스 선언 시 다른 클래스를 상속받지 않으면 암시적으로 java.lang.Object 클래스를 상속)
- 모든 객체는 object로 자동 타입 변환이 가능하다.
java.lang 패키지의 클래스 구조
Object 클래스 주요 메소드
Object 클래스는 필드를 가지지 않으며, 총 11개의 메소드만으로 구성
메서드 | 설명 |
protected Object clone() | 해당 객체의 복제본을 생성하여 반환함. |
boolean equals(Object obj) | 해당 객체와 전달받은 객체가 같은지 여부를 반환함. |
protected void finalize() | 해당 객체를 더는 아무도 참조하지 않아 가비지 컬렉터가 객체의 리소스를 정리하기 위해 호출함. |
Class getClass() | 해당 객체의 클래스 타입을 반환함. |
int hashCode() | 해당 객체의 해시 코드값을 반환함. |
void notify() | 해당 객체의 대기(wait)하고 있는 하나의 스레드를 다시 실행할 때 호출함. |
void notifyAll() | 해당 객체의 대기(wait)하고 있는 모든 스레드를 다시 실행할 때 호출함. |
String toString() | 해당 객체의 정보를 문자열로 반환함. |
void wait() | 해당 객체의 다른 스레드가 notify()나 notifyAll() 메소드를 실행할 때까지 현재 스레드를 일시적으로 대기(wait)시킬 때 호출함. |
void wait(long timeout) | 해당 객체의 다른 스레드가 notify()나 notifyAll() 메소드를 실행하거나 전달받은 시간이 지날 때까지 현재 스레드를 일시적으로 대기(wait)시킬 때 호출함. |
void wait(long timeout, int nanos) | 해당 객체의 다른 스레드가 notify()나 notifyAll() 메소드를 실행하거나 전달받은 시간이 지나거나 다른 스레드가 현재 스레드를 인터럽트(interrupt) 할 때까지 현재 스레드를 일시적으로 대기(wait)시킬 때 호출함. |
심도있게 공부해야 할것
참고한 곳
해당 포스트는 다음 글들을 참고하였음
https://leemoono.tistory.com/20
https://blog.naver.com/swoh1227/222181505425
https://blog.naver.com/sejonghumble/222183439699
https://roeldowney.tistory.com/486
https://www.notion.so/e5c33507880b4d098f83a2c4f8f02c04
https://b-programmer.tistory.com/236
https://github.com/ByungJun25/study/tree/main/java/whiteship-study/6week
https://leegicheol.github.io/whiteship-live-study/whiteship-live-study-06-Inheritance/
'Language > Java-Weekly-study' 카테고리의 다른 글
[Java] W08: 자바 인터페이스 (0) | 2022.07.12 |
---|---|
[Java] W07: 자바 패키지 (0) | 2022.07.10 |
[Java] W05: 자바 클래스 (0) | 2022.06.19 |
[Java] W04: 자바 제어문 (0) | 2022.06.11 |
[Java] W03: 자바 연산자 (0) | 2022.06.03 |