객체지향 언어에서 상속은 매우 중요한 개념이라고 할 수 있다. 상위 개념의 공통적 속성과 동작을 하나의 클래스로 만들고, 하위의 개념은 상위 개념 클래스를 상속하여 속성과 동작을 추가할 수 있다. 이 때 상위 개념이 되는 부모 클래스를 슈퍼 클래스, 하위 개념이 되는 자식 클래스를 서브 클래스라 부른다.
상속을 통해 중복되는 코드 작성을 피할 수 있으며 쉬운 확장이 가능해진다.
클래스의 상속은 다음과 같이 extends 키워드를 사용한다.
class Person{
//슈퍼 클래스
}
class Teacher extends Person{
//서브 클래스
}
class Student extends Person{
//서브 클래스
}
class SchoolPresident extends Student{
//서브 클래스 Student를 상속받은 서브 클래스
}
SchoolPresident 클래스와 같이 서브 클래스를 상속받는 것도 가능하며, 이 경우 Student 클래스의 멤버들을 상속받은 서브 클래스가 된다.
슈퍼 클래스의 객체와 서브 클래스의 객체를 생성하려면 일반적인 클래스 생성과 같은 방법으로 new 연산자를 사용하면 된다.
Person p = new Person();
Teacher t = new Teacher();
Student s = new Student();
SchoolPresident sp = new SchoolPresident();
이 때 각각 별개의 객체가 생성되며, 서브 클래스 객체들은 슈퍼 클래스의 멤버들을 모두 포함하고 있다.
❗자바에서는 클래스의 다중 상속을 지원하지 않는다.(C++에서는 가능) 또, 상속 횟수에 제한이 없다.
서브 클래스에서는 슈퍼 클래스의 멤버 중 private을 제외하고 모두 접근할 수 있다.
슈퍼 클래스의 private 멤버는 오직 자기 자신 클래스에서만 접근할 수 있어야 하므로, 다른 클래스는 물론 서브 클래스에서도 접근 불가하다.
default 멤버는 같은 패키지 내 모든 클래스에서 접근 가능하며 서브 클래스라도 다른 패키지에 있다면 슈퍼 클래스의 default 멤버에 접근할 수 없다.
protected 멤버는 default 멤버와 같이 같은 패키지 내 모든 클래스에서 접근 가능하며 다른 패키지에 있는 서브 클래스에서도 접근할 수 있다.
public 멤버의 경우 패키지에 상관 없이 모든 클래스에서 접근 가능하다.
서브 클래스 객체 생성 시 슈퍼 클래스의 객체도 함께 호출된다. 생성자의 목적은 객체의 속성들을 초기화하는 데 있으므로 서브 클래스의 생성자가 호출된다고 슈퍼 클래스의 생성자가 호출되지 않으면 안된다. 객체 생성에 필요한 초기화를 각각 수행해야 한다.
이 때 슈퍼 클래스의 생성자가 먼저 실행된 후 서브 클래스의 생성자가 실행된다.
상속 관계에 있는 클래스 생성자의 호출 순서에 대해 알아보기 위해 다음 예시를 보자.

C의 생성자 호출 시 C부터 슈퍼 클래스로 거슬러 올라가며 생성자를 호출하며 실행은 호출 역순으로 상위 클래스부터 실행된다.
우리는 생성자를 공부할 때 여러 개의 생성자를 둘 수 있음을 공부했다. 그렇다면 자동으로 슈퍼 클래스의 생성자가 호출될 때 어떤 생성자를 호출하게 될 것인가?
서브 클래스의 생성자에서 호출될 슈퍼 클래스의 생성자를 따로 지정하지 않으면 자바 컴파일러에 의해 기본 생성자가 호출된다.

이 때, 슈퍼 클래스에 기본 생성자가 없으면 다음과 같이 오류가 발생한다.

서브 클래스 생성자에서 호출할 슈퍼 클래스의 생성자를 지정하고 싶은 경우 super()를 사용한다. 괄호 안에 인자를 전달할 수 있다. super()는 this()가 그랬듯 생성자의 첫 라인에 와야 한다.

