일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 | 30 | 31 |
- java스터디
- java 애노테이션
- this키워드
- java final 키워드
- java 제네릭
- java 패키지
- java
- Java 자료구조
- javautil패키지
- java 추상 클래스
- import 키워드
- 오블완
- 창의적도구
- JAVA데이터타입
- java 람다식
- 생성형AI
- 화살표연산자
- java_this
- 티스토리챌린지
- java super메소드
- java반복문
- binraytree
- java 이진트리
- java 메서드 오버라이딩
- java objact클래스
- javascript
- javatime
- asyncawait
- gpt활용팁
- ai활용법
- Today
- Total
코딩쿠의 코드 연대기
Java) 상속_객체지향 프로그래밍(OOP) 본문
학습목표
- 자바 상속의 특징
- super 키워드
- 메서드 오버라이딩
- 다이내믹 메서드 디스패치 (Dynamic Method Dispatch)
- 추상 클래스
- final 키워드
- Object 클래스
자바 상속의 특징
Java 상속은 객체 지향 프로그래밍의 핵심 개념 중 하나로, 기존 클래스의 필드와 메서드를 물려받아 새로운 클래스를 생성하는 것을 의미합니다. Java 상속에는 다음과 같은 주요 특징들이 있습니다.
1. 단일 상속:
- Java는 단일 상속만 지원합니다. 즉, 한 클래스는 오직 하나의 부모 클래스만 가질 수 있습니다.
- 다중 상속을 허용하지 않는 이유는 다중 상속으로 인해 발생할 수 있는 모호성과 복잡성을 피하기 위함입니다. (예: 두 부모 클래스에 같은 이름의 메서드가 있는 경우 어떤 메서드를 상속받아야 할지 모호해짐)
2. 계층 구조:
- 모든 클래스는 묵시적으로
java.lang.Object
클래스를 상속받습니다. 즉,Object
클래스는 모든 클래스의 최상위 부모 클래스입니다. - 이를 통해 모든 클래스는
Object
클래스의 메소드 (예:toString()
,equals()
,hashCode()
)를 사용할 수 있습니다.
3. 접근 제어자:
- 상속받은 멤버에 대한 접근은 접근 제어자 (public, protected, private)에 의해 제한될 수 있습니다.
public
멤버는 어디서든 접근 가능하며,protected
멤버는 같은 패키지 내의 클래스 또는 자식 클래스에서 접근 가능합니다.private
멤버는 해당 클래스 내에서만 접근 가능하며 상속되지 않습니다.
4. 메소드 오버라이딩:
- 자식 클래스는 상속받은 메서드를 재정의 (오버라이딩)할 수 있습니다.
- 이를 통해 자식 클래스는 부모 클래스의 메서드와 동일한 이름을 가지지만, 다른 기능을 수행하는 메서드를 가질 수 있습니다.
5. super
키워드:
super
키워드를 사용하여 부모 클래스의 멤버에 접근할 수 있습니다.- 메소드 오버라이딩 시,
super
키워드를 사용하여 부모 클래스의 메서드를 호출할 수 있습니다.
6. final
키워드:
final
키워드를 사용하여 클래스를 상속할 수 없도록 하거나, 메서드를 오버라이딩할 수 없도록 할 수 있습니다.
7. 추상 클래스와 인터페이스:
- 추상 클래스는 하나 이상의 추상 메소드를 가지는 클래스입니다. 추상 메서드는 선언만 되어 있고 구현은 되어 있지 않은 메서드입니다.
- 인터페이스는 모든 메서드가 추상 메서드인 클래스입니다.
- 추상 클래스와 인터페이스는 상속을 통해 구현되어야 합니다.
8. 다형성:
- 상속은 다형성을 구현하는 데 중요한 역할을 합니다. 다형성이란 하나의 객체가 여러 가지 타입을 가질 수 있는 것을 의미합니다.
- 예를 들어, 부모 클래스 타입의 변수에 자식 클래스 객체를 할당할 수 있습니다.
이러한 특징들을 이해하면 Java 상속을 효과적으로 사용하여 코드 재사용성을 높이고, 유지보수를 용이하게 하며, 객체 지향 프로그래밍의 장점을 극대화할 수 있습니다.
super 키워드
Java의 super
키워드는 상속 관계에서 부모 클래스의 멤버에 접근하기 위해 사용되는 키워드입니다.
super
키워드는 다음과 같은 두 가지 주요 용도로 사용됩니다.
1. 부모 클래스의 멤버에 접근:
- 자식 클래스에서 부모 클래스의 필드나 메서드를 사용하고 싶을 때
super
키워드를 사용합니다. - 특히 자식 클래스에 부모 클래스와 동일한 이름의 필드나 메서드가 정의된 경우,
super
키워드를 사용하여 부모 클래스의 멤버를 명시적으로 참조할 수 있습니다.
Java
class Parent {
int num = 10;
void printNum() {
System.out.println(num);
}
}
class Child extends Parent {
int num = 20;
void printNum() {
System.out.println(num); // 자식 클래스의 num 출력 (20)
System.out.println(super.num); // 부모 클래스의 num 출력 (10)
super.printNum(); // 부모 클래스의 printNum() 메소드 호출
}
}
2. 부모 클래스의 생성자 호출:
- 자식 클래스의 생성자에서
super()를
사용하여 부모 클래스의 생성자를 호출할 수 있습니다. super()는
반드시 자식 클래스 생성자의 첫 번째 줄에 위치해야 합니다.- 부모 클래스에 기본 생성자가 없는 경우,
super()를
사용하여 명시적으로 부모 클래스의 생성자를 호출해야 합니다.
Java
class Parent {
Parent(int num) {
System.out.println("Parent 생성자 호출: " + num);
}
}
class Child extends Parent {
Child(int num) {
super(num); // 부모 클래스의 생성자 호출
System.out.println("Child 생성자 호출: " + num);
}
}
주의 사항:
super
키워드는 자식 클래스 내에서만 사용할 수 있습니다.private
접근 제어자로 선언된 부모 클래스의 멤버는super
키워드를 사용해도 접근할 수 없습니다.
super
키워드를 효과적으로 사용하면 상속 관계에서 부모 클래스의 멤버를 활용하여 코드의 재사용성을 높이고, 효율적인 코드를 작성할 수 있습니다.
메서드 오버라이딩
Java 메서드 오버라이딩은 상속 관계에서 자식 클래스가 부모 클래스로부터 상속받은 메소드를 재정의하는 것을 의미합니다. 즉, 부모 클래스의 메서드와 이름, 매개변수, 반환 타입은 동일하지만, 자식 클래스에서 해당 메서드의 기능을 다르게 구현하는 것입니다.
오버라이딩 조건:
- 상속 관계: 부모 클래스와 자식 클래스 사이에 상속 관계가 있어야 합니다.
- 메소드 시그니처 동일: 오버라이딩하려는 메소드의 이름, 매개변수, 반환 타입이 부모 클래스의 메소드와 완전히 동일해야 합니다.
- 접근 제어자: 자식 클래스에서 오버라이딩하는 메소드의 접근 제어자는 부모 클래스의 메서드보다 좁은 범위로 변경할 수 없습니다. (예: 부모 클래스 메서드가
protected
이면 자식 클래스에서는protected
또는public으로만
오버라이딩 가능) - 예외: 자식 클래스에서 오버라이딩하는 메서드는 부모 클래스의 메서드보다 더 많은 수의 예외를 선언할 수 없습니다.
오버라이딩 예시:
Java
class Animal {
public void sound() {
System.out.println("동물 소리");
}
}
class Dog extends Animal {
@Override
public void sound() {
System.out.println("멍멍");
}
}
class Cat extends Animal {
@Override
public void sound() {
System.out.println("야옹");
}
}
위 예시에서 Animal
클래스의 sound()
메서드를 Dog
클래스와 Cat
클래스에서 각각 오버라이딩하여 "멍멍"과 "야옹"을 출력하도록 재정의했습니다.
@Override 어노테이션:
@Override
어노테이션은 메서드가 오버라이딩되었음을 컴파일러에게 알려주는 역할을 합니다.- 이 어노테이션을 사용하면 컴파일러가 오버라이딩 조건을 검사하여 오류를 방지할 수 있습니다. (예: 메소드 이름을 잘못 입력했거나 매개변수 타입이 다른 경우 컴파일 오류 발생)
super 키워드 활용:
- 자식 클래스에서 오버라이딩된 메소드 내에서
super
키워드를 사용하여 부모 클래스의 메서드를 호출할 수 있습니다. - 이를 통해 부모 클래스의 기능을 재사용하면서 자식 클래스에서 필요한 부분만 추가하거나 변경할 수 있습니다.
오버라이딩의 장점:
- 다형성 구현: 부모 클래스 타입의 변수로 자식 클래스 객체를 참조하여 다양한 자식 클래스의 메소드를 호출할 수 있습니다.
- 코드 재사용성 향상: 부모 클래스의 기본적인 기능을 재사용하면서 자식 클래스에서 필요한 부분만 수정하여 코드 중복을 줄일 수 있습니다.
- 유연성 증가: 프로그램의 요구사항 변화에 따라 자식 클래스에서 메소드를 재정의하여 유연하게 대응할 수 있습니다.
오버로딩과의 차이점:
- 오버라이딩은 상속 관계에서 부모 클래스의 메서드를 재정의하는 것이고, 오버로딩은 같은 클래스 내에서 동일한 이름의 메소드를 여러 개 정의하는 것입니다.
- 오버라이딩은 메소드 시그니처가 동일해야 하지만, 오버로딩은 매개변수의 개수나 타입이 달라야 합니다.
메소드 오버라이딩은 Java에서 객체 지향 프로그래밍의 핵심 개념 중 하나이며, 다형성을 구현하고 코드 재사용성을 높이는 데 중요한 역할을 합니다.
다이내믹 메서드 디스패치 (Dynamic Method Dispatch)
Java 다이내믹 메서드 디스패치는 런타임에 오버라이딩된 메서드를 호출할 객체를 결정하는 메커니즘입니다. 즉, 컴파일 시점에는 어떤 메소드가 호출될지 알 수 없고, 프로그램 실행 중에 객체의 타입에 따라 호출될 메소드가 결정됩니다.
다이나믹 메소드 디스패치의 작동 방식:
- 컴파일 타임: 컴파일러는 변수의 선언된 타입을 기반으로 메소드 호출을 확인합니다.
- 런타임: JVM (Java Virtual Machine)은 실제 객체의 타입을 확인하고, 해당 타입에 맞는 오버라이딩된 메소드를 호출합니다.
다이나믹 메소드 디스패치의 예시:
Java
class Animal {
public void sound() {
System.out.println("동물 소리");
}
}
class Dog extends Animal {
@Override
public void sound() {
System.out.println("멍멍");
}
}
class Cat extends Animal {
@Override
public void sound() {
System.out.println("야옹");
}
}
public class Main {
public static void main(String[] args) {
Animal animal1 = new Dog();
Animal animal2 = new Cat();
animal1.sound(); // "멍멍" 출력
animal2.sound(); // "야옹" 출력
}
}
위 예시에서 animal1
과 animal2
변수는 모두 Animal
타입으로 선언되었지만, 런타임에 각각 Dog
객체와 Cat
객체를 참조합니다. 따라서 animal1.sound()
는 Dog
클래스의 sound()
메소드를 호출하고, animal2.sound()
는 Cat
클래스의 sound()
메소드를 호출합니다.
다이나믹 메소드 디스패치의 장점:
- 다형성 구현: 다이나믹 메소드 디스패치는 Java에서 다형성을 구현하는 핵심 메커니즘입니다. 다형성을 통해 코드를 유연하고 확장 가능하게 만들 수 있습니다.
- 코드 재사용성 향상: 부모 클래스의 메소드를 재사용하면서 자식 클래스에서 필요에 따라 메소드를 재정의하여 코드 중복을 줄일 수 있습니다.
- 유지보수 용이성: 프로그램의 요구사항 변경에 따라 자식 클래스에서 메소드를 재정의하여 유연하게 대응할 수 있습니다.
다이내믹 메서드 디스패치와 관련된 개념:
- 가상 메소드 테이블 (vtable): JVM은 각 클래스에 대해 가상 메서드 테이블을 생성하고, 이 테이블에는 클래스의 오버라이딩된 메소드에 대한 정보가 저장됩니다. 런타임에 객체의 타입을 확인하고, 해당 타입의 가상 메소드 테이블에서 메소드를 찾아 호출합니다.
- 정적 메소드 디스패치: 컴파일 시점에 호출될 메서드가 결정되는 메커니즘입니다. private 메소드, static 메소드, final 메소드는 정적 메소드 디스패치를 사용합니다.
다이내믹 메서드 디스패치는 Java의 강력한 기능 중 하나이며, 객체 지향 프로그래밍의 핵심 개념인 다형성을 구현하는 데 중요한 역할을 합니다.
추상 클래스
Java에서 추상 클래스는 미완성 설계도와 같은 역할을 합니다. 쉽게 말해, 객체를 직접 생성할 수 없는 클래스입니다.
추상 클래스의 특징
abstract
키워드:abstract
키워드를 사용하여 선언합니다.- 추상 메서드: 본문이 없는, 선언만 있는 메서드를 포함할 수 있습니다. 이러한 메소드는 상속받는 클래스에서 반드시 구현해야 합니다.
- 인스턴스 생성 불가: 추상 클래스는 미완성이기 때문에 직접 객체를 생성할 수 없습니다.
- 상속: 다른 클래스에게 상속되어 사용됩니다. 상속받는 클래스는 추상 클래스의 모든 추상 메소드를 구현해야만 합니다.
- 일반 메서드와 멤버 변수: 추상 메서드뿐만 아니라 일반 메서드와 멤버 변수도 가질 수 있습니다.
추상 클래스 예시
Java
abstract class Animal {
String name;
public Animal(String name) {
this.name = name;
}
public void breathe() {
System.out.println("숨을 쉽니다.");
}
public abstract void sound(); // 추상 메소드
}
class Dog extends Animal {
public Dog(String name) {
super(name);
}
@Override
public void sound() {
System.out.println("멍멍");
}
}
class Cat extends Animal {
public Cat(String name) {
super(name);
}
@Override
public void sound() {
System.out.println("야옹");
}
}
위 예시에서 Animal
은 추상 클래스이며, sound()
메서드는 추상 메서드입니다. Dog
과 Cat
클래스는 Animal
을 상속받아 sound()
메서드를 각자의 방식으로 구현합니다.
추상 클래스를 사용하는 이유
- 공통적인 특징 정의: 관련된 클래스들의 공통적인 특징을 추상 클래스로 정의하여 코드 중복을 줄이고 재사용성을 높일 수 있습니다.
- 구현 강제: 추상 메소드를 통해 상속받는 클래스에서 특정 메소드를 반드시 구현하도록 강제할 수 있습니다.
- 설계 유연성: 추상 클래스를 사용하면 프로그램의 구조를 유연하게 설계하고 변경할 수 있습니다.
- 다형성: 추상 클래스를 통해 다형성을 구현하여 코드를 더욱 효율적으로 관리할 수 있습니다.
추상 클래스 vs 인터페이스
추상 클래스와 인터페이스는 모두 추상화를 제공하지만 몇 가지 차이점이 있습니다.
특징 | 추상 클래스 | 인터페이스 |
---|---|---|
메소드 | 추상 메소드와 일반 메소드 모두 가질 수 있음 | 모든 메소드는 추상 메소드 |
멤버 변수 | 가질 수 있음 | 상수만 가질 수 있음 |
상속 | 단일 상속만 가능 | 다중 상속 가능 |
관계 | "is-a" 관계 | "can-do" 관계 |
추상 클래스는 인터페이스보다 더 많은 기능을 제공하지만, 단일 상속만 가능하다는 제약이 있습니다. 인터페이스는 다중 상속이 가능하여 더 유연한 설계가 가능하지만, 추상 클래스만큼 풍부한 기능을 제공하지는 않습니다.???
어떤 것을 사용할지는 프로그램의 요구사항과 설계에 따라 선택해야 합니다.
final 키워드
Java에서 final
키워드는 "변경 불가능"을 의미합니다. 변수, 메서드, 클래스에 적용되어 각각 다른 의미를 갖습니다.
1. final 변수
- 값 변경 불가: 한 번 값이 할당되면 더 이상 변경할 수 없습니다. 즉, 상수가 됩니다.
- 초기화 시점: 선언과 동시에 초기화하거나, 생성자에서 초기화해야 합니다.
- 장점:
- 불변성 보장: 프로그램 실행 중 값이 변경되지 않아 안정성을 높입니다.
- 코드 가독성 향상: 변수가 상수임을 명확하게 나타내어 코드 이해를 돕습니다.
- 멀티스레드 환경에서 안전성: 값 변경으로 인한 동시성 문제를 예방합니다.
Java
final int MAX_VALUE = 100;
MAX_VALUE = 200; // 오류 발생! final 변수는 값 변경 불가
2. final 메소드
- 오버라이딩 불가: 상속받은 클래스에서 해당 메서드를 재정의할 수 없습니다.
- 장점:
- 메소드 기능 유지: 중요한 메서드의 기능이 의도치 않게 변경되는 것을 방지합니다.
- 클래스 설계 제어: 상속 관계에서 메소드의 동작을 일관되게 유지합니다.
Java
class Parent {
final void print() {
System.out.println("부모 클래스 메소드");
}
}
class Child extends Parent {
@Override
void print() { // 오류 발생! final 메소드는 오버라이딩 불가
System.out.println("자식 클래스 메소드");
}
}
3. final 클래스
- 상속 불가: 해당 클래스를 상속하여 새로운 클래스를 만들 수 없습니다.
- 장점:
- 클래스 기능 보호: 클래스의 기능이 확장되거나 변경되는 것을 방지합니다.
- 보안 강화: 악의적인 목적으로 클래스를 상속하여 기능을 변경하는 것을 막습니다.
Java
final class FinalClass {
// ...
}
class SubClass extends FinalClass { // 오류 발생! final 클래스는 상속 불가
// ...
}
주의 사항
- final 변수와 객체: final 변수에 객체를 할당하는 경우, 변수가 참조하는 객체 자체는 변경할 수 없습니다. 하지만 객체 내부의 상태 (멤버 변수 값)는 변경 가능할 수 있습니다.
- final과 static:
static final
키워드를 함께 사용하면 클래스 레벨의 상수를 정의할 수 있습니다. 이는 프로그램 전체에서 공유되는 불변 값을 나타낼 때 유용합니다.
final
키워드는 Java 프로그램의 안정성, 유지보수성, 보안성을 향상하는 데 도움이 되는 중요한 키워드입니다.
Object 클래스
Java에서 Object
클래스는 모든 클래스의 최상위 부모 클래스입니다. 즉, Java에서 생성하는 모든 클래스는 암시적으로 Object
클래스를 상속받습니다.
Object 클래스의 특징
- 모든 클래스의 조상: 모든 클래스는 직접적으로든 간접적으로든
Object
클래스를 상속받습니다. 즉,Object
클래스의 모든 메서드와 속성을 물려받습니다. - 기본적인 메소드 제공: 객체의 생성, 비교, 해시 코드 생성, 문자열 표현 등 객체 조작에 필요한 기본적인 메서드를 제공합니다.
- 다형성 지원:
Object
타입의 변수는 어떤 객체든 참조할 수 있어 다형성을 구현하는 데 중요한 역할을 합니다.
Object 클래스의 주요 메소드
메소드 | 설명 |
---|---|
toString() |
객체를 문자열로 표현합니다. |
equals(Object obj) |
현재 객체와 지정된 객체가 같은지 비교합니다. |
hashCode() |
객체의 해시 코드를 반환합니다. |
getClass() |
객체의 클래스 정보를 반환합니다. |
clone() |
객체의 복사본을 생성합니다. |
finalize() |
객체가 가비지 컬렉션되기 전에 호출됩니다. |
notify() |
객체를 기다리는 하나의 스레드를 깨웁니다. |
notifyAll() |
객체를 기다리는 모든 스레드를 깨웁니다. |
wait() |
현재 스레드가 다른 스레드가 notify() 또는 notifyAll() 메소드를 호출할 때까지 기다리도록 합니다. |
wait(long timeout) |
지정된 시간 동안 또는 다른 스레드가 notify() 또는 notifyAll() 메소드를 호출할 때까지 현재 스레드가 기다리도록 합니다. |
wait(long timeout, int nanos) |
지정된 시간 동안 또는 다른 스레드가 notify() 또는 notifyAll() 메소드를 호출할 때까지 현재 스레드가 기다리도록 합니다. |
Object 클래스 활용
- 메소드 오버라이딩:
toString()
,equals()
,hashCode()
등의 메서드는 클래스의 특성에 맞게 재정의하여 사용할 수 있습니다. - 다형성:
Object
타입의 변수는 어떤 객체든 참조할 수 있으므로, 다양한 타입의 객체를 동일한 방식으로 처리할 수 있습니다. - 객체 비교:
equals()
메서드를 사용하여 객체의 내용을 비교할 수 있습니다. - 해시 기반 컬렉션:
hashCode()
메서드는 해시 기반 컬렉션 (HashMap, HashSet 등)에서 객체를 저장하고 검색하는 데 사용됩니다.
Object 클래스 예시
Java
class Person {
String name;
int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Person person = (Person) o;
return age == person.age && Objects.equals(name, person.name);
}
@Override
public int hashCode() {
return Objects.hash(name, age);
}
}
위 예시에서는 Person
클래스가 Object
클래스의 toString()
, equals()
, hashCode()
메서드를 오버라이딩하여 Person
객체의 특성에 맞게 재정의했습니다.
Object
클래스는 Java 클래스 계층 구조의 뿌리이며, 모든 객체의 공통적인 동작을 정의합니다. Java 프로그래밍에서 매우 중요한 역할을 하므로, 그 기능과 활용법을 이해하는 것이 중요합니다.
'코딩스터디 > JAVA스터디' 카테고리의 다른 글
Java) 인터페이스 (interface) (6) | 2024.11.14 |
---|---|
Java 패키지 (6) | 2024.11.13 |
java 자료구조) 이진트리 (BinrayTree) (0) | 2024.11.11 |
객체지향 프로그래밍 (OOP) (5) | 2024.11.10 |
Java 자료구조) ArrayList, LinkedList (Stack, Queue) (2) | 2024.11.09 |