상속의 특징
- 클래스 다중 상속 X (extends 뒤에 하나의 클래스만 가능)
- 슈퍼 클래스(부모클래스)의 생성자가 먼저 실행된 후, 서브 클래스(자식클래스)의 생성자가 실행된다.
- 서브 클래스에서 슈퍼 클래스의 생성자를 명시적으로 선택하지 않은 경우에 슈퍼 클래스의 기본 생성자를 실행하기 때문에, 슈퍼 클래스에서 기본 생성자 없이 매개변수 생성자만 있는 경우에는 아래와 같은 오류가 발생한다.
class A {
public A(int x) { // 기본 생성자 없어서 오류 발생
System.out.println("매개변수 생성자 A");
}
}
class B extends A {
public B() { // 상속으로 인해 A 생성자 호출
System.out.println("생성자 B");
}
}
public class ConstructorEx {
public static void main(String[] args) {
B b = new b();
}
}
// 에러 메세지
Implicit super constructor A() is undefined. Must explicitly invoke another constructor.
만약 슈퍼 클래스의 특정 생성자를 호출하고자 한다면 super()를 사용하면 된다.
단,부모 클래스가 먼저 선언되어야 부모 클래스의 멤버를 사용할 수 있기 때문에, super()는 반드시 생성자의 첫 라인에 사용되어야 한다.
class A {
public A() {
System.out.println("생성자 A");
}
public A(int x) {
System.out.println("매개변수 생성자 A");
}
}
class B extends A {
public B(int x) {
super(x); // 슈퍼클래스에서 int형 매개변수 하나를 가지는 생성자 호출
System.out.println("매개변수 생성자 B");
}
}
public class ConstructorEx {
public static void main(String[] args) {
B b = new b(5);
}
}
캐스팅 (=타입 변환)
- 업캐스팅
서브 클래스는 슈퍼 클래스의 속성을 상속받기 때문에, 서브 클래스 객체에 대한 레퍼런스를 슈퍼 클래스 타입으로 변환할 수 있다. 단, 업캐스팅한 레퍼런스로는 서브 클래스 객체 내 모든 멤버에 접근할 수는 없고, 슈퍼 클래스의 멤버에만 접근할 수 있다.
Person p;
Student s = new Student();
p = s; // 업캐스팅
Person pers = new Student(); // 업캐스팅
- 다운캐스팅
업캐스팅과는 달리, 다운캐스팅은 아래와 같이 명시적으로 타입 변환을 지정해야 한다.
업캐스팅한 레퍼런스를 다시 다운캐스팅하는 과정을 거치면 해당 레퍼런스는 이전처럼 서브 클래스 객체 내 모든 멤버에 접근할 수 있게 된다.
Student s = (Student)p; // 다운캐스팅
public class Main {
public static void main(String[] args) {
// 다형성 활용
Animal animal = new Cat();
animal.exist();
animal.makeSound();
Cat cat = (Cat) animal; // ✅ 다운캐스팅(부모Animal -> 자식Cat)
cat.scratch(); // ✅ 자식 클래스의 기능 활용 가능
}
}
다운캐스팅 시 주의사항과 instanceof 연산자
만약 여러 클래스가 상위 클래스를 상속하고, 각 클래스가 모두 상위 클래스로 업캐스팅한 레퍼런스를 가지고 있다고 가정해보자.
컴파일러는 다운캐스팅이 문법적으로 올바른지 여부만 검사해주기 때문에, 런타임시에 실제 어떤 객체가 변수에 할당되는지 검사해 주지 않는다.
컴파일 시점에는 오류 없이 통과되지만 런타임시점에 ClassCastException 이 발생할 가능성이 있다.
public class Main {
public static void main(String[] args) {
// 다운 캐스팅
Animal dog = new Dog();
// 문법적으로 잘못된건 아니라서 에러가 발생하지 않는다.
Cat cat1 = (Cat) dog; // ⚠️
cat1.scratch(); // ❌ 해당 라인을 실행할때만 에러 여부를 확인할 수 있다.
}
}
이때 상위 클래스의 객체에 어떤 클래스의 객체가 전달되어 있는지 쉽게 알 수 있는 방법이 바로 instanceof 연산자이다.
즉, instanceof 연산자는 레퍼런스가 가리키는 객체가 어떤 클래스 타입인지 구분할 수 있다.
주로 다운캐스팅 하기 전에 타입을 검사해서 ClassCastException 을 예방하는데 활용한다.
if(kim instanceof Person) // true
if(kim instanceof Student) // false
if(kim instanceof Professor) // true
public class Main {
public static void main(String[] args) {
Animal animal2 = new Dog();
// ✅ 안전한 다운캐스팅(animal2 가 Cat 의 인스턴스 유형인지 확인)
if (animal2 instanceof Cat) {
Cat cat = (Cat) animal2;
cat.scratch();
} else {
System.out.println("객체가 고양이가 아닙니다.");
}
}
}
메소드 오버라이딩(=동적 바인딩)
- 슈퍼 클래스 메소드의 접근 지정자보다 접근의 범위를 좁혀서 오버라이딩할 수 없음 (public > protected > default > private)
- static, private, final로 선언된 메소드는 서브 클래스에서 오버라이딩할 수 없음
class Shape {
protected String name;
public void Paint() {
draw(); // 동적 바인딩으로 인해 Circle 출력
}
public void draw() {
System.out.println("Shape");
}
}
public class Circle extends Shape {
@override
void draw() { // ❌ default로 접근 지정자 좁히면 오버라이딩 불가!
System.out.println("Circle");
}
public static void main(String[] args) {
Shape b = new Circle();
b.paint();
}
}
정적 바인딩 (super 사용)
정적 바인딩에 사용되는 super는 자바 컴파일러에 의해 지원되는 슈퍼 클래스에 대한 레퍼런스이다.
class Shape {
protected String name;
public void Paint() {
draw();
}
public void draw() {
System.out.println("Shape");
}
}
public class Circle extends Shape {
@override
public void draw() {
System.out.println("Circle");
super.name = "Shape"; // 정적 바인딩
super.draw(); // 정적 바인딩
System.out.println(name);
}
public static void main(String[] args) {
Shape b = new Circle();
b.paint();
}
}
추상 클래스 (abstract)
추상 메소드란 선언은 되어 있으나 구현되어 있지 않은 메소드이다.
추상 클래스란 객체를 생성할 수 없는 클래스로, 하위 클래스에 구현을 강제하기 위해 사용한다.
abstract class Naming { // 추상 클래스
public abstract String getName(); // 추상 메소드
}
추상 클래스의 조건
1. 추상 메소드를 포함하는 클래스
2. 추상 메소드가 없지만 abstract로 선언한 클래스
위 두 가지 경우 모두 추상 클래스로 선언해야 한다.
추상 클래스의 구현
추상 클래스를 상속 받은 서브 클래스는 반드시 추상 메소드를 오버라이딩해야 한다.
추상 클래스의 목적
슈퍼 클래스에 선언된 모든 추상 메소드를 서브 클래스에서 오버라이딩하여 실행 가능한 코드로 구현하는 것이다.
즉, 추상 클래스는 추상 메소드를 통해 서브 클래스가 구현할 메소드를 명료하게 알려주는 인터페이스 역할을 하고 서브 클래스는 추상 메소드를 목적에 맞게 구현하는 다형성을 실현할 수 있다.
인터페이스
- 객체를 생성할 수 없고, 소프트웨어 모듈을 규격화하게 위해 필요한 개념
- 필드(멤버 변수)를 만들 수 없으므로, 자동으로 변수가 `public static final`로 선언된다.
- 인테페이스는 규격을 정의하기 위한 것이기 때문에, 변수는 최소한으로 사용
- 인터페이스 구현을 위한 클래스는 implements 키워드 사용
- 상속 가능 (인터페이스는 다중 상속 가능)
인터페이스 구성
- 상수
- 추상 메소드
- default 메소드
- private 메소드
- static 메소드
interface PhoneInterface {
public static final int TIMEOUT = 10000; // 상수
int TIMEOUT2 = 10000;
public abstract void sendCall(); // 추상 메소드
void sendCall2();
public default void printLogo() { 생략 }; // 디폴트 메소드
default void printLogo() { 생략 };
}
class SamsungPhone implements PhoneInterface { // 인터페이스 구현 클래스
// PhoneInterface의 모든 추상메소드 구현
// 메소드 추가 작성 가능
}
추상 클래스 - 인터페이스 비교
공통점
- 객체 생성 불가
- 클래스의 다형성이 목적
차이점
추상 클래스 | 인터페이스 |
상수, 추상 메소드, 일반 메소드 포함 | 상수, 추상 메소드, 일반 메소드, 디폴트 메소드, static 메소드 포함 |
변수 필드 포함 | protected 접근 지정 선언 불가 |
다중 상속 지원 안 함 | 다중 상속 지원 |
Extends : 상위 클래스 멤버 상속
Implements : 상위 클래스 기능 필수 구현
'언어, 프레임워크 > Java' 카테고리의 다른 글
제네릭 컬렉션 (Vector, ArrayList, Iterator, HashMap, LinkedList) (0) | 2025.02.05 |
---|---|
JDK 패키지와 클래스 (1) | 2025.02.04 |
접근 지정자 / final (0) | 2025.02.03 |
this() / 가비지 컬렉션 (1) | 2025.02.03 |
예외 처리 (0) | 2025.01.31 |