👉🏻생성자 관련 규칙
캐스팅이란 형 변환을 의미한다. 캐스팅에는 업캐스팅과 다운캐스팅이 있다.
다음과 같이 슈퍼 클래스의 레퍼런스로 서브 클래스 객체를 가리키는 것을 업캐스팅이라 한다.
class Person{
//내용
}
class Student extends Person{
//내용
}
Student s = new Student();
Person p = s; //Person 타입의 레퍼런스로 Student 타입의 객체 가리킴
이 때 주의할 점은 레퍼런스 p로는 Person 클래스의 멤버들만 접근할 수 있고 Student 클래스에 작성한 멤버들에 접근할 수 없다는 것이다.
즉, 업캐스팅한 레퍼런스로는 객체 내의 모든 멤버들에 접근할 수 없고 슈퍼 클래스의 멤버에만 접근할 수 있다.

서브 클래스의 레퍼런스로 슈퍼 클래스 객체를 가리키는 것을 다운캐스팅이라 한다. 다음과 같이 명시적 형 변환을 해주면 된다.
Person p = new Person();
Student s = (Student)p;
이렇게 되면 Student 타입 레퍼런스 s를 통해 Student 타입의 객체 전체에 접근할 수 있게 된다.

업캐스팅을 하다보면 레퍼런스가 어떤 타입의 객체를 가리키고 있는 것인지 알기 어려운 경우가 있다. 다음과 같은 경우가 그 예시이다.

따라서 이 객체가 어떤 클래스의 객체인지 구별해야 할 때 instanceof 연산자를 사용한다. instanceof 연산자는 다음과 같이 사용한다.
레퍼런스 instanceof 클래스명
결과값은 true 혹은 false의 boolean값이다. instanceof는 클래스에만 적용되므로 기본 자료형이나 다른 값을 넣으면 안된다.
슈퍼 클래스의 메소드를 서브 클래스에서 덮어쓰는 것이 메소드 오버라이딩이다. 서브 클래스에서 재작성하기 위해서는 메소드 이름, 리턴 타입, 매개 변수를 모두 동일하게 유지해야 한다.
이렇게 슈퍼 클래스의 메소드를 무시하고 서브 클래스에서 오버라이딩 된 메소드를 실행하는 것을 동적 바인딩이라 부른다.
👉🏻동적 바인딩 - 실행할 메소드를 컴파일 시에 결정하지 않고 런타임에 결정하는 것
아래는 오버라이딩을 설명할 때 자주 나오는 예시로, 슈퍼 클래스 Shape의 메소드 drow()를 세 서브 클래스에서 오버라이딩하고 있다.

이 때, 메소드 draw()의 호출을 살펴보자.
Line line = new Line();
line.draw();
위와 같은 경우 Line 클래스에서 오버라이딩한 draw()가 호출된다.
Shape shape = new Line();
shape.draw();
위의 경우는 업캐스팅을 한 경우이다. 이 때에도 동적 바인딩에 의해 Line 클래스에서 오버라이딩한 draw()가 호출된다.
호출 방식에 상관 없이, 무조건 오버라이딩 된 메소드가 호출된다.
만약 오버라이딩 된 메소드가 아닌 원래의 슈퍼 클래스의 메소드를 호출하고 싶은 경우, super 키워드를 사용하면 된다. 메소드 뿐만 아니라 필드의 경우에도 가능하다. 다음과 같이 작성하면 된다.
super.name = 10; //서브 클래스가 아닌 슈퍼 클래스의 필드 접근
super.draw(); //서브 클래스가 아닌 슈퍼 클래스의 메소드 접근
❗오버라이딩을 할 때, 슈퍼 클래스 메소드의 접근 지정자보다 접근의 범위를 넓힐 수는 있으나 좁힐 수는 없다.
접근 범위가 넓은 순으로 public > protected > default > private
❗static, private, final로 선언된 메소드는 오버라이딩 불가
추상 메소드란 선언만 되어있고 구현되지 않은 메소드이다. 다음과 같이 abstract 키워드를 앞에 붙이고 내용은 제외하고 작성한다.
public abstract int result(int a);
public abstract void show();
선언만 있고 구현은 하지 않는다.
클래스 앞에 abstract 키워드를 붙이면 추상 클래스가 된다. 이 때, 클래스는 추상 메소드를 포함할 수 있고 포함하지 않을 수 있다. 추상 메소드가 하나라도 있는 경우 반드시 추상 클래스로 선언되어야 한다.
추상 클래스는 객체를 생성할 수 없다. 다음과 같이 추상 클래스의 레퍼런스 변수는 선언할 수 있으나 객체 생성은 불가하다.
abstract class Ab{
//내용
}
Ab ab; //오류 발생하지 않음. 레퍼런스 변수 선언 가능
ab = new Ab(); //오류 발생. 추상 클래스의 객체 생성 불가
추상 클래스를 상속 받는 클래스는 추상 클래스가 된다. 추상 클래스를 상속 받는 서브 클래스에도 abstract 키워드를 붙여주어야 한다.
추상 클래스의 목적은 다양한 구현에 있다. 추상 클래스의 구현이란 슈퍼 클래스에 선언된 모든 추상 메소드를 서브 클래스에서 오버라이딩하여 구현하는 것을 말한다.
아까의 예시에서 슈퍼 클래스 Shape를 추상 클래스로 선언하고 서브 클래스에서 구현하면 다음과 같다.

