상속
상속은 상위 클래스의 필드와 메서드를 허위클래스가 물려받는것을 말한다.
상속을 사용하면 코드의 재사용성이 증가하고 클래스 간 계층관계를 구분하고 관리하는게 편해진다.
- 상속해주는 클래스
상위 클래스, 슈퍼 클래스, 부모 클래스, 기반 클래스(Base Class)라고도 부른다. - 상속을 받는 클래스
하위 클래스, 서브 클래스, 자식 클래스, 파생 클래스(derived Class)라고도 부른다.
상속을 한다고해서 부모 클래스의 모든 필드와 메서드를 상속받는것은 아니다.
private
접근 지정자를 가지고 있는 필드와 메서드는 상속에서 제외되며 다른 패키지일 경우default
접근 지정자 또한 제외된다.
extends
키워드
자바에서는 상속을 위해서 extends
라는 키워드를 사용한다. 부모 클래스의 메서드를 상속받아 재정의(overide) 할 수 있기때문에 확장(extends) 이라고 한다.
자바 상속의 특징
- 자바에서는 단일 상속만 가능하다. (다중 상속 불가능)
- 부모 클래스의 생성자는 상속되지 않는다. (생성자는 멤버가 아니다.)
- 부모 클래스의 초기화 블록
{}
은 상속되지 않는다. static
메서드 혹은 변수도 상속 된다.- 동일한 이름의 변수가 부모 클래스와 자식 클래스에 모두 있을경우 부모 클래스의 변수는 가려진다.
- 자바에서 최상위 클래스는
Object
클래스이며 모든 클래스는Object
클래스의 자식 클래스이다. - 접근 지정자에 따라 필드와 메서드가 상속되지 않을 수 있다.
- 다중 상속은 불가능하지만 상속에 대한 횟수는 제한이 없다.
동일한 변수가 부모 클래스와 자식 클래스 모두 존재할 경우 부모 클래스의 변수가 가려진다. 이때 부모 클래스의 변수에 접근하고자 할때 super
키워드를 사용한다.
super
키워드
super
키워드는 자식 클래스가 부모 클래스의 필드와 메서드를 참조하는데 사용하는 참조변수이다. 인스턴스 변수와 매개변수, 지역변수의 이름이 같을 경우 this
키워드로 인스턴스 변수를 가리킬 수 있듯이 super
키워드로 부모 클래스를 가리킬 수 있다.
- 부모 클래스의 변수 접근 :
super.변수
- 부모 클래스의 메서드 접근 :
super.메소드(파라미터)
- 부모 클래스의 생성자 호출 :
super(파라미터)
자바에서는 자식 객체를 생성하면 부모 객체가 먼저 생성되고 자식 객체가 그 다음에 생성이 된다.
public class Sub extends Super {
public static void main(String[] args) {
Sub sub = new Sub();
}
}
위의 코드에서 Sub 클래스의 인스턴스만 생성 하였지만 사실은 내부적으로 부모인 Super 클래스의 객체가 먼저 생성되고 Sub 클래스의 객체가 생성된다.
부모 클래스 생성자를 호출할 경우 반드시 자식 클래스의 생성자의 첫 번째 줄에서 호출해야 한다. 명시적으로 부모 클래스 생성자를 호출하지 않아도 super()가 숨어있어 자동으로 부모 클래스의 생성자를 호출하게 된다. 매개변수를 가진 자식 클래스의 생성자가 있다면 super(매개변수)를 직접 명시해줘야한다. 이때 만약 부모 클래스의 생성자와 super()의 매개변수가 다를경우 컴파일 오류가 발생한다. 부모 클래스에 기본 생성자가 없고 매개 변수가 있는 생성자만 있다면 자식 생성자에서는 반드시 부모 생성자 호출을 위해 super(매개변수)를 명시적으로 호출해야 한다.
자식 클래스의 멤버에는 부모 클래스의 멤버 또한 포함되어있다. 부모 클래스의 멤버를 초기화하기 위해서는 자식 클래스의 생성자에서 부모 클래스의 생성자까지 호출해야한다. 이 과정은 모든 클래스의 부모인 Object
클래스의 생성자까지 계속 올라가며 수행한다.
메소드 오버로딩 (Method Overload)
메소드 오버로딩은 메소드의 이름과 반환타입은 같지만 매개변수가 다른 메소드를 의미한다.
public void test() {
System.out.println("test1");
}
public void test(String str) {
System.out.println(str);
}
public void test(int a) {
System.out.println(a);
}
메소드 오버라이딩 (Method Override)
부모 클래스와 자식 클래스가 상속관계일때 자식 클래스는 부모 클래스의 메서드를 상속 받아 사용할 수 있다. 부모 클래스의 메서드를 그대로 사용할 수도 있지만 부모 클래스의 메서드를 재정의하여 사용할 수도 있는데 이것을 오버라이딩이라고 한다.
public class Super {
public void test() {
System.out.println("Super");
}
}
public class Sub extends Super{
// 메소드 오버라이딩
// 부모 클래스의 test 메서드를 상속 받아 자식 클래스에서 구현부를 재정의 하였다.
@Override
public void test() {
System.out.println("Sub");
}
}
위 예제 코드와 같이 부모 클래스의 메서드를 자식 클래스가 상속 받아 사용할 수 있는데, 자식 클래스에서 메서드의 구현부를 새로 정의하여 사용할 수 있다. 이것을 메서드 재정의라는 의미로 오버라이딩이라고 부른다. 함수명, 리턴타입, 파라미터와 같은 메서드 선언부는 변경할 수 없다.
하지만 오버라이딩된 메서드는 부모 클래스의 메서드의 반환타입의 하위타입으로 반환할 수 있다. 이러한 하위 타입을 covariant return type
이라고 한다.
public class Super {
// 반환타입이 Super 타입
public Super test() {
System.out.println("Super");
return new Super();
}
}
public class Sub extends Super{
// 부모 클래스의 메서드는 반환타입이 Super이지만
// Super의 하위 클래스인 (자식 클래스) Sub를 반환하는 오버라이딩 메서드도 가능하다.
@Override
public Sub test() {
System.out.println("Sub");
return new Sub();
}
}
또한 메서드 오버라이딩시 접근 지정자를 변경할 수도 있는데, 부모 클래스에 정의된 접근 지정자보다 더 많은 제한을 가진 접근 지정자로는 변경할 수 가없다. 더 넓은 접근을 허용하는 접근 지정자로만 변경이 가능하다.
만약 부모 클래스 메서드의 접근 지정자가 protected
라고 한다면, 오버라이딩한 자식 클래스의 메서드는 public
으로는 변경이 가능하지만 private
으로는 변경할 수 없다.
메소드 디스패치 (Method Dispatch)
메소드 디스패치는 어떤 메소드를 호출할 지 결정하여 실제로 실행시키는 과정을 말한다. 자바는 런타임 시에 객체를 생성하고 컴파일 시에는 생성할 객체 타입에 대한 정보만 보유하고 있다. 이런 메서드 디스패치는 3가지가 있다.
- 정적 메소드 디스패치 (Static Method Dispatch)
- 동적 메소드 디스패치 (Dynamic Method Dispatch)
- 더블 디스패치 (Double Dispatch)
정적 메소드 디스패치 (Static Method Dispatch)
컴파일 시점에서 컴파일러가 어떤 메소드를 호출할지 명확하게 알고 있는경우를 정적 메소드 디스패치 (Static Method Dispatch) 라고 부른다.
public class A {
public void print() {
System.out.println("A");
}
}
public class B extends A {
@Override
public void print() {
Systen,out.println("B");
}
}
public class Test {
public static void main(String[] args) {
B b = new B();
b.print(); // B 출력
}
}
동적 메서드 디스패치(Dynamic Method Dispatch)
동적 메소드 디스패치는 정적 메서드 디스패치와 다르게 컴파일러가 어떤 메서드를 호출해야되는지 모르는 경우를 말한다.
class A {
private BInterface bInter;
A(BInterface bInter) {
this.bInter = bInter;
}
void print() {
bInter.print();
}
}
class B1 implements BInterface{
public void print() {
System.out.println("B1");
}
}
class B2 implements BInterface {
public void print() {
System.out.println("B2");
}
}
interface BInterface {
void print();
}
위의 예제에서 BInterface 라는 추상 클래스는 B1, B2로 각각 구현되고 있다. A라는 클래스는 이 추상 클래스를 받아 bInter.print()라는 함수를 호출하고 있다. A 클래스의 print() 함수를 실행하면 어떤 함수가 호출될지 모른다. 아마도 A 클래스의 객체를 생성할 때 할당된 Object를 보고 어떤 함수를 실행할지 결정하게 될것이다. 이처럼 컴파일러는 어떤 함수가 실행될지를 모른다. B1 클래스의 print()를 호출할지, B2 클래스의 print()를 호출할지를 아는 시점은 런타임 시점일 것이다. 이처럼 호출되는 메서드가 런타임 시에 결정되는 것을 다이나믹 디스패치라고 한다.
더블 디스패치 (Double Dispatch)
더블 디스패치는 Dynamic Dispatch가 두 번 발생하는 것을 말한다. 디자인 패턴 중 방문자 패턴 (Visitor Pattern)과 밀접한 관계가 있다. Double Dispatch와 오버로딩의 차이점은 오버로딩의 경우 객체의 타입은 정해져 있지만 매개변수의 타입만 다른것이고, Double Dispatch는 호출하는 객체의 타입과 매개변수의 타입 모두 런타임 시점에 결정되는 것을 의미한다.
interface Post {
void postOn(SNS sns);
}
class Text implements Post {
public void postOn(SNS sns) {
sns.post(this)
}
}
class Picture implements Post {
public void postOn(SNS sns) {
sns.post(this);
}
}
interface SNS {
void post(Text text);
void post(Picture picture);
}
class Facebook implements SNS {
public void post(Text text) {
// text -> Facebook
}
public void post(Picture picture) {
// picture -> Facebook
}
}
class Twitter implements SNS {
public void post(Text text) {
// text -> Twitter
}
public void post(Picture picture) {
// picture -> Twitter
}
}
public static void main(String[] args) {
List<Post> posts = Arrays.asList(new Text(), new Picture());
List<SNS> sns = Arrays.asList(new Facebook(), new Twitter());
posts.forEach(p -> sns.forEach(s -> p.postOn(s)));
}
위의 예제는 총 두 번의 Dynamic Dispatch가 이루어진다.
- Post에서 어떤 구현체의 postOn을 사용할지
- postOn에서 SNS의 어떤 구현체의 post함수를 사용할지
이렇게 더블 디스패치를 사용하게 되면 구현체를 생성하는 것에 자유로워 진다. 만약 새로운 코드를 추가할 시 아래 예제와 같이 추가하기만 하면 된다.
class Instagram implements SNS {
public void post(Text text) {
// text -> Instagram
}
public void post(Picture picture) {
// picture -> instagram
}
}
자바에서는 현재 Double Dispatch를 지원하고 있지 않지만 방문자 패턴을 통해 유사한 행동을 구현할 수 있다.
방문자 패턴 (Visitor Pattern)
일반적으로 OOP는 객체가 스스로 행위를 수행하게 하지만 경우에 따라서는 객체의 행위 수행을 외부 클래스에 위임할 수 있다. 이때 사용하는 디자인 패턴의 종류는 전략 패턴, 커맨드 패턴, 방문자 패턴이 있다.
- 전략 패턴 (Strategy Pattern)
하나의 객체가 여러 동작을 하게 하는 패턴 (1 : N) - 커맨드 패턴 (Command Pattern)
하나의 객체가 하나의 동작(+보조동작)을 하게 하는 패턴 (1 : 1) - 방문자 패턴 (Visitor Pattern)
여러 객체에 대해 각 객체의 동작들을 지정하는 패턴 (N : N)
추상 클래스 (Abstract Class)
추상이란 실체 간에 공통적인 특성을 추출한 것을 말한다. 추상 클래스는 객체를 직접 생성할 수 있는 실체 클래스의 공통적인 특성(필드, 메서드)를 추출하여 선언한 클래스이다. 클래스를 만들어 내기 위한 일종의 설계도라고 볼 수 있다. 추상클래스는 인스턴스를 생성할 수 없다. 추상 클래스를 사용하기 위해서는 반드시 자식 클래스에서 상속받아 추상 클래스를 모두 구현해야한다.
추상 클래스의 목적
- 실체 클래스의 공통 필드와 메소드 이름을 통일하기 위함
- 실체 클래스의 작성 시간을 절약
추상 클래스 선언
abstract
키워드를 이용하여 추가한다.
public abstract class 클래스이름 {
// 필드
// 생섲아
// 메서드
}
추상 클래스를 상속 받은 자식 클래스의 객체가 생성될 때 내부에서 super
키워드가 실행되어 추상 클래스 객체를 생성하기 때문에 생성자가 필요하다
추상 클래스는 반드시 하나 이상의 추상 메서드를 포함하고 있어야하며 생성자와 멤버변수, 일반 메서드 모두 가질 수 있다.
추상 메서드 (Abstract Method)
abstract 리턴타입 메서드이름();
추상 클래스에서 메서드의 선언부만 작성하고 구현부는 미완성인 채로 남겨두는 메서드를 말한다. 추상 클래스에서는보통 어떤 기능을 수행하는 지 주석을 남겨놓고 구현부는 추상 클래스를 상속받은 자식 클래스에서 오버라이딩하여 구현한다. 추상 클래스를 상속받았을 때는 반드시 추상 메서드를 구현해야한다. 만약 구현하지 않을 경우 자식 클래스 또한 추상 클래스가 되어야 한다. 추상 클래스를 사용할 시 자식 클래스에서 오버라이딩을 강제할 수 있다. 추상 메서드는 자식 클래스가 반드시 구현을 해야하기 때문에 private
접근 지정자를 사용할 수 없다.
인터페이스
인터페이스는 추상 클래스의 일종으로 추상 클래스보다 추상화 정도가 높아 일반 메서드나 멤버변수 또한 가질 수 없다. 추상 메서드와 상수만 멤버로 가질 수 있다.
interface 인터페이스이름{
// ...
}
인터페이스는 같은 인터페이스들끼리만 상속이 가능하며 인터페이스를 구현할 경우 extends
라는 키워드를 사용하며 다중 상속이 가능하다.
interface A extends B, C, D {
}
class A extends B implements C, D {
}
인터페이스의 모든 멤벼 변수는 public static final
이어야 하며 기본적으로 자동으로 등록되기 때문에 따로 명시하지않고 생략할 수 있다. 메서드 또한 public abstract
이어야 하며 마찬가지로 생략할 수 있다.
final
final
키워드는 엔티티를 한 번만 할당하겠다는 의미로 자바에서는 총 3가지의 의미로 사용된다.
final 변수
우리가 흔히 알고 있는 상수를 의미한다. 생성자나 대입연산자를 값을 초기화하고 이후엔 값을 변경할 수 없는 변수이다.public final int number = 10;
final 메서드
오버라이드하거나 숨길 수 없음을 의미한다.public final String message(String msg) { ... }
final 클래스
해당 클래스를 상속할 수 없다는 의미. 상속할 수 없기 때문에 상속 계층에서 마지막 클래스라는 의미로 쓰인다.public final class 클래스명 { ... }
Object 클래스
java.lang.Object 클래스는 모든 클래스의 최상위 클래스이다. 클래스 선언할 때 다른 클래스를 상속받지 않으면 암시적으로 java.lang.Object
클래스를 상속 받는다. 다형성으로 인해 모든 객체는 Object
타입으로 변환할 수 있다.
메소드 | 설명 |
---|---|
boolean eqauls(Object obj) | 두 객체의 논리적인 값(객체가 저장하고 있는 데이터, primitive type일 경우 실제 데이터를 비교, reference type일 경우 객체의 주소값)을 비교 |
String toString() | 객체를 문자열로 표현한 값을 반환한다. 기본적으로는 "클래스명@16진수해시코드"로 구성된 문자 정보를 반환한다. |
protected Object clone() | 원본 객체의 필드값과 동일한 값을 가지는 새로운 객체를 생성하는 것으로 원본 데이터가 훼손되지 않도록 객체를 복제한다. 이 메서드를 사용하여 객체를 복제하려면 반드시 Cloneable 인터페이스를 구현해야한다. |
protected void finalize() | 더이상 참조하지 않는 배열이나 객체는 GC가 힙 영역에서 자동으로 소멸 처리한다. GC는 소멸 처리 직전에 객체 소멸자인 finalize() 를 호출한다. 객체 소멸전에 마지막으로 사용했던 자원을 닫거나 중요한 데이터를 저장하고 싶다면 메서드를 재정의 한다. |
Class getClass() | 객체의 클래스형을 반환한다. |
int hashCode() | 객체의 코드값을 반환한다. |
void notify() | wait된 스레드 실행을 재개할 때 호출한다. |
void notifyAll() | wait된 모든 스레드 실행을 재개할 때 호출한다. |
void wait() | 스레드를 일시적으로 중지할 때 호출한다. |
void wait(long timeout) | 주어진 시간만큼 스레드를 일시적으로 중지할 때 호출한다. |
void wait(long timeout, int nanos) | 주어진 시간만큼 스레드를 일시적으로 중지할 때 호출한다. |
참고
https://geunyang93.tistory.com/39
https://github.com/mongzza/java-study/blob/main/study/6%EC%A3%BC%EC%B0%A8.md#%EC%83%81%EC%86%8D-inheritance
https://www.notion.so/e5c33507880b4d098f83a2c4f8f02c04
https://github.com/ByungJun25/study/tree/main/java/whiteship-study/6week
https://blog.naver.com/swoh1227/222181505425
https://github.com/ByungJun25/study/tree/main/java/whiteship-study/6week