목표
자바의 상속에 대해 학습하세요.
학습할 것 (필수)
- 자바 상속의 특징
- super 키워드
- 메소드 오버라이딩
- 다이나믹 메소드 디스패치 (Dynamic Method Dispatch)
- 추상 클래스 (abstract)
- final 키워드
- Object 클래스
# 모든 자료는 "이것이 자바다" 라는 책을 참고하여, 작성하고 있습니다. 참고 부탁드립니다.
1. 상속이란?
상속은 우리가 현실세계에서 생각하는 상속과 매우 유사한 개념이다. 즉 부모가 자식에게 돈이나 물건 또는 가진 것들을 물려주는 행위를 일컫는다. 이걸 자바에서 적용을 해보면, 상속은 클래스에서 출발을 한다.
클래스란, 객체를 만들기 위한 설계도라는 것을 저번 주에 배웠다.
이 설계도에는 필드, 생성자, 메소드라는 3가지의 요소들이 들어있다.
이러한 클래스의 기본적인 요소들을 가지고 상속에 대해 이해를 해야한다.
상속은 클래스에서 부모 클래스의 필드, 생성자, 메서드를 모두 자녀 클래스에 물려 줄 때, 상속이라고 일컫는다. 상속 덕분에 자녀 클래스는 부모 클래스에 존재하는 필드, 생성자, 메서드를 자유롭게 사용이 가능하다.
프로그램에서는 이런 관계를 두고 여러 가지 이름으로 부른다. 다음은 이를 정리한 표이다.
부모 클래스 | 자녀 클래스 |
상위 클래스 | 하위 클래스 |
슈퍼 클래스(Super Class) | 서브 클래스(Sub Class) |
이렇게 다양하게 혼용해서 사용을 하니, 용어에 익숙해져야 한다.
그렇다면, 클래스가 상속이 되었다고는 소스코드에서는 어떻게 표현을 할까?
다음은 이와 관련된 예시 코드이다. 먼저 부모 클래스부터 살펴 보겠다. 편의상 부모 클래스의 이름은 Parents로 설정했다.
위의 소스 코드를 살펴보면, 부모 클래스에 필드값으로 price, name이 설정된 것을 확인할 수 있다.
그리고 get과 set의 메소드들이 나열된 것을 볼 수 있다. 이 메소드들은 지금은 모르지만, 추후에 사용 쓰임새가 있어 일단 만들었다.
그 다음엔 이 부모 클래스를 상속하는 자녀 클래스이다. 편의 상 이름은 Child Class로 명명하였다.
해당 자녀 클래스의 3번째 줄에 주목을 해야 한다. 보통이라면 없을 extend라는 예약어가 사용된 것을 확인할 수 있다. 이 단어를 사용해, 지금 클래스를 확장하면, 부모 클래스가 된다는 말이다. 즉 상속을 받았다는 뜻이다.
정리하면,
자녀 클래스 extends 부모 클래스
이런 방식이다.
매우 간편한 방식이다. 이렇게 될 경우 자녀 클래스에서는 존재하지 않는 필드값인 name과 price를 자유롭게 사용하는 것을 확인할 수 있다.
이에 관련한 예시는 10번째 생성자를 확인하면 볼 수 있다.
10번째 생성자에 의하면, 생성자의 매개변수가 String name이다.
그리고 this를 활용하여 setName 메소드를 활용하는 모습이 보이는데, Child class에는 setName 메소드가 존재하지 않는 모습을 확인할 수 있다.
이 메소드는 부모 클래스였던, Parents 클래스에 존재하는 메소드이다. 예시 코드 그림을 다시 확인 해보자.
밑줄 친 부분이 Parent 클래스에 존재하는 메소드이다. 이 메소드는 매개 변수에 String 자료를 넣어주면, 필드값 name에 매개변수의 값으로 초기화를 해주는 작업을 진행한다.
그래서 Child Class는 Parent 클래스의 setName 메소드를 this로 호출을 해서, 사용을 한다. 다시 Child Class 예시코드로 돌아가자.
그래서 11번째 줄에서, Child 생성자의 매개변수에 String 형이 들어간 값으로 필드 값 name을 초기화 해야 하기때문에, this로 Parents 클라스에 있던, setName 메소드를 호출하고, 그 메소드의 매개 변수에 Child 생성자의 매개변수를 넣어준다.
이와 같은 동일한 방식으로 15번 행, 21번 행 생성자도 생성된 모습을 확인할 수 있다.
하지만, 지금 동일한 코딩이 반복되는 모습을 확인할 수 있다.
이는 저번 시간에 배웠던 클라스에서 this 이용해 보다 더 간결하게 줄일 수 있다.
11번행 생성자와 14번행 생성자를 확인해보면, this를 이용해 엄청나게 간결해진 코드를 확인할 수 있다.
이 this 활용법은 간단하게 설명하면 18번 행 생성자를 주목하면 된다.
매개 변수를 가장 길게 가진 생성자를 차분히 다 적어준다.
다음 매개 변수를 적게 가진 생성자에 this를 사용하여, ()안에 매개 변수를 차례대로 넣는다.
생성자가 지정한 매개 변수는 받는 인자값으로 넣고, 나머지는 해당하는 자료형의 초기값을 넣으면 된다.
그 결과가 11번행과 16번 행이다.
이처럼 this는 다양한 활용도가 있으니, 꾸준히 사용하면 굉장히 편리하게 코딩이 가능하다.
이렇게 부모와 자녀 클래스를 상속관계로 만들었을 때, JVM 에는 다음과 같이 구성되었다고 이해를 하면 편하다.
Child 라는 클래스 가 생성이 될때, 가장 먼저 Parent 라는 클래스 영역이 생성이 된다. 이때 이 안에 필드값, 생성자, 메소드가 모드 들어가는 것을 볼 수 있다.
Parent 클래스 영역이 모두 생성이 완료된 후에는 Child 클래스에만 존재하는 필드값, 생성자가 생성이 되는 것을 확이할 수 있다.
자녀가 상속받지 못하는 것이 있다고?
상속이 되었을 경우, 부모 클래스에 있는 필드값, 메소드를 전부 자녀 클래스에서 자유롭게 사용이 가능하지만, 사용을 못하게 하는 경우도 존재한다.
이는 부모 클래스에서 private라는 접근제어자를 사용했을 때이다.
private 접근 제어자는 클래스 내에서만 사용이 가능한 접근 제어자로, 외부 클래스 그게 아무리 자녀 클래스여도 사용이 불가하다.
이를 사용하고 싶을 경우, 부모 클래스에서 public 으로 된 메소드를 생성하면 메소드를 활용하는 방법이 존재한다.
별도로 default 접근제어자도 존재한다. 이 default 접근제어자는 접근제어자를 아무것도 쓰지 않은 경우에 해당하는데, 일반적으로 클래스에서 같은 패키지내에서만 이용이 가능하게 끔 해주는 접근제어자다.
상속에서도 동일하게 적용되어, 만약에 부모 클래스와 자녀클래스가 동일한 패키지 내라면, 사용이 가능하지만, 다른 패키지일경우에는 private처럼 사용을 못하는게 특징이다.
+) 추가 생성자도 상속이 안되니까! 주의해야 한다!!!!
Java만의 특별한 상속!
상속을 하는 예약어를 배우니, 이 클래스 저 클래스를 사용하여, 상속을 받는다면, 자녀 클래스에 쓰는 내용이 적어져 얼마나 편할까? 라고 생각하기 쉽겠지만 ㅋㅋ Java는 단일 상속이다. 이 뜻은 한 개의 클래스에는 한 개의 클래스만 상속이 가능하다는 뜻이다.
C+ 같은 경우에는 다중 상속이라 여러 클래스를 상속받을 수 있지만, 이렇게 여러 클래스를 상속 받다보니 부모 클래스끼리 겹치는 등 굉장히 복잡해졌다고 한다. 그래서 Java는 단일 상속이라는 점을 기억해야 한다.
2. Super 키워드
상속은 부모 클래스와 자식 클래스간에 자식 클래스에서 자유롭게 부모 클래스를 이용하는 개념이다. 이때, super라는 특이한 예약어가 존재한다. super는 부모 클래스를 불러 올 수 있다. 즉 호출하는 예약어라고 생각하면 편리하다.
이 super가 어디서 활용이 되는지 궁금하면, 가장 기본적인 클래스의 생성 과정의 시작부터 이해해야 한다.
객체를 생성할 때, 만약 클래스에 생성자를 넣지 않으면, default 생성자가 생성이 된다.
이 default 생성자는 매개 변수도 없고, 실행문에도 아무것도 없다고 생각하겠지만, 상속 관계의 클래스라면 super();라는 것이 생성이 된다. 이와 관련된 예시 코드는 다음과 같다.
6번째 줄의 생성자를 보면, 매개변수도 아무것도 없는 default 생성자에 super();가 기재된 것을 볼 수 있다.
이는 부모 생성자를 호출하는 this.parent(); 와 동일한 의미이며, 부모 객체를 호출하는 것이라고 생각하면 편리하다.
즉, 이를 통해 알 수 있는 것은 부모 생성자는 자식 생성자의 첫 줄에서 호출이 되면서 자식보다 먼저 생성이 된다는 점을 기억해야 한다.
이러한 super 메소드는 부모 클래스의 생성자에 따라서 매개 변수를 넣어야 하는 경우도 존재한다. 다음은 이와 관련된 예시코드이다. 먼저, 부모 클래스에 생성자를 만들었는데, 이 생성자에는 매개 변수가 존재한다.
7번째 줄을 확인하면 매개 변수에 int 자료형인 price,와 String 자료형인 name 변수들이 필요한 것을 알 수 있다. 이렇게 생성이 되자마자 갑자기 Child Class에 빨간 밑줄들이 미친듯이 생긴다.
이와 같은 문제가 발생한 이유는 먼저, Child라는 자녀 클래스에서 부모 생성자를 생성한다는 생성자 즉, super() 관련 코드가 존재하지 않아, 컨파일러가 자동으로 집어넣는다.
그런데, super()라는 생성자로 부모 클래스를 생성하려고, 부모 클래스를 보니, 생성자 중에서 super() 처럼 매개변수가 없는 생성자가 없다.
그래서 컨파일이 되지 않는다. 이러한 문제를 해결하기 위해서는 다음과 같은 방법을 사용할 수 있다
1. 부모 클래스에 매개변수를 이용하여 다양한 생성자를 만든다.
: 사용자 소스코드에서 메소드를 사용할 때, 어떤 방식으로 사용을 할지 알 수 없기 때문에, 항상 제일 먼저 생성되는 부모 클래스에서는 다양한 방식으로 생성자를 생성하는게 좋다. 이번 경우에는 매개 변수가 없는 생성자
public Parent(){
}
와 같이 기본 생성자만 만들면 간단하게 해결이 된다.
2. 자녀 클래스에서 부모 생성자를 호출한다.
자녀 클래스에서 super()를 활용하여, 부모 생성자를 호출하면 된다. 단, 부모 클래스에서는 생성자가 무조건 매개 변수가 있어야 하기 때문에, 자녀 클래스에서 super()를 활용할 때는 매개 변수를 초기값을 집어넣고 작업을 하면 된다.
이와 같은 경우에는
super(0, null);
이렇게 집어넣으면 된다.
3. 메소드 오버라이딩 (Method Overriding)
상속의 꽃이자, 가장 핵심적인 부분이 등장했다. 메소드 오버라이딩으로 인해 다형성이라는 어려워보이는 개념도 등장을 하게 된다.
우선, 메소드 오버라이딩을 간단하게 설명하면, 메소드가 재정의 된다는 뜻이다. 메소드의 재정의는 그렇다면 언제 일어나는 것일까? 상속을 하게 되면, 자녀 클래스는 자유롭게 부모 클래스의 메소드들을 사용할 수 있다. 하지만, 만약 부모 클래스에 존재하는 메소드들을 수정하고 싶을 땐 어떨까? 이런 경우를 위해 메소드 오버라이딩이 존재한다.
다시 적어보면, 자식이 부모가 가지고 있는 메소드를 자기 입맛에 맞게 변형해서 사용하고 싶을 때, 메소드 오버라이딩을 하면 된다. 그렇다면, 메소드 오버라이딩은 어떻게 하고, JVM 메모리에서는 어떤 방식으로 표현이 될까?
3.1 메소드 오버라이딩을 하는 방법
3.1.1 메소드 오버라이딩을 위한 규칙
(1) 부모의 메소드와 동일한 시그니처 (리턴 타입, 메소드 이름, 매개 변수 리스트)를 가져야 한다.
(2) 접근 제한을 더 강하게 오버라이딩을 할 수 없다.
(3) 새로운 예외(Exception)를 throws 할 수 없다.
(1) 부모의 메소드와 동일한 시그니처 (리턴 타입, 메소드 이름, 매개 변수 리스트)를 가져야 한다.
이 규칙은 부모의 메소드와 동일한 시그니처란, 메소드의 특징을 생각해보면 된다.
먼저 부모 클래스의 메소드에 관한 예시코드이다.
지금 보면, 16번 줄에 메소드가 선언된 것을 볼 수 있다. 이때, 이 setPrice 메소드를 오버라이딩을 하고 싶다면, Child 클래스 즉 상속을 받은 클래스에서는 다음과 같이 하면 된다. 다음은 Child Class 에서 가져야 하는 메소드이다.
지금 보면, Child Class에 부모 클래스와 동일한 부분들이 보이는데 이를 표로 정리하면 다음과 같다.
접근제어자는 동일하지 않아도 되지만, 이와 관련된 예시는 2번째 규칙때 설명하겠다. 우선 동일하니, 표로 정리해두었다. 이처럼, 메소드 리턴타입, 메소드 이름, 매개 변수가 모두 동일해야지만, 메소드 오버라이딩이 일어난다.
만약 위의 상황에서 메소드 오버라이딩이 일어나게 된다면, 부모 클래스의 setPrice 메소드의 실행문이었던,
ths.price = price;
는 실행이 되지 않고, 자녀 클래스에 존재하는
System.out.println("Child Class의 메소드 오버라이딩");
이 부분이 실행된다. 이렇게 메소드의 규칙을 지키는 것은 가장 기본적인 규칙이므로, 항상 지켜야 한다.
(2) 접근 제한을 더 강하게 오버라이딩을 할 수 없다.
이 규칙은 방금 전에 설명한 접근 제어자와 연관된 부분이다. 먼저, 접근제어자가 무엇인지 부터 설명하겠다. 접근제한자란, public, protected, default, private로 총 네 가지의 종류가 존재한다. 이 접근제한자들은 서로의 접근 제한 범위에 따라서 각각 다른데, 이와 관련 된 그림을 보면 이해가 쉽다.
이 접근 제어자는 사용할 수 있는 영역과 어느 클래스에 이를 활용할 수 있는지가 정해져있다. 다음은 이와 관련된 내용을 정리한 표이다.
그렇다면, 메서드 오버라이딩에서 접근 제한을 더 강하게는 못한다는 무슨 의미일까? 이는 부모 클래스를 기준으로 설명을 해야 한다. 즉, 부모 클래스 메소드보다 접근 제한을 같거나 더 크게 해야 한다는 뜻이다. 다음은 각 부모 접근제한 별로 자식 접근 제한을 할 수 있는 범위를 정리했다. 한 두개 정도 보면 감이 온다.
현재 protected는 존재하지 않는데, 이는 조금 특수한 상황이라 그렇다. protected 접근제어자는 다른 패키지여도, 상속 관계면 호출이 가능하기 때문에 같은 패키지내에서만 호출할 수 있는 default보다 범위가 더 크기 때문에, 부모 클래스가 protected 접근제어자일 때, 자녀 클래스에서 default 사용이 불가하다.
마지막 3번 규칙은 추후에 배울 예외처리와 함께 정리를 하겠다.
3.2 메소드 오버라이딩이 JVM에서 표현되는 방식
그렇다면, 메소드 오버라이딩이 일어날 때, JVM에서는 어떤 일이 벌어지고 있는걸까?
이는 아까 사용했던 예시코드들을 이용하여 설명해보겠다. 먼저, 가장 중요한 실행문을 담고 있는 main method 및, 부모와 자녀 클래스이다.
먼저, 이 세 코드를 한번 보겠다. 우선, 실행은 항상 메인에서 이루어지기 때문에, main method를 기준으로 JVM 메모리가 어떻게 변경되는지 해보겠다. 해당 그림에는 main method 관련 영역도 존재해야하지만, 가독성을 위해 실행문만 작업하도록 하겠다.
먼저, Parents 클래스 타입을 갖는 p 변수가 생성되었다. 그렇기 때문에, p라는 변수의 메모리 영역이 Stack에 생성이 되고, Heap영역에 p 변수가 참조하는 Parent 클래스가 생성이 된다. 이를 표현하면 다음 그림과 같다.
이 상태에서 다음 테스트 코드를 살펴보자.
p 변수에 . 점 호출자를 이용하여, setPrice라는 메소드를 호출하는 모습을 볼 수 있다. 이를 JVM 메모리로 표현하면 다음과 같다.
매개변수를 위해 stack에 메모리 공간이 할당된다. 그리고, Parent 클래스에서 setPrice(int Price)라는 메서드를 찾는다. 현재 Parent 클래스에는 메소드의 선언부만 있기 때문에 실행부분을 찾기 위해 Class Area에 있는 Parent class를 찾아간다. 그 곳에서 실행문에 적힌 내용을 실행한다. 이 경우에는 다음과 같다.
setPrice메소드의 실행부분에 있는 위 예시코드 그림의 빨간 줄들이 실행이 된다. 이를 JVM 메모리로 표현하면, (println은 생략한다.) 다음과 같다.
이렇게 할일을 다 끝내고, 메서드가 끝나, parent 의 매개변수였던 int 메모리 영역은 Stack에서 사라진다.
그리고, 이제 다시 테스트 코드를 살펴보자.
이번에는 Child의 클래스가 새로 생성되는 것을 확인할 수 있다. Child 객체 c는 다음과 같이 생성이 된다.
여기서 주목해야할 점은 바로 저 Child 클래스가 생성된 방식이다. Child는 Parent 클래스를 상속받는 것을 알 수 있다. 그래서, 생성과정에서 Parent 클래스 관련 필드, 생성자, 메서드가 먼저 생성된 뒤에 Child 클래스 내용이 생성되는 것을 알 수 있다.
그러면 이제 다음 코드를 실행하기 위해 테스트 코드를 다시 보자.
다음 코드를 살펴보면, c라는 객체의 setPrice 메소드를 호출하는 것을 알 수 있다. 그런데, 현재 c 가 참조하는 Child Class안에는 총 두개의 setPrice 메소드가 존재하는 것을 알 수 있다. 이런 경우 바로, 오버라이딩이 발생한다. 먼저, 동일한 Parent 메소드인 setPrice가 잠시 접힌다. 존재는 하지만, 잠시 가려진다. 그리고 Child가 가지고 있는 setPrice가 실행이 되는 것을 알 수 있다. 이를 JVM 그림으로 살펴보자. 우선, Child 예시 코드부터 다시 보자.
위의 그림을 참고하면, Parent 클래스 영역에 있던 setPrice(int price)가 잠깐 가려지고, Child 클래스 영역에 있는, setPrice(int Price)가 호출이 되면서, 상세 실행문을 위해 Class Area 의 Child Class를 참조하러 가는 모습이다. 이렇게 메소드가 끝나게 되면, 매개변수는 사라지고, 다음은 실행 결과이다.
이처럼, 오버라이딩은 다양한 방식으로 활용될 수 있다. 만약 저 상황에서, 업캐스팅, 다운캐스팅을 해서 이리 저리 돌아다니면서 다양한 방식으로 메서드를 재정의하는 방식들이 존재한다.
오버라이딩은 굉장히 중요한 개념이다. 확실하게 잘 정리해두도록 하자.
해당 글을 크리스마스 전날까지 작성을 한다면, 업캐스팅과 다운캐스팅에 대해서도 설명해보겠다. (가능할까..ㅎ)
4. 다이나믹 메소드 디스패치 (Dynamic Method Dispatch)
다이나믹 메소드 디스패치란 무엇일까? 이를 이해하기 위해선 오버라이딩과 상속에 대한 개념 정리가 되어 있어야 한다. 먼저 자바에서는 메소드 오버라이딩이 이루어진다. 이는 객체지향 언어라는 가장 큰 꽃인 다형성을 보여주는 부분이기도 하다. 다이나믹 메소드 디스패치는 메소드의 오버라이딩이 호출될 떄, 실행시 이루어지는 과정을 말한다. (컴파일 시간이 아니라!)
메소드 오버라이딩이 이루어졌을 때, 자바는 호출된 시점을 기준으로 어떤 메소드를 오버라이딩 해야하는지 판단을 하게 된다. 즉 실행이 되었을 때, 메소드 오버라이딩이 이루어지는 것을 말한다. 실행 시, 객체가 어떤 타입의 클래스를 상속받는지에 따라 작업이 이루어지게 된다.
5. 추상 클래스 (abstract)
추상 클래스는 클래스의 심화 버전이라고 생각하면 좋다. 클래스는 클래스인데, 이 클래스는 조금 특별하다. 이 클래스는 다른 클래스들 처럼 객체를 직접 생산하지는 못한다. 대신, 여러 클래스들의 공통점을 가지는 부모 클래스로서 다른 클래스들과 상속 관계를 유지할 수는 있다. 굉장히 이해가 안가는 말이다. 상속관계를 유지하는데, 왜 객체는 못만드는걸까? 이런 의문을 생각하면서 다시 추상 클래스에 대해 설명해보겠다.
객체를 직접 생성할 수 있는 클래스는 실체 클래스라고 하는데, 이 실체 클래스들이 공통점 (필드값, 메서드)를 추출해서 선언한 클래스가 바로 추상 클래스이다. 아직도 감이 안 오는가? 그럼 다시 또 설명해보겠다. 추상 클래스는 객체를 만들 수 있는 실체 클래스들의 부모 클래스가 되어, 추상 클래스에서 가지고 있던 필드값과 메서드를 사용할 수 있게끔 해준다. 추상클래스는 여러 실체 클래스들의 공통점을 뽑아서, 추상클래스에 넣음으로 여러 이점이 있기 때문에 생겼다고 볼 수 있다. 그럼, 자연스럽게 고민이 이어진다. 도대체 왜? 그렇게 해야할까?
추상 클래스는 왜 필요한 것일까?
1) 실체 클래스들의 공통된 필드와 메소드의 이름을 통일할 목적
만약, 추상 클래스 없이, 사람들이 일을 할 때, 핸드폰과 관련된 클래스들을 만든다고 생각해보자. 누군가는 해당 클래스에 Telephone 클래스의 사용자를 owner라는 필드값을 지정하고, 누군가는 Mobile라는 클래스의 사용자를 user라고 필드값을 저장할수 있다.
즉 같은 전화기와 관련된 내용을 클래스를 작성하고, 같은 사용자를 가리켜야 하는데 누군 owner, 누구는user라고 중구난방으로 될 수 있다. 필드값 뿐만 아니다. 메소드도 마찬가지이다.
Telephone 클래스에서는 전화기의 전화를 키는 메소드를 turnOn() 이라고 할 수 있지만, Mobile 이라는 클래스에서는 전화기를 키는 메소드를 powerOn()이라고도 할 수 있다.
하지만, 만약 추상 클래스가 존재하여, 전화기의 사용자를 통일하는 필드값 user를 사용하고, 전화기의 전원을 키는 메서드를 powerOn()으로 통일을 하게 된다면, 다양한 클래스에서 사용되는 것들을 통일할 수 있는 장점이 존재한다.
2) 실체 클래스를 작성할 때 시간을 절약한다.
실체 클래스들의 공통되는 필드와 메소드를 추상 클래스에 선언하면, 실체 클래스는 필요한 부분만 선언을 할 수 있다. 즉, 실체 클래스를 만들 때마다 시간이 절약 된다. 이런 방식으로 진행을 하게 된다면, 추상 클래스에서 메서드들의 규격이 결정되면, 실체 클래스가 그 메소드를 사용하기 위해 해당 규격들을 가져다가 쓰기 때문에 코딩의 시간도 절약되고 여러 사람이 동일한 규격에서 작업을 할 수 있다는 것을 알 수 있다.
5.1 추상 클래스 선언
추상클래스를 선언하는 방법은 abstract 키워드를 사용하면 된다. 그리고 다시 한 번 강조를 하면, abstract 키워드를 붙이면 추상 클래스가 되기 떄문에 새로운 객체를 생성하지 못하고 상속을 통해 자신의 클래스만 만들 수 있다.
추상 클래스에는 일반 클래스처럼 필드, 생성자, 메서드 선언을 할 수 있다. 추상클래스는 new 생성자로 직접 객체 생성이 불가하지만, 자식 클래스에서 super를 통해 부모 클래스를 호출할 수 있기 때문에 생성자를 만들어야 한다는 사실을 기억하자.
그렇다면, 추상 클래스를 선언한 예시 코드를 살펴보겠다.
이렇게 간단하다. 그냥 클래스 앞에 abstract를 붙여주기만 하면 된다. 그렇다면, 이 추상 클래스에는 무엇이 들어갈 수 있을까? 앞에서 배웠듯이, 필드, 생성자, 메서드 모두를 선언할 수 있다. 다음은 이와 관련된 예시 코드이다.
이렇게, 필드값, 메서드, 생성자를 모두 기존의 클래스처럼 생성을 할 수 있는 것을 볼수 있다. 하지만 가장 마지막줄에 추상 메서드라고 기존에 보지 못한 메서드가 존재한다. 메서드임에도 불구하고, 실행문이 존재하지 않는 것이 보이고, abstract 라는 수식어가 붙여져 있는 모습을 볼 수 있다.
이 abstract가 붙은 마지막 줄 코드가 바로 추상 메서드이다. 이 추상 메서드의 특징은 실행문이 존재하지 않고, 앞에 abstract라는 키워드가 붙여져있는 것을 볼 수 있다. 단, 주의해야 하는 점이 있다. 만약 클래스 내에서 abstract 예약어를 사용하는 추상 메서드가 있다면, 반드시 클래스도 추상 클래스여야 한다. 즉, 이와 같은 상관 관계를 표로 정리하면 다음과 같다.
위의 표 정리처럼, 추상 메소드가 존재하면 추상 클래스는 반드시 존재해야 한다. 하지만, 추상 클래스에 추상 메소드는 반드시 있을 필요가 없다. 이거 하나만 기억하자! 추상 메소드가 존재하면, 반드시 클래스도 추상 클래스여야 한다.
5.2 추상 클래스 오버라이딩
앞서, 오버라이딩은 추상클래스를 사용하기 위해 배웠다고 해도 과언이 아니다. 이는 추상 클래스에 관한 예시코드를 보면서 이해하면 훨씬 쉽게 이해가 가능하다.
두 번째 실체 클래스 AbstractClassTest라는 클래스가 Test 클래스를 상속받은 모습을 볼 수 있다. 그런데, 특이한 점은 오버라이딩 메소드를 쓰지 않으면, 해당 클래스 작성이 불가하다. 왜냐하면 상속받는 부모 클래스가 추상 클래스 이기 때문이다.
즉 추상 클래스에서 선언된 추상 메소드들은 반드시, 자녀 클래스에서 메소드 작성을 해줘야 한다. 그래서 상단에 getPrice라는 추상 메소드를 AbstractClassTest 자녀 클래스에서 다시 선언된 모습을 볼 수 있다.
그 후, 메인 메소드에서 AbstractClassTest 타입의 객체 a를 생성한 후, a 객체의 getPrice()메소드를 불러오면, 부모 클래스인 Test 클래스의 메소드가 아니라 오버라이딩된 AbstractClassTest의 getPrice()를 가져오며 오버라이딩 된 결과를 볼 수 있다. 실행 결과는 다음 사진과 같다.
6. final 키워드
final 키워드는 더 이상 수정이 불가할 때 쓰는 키워드라 생각하면 편리하다. 즉, final 키워드가 사용 되면, 최종 더이상의 수정은 없다. final 키워드는 클래스, 필드, 메소드 선언시에 다양하게 사용이 가능하다. final 키워드는 abstract 키워드랑 상반되는 의미로 많이 사용되는데, 이는 다음과 같은 이유 때문이다.
두 키워드를 클래스와 함께 쓸 때, final 키워드는 이를 이용해 새로운 객체 생성이 가능하지만, 상속은 불가하다. 그에 반해 abstract 키워드는 새로운 객체 생성은 안 되지만, 상속은 가능하다. 위에서 abstract 클래스를 배웠기 때문에 조금 감이 올 것이다. 그렇다면, 먼저 final 키워드를 이용하여 클래스를 선언해보자.
6.1 final 클래스 선언
final 클래스 선언 방법은 간단하다. Abstract 클래스 때와 마찬가지로 그냥 클래스 앞에 final만 적어주면 된다. 이는 다음과 같다. 예시코드를 참조하자.
Parent 클래스가 final 키워드를 이용해 선언이 되었다, 이 안에는 필드값, 생성자, 메소드부터 다양하게 존재하는 것을 알 수 있지만, Child 클래스가 Parent 클래스를 상속하려고 하자, 빨간줄이 그어진것을 볼 수 있다. (19번째 줄) 이처럼 final 클래스는 상속이 되지 않는다.
6.2 final 메소드 오버라이딩
그렇다면, 클래스는 final 이 아닌 상태에서 메소드만, final 인 경우는 어떨까? 방금 전 예시코드의 Parent 클래스에서 final만 빼고 다시 해보겠다.
Child 클래스를 Parent 클래스의 상속을 받은 다음 Parent 클래스에 존재하는 getPrice 메소드를 오버라이딩 하려고 했다. 하지만, 빨간줄이 보이는가?21번째줄.
Parent 클래스의 14번째 줄 getPrice() 메소드가 final 수식어가 사용이 되었기에, 오버라이딩이 불가능하여 컴파일도 안된다고 나오게 된다.
이처럼, final 은 필드 메서드 클래스에서 다양하게 값을 더이상 수정 못하게 하고 싶을 떄 사용이 가능하다.
7. Object 클래스
object 클래스는 모든 클래스의 부모님이다. 즉 어떤 클래스를 만들어도, 자동으로 해당 클래스 명 뒤에는 extends Object가 붙어 있다. 그래서, Object 클래스에 존재하는 메소드와 필드값들을 자유롭게 사용이 가능한데, Object 클래스에는 그럼 어떤 메소드들이 존재할까?
이렇게 Object 클래스들의 메소들을 파악했다면, 모든 클래스는 Object클래스를 상소가 받기 때문에 오버라이딩을 이용하여, 자유롭게 오버라이딩이 필요한 시점에 추가해서 사용이 가능하다는 것을 기억하자.
참고 자료
1) 이것이 자바다
2) docs.oracle.com/javase/8/docs/api/index.html
'JAVA > 온라인 스터디' 카테고리의 다른 글
7주차 과제: 패키지 (0) | 2020.12.27 |
---|---|
[5주차] 클래스 (0) | 2020.12.14 |
[4주차] 제어문 #4 (0) | 2020.12.10 |
[3주차] 연산자 (0) | 2020.11.27 |
2주차 라이브 방송 정리 (0) | 2020.11.22 |