1. 상속의 개념
상속은 객체지향 프로그래밍에서 부모 클래스가 자식 클래스에게 속성과 메서드를 물려주는 행위를 말합니다. 이는 이미 구현된 클래스의 기능을 새로운 클래스에서 재사용하는 개념을 포함하고 있습니다.
2. 재사용을 통한 코드 중복 감소
상속은 이미 잘 개발된 클래스의 기능을 새로운 클래스에서 활용함으로써 코드의 중복을 피할 수 있습니다. 부모 클래스에서 정의된 속성과 메서드를 자식 클래스에서 그대로 사용하거나 필요에 맞게 수정하여 재활용함으로써 개발 시간을 단축시킬 수 있습니다.
이와 같이 상속은 코드의 재사용성을 높여 유지보수성을 향상시키는 중요한 프로그래밍 개념 중 하나입니다.
1. 부모 클래스 수정의 효과
상속은 부모 클래스를 수정함으로써 모든 자식 클래스에게 해당 변경 사항이 전파된다는 특징을 가지고 있습니다. 즉, 부모 클래스의 수정이 자식 클래스들에게 영향을 미치게 되어, 일관성 있는 변경을 쉽게 적용할 수 있습니다.
2. 코드의 일관성과 유지보수성 향상
부모 클래스의 수정이 필요한 경우, 해당 변경 내용이 모든 자식 클래스에 자동으로 적용되므로 각 클래스를 일일이 수정할 필요가 없습니다. 이는 코드의 일관성을 유지하고 유지보수성을 향상시킵니다. 새로운 기능이나 수정된 기능을 추가하기 위해서는 기존의 구조를 크게 건드리지 않고도 변경이 가능하다는 장점을 제공합니다.
이러한 특성은 큰 프로젝트에서 특히 유용하며, 코드의 확장성을 높여 새로운 요구사항에 대응하기 쉽도록 만들어줍니다.
프로그래밍에서 상속 관계에서 자식 클래스는 어떤 부모 클래스로부터 상속받을 것인지를 선택하는 개념이 중요합니다. 이 선택은 자식 클래스가 부모 클래스의 특성과 기능을 활용하고 확장할 수 있게 합니다.
1. 클래스의 목적에 따른 선택
자식 클래스는 자신의 목적에 맞게 어떤 부모 클래스를 선택할지 결정합니다. 부모 클래스가 제공하는 기본 기능을 활용하면서도, 자식 클래스는 추가적인 특성이나 동작을 정의하여 부모 클래스를 확장할 수 있습니다.
2. 코드의 재사용과 일관성
적절한 부모 클래스 선택은 코드의 재사용성을 높이고 일관성을 유지하는 데 도움이 됩니다. 자식 클래스가 부모 클래스를 선택함으로써, 이미 검증된 코드를 활용하고 필요한 기능을 간편하게 추가할 수 있습니다.
3. 다중 상속과 인터페이스의 선택
일부 프로그래밍 언어에서는 다중 상속이 허용되기도 하며, 이때 자식 클래스는 여러 부모 클래스로부터 상속을 받을 수 있습니다. 또한, 인터페이스를 통한 다중 상속도 가능하며, 자식 클래스는 필요한 인터페이스를 선택하여 해당 기능을 구현할 수 있습니다.
이러한 선택은 객체지향 프로그래밍에서의 유연하고 효율적인 코드 작성을 지원하며, 각 클래스 간의 관계를 명확히 정의하여 소프트웨어의 구조를 개선할 수 있습니다.
객체지향 프로그래밍에서 모든 객체는 생성자를 호출하여 생성됩니다. 이는 부모 객체에 대해서도 마찬가지이며, 부모 객체의 생성자는 자식 객체의 생성 과정에서 특정한 방법으로 호출됩니다. 이를 통해 부모 클래스의 초기화 코드가 실행되고 자식 클래스에서 추가된 내용이 올바르게 초기화됩니다.
1. 부모 생성자 호출: super()
자식 클래스의 생성자 내부에서 super()
라는 키워드를 사용하여 부모 클래스의 생성자를 호출합니다. 이는 자식 클래스가 부모 클래스를 상속받을 때 사용됩니다. super()
를 호출함으로써 부모 클래스의 생성자가 실행되어 부모 객체가 초기화되고, 이후에 자식 클래스의 추가적인 초기화 코드가 실행됩니다.
2. 초기화 순서와 부모 클래스 호출
상속 관계에서는 초기화 순서가 중요합니다. 자식 클래스의 생성자가 호출되면 그 안에서 가장 먼저 super()
를 통해 부모 클래스의 생성자를 호출하므로, 부모 객체가 올바르게 초기화되어야 자식 객체가 적절하게 생성됩니다.
이런 초기화 과정은 객체의 상태를 안정적으로 설정하고, 상속 관계에서의 일관성을 유지하는 데에 기여합니다. 부모 클래스의 생성자 호출은 객체지향 프로그래밍에서 중요한 원칙 중 하나인 "최상위 클래스에서부터 초기화"를 따르는 일환입니다.
어떤 메소드는 부모 클래스에서 구현되었지만, 자식 클래스에서는 특정 상황에 맞게 수정이 필요한 경우가 있습니다. 이때 자식 클래스에서 부모 클래스의 메소드를 다시 정의하는 것을 "메소드 오버라이딩" 또는 "메소드 재정의"라고 합니다.
1. 적합하지 않은 메소드 수정
일부 메소드는 부모 클래스에서는 기본적으로 구현되었지만, 자식 클래스에서는 특별한 상황이나 요구사항에 맞게 수정이 필요한 경우가 있습니다. 이런 경우 자식 클래스에서 해당 메소드를 재정의하여 새로운 구현을 제공할 수 있습니다.
2. 메소드 오버라이딩의 특징
3. 예시: Java에서의 메소드 오버라이딩
class Parent {
void display() {
System.out.println("Parent Class");
}
}
class Child extends Parent {
// 메소드 오버라이딩
@Override
void display() {
System.out.println("Child Class");
}
}
public class Main {
public static void main(String[] args) {
Parent obj = new Child(); // 다형성
obj.display(); // Child Class가 출력됨
}
}
이 예시에서 Child
클래스는 Parent
클래스의 display
메소드를 오버라이딩하였습니다. 이후 Parent
타입의 객체로 Child
클래스를 참조하면, 메소드 오버라이딩에 의해 Child
클래스의 메소드가 호출됩니다.
자바에서는 @Override
어노테이션을 사용하여 메소드가 정확히 오버라이딩되었는지를 체크할 수 있습니다. 이 어노테이션을 메소드 위에 명시하면 컴파일러가 해당 메소드가 부모 클래스의 메소드를 오버라이딩하는지를 확인하고, 오버라이딩이 아니라면 컴파일 오류를 발생시킵니다.
예시:
class Parent {
void display() {
System.out.println("Parent Class");
}
}
class Child extends Parent {
// @Override 어노테이션을 사용하여 오버라이딩을 명시
@Override
void display() {
System.out.println("Child Class");
}
}
public class Main {
public static void main(String[] args) {
Parent obj = new Child(); // 다형성
obj.display(); // Child Class가 출력됨
}
}
여기서 @Override
어노테이션을 사용하면, 만약 display
메소드가 Parent
클래스의 메소드를 정확히 오버라이딩하지 않으면 컴파일러가 오류를 발생시킵니다. 이는 개발자가 오버라이딩의 실수를 미리 방지하고 코드의 안정성을 높이는데 도움을 줍니다.
1. super 키워드의 역할
자식 클래스에서 부모 클래스의 메소드를 오버라이딩할 때, super 키워드를 사용하면 숨겨진(가려진) 부모 클래스의 메소드를 호출할 수 있습니다. super 키워드는 부모 클래스를 가리키는 참조 변수로 사용되며, 이를 통해 부모 클래스의 멤버에 접근할 수 있습니다.
2. 부모 메소드 호출 방법
3. 예시: Java에서 super를 이용한 부모 메소드 호출
class Parent {
void display() {
System.out.println("Parent Class");
}
}
class Child extends Parent {
// 부모 메소드 오버라이딩
@Override
void display() {
super.display(); // super 키워드를 사용하여 부모 클래스의 메소드 호출
System.out.println("Child Class");
}
}
public class Main {
public static void main(String[] args) {
Child obj = new Child();
obj.display();
}
}
이 예시에서 Child
클래스에서 display
메소드를 오버라이딩하면서, super.display()
를 통해 부모 클래스의 display
메소드를 호출하고 자식 클래스에서 필요한 추가 동작을 수행합니다. 결과적으로, 부모 클래스의 기능을 유지하면서 자식 클래스에서의 특별한 처리를 할 수 있습니다.
final
키워드를 클래스 앞에 붙이면 해당 클래스는 상속이 불가능하며, 다른 클래스가 이 클래스를 확장(상속)할 수 없습니다. 이러한 final
클래스는 변하지 않고 고정된 형태로 사용되어야 하는 경우에 유용합니다.
1. 상속 불가능한 특성
final
클래스로 선언된 클래스는 다른 클래스가 이를 상속할 수 없습니다.2. 예시: Java에서의 final 클래스
final class FinalClass {
// final 클래스 내용
}
// 아래의 코드는 컴파일 오류를 발생시킵니다.
// final 클래스는 상속이 불가능하므로 아래 코드는 허용되지 않습니다.
class ChildClass extends FinalClass {
// ...
}
3. 주요 용도
final
키워드는 클래스뿐만 아니라 메소드와 변수에도 적용될 수 있어, 상속 불가능성 이외에도 다양한 의미로 활용될 수 있습니다.
메소드에 final
키워드를 붙이면 해당 메소드는 하위 클래스에서 오버라이딩(재정의)할 수 없습니다. 이렇게 선언된 final
메소드는 부모 클래스에서 정의된 그대로의 동작을 유지하며 하위 클래스에서 변경할 수 없게 됩니다.
1. 오버라이딩 불가능한 특성
final
메소드로 선언된 메소드는 하위 클래스에서 오버라이딩을 할 수 없습니다.2. 예시: Java에서의 final 메소드
class Parent {
// final 메소드 선언
final void display() {
System.out.println("Parent Class");
}
}
class Child extends Parent {
// 아래의 코드는 컴파일 오류를 발생시킵니다.
// final 메소드는 오버라이딩이 불가능하므로 아래 코드는 허용되지 않습니다.
void display() {
System.out.println("Child Class");
}
}
3. 주요 용도
final
키워드는 클래스, 메소드, 변수에 적용될 수 있어, 다양한 상황에서 사용됩니다. final
메소드는 부모 클래스에서의 메소드 동작을 하위 클래스에서 변경하지 않도록 하는 중요한 개념 중 하나입니다.
protected
접근 제한자는 해당 멤버(변수 또는 메소드)가 선언된 클래스의 패키지 내에서는 물론, 해당 클래스를 상속받은 하위 클래스에서도 접근이 가능하도록 하는 제한자입니다. 이는 패키지 외부에서는 접근이 불가능합니다.
1. 사용 가능한 범위
protected
멤버에 접근 가능protected
멤버에 접근 가능2. 예시: Java에서의 protected 접근 제한자
// 같은 패키지에 위치한 클래스
package com.example;
class Parent {
protected int protectedVariable = 10;
}
// 같은 패키지에 위치한 클래스에서 사용 가능
class SamePackageClass {
void accessProtectedMember() {
Parent parent = new Parent();
int value = parent.protectedVariable; // 접근 가능
}
}
// 다른 패키지에 위치한 하위 클래스
package com.example.otherpackage;
class Child extends com.example.Parent {
void accessProtectedMember() {
int value = protectedVariable; // 접근 가능
}
}
3. 사용 시 주의사항
protected
멤버에 대한 남용은 적절하지 않을 수 있습니다. 과도한 공개는 캡슐화를 위반하고 의도치 않은 접근을 허용할 수 있습니다.protected
멤버를 사용할 때 해당 멤버가 외부에 공개될 필요가 있는지 신중히 고려해야 합니다.클래스의 타입 변환은 상속 관계에 있는 클래스 사이에서 발생할 수 있습니다. 특히, 자식 클래스는 부모 클래스의 타입으로 자동으로 변환될 수 있습니다. 이러한 현상은 다형성(Polymorphism)을 통해 구현되며, 자식 클래스는 부모 클래스의 일종으로 취급될 수 있습니다.
1. 상위 타입으로의 자동 타입 변환
2. 예시: Java에서의 자동 타입 변환
class Parent {
void display() {
System.out.println("Parent Class");
}
}
class Child extends Parent {
void show() {
System.out.println("Child Class");
}
}
public class Main {
public static void main(String[] args) {
// 상위 타입으로의 자동 타입 변환
Parent parentObj = new Child(); // 다형성(Polymorphism)
// 부모 클래스의 메소드 호출
parentObj.display(); // 출력: Parent Class
// 아래의 코드는 컴파일 오류를 발생시킵니다.
// 자동 타입 변환된 변수는 부모 클래스의 멤버만 직접적으로 접근 가능합니다.
// parentObj.show(); // 오류: The method show() is undefined for the type Parent
}
}
위의 예시에서 Child
클래스의 인스턴스를 Parent
클래스의 변수에 할당하면 자동으로 상위 타입으로 변환되어 부모 클래스의 멤버에 접근할 수 있습니다. 하지만, 부모 클래스의 변수로는 자식 클래스에 추가된 멤버에 직접적으로 접근할 수 없습니다.
3. 다운캐스팅 (Downcasting)
Child childObj = (Child) parentObj; // 다운캐스팅
childObj.show(); // 다운캐스팅 후 자식 클래스의 메소드 호출
다운캐스팅은 실제로 가리키는 객체가 해당 클래스의 인스턴스인지 확인하는 작업이 필요하며, 이를 위해 instanceof
연산자를 사용할 수 있습니다.
if (parentObj instanceof Child) {
Child childObj = (Child) parentObj; // 다운캐스팅
childObj.show(); // 다운캐스팅 후 자식 클래스의 메소드 호출
}
클래스의 타입 변환과 다운캐스팅을 활용하면 유연하고 다양한 형태의 객체를 처리할 수 있으며, 다형성을 통해 코드의 확장성을 높일 수 있습니다.
다형성을 통해 부모 타입으로 자동 변환된 이후에는 부모 클래스에 선언된 필드와 메소드만 직접적으로 접근이 가능합니다. 그러나 자식 클래스에서 해당 메소드가 오버라이딩(재정의)되었다면, 객체의 실제 타입에 맞춰서 오버라이딩된 메소드가 호출됩니다.
1. 필드와 메소드 접근
2. 오버라이딩된 메소드 호출
3. 예시: Java에서의 다형성과 오버라이딩
class Parent {
void display() {
System.out.println("Parent Class");
}
}
class Child extends Parent {
// 부모 클래스의 메소드를 오버라이딩
@Override
void display() {
System.out.println("Child Class");
}
// 자식 클래스에 추가된 메소드
void show() {
System.out.println("Show method in Child Class");
}
}
public class Main {
public static void main(String[] args) {
// 다형성을 통한 자동 타입 변환
Parent parentObj = new Child();
// 부모 클래스의 메소드 호출
parentObj.display(); // 출력: Child Class (오버라이딩된 메소드 호출)
// 아래의 코드는 컴파일 오류를 발생시킵니다.
// 부모 클래스의 타입으로 선언된 변수는 자식 클래스에 추가된 메소드에 직접 접근할 수 없음
// parentObj.show(); // 오류: The method show() is undefined for the type Parent
}
}
위의 예시에서 Parent
클래스의 display
메소드를 자식 클래스 Child
에서 오버라이딩하면, 객체의 실제 타입이 Child
이기 때문에 Child
클래스의 display
메소드가 호출됩니다. 하지만 Parent
클래스의 타입으로 선언된 변수는 자식 클래스에 추가된 show
메소드에 직접적으로 접근할 수 없습니다. 이는 다형성을 통해 부모 클래스의 일반적인 동작을 유지하면서도, 객체의 실제 타입에 따라 특정한 동작을 수행할 수 있도록 하는 중요한 특성 중 하나입니다.
다형성은 객체지향 프로그래밍에서 중요한 개념 중 하나로, 같은 메소드나 필드를 사용하는데 있어 실행 결과가 다양하게 나오는 성질을 나타냅니다. 이는 주로 메소드 오버라이딩과 자동 타입 변환을 통해 구현됩니다. 필드 다형성과 매개변수 다형성은 이러한 다형성의 구체적인 형태 중 일부입니다.
1. 필드 다형성(Field Polymorphism)
2. 매개변수 다형성(Parameter Polymorphism)
3. 예시: Java에서의 필드 다형성과 매개변수 다형성
class Animal {
String sound = "Generic Sound";
void makeSound() {
System.out.println(sound);
}
}
class Dog extends Animal {
Dog() {
sound = "Bark";
}
}
class Cat extends Animal {
Cat() {
sound = "Meow";
}
}
public class Main {
static void useAnimal(Animal animal) {
animal.makeSound();
}
public static void main(String[] args) {
// 필드 다형성
Animal genericAnimal = new Animal();
Animal dog = new Dog();
Animal cat = new Cat();
System.out.println("Field Polymorphism:");
System.out.println(genericAnimal.sound); // 출력: Generic Sound
System.out.println(dog.sound); // 출력: Bark
System.out.println(cat.sound); // 출력: Meow
// 매개변수 다형성
System.out.println("\nParameter Polymorphism:");
useAnimal(genericAnimal); // 출력: Generic Sound
useAnimal(dog); // 출력: Bark
useAnimal(cat); // 출력: Meow
}
}
위의 예시에서 Animal
클래스를 상속받은 Dog
와 Cat
클래스는 각각 다른 소리를 내도록 구현되어 있습니다. useAnimal
메소드는 Animal
타입의 매개변수를 받으므로, 이를 활용하여 Dog
와 Cat
객체를 동일한 메소드로 처리할 수 있습니다. 이는 다형성의 한 예로, 같은 메소드를 사용하더라도 실행 결과가 다양하게 나올 수 있도록 합니다.
자바에서는 instanceof
연산자를 사용하여 객체가 특정 타입의 인스턴스인지를 확인할 수 있습니다. 이 연산자는 주어진 객체가 특정 클래스나 인터페이스의 인스턴스인 경우에 true
를 반환하고, 그렇지 않은 경우에는 false
를 반환합니다.
instanceof
연산자의 사용:
boolean result = 객체 instanceof 타입;
예시:
class Animal {
// Animal 클래스의 멤버
}
class Dog extends Animal {
// Dog 클래스의 멤버
}
public class Main {
public static void main(String[] args) {
Animal animal = new Dog();
// instanceof 연산자를 사용하여 객체의 타입 확인
boolean isDog = animal instanceof Dog;
if (isDog) {
System.out.println("This is a Dog!");
} else {
System.out.println("This is not a Dog.");
}
}
}
위의 예시에서 animal instanceof Dog
는 animal
이 Dog
클래스의 인스턴스인지를 확인합니다. 이를 통해 프로그램이 실행 중에 객체의 실제 타입을 확인하고, 이에 따른 특정한 동작을 수행할 수 있습니다. 이는 다형성과 함께 사용되어 메소드의 다양한 매개변수 처리에 유용하게 활용될 수 있습니다.
추상 클래스는 클래스들 간의 공통된 필드나 메소드를 추출하여 선언한 클래스로, 객체를 직접 생성할 수 없고 상속을 통해 자식 클래스를 만들도록 설계됩니다. 추상 클래스는 abstract
키워드를 사용하여 선언되며, 추상 메소드를 포함할 수 있습니다.
1. 특징:
2. 예시: Java에서의 추상 클래스
// 추상 클래스 선언
abstract class Shape {
// 추상 메소드 선언 (하위 클래스에서 반드시 구현되어야 함)
abstract void draw();
// 구현된 메소드
void display() {
System.out.println("Displaying Shape");
}
}
// 추상 클래스를 상속받은 구체적인 클래스
class Circle extends Shape {
// 추상 메소드 구현
@Override
void draw() {
System.out.println("Drawing Circle");
}
}
class Rectangle extends Shape {
// 추상 메소드 구현
@Override
void draw() {
System.out.println("Drawing Rectangle");
}
}
public class Main {
public static void main(String[] args) {
// 객체 생성 불가능 (추상 클래스는 직접 객체 생성이 불가능)
// Shape shape = new Shape(); // 오류
// 추상 클래스를 상속받은 객체 생성
Shape circle = new Circle();
Shape rectangle = new Rectangle();
// 메소드 호출
circle.draw(); // Drawing Circle
circle.display(); // Displaying Shape
rectangle.draw(); // Drawing Rectangle
rectangle.display(); // Displaying Shape
}
}
추상 클래스인 Shape
는 abstract
키워드로 선언되었으며, draw
메소드는 추상 메소드로 선언되었습니다. 하위 클래스인 Circle
와 Rectangle
에서는 추상 메소드 draw
를 반드시 구현해야 합니다. 추상 클래스는 공통된 동작이나 구조를 정의할 때 유용하며, 다형성을 통해 구현체들을 통일적으로 다룰 수 있습니다.
Java 15에서 도입된 Sealed Classes(봉인된 클래스)는 클래스의 상속을 특정한 하위 클래스들로 제한하여 보다 엄격한 제어를 가능케 합니다. sealed
키워드를 사용하여 클래스를 봉인하고, permits
키워드를 통해 허용된 하위 클래스들을 명시합니다.
1. Sealed Class의 선언:
sealed class SealedParent permits Subclass1, Subclass2 {
// 클래스의 내용
}
2. 하위 클래스 선언:
non-sealed
로 선언하거나, 다른 봉인된 클래스로 선언할 수 있습니다.non-sealed class NonSealedSubclass extends SealedParent {
// 클래스의 내용
}
// 또는
sealed class AnotherSealedClass permits Subclass3 {
// 클래스의 내용
}
final class Subclass1 extends SealedParent {
// 클래스의 내용
}
final class Subclass2 extends SealedParent {
// 클래스의 내용
}
non-sealed class Subclass3 extends AnotherSealedClass {
// 클래스의 내용
}
3. 사용 예시:
public class Main {
public static void main(String[] args) {
SealedParent obj1 = new Subclass1(); // 허용된 하위 클래스의 인스턴스 생성
SealedParent obj2 = new Subclass2(); // 허용된 하위 클래스의 인스턴스 생성
// SealedParent obj3 = new NonSealedSubclass(); // 오류: 허용되지 않은 하위 클래스
// Sealed 클래스를 상속받은 다른 봉인 클래스의 인스턴스 생성
AnotherSealedClass obj4 = new Subclass3();
}
}
4. 주의 사항:
permits
키워드를 사용하여 허용된 하위 클래스를 명시해야 합니다.final
이어야 합니다.final
이거나 non-sealed
로 선언되어야 합니다.봉인된 클래스를 사용함으로써 클래스 계층 구조를 명시적으로 제어할 수 있어, 코드의 안정성과 유지보수성을 향상시킬 수 있습니다.