자바의 상속에 대해 학습하세요.

  • 자바 상속의 특징
  • super 키워드
  • 메소드 오버라이딩
  • 다이나믹 메소드 디스패치 (Dynamic Method Dispatch)
  • 추상 클래스
  • final 키워드
  • Object 클래스

자바 상속의 특징

상속

  • 상위 클래스는 하위 클래스보다 일반적인 의미를 가짐
  • 하위 클래스는 상위 클래스보다 구체적인 의미를 가짐

클래스 상속 문법

class B extends A {
}

Protected 접근 제어자

  • 상속 관계에 있을 때, 다른 패키지에 있더라도 Protected 접근 제어자를 이용하여 접근 가능하다.
  • private > default(같은 패키지) > protected > public 순서


상속의 특징

  • 상속은 단일 상속만 가능하다.
  • 자바의 계층 구조 최상위에는 java.lang.Object 클래스가 존재한다.
  • 자바에서는 상속의 횟수에 제한을 두지 않는다.
class A{};
class B extends A{};
class C extends B{}; //상속에는 횟수제한이 없다.
  • 부모의 메소드와 변수만 상속되며 생성자는 상속되지 않는다.
  • 부모의 메소드는 재정의하여 사용 가능하다 - 오버라이딩
  • 부모의 private 접근 제한을 갖는 멤버는 상속 불가능
  • final 클래스는 상속할 수 없고, final 메소드는 오버라이딩 불가능

super 키워드

상속에서 클래스의 생성 과정

  • 하위 클래스가 생성될 때 상위 클래스가 먼저 생성된다.
  • 상위 클래스의 생성자가 호출되고 하위 클래스의 생성자가 호출된다.
  • 하위 클래스 생성자에서는 무조건 상위 클래스의 생성자가 호출되어야 한다.
  • 아무것도 없는 경우 컴파일러는 상위 클래스 기본 생성자를 호출하기 위한 super()를 코드에 넣어준다
  • super() 가 호출되는 생성자는 상위 클래스의 기본 생성자이다.
  • 만약 상위 클래스의 기본 생성자가 없는경우 (매개변수가 있는 생성자만 존재하는 경우) 하위 클래스는 명시적으로 상위 클래스를 호출해야 한다.

상속에서의 메모리 상태

  • 상위 클래스의 인스턴스가 먼저 생성이 되고, 하위 클래스의 인스턴스가 생성됨


super 예약어

  • 하위 클래스가 상위 클래스에 대한 주소를 가짐
  • 하위 클래스가 상위 클래스에 접근할 때 사용 가능

메소드 오버라이딩

  • 메소드를 오버라이딩 할 경우 자식 객체의 메소드 호출 시, 부모 기능은 숨겨지고 자식이 재정의한 기능이 실행됨
class Parent {
  void method1() {}

  void method2() {}
}

class Child extends Parent {
  @Override
  void method2() {}

  void method3() {}
}
class Example {
  public static void main(String[] args) {
    Child child = new Child();

    child.method1();  // 상속 받은 부모 메소드 호출
    child.method2();  // 재정의한 자식 메소드 호출
    child.method3();  // 자식 메소드 호출
  }
}

오버라이딩 주의사항

부모의 메소드와 동일한 메소드 시그니처(메소드 이름, 매개변수 리스트) 사용

새로운 예외 추가 불가능

부모 메소드의 접근 제한자보다 강한 제한 사용 불가능

  • 부모 public -> 자식 public 가능
  • 부모 default -> 자식 public protected default 가능

다이나믹 메소드 디스패치 (Dynamic Method Dispatch)

메소드 디스패치 (Method Dispatch)

  • 어떤 메소드를 호출할지 결정하여 실제로 실행시키는 과정
  • 자바는 런타임 시 객체를 생성하고 컴파일 시 생성할 객체 타입에 대한 정보만 보유한다.