추상 클래스 구현 시에도 키워드 extends를 사용한다.
이렇게 추상 클래스를 작성하면 설계와 구현을 분리할 수 있다는 장점이 있다. 추상 클래스에서 공통 구조를 설계하고 서브 클래스에서 이를 각각에 맞게 구체화한다.
여기 또 다른 골격/규격에 대한 개념이 있다. 상수와 메소드로 이루어진 인터페이스는 다음과 같이 interface 키워드를 통해 선언한다.

인터페이스의 멤버로는 5가지 형태의 멤버만 가능하다.
❗필드는 멤버로 올 수 없다. 추상 메소드는 public abstract만 가능하며 다른 접근 지정은 불가하고, 생략 가능하다. 상수의 경우 public static final로 접근 지정하고 생략 가능하다. default, private, static 메소드의 경우에는 인터페이스 내에 구현되어 있어야 한다. default 메소드의 접근 지정은 public이며 private 메소드는 인터페이스 내에서만 호출 가능하다. static 메소드의 경우 접근 지정자가 생략되면 public이며 private으로 지정 가능하다.
인터페이스 또한 객체를 생성할 수 없으며 인터페이스의 레퍼런스 변수는 선언 가능하다.
인터페이스 구현 시 implements 키워드를 사용한다. 이 때 인터페이스의 모든 추상 메소드를 구현해야 한다. 인터페이스를 구현한 클래스에는 메소드를 추가로 작성할 수 있으며 인터페이스에 이미 구현되어 있는 default, private, static 메소드의 경우 따로 다시 작성하지 않아도 된다. 이 경우 이미 구현되어있는 메소드를 그대로 사용하게 된다.
클래스의 경우 인터페이스를 상속받을 수 없고 구현해야 한다. 인터페이스끼리만 상속 가능하며 extends 키워드를 사용한다. 자바에서 인터페이스를 다중 상속할 수 있다. 다음과 같이 여러 인터페이스를 나열하여 상속하면 된다.

클래스는 여러 인터페이스를 구현할 수 있다. 인터페이스를 나열하여 구현하고, 각 인터페이스에 선언된 모든 추상 메소드를 구현해야 한다.
클래스를 상속 받으면서 동시에 인터페이스를 구현할 수 있다. 다음과 같은 구조가 가능하다.
class A extends B implements C, D{
//내용
}
인터페이스는 다중 구현을 지원한다. 클래스는 다중 상속이 불가하므로 다중 구현을 통해 다중 상속을 대신할 수 있다. 설계를 위한 선언과 구현을 분리할 수 있다. 따라서 하나의 인터페이스로 필요에 맞게 다양하게 구현할 수 있다. 인터페이스를 공동으로 사용하더라도 다른 개발자의 메소드 구현부를 볼 수 없도록 할 수 있다. 이는 보안 측면 상에서 이점을 가진다.