Java에서의 상속

Moom2n·2024년 2월 27일
0

Java

목록 보기
11/26
post-thumbnail

상속은 부모 객체로부터 데이터의 기능을 물려받는 객체의 능력입니다. 상속을 이용하면 자식 개체가 부모 개체를 대체할 수 있습니다. 상속을 사용하면 클래스를 처음부터 새로 생성하는 대신 기존 클래스에서 클래스를 생성하고, 기능을 추가하여 새 클래스를 생성할 수 있습니다. 새 클래스의 기반이 되는 부모 클래스를 슈퍼 클래스(Super Class), 기본 클래스(Base Class) 등으로 부르터 자식 클래스를 파생 클래스(Derived Class) 또는 서브 클래스(Sub Class)라고 부릅니다.

서브 클래스 작성

- 클래스 확장

class Product {
    ...
}

class IDCard extends Product {
    ...
}

서브 클래스는 슈퍼 클래스의 생성자와 소멸자를 제외하고 모든 것을 슈퍼 클래스에서 상속합니다. 슈퍼 클래스의 public 멤버는 암시적으로 서브 클래스의 public 클래스의 멤버가 됩니다. 슈퍼 클래스의 private 멤버는 상속되지만, 슈퍼 클래스의 멤버만 액세스 할 수 있습니다.

클래스는 하나의 클래스를 항상 상속하고 있습니다. 슈퍼 클래스를 명시하지 않았다해도 암시적으로 Object클래스의 서브 클래스로 선언됩니다.

- 슈퍼 클래스 멤버에 액세스

  • protected 멤버는 암시적으로 파생된 클래스에 상속됨
  • 같은 패키지에 있는 클래스이거나 서브 클래스의 멤버는 슈퍼 클래스의 protected 멤버에 액세스 할 수 있습니다.
class Product {
    ...
    protected String name;
}

class IDCard extends Product {
    ...
    public String getTitle() {
        return name;
    }
}

class External {
    void access(Product product) {
        product.name;	// 컴파일시 오류
    }
}

서브 클래스가 protected 멤버를 상속하는 경우 해당 멤버는 암시적으로 서브 클래스의 protected 멤버이기도 합니다. 계층 구조에 있는 모든 서브 클래스가 protected 멤버에 액세스 할 수 있음을 의미합니다.

class SuperClass {
    protected String name;
}

class SubClass extends SuperClass {

}

class FurtherSubClass extends SubClass {
    void success() {
        System.out.println(name); // Okay
    }
}

- 슈퍼 클래스 생성자 호출

  • 생성자는 반드시 super 키워드를 사용해서 호출
  • private 생성자는 파생 클래스에서 액세스 할 수 없음
  • super 키워드로 식별자 범위 한정
class Product {
    protected Product(String name)
    ...
}

class IDCard extends Product {
    public IDCard(String name) {
        super(name);
    }
}

생성자가 private 인 클래스에서는 자식 클래스가 파생될 수 없습니다.

class NonDerivable {
    private NonDerivable() {  }
}

class Impossible extends NonDerivable {
    public Impossible() {  } // Compile-time error
}

메소드 구현

- 가상 메소드 (Virtual Method)

클래스 계층구조 내에서 같은 시그너처로 하위 클래스에서 오버라이딩 될 수 있는 메소드

가상 메소드는 파생 클래스에서 다형성을 재정의할 수 있는 메소드의 구현을 지정합니다. 객체지향 프로그래밍에서, 파생된 클래스가 기본 클래스를 상속할 때, 파생된 클래스의 객체는 파생된 클래스 타입이 아닌 기본 클래스 타입의 참조 또는 포인터로 여겨집니다. 만약 기본 클래스의 메소드가 파생된 클래스의 메소드에 의해 오버라이딩 된다면, 실제로 참조나 포인터에 의해 호출되는 메소드는 컴파일 타임에 바인딩 되거나 런타임에 바인딩 될 수 있습니다.

- 메소드 오버라이딩

서브 클래스에서 슈퍼 클래스의 메소드와 같은 시그너처로 정의

class Product {
    public String getName() {
        return "Product";
    }
}

public class IDCard extends Product {
    public String getName() {
        return "IDCard";
    }
}
Product product = new Product();
IDCard iDCard = new IDCard();
System.out.println(product.getName());	// “Product”
System.out.println(iDCard.getName());		// “IDCard”

- final 메소드

final 로 선언된 메소드는 서브 클래스에서 오버라이드 할 수 없음

class Product {
    final String getName() {
        return "Product";
    }
}