스태틱 메소드 디스패치 (Static Method Dispatch)

  • 구현 클래스를 통해 컴파일 시점에 컴파일러가 어떤 메소드를 호출할지 명확하게 알고있는 경우
  • 컴파일 시 생성된 바이트 코드에도 정보가 남아있으며, 애플리케이션 실행 전에 호출할 메소드 결정
  • 메소드를 오버로딩하면 매개변수 타입과 개수에 따라 어떤 메소드를 호출할지 알 수 있는 경우
  • 상위 클래스가 있더라도 하위 클래스 ( 구현 클래스 ) 로 선언을 하고 하위 클래스의 인스턴스를 생성


다이나믹 메소드 디스패치 (Dynamic Method Dispatch)

  • 인터페이스나 추상 클래스에 정의된 추상 메소드를 호출하는 경우
  • 호출되는 메소드가 런타임 시 동적으로 결정 되는 것
  • 인터페이스 또는 추상 클래스로 선언하고 구현/상속 받은 하위 클래스의 인스턴스 생성
  • 컴파일러가 알고 있는 타입에 대한 정보를 토대로 런타임 시 해당 객체를 생성하고 메소드를 호출
public interface Animal {
  String method();
}

public class Dog implements Animal {

  @Override
  public String method() { . . . }

  public void bark() { . . . }
}

public class Cat implements Animal {

  @Override
  public String method() { . . . }

  public void meow() { . . . }
}
// 예시 1
public static void main(String[] args) {
  // 다이나믹 메소드 디스패치
  Animal animal = new Dog();
  System.out.println(animal.method());
}

// 예시 2
public class Example {
  private Animal animal;

  public Example(Animal animal) {
    this.animal = animal;
  }

  public void print() {
    // 다이나믹 메소드 디스패치
    System.out.println(animal.method());
  }
}
  • 런타임 전에는 객체 생성이 되지 않기에 Animal animal = new Dog();를 해도 컴파일러는 Dog가 생성됨을 알 수 없으므로 Animal이 정의한 method()만 접근 가능
  • Example 클래스의 생성자 인자로 넘어오는 매개값의 타입이 Dog 인지 Cat인지는 실행 시 확인 가능함

업캐스팅

  • 상위클래스 형으로 변수를 선언하고 하위클래스 인스턴스를 생성할 수 있다.
  • 하위클래스는 상위 클래스 타입을 내포하고 있으므로 상위클래스로의 묵시적 형변환이 가능하다.
  • Customer vc = new VIPCustomer(); 에서 vc가 가르키는건 무엇일까?
  • VIPCustomer() 생성자의 호출로 인스턴스는 모두 생성되었지만 타입이 Customer이므로 접근할 수 있는 변수나 메소드는 Customer의 변수와 메소드이다.

다운캐스팅

  • 하위클래스가 상위클래스로 형변환 되는 것은 묵시적
  • 원래 자료형인 하위 클래스로 형변환 하려면 명시적으로 다운캐스팅을 해야 함
  • 원래 인스턴스의 타입을 체크하는 예약어 instanceof 사용


추상 클래스

  • 추상 메서드를 포함한 클래스
  • 추상 메서드는 구현코드 없이 메서드의 선언만 있다.
  • abstract 예약어 사용
  • 추상 클래스는 인스턴스화 할 수 없다.
  • 생성자와 멤버변수, 일반 메서드 모두를 가질 수 있다.
//추상클래스 선언방법
abstract class 클래스이름{
}

추상 메서드

  • 메서드의 선언부만 작성하고 구현부는 미완성인 채로 남겨두는 메소드
  • 자식클래스는 반드시 추상 메서드를 구현해야 하며, 만약 구현하지 않은 경우 자식 클래스도 추상클래스가 되어야 한다.
  • 추상 메서드의 접근 지정자에는 private를 사용할 수 없다.
abstract 리턴타입 메서드이름();

final 키워드

  • final 변수는 값이 변경될 수 없는 상수이며 오직 한 번만 값을 할당할 수 있다.
  • final 메서드는 하위클래스에서 재정의 할 수 없다.
  • final 클래스는 더 이상 상속되지 않는다.

