상속은 부모 객체로부터 데이터의 기능을 물려받는 객체의 능력입니다. 상속을 이용하면 자식 개체가 부모 개체를 대체할 수 있습니다. 상속을 사용하면 클래스를 처음부터 새로 생성하는 대신 기존 클래스에서 클래스를 생성하고, 기능을 추가하여 새 클래스를 생성할 수 있습니다. 새 클래스의 기반이 되는 부모 클래스를 슈퍼 클래스(Super Class), 기본 클래스(Base Class) 등으로 부르터 자식 클래스를 파생 클래스(Derived Class) 또는 서브 클래스(Sub Class)라고 부릅니다.
class Product {
...
}
class IDCard extends Product {
...
}
서브 클래스는 슈퍼 클래스의 생성자와 소멸자를 제외하고 모든 것을 슈퍼 클래스에서 상속합니다. 슈퍼 클래스의 public 멤버는 암시적으로 서브 클래스의 public 클래스의 멤버가 됩니다. 슈퍼 클래스의 private 멤버는 상속되지만, 슈퍼 클래스의 멤버만 액세스 할 수 있습니다.
클래스는 하나의 클래스를 항상 상속하고 있습니다. 슈퍼 클래스를 명시하지 않았다해도 암시적으로 Object클래스의 서브 클래스로 선언됩니다.
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
}
}
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
}
클래스 계층구조 내에서 같은 시그너처로 하위 클래스에서 오버라이딩 될 수 있는 메소드
가상 메소드는 파생 클래스에서 다형성을 재정의할 수 있는 메소드의 구현을 지정합니다. 객체지향 프로그래밍에서, 파생된 클래스가 기본 클래스를 상속할 때, 파생된 클래스의 객체는 파생된 클래스 타입이 아닌 기본 클래스 타입의 참조 또는 포인터로 여겨집니다. 만약 기본 클래스의 메소드가 파생된 클래스의 메소드에 의해 오버라이딩 된다면, 실제로 참조나 포인터에 의해 호출되는 메소드는 컴파일 타임에 바인딩 되거나 런타임에 바인딩 될 수 있습니다.
서브 클래스에서 슈퍼 클래스의 메소드와 같은 시그너처로 정의
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 로 선언된 메소드는 서브 클래스에서 오버라이드 할 수 없음
class Product {
final String getName() {
return "Product";
}
}
class IDCard extends Product {
public String getName() { // 컴파일 시 오류
return "IDCard";
}
}
그러나, 오버라이드 할 수 없다고 해서 상속되지 않는 것은 아닙니다. final로 선언된 메소드를 가진 슈퍼 클래스에서 파생된 서브 클래스는 슈퍼 클래스의 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의 서브 타입을 허용합니다.
이름
이름은 당연히 같아야 합니다.
파라미터 목록
파라미터의 목록과 순서, 타입이 반드시 일치해야 합니다.
인터페이스에 타입에서 공통적인 기능을 메소드로 선언하고 구현(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);
}
}
선언된 인터페이스에 추가 기능이 필요할 경우, 인터페이스에 메소드를 추가하면 인터페이스를 구현하는 모든 클래스는 인터페이스에 추가된 메소드를 구현해야 합니다. 이는 전체 응용 프로그램의 대규모 수정을 의미할 수 있으며, 확장에는 열려있고 수정에는 닫혀 있어애 하는 객체지향의 기본 원칙을 위반하는 수정이 될 수 있습니다.
interface Sort {
static void SortIntArray(int[] array) {
...
}
}
인터페이스에 static
메소드를 선언하는 것은 클래스에 static
메소드를 정의하는 것과 동일합니다.
추상 클래스는 파생된 클래스에서 완료할 수 있는 부분적인 구현을 제공하는데 사용됩니다. 추상 클래스는 여러 서브 클래스에서 재사용할 수 있는 코드를 상속하여 재사용을 제공한다는 데에서 유용합니다.
abstract class Factory {
...
}
class Test {
public static void main(String[] args) {
Factory factory = new Factory(); // 컴파일시 오류
}
}
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)는 모두 추상화를 위한 도구이다.
인스턴스 변수:
구현된 메소드:
상속과 구현:
extends
키워드를 사용해 상속받을 수 있습니다. 한 클래스는 한 번에 하나의 추상 클래스만 상속받을 수 있습니다.implements
키워드를 사용해 구현할 수 있습니다. 한 클래스는 여러 인터페이스를 동시에 구현할 수 있습니다.생성자:
추상 클래스
는 관련성이 있는 여러 클래스의 공통적인 특성을 추출하여 상위 클래스로 사용하는 경우에 주로 사용됩니다. 반면, 인터페이스
는 서로 관련성이 없는 클래스들이 동일한 동작을 수행하도록 하는 경우에 주로 사용됩니다.
public abstract class StringInstrument {
String name;
int numberOfString;
public abstract void changeString();
}
public class Guitar extends StringInstrument {
public void changeString() { ... }
}
interface Instrument {
abstract void play();
abstract void tune();
}
class violin {
abstract void play(); // 컴파일 시 오류
}
추상 메소드가 하나만 있는 인터페이스를 functional Interface 라고 한다.
functional Interface 가 있는 자리에는 람다 표현식이 들어갈 수 있다.
@FunctionalInterface
interface BinaryOp {
int apply(int left, int right);
}