class IDCard extends Product {
    public String getName() {   // 컴파일 시 오류
        return "IDCard";
    }
}

그러나, 오버라이드 할 수 없다고 해서 상속되지 않는 것은 아닙니다. final로 선언된 메소드를 가진 슈퍼 클래스에서 파생된 서브 클래스는 슈퍼 클래스의 final 메소드를 상속합니다.

- final 클래스

  • final로 선언된 클래스에서는 서브 클래스를 파생할 수 없음
  • Java의 String, StringBuilder등의 클래스는 final로 선언됨
public final class Product {
    final String getName() {
        return "Product";
    }
}

class IDCard extends Product {	// 컴파일시 오류

}

인터페이스

인터페이스는 파생되는 모든 클래스가 준수해야하는 구문 및 의미 계약을 지정합니다. 특히 인터페이스는 계약의 어떤 부분을 설명하고 인터페이스를 구현하는 클래스는 계약의 일부를 설명합니다.

인터페이스 선언

interface BinaryOp {
    void apply(int right, int left);
}

class Adder implements BinaryOp {
    public int apply(int right, int left) {
        ...
    }
}

인터페이스는 코드가 없는 클래스와 비슷합니다. 클래스를 선언하는 방법과 같은 방식으로 인터페이스를 선언합니다. class 키워드 대신 interface 키워드를 사용하는 것만 다릅니다.
인터페이스의 메소드는 기본적으로 구현을 가질 수 없습니다. 인터페이스의 메소드는 본문이 없는 시그너처만 가진 메소드여야 합니다.

다중 인터페이스 구현

interface Flyable {
    void fly();
}

interface Sucklable extends Flyable {
    void suckle();
}

class Bat implements Flyable, Sucklable {
    void fly() { ... }
    void suckle() { ... }
}

클래스가 단 하나의 클래스만 상속할 수 있는 것과는 달리, 클래스는 하나 이상의 인터페이스를 구현할 수 있습니다.

interface Swimable { ... }
interface AirBreathable { ... }
interface Cetacean extends Swimable, AirBreathable { ... }
class Whale implements Cetacean { ... }

인터페이스는 여러 개의 인터페이스를 상속할 수 있습니다.
그러나, 인터페이스는 클래스를 상속할 수 없습니다.

interface Attackable {}		// default
class Soldier implements Attackable {}

인터페이스는 접근 제한자 없이 선언하면 default 제한이 되며, public을 접근 제한자로 선언할 수 있습니다.

- 인터페이스 메소드 구현

클래스가 인터페이스를 구현할 때 해당 인터페이스에 선언된 모든 메소드를 구현해야 합니다.

interface Attackable {
    int Attack(int power, String targetName);
}

class Tank implements Attackable {
    public int Attack(int power, String targetName) {}
    public int Attack(String targetName, int power) {}	// 컴파일시 오류
}
  • 접근 제한
    인터페이스 메소드는 암시적으로 public으로 선언됩니다. 따라서, 인터페이스를 구현하는 클래스의 인터페이스의 메소드를 구현하는 메소드 역시 public으로 선언되어야 합니다. Java의 클래스 메소드 기본 접근 제한자는 default입니다. 따라서, public으로 명시적으로 선언되어야 합니다.

  • return 타입
    인터페이스의 메소드 return 타입이 T로 선언되었으면, 인터페이스를 구현하는 클래스의 구현 메소드의 return 타입은 반드시 T 타입이어야 합니다. T의 서브 타입을 허용합니다.

  • 이름
    이름은 당연히 같아야 합니다.

  • 파라미터 목록
    파라미터의 목록과 순서, 타입이 반드시 일치해야 합니다.

- 인터페이스의 default 메소드

인터페이스에 타입에서 공통적인 기능을 메소드로 선언하고 구현(body)을 포함

public interface Sample {
    int apply(int i, int j);
    default int apply(int i, int j, int k) {
        return apply(apply(i, j), k);
    }
}

class Adder implements Sample {
    public int apply(int i, int j) {
        return i+j;
    }

    public static void main(String[] args) {
        Adder a = new Adder();
        a.apply(1, 2, 3);
    }
}

선언된 인터페이스에 추가 기능이 필요할 경우, 인터페이스에 메소드를 추가하면 인터페이스를 구현하는 모든 클래스는 인터페이스에 추가된 메소드를 구현해야 합니다. 이는 전체 응용 프로그램의 대규모 수정을 의미할 수 있으며, 확장에는 열려있고 수정에는 닫혀 있어애 하는 객체지향의 기본 원칙을 위반하는 수정이 될 수 있습니다.