Object 클래스

  • 자바의 최상위 클래스
  • 클래스 선언 시 다른 클래스를 상속받지 않으면 암시적으로 java.lang.Object 클래스를 상속받는다.
  • 모든 객체는 Object로 자동 타입변환이 가능하다.

객체 비교 (equals())

public boolean equals(Object obj) {
    return (this == obj);
}
  • 두 객체의 논리적인 값을 비교
  • 객체가 저장하고 있는 데이터를 비교함
  • 기본 타입의 경우 실제 데이터
  • 참조 타입의 경우 참조하는 객체의 주소값

객체 해시코드 (hashcode())

public native int hashCode();
  • 객체를 식별할 하나의 정수 값
  • 객체의 메모리 주소값을 이용해 해시코드를 만들어 리턴하기 때문에 객체마다 다른 값을 가지고 있음

객체 문자정보 (toString())

public String toString() {
    return getClass().getName() + "@" + Integer.toHexString(hashCode());
}
  • 객체를 문자열로 표현한 값 반환
  • 기본값 "클래스명@16진수해시코드" 반환
  • System.out.Println(); 은 매개값이 기본 타입이면 그대로 출력, 참조 타입 객체면 toString() 호출하여 반환값 출력

객체 복제 (clone())

protected native Object clone() throws CloneNotSupportedException;
  • 원본 객체의 필드값과 동일한 값을 가지는 새로운 객체를 생성하는 것
  • 원본 객체의 데이터가 훼손되지 않도록 안전하게 보호하기 위해 객체 복제
  • 이 메소드를 사용하여 객체를 복제하려면 반드시 Cloneable 인터페이스를 구현해야 한다.

얕은 복제(thin clone)

  • 단순히 필드 값을 복사해서 객체를 복제
  • 기본 타입 필드는 값 복사, 참조 타입 필드는 객체 주소값 복사
  • Object의 clone() 메소드는 얕은 복제된 객체 리턴

깊은 복제(deep clone)

  • 참조 타입 필드의 경우 얕은 복제 사용 시 문제 발생
  • 참조 타입은 객체의 주소값을 복사하므로 원본 객체의 참조 필드와 복제 객체의 참조 필드가 같은 위치를 가리키게 되어 복제 객체의 참조 필드 값을 변경하면 원본 객체에서도 변경됨
  • 깊은 복제를 하려면 Object의 clone()을 재정의하여 사용
public class Member implements Cloneable {
  public String id;
  public int[] scores;
  public Car car;

  public Member(String id, int[] scores, Car car) {
    this.id = id;
    this.scores = scores;
    this.car = car;
  }

  public Member getMember() {
    Member cloned = null;
    try {
      cloned = (Member) clone();
    } catch(CloneNotSupportedException e) { }

    return cloned;
  }

  @Override
  public Oject clone() throws CloneNotSupportedException {
    // 얕은 복제 가능한 필드 복제
    Member cloned = (Member) super.clone();
    // 각 참조 타입 필드 깊은 복제
    cloned.scores = Arrays.copyOf(this.scores, this.scores.length);
    cloned.car = new Car(this.car.model);

    return cloned;
  }

  @Override
  public String toString() {
    return id + ", " + scores + ", " + car;
  }
}
public class Example {
  public staic void main(String[] args) {
    Member member = new Member("111", new int[] {10, 20}, new Car("아반떼"));

    Member cloned = member.getMember();
    cloned.scores = new int[] {20, 5};
    cloned.car = new Car("소나타");

    System.out.println(member);
    System.out.println(cloned);
  }
}
  • Member의 참조 타입 필드인 scores와 car의 값을 변경해도 원본 객체 데이터 유지

'Back-end > java' 카테고리의 다른 글

스터디 8주차 - 인터페이스  (0) 2021.07.01
스터디 7주차 - 패키지  (0) 2021.07.01
스터디 5주차 - 클래스  (0) 2021.06.30
스터디 4주차 - 제어문  (0) 2021.06.28
스터디 3주차 - 연산자  (0) 2021.06.28

+ Recent posts