- 인터페이스에서 static 메소드 구현

interface Sort {
    static void SortIntArray(int[] array) {
        ...
    }
}

인터페이스에 static 메소드를 선언하는 것은 클래스에 static 메소드를 정의하는 것과 동일합니다.


추상클래스

추상 클래스는 파생된 클래스에서 완료할 수 있는 부분적인 구현을 제공하는데 사용됩니다. 추상 클래스는 여러 서브 클래스에서 재사용할 수 있는 코드를 상속하여 재사용을 제공한다는 데에서 유용합니다.

- 추상 클래스 선언

abstract class Factory {
    ...
}

class Test {
    public static void main(String[] args) {
    Factory factory = new Factory(); 	// 컴파일시 오류
    }
}
  • 클래스 인스턴스를 생성할 수 없음
  • 선언이 아닌 구현(코드)을 상속하는 형태로 사용
  • abstract 키워드를 사용해서 선언

- 클래스 계층 구조에서 추상 클래스 사용

interface Moveable {
    void move(int x, int y);
}

abstract class Unit implements Moveable {public void move(int x, int y) {}
}

class Tank extends Unit {}
class Medic extends Unit {}

또는 아래의 경우도 가능하다.

interface Instrument {
    void play();
    void tune();
}

public abstract class StringInstrument {
    String name;
    int numberOfString;
}

public class Guitar extends StringInstrument implements Instrument {
    public void play() {}
    public void tune() {}
}

- 추상 클래스와 인터페이스

추상 클래스(Abstract Class)와 인터페이스(Interface)는 모두 추상화를 위한 도구이다.

차이점

  1. 인스턴스 변수:

    • 추상 클래스는 인스턴스 변수를 가질 수 있습니다.
    • 인터페이스는 인스턴스 변수를 가질 수 없고, 대신에 상수만을 가질 수 있습니다.
  2. 구현된 메소드:

    • 추상 클래스는 추상 메소드뿐만 아니라 구현된 메소드를 가질 수 있습니다. 또한 추상 클래스의 메소드는 모든 접근 제한을 사용할 수 있습니다.
    • 인터페이스는 Java 8부터 default 메소드와 static 메소드를 가질 수 있지만, 그 외의 메소드는 모두 추상 메소드여야 합니다. 또한 인터페이스의 메소드는 public으로 선언됩니다.
  3. 상속과 구현:

    • 추상 클래스는 다른 클래스에서 extends 키워드를 사용해 상속받을 수 있습니다. 한 클래스는 한 번에 하나의 추상 클래스만 상속받을 수 있습니다.
    • 인터페이스implements 키워드를 사용해 구현할 수 있습니다. 한 클래스는 여러 인터페이스를 동시에 구현할 수 있습니다.
  4. 생성자:

    • 추상 클래스는 생성자를 가질 수 있습니다. 이를 통해 인스턴스 변수를 초기화할 수 있습니다.
    • 인터페이스는 생성자를 가질 수 없습니다.

추상 클래스는 관련성이 있는 여러 클래스의 공통적인 특성을 추출하여 상위 클래스로 사용하는 경우에 주로 사용됩니다. 반면, 인터페이스는 서로 관련성이 없는 클래스들이 동일한 동작을 수행하도록 하는 경우에 주로 사용됩니다.

- 추상 메소드 구현

  • 추상 클래스에서 구현이 없는 메소드 선언
  • abstract 키워드 사용
  • 추상 클래스에서만 추상 메소드를 선언할 수 있음
public abstract class StringInstrument {
    String name;
    int numberOfString;

    public abstract void changeString();
}

public class Guitar extends StringInstrument {
    public void changeString() { ... }
}
  • 인터페이스에서 default와 static을 제외한 모든 메소드는 추상 메소드입니다. abstract 키워드를 사용해서 선언할 수도 있습니다.
interface Instrument {
    abstract void play();
    abstract void tune();
}
  • 일반 클래스에서는 abstract 키워드를 사용한 추상 메소드를 선언할 수 없습니다.
class violin {
    abstract void play();	// 컴파일 시 오류
}

@Functional Interface

추상 메소드가 하나만 있는 인터페이스를 functional Interface 라고 한다.
functional Interface 가 있는 자리에는 람다 표현식이 들어갈 수 있다.

@FunctionalInterface
interface BinaryOp {
    int apply(int left, int right);
}

0개의 댓글

관련 채용 정보