프로그래밍 공부
객체지향 프로그래밍(클래스) 본문
클래스를 만들때 중요한 점
- 상태 ← 맴버 변수
- 객체의 생성 ← 생성자
- 어떤 행동을 할지 ← 메서드
생성자
기본 생성자
- 기본 생성자는 매개변수가 없는 생성자입니다. 만약 클래스에 생성자가 정의되어 있지 않다면
Java 컴파일러가 자동으로 매개변수가 없는 기본 생성자를 제공 - 기본 생성자는 클래스의 모든 필드를 기본값으로 초기화
- 기본 생성자는 매개변수가 없는 생성자입니다. 만약 클래스에 생성자가 정의되어 있지 않다면
매개변수가 있는 생성자
- 매개변수가 있는 생성자는 객체를 생성할 때 초기화해야 할 데이터를 인수로 전달받음.
이를 통해 생성되는 객체의 상태를 다양하게 설정가능
- 매개변수가 있는 생성자는 객체를 생성할 때 초기화해야 할 데이터를 인수로 전달받음.
생성자 오버로딩
- Java에서는 메소드 오버로딩과 마찬가지로 생성자도 오버로딩할 수 있음
- 즉, 같은 이름의 생성자를 여러 개 정의할 수 있으며, 각각 다른 매개변수 리스트를 가질 수 있음
this
키워드를 사용한 생성자 호출- 같은 클래스 내에서 다른 생성자를 호출할 때
this
키워드를 사용할 수 있음.
이를 통해 중복된 코드 작성을 피할 수 있음 - 기본 개념
this
키워드를 사용하면 같은 클래스 내에서 다른 생성자를 호출할 수 있습니다.this
를 사용한 생성자 호출은 반드시 생성자의 첫 번째 줄에 위치해야 합니다.- 이를 통해 공통된 초기화 작업을 중앙 집중화할 수 있음
- 같은 클래스 내에서 다른 생성자를 호출할 때
예제 코드
public class MyClass { int x; String y; // 기본 생성자 public MyClass() { this(0, "Default"); // x = 0; // y = "Default"; // 위의 코드 대신 this(0, "Default");를 호출하여 코드 중복을 방지. } // 매개변수가 있는 생성자 1 public MyClass(int val) { this(val, "Default"); // x = val; // y = "Default"; // 위의 코드 대신 this(val, "Default");를 호출하여 코드 중복을 방지. } // 매개변수가 있는 생성자 2 (모든 초기화 작업을 수행) public MyClass(int val, String str) { x = val; y = str; } public static void main(String[] args) { MyClass obj1 = new MyClass(); // x = 0, y = "Default" MyClass obj2 = new MyClass(10); // x = 10, y = "Default" MyClass obj3 = new MyClass(20, "Hi"); // x = 20, y = "Hi" System.out.println(obj1.x + ", " + obj1.y); // 출력: 0, Default System.out.println(obj2.x + ", " + obj2.y); // 출력: 10, Default System.out.println(obj3.x + ", " + obj3.y); // 출력: 20, Hi } }
객체 컴포지션
- 두 개 이상의 객체를 조합하여 새로운 기능을 만드는 기법
- 복잡한 객체를 구성하고 재사용 가능성을 높이며, 상속보다 유연한 설계 가능
- 컴포지션(Composition)은 "has-a" 관계로 설명할 수 있음. 한 객체가 다른 객체를 포함(포함 관계)하고 있다는 의미
- 컴포지션을 사용하면 큰 클래스가 여러 작은 클래스의 인스턴스를 포함하여 더 복잡한 기능을 수행 할 수 있음
- 장점
- 유연성: 상속에 비해 컴포지션은 클래스 간 결합도가 낮아 더 유연하게 클래스를 설계할 수 있음
- 재사용성: 객체 컴포지션을 사용하면 코드 재사용성을 높일 수 있으며, 서로 다른 클래스에서 동일한 기능을 쉽게 사용할 수 있음
- 캡슐화: 컴포지션을 통해 구현 세부 사항을 숨길 수 있으며, 인터페이스를 통해 객체 간의 상호작용을 정의할 수 있음.
컴포지션 vs 상속
- 상속(Inheritance)은 "is-a" 관계를 나타냅니다. 예를 들어,
Dog
클래스가Animal
클래스를 상속받는다면, "개는 동물이다"라는 의미가 됨 - 컴포지션(Composition)은 "has-a" 관계를 나타내며, 상속에 비해 더 유연하고 캡슐화된 설계를 할 수 있다. 객체 간의 결합도가 낮아지고, 변경에 더 강하게 대응할 수 있음.
- 컴포지션을 사용할 때
- 상속보다 컴포지션이 더 적합한 경우는 클래스 간의 관계가 명확하게 "is-a" 관계가 아닌 경우입니다. 예를 들어, 자동차가 엔진을 "포함"하고 있지만, 자동차는 엔진이 아닙니다. 이럴 때 컴포지션을 사용하여 객체 간의 관계를 설계하는 것이 좋습니다.
상속
클래스가 다른 클래스의 속성과 메소드를 물려받아 재사용할 수 있도록 하는 기능.
상속을 통해 코드의 중복을 줄이고, 클래스 간의 관계를 정의할 수 있음기본 개념
- 부모 클래스(슈퍼 클래스, Superclass): 상속을 제공하는 클래스.
상속을 받는 클래스에게 속성과 메소드를 물려줌. - 자식 클래스(서브 클래스, Subclass): 부모 클래스로부터 상속을 받는 클래스.
부모 클래스의 모든 속성과 메소드를 물려받으며, 이를 확장하거나 오버라이드(재정의)할 수 있음. extends
키워드: Java에서 상속을 구현할 때 사용.
자식 클래스가 부모 클래스를 상속받을 때, 클래스 선언부에서extends
키워드를 사용.
- 부모 클래스(슈퍼 클래스, Superclass): 상속을 제공하는 클래스.
장점
- 코드 재사용: 부모 클래스의 코드(필드와 메소드)를 자식 클래스에서 그대로 사용할 수 있으므로,
중복된 코드를 작성할 필요가 없음. - 유지보수성 향상: 공통된 기능은 부모 클래스에 정의하고, 특정 기능은 자식 클래스에 정의함으로써
코드를 더 잘 조직화할 수 있음. 수정할 부분이 생기면 부모 클래스에서 한 번만 수정하면 됨. - 다형성(Polymorphism): 상속을 통해 자식 클래스는 부모 클래스의 형태로 취급될 수 있다.
즉, 자식 클래스 객체를 부모 클래스 타입으로 참조할 수 있음.
이를 통해 다양한 객체를 일관되게 처리할 수 있음.
- 코드 재사용: 부모 클래스의 코드(필드와 메소드)를 자식 클래스에서 그대로 사용할 수 있으므로,
한계와 주의점
- 다중 상속 불가: Java에서는 클래스가 하나의 부모 클래스만을 상속받을 수 있다.
이를 단일 상속(Single Inheritance)이라 하며, 다중 상속(Multiple Inheritance)을 지원하지 않음.
다중 상속은 인터페이스를 통해서만 구현할 수 있음. - 부모 클래스와의 강한 결합: 자식 클래스는 부모 클래스와 강하게 결합되어 있기 때문에
부모 클래스의 변경이 자식 클래스에 영향을 미칠 수 있음
잘못 설계된 상속 구조는 유지보수에 어려움을 줄 수 있다. - 객체의 복잡도 증가: 상속을 남용하면 클래스 계층 구조가 복잡해질 수 있다.
이는 코드의 가독성과 유지보수성을 떨어뜨릴 수 있다.
- 다중 상속 불가: Java에서는 클래스가 하나의 부모 클래스만을 상속받을 수 있다.
요약
- 상속은 부모 클래스의 속성과 메소드를 자식 클래스가 물려받아 사용하는 기능.
- 코드 재사용성과 유지보수성을 향상시키며, 다형성을 통해 유연한 코드를 작성할 수 있게 해줌.
- 그러나 상속의 사용에는 주의가 필요하며, 적절한 설계가 이루어져야 함.
객체(Object)는 상속의 최상의 순위임
아무것도 상속받지 않으면 자바는 기본적으로 Object라는 클래스를 상속받음
클래스의 근본
모든 클래스는 객체 클래스의 하위 클래스
public class Person { ~~~ } // 이런 클래스가 있으면 실제로는 public class Person extends Object { ~~ } // 이런 방식으로 실행
오버라이딩
함수를 덮어씌우는 것
ex) toString() 오버라이딩
public String toString(){ retrun "data"; }
super()
super
키워드는 자바에서 부모 클래스의 멤버(필드, 메소드, 생성자 등)에 접근하기 위해 사용super
키워드를 통해 부모 클래스의 기능을 활용하거나 확장할 수 있음하위 클래스는 객체를 생성할때 생성자가 자동으로 super()를 호출함
하위 클래스 생성자는 상위 생성자가 무조건 정의 되어야 함
예시 코드
class Parent { Parent() { System.out.println("Parent's default constructor"); } Parent(String message) { System.out.println("Parent's parameterized constructor: " + message); } void show() { System.out.println("This is Parent's show method."); } } class Child extends Parent { Child() { super("Hello from Child!"); // 부모 클래스의 매개변수 있는 생성자 호출 System.out.println("Child's constructor"); } @Override void show() { super.show(); // 부모 클래스의 show() 메소드 호출 System.out.println("This is Child's show method."); } } public class Main { public static void main(String[] args) { Child child = new Child(); child.show(); } }
this
와super
: 생성자에서this()
와super()
는 동시에 사용할 수 없습니다. 둘 중 하나만 생성자의 첫 번째 줄에서 호출할 수 있다.생성자에서
this()
또는super()
를 호출할 때, 호출된 생성자가 완전히 실행된 후에만 그 다음의 코드가 실행됩니다. 생성자는 객체가 초기화되는 첫 단계이기 때문에, 객체의 초기화 순서를 보장하기 위해 반드시 생성자의 첫 번째 줄에서만 다른 생성자를 호출해야 함.this()
: 현재 클래스의 다른 생성자를 호출하여 현재 클래스의 초기화를 처리.super()
: 부모 클래스의 생성자를 호출하여 부모 클래스의 초기화를 처리.예시
// 잘못된 사용 class Parent { Parent() { System.out.println("Parent constructor"); } } class Child extends Parent { Child() { // 컴파일 오류: this()와 super()를 동시에 사용할 수 없음 super(); // 부모 클래스의 생성자 호출 this("Hello"); // 같은 클래스의 다른 생성자 호출 } Child(String message) { System.out.println("Child constructor with message: " + message); } } // 옳게된 사용 class Parent { Parent() { System.out.println("Parent constructor"); } } class Child extends Parent { Child() { super(); // 부모 클래스의 생성자 호출 System.out.println("Child default constructor"); } Child(String message) { this(); // 같은 클래스의 기본 생성자 호출 System.out.println("Child constructor with message: " + message); } }
자바는 다중상속을 허용하지 않음, 상속계층은 있을 수 있음
하위 클래스 변수를 담을 수 있는 상위 클래스 참조 변수를 만들 수 있음
가능한 이유
- 객체의 생성과정
- 자바에서 객체를 생성할 때, 하위 클래스 객체가 생성되면 상위 클래스의 생성자가 먼저 호출
- 메서드 호출 과정
- 메소드 호출은 참조 변수의 타입이 아니라 실제 객체의 타입에 따라 결정
- 상위 클래스 타입의 참조 변수를 통해 메소드를 호출할 때, 실제로는 참조된 객체의 타입을 기준으로 메소드가 호출됩니다. 이것이 다형성의 핵심
- 메서드 디스패치
- 자바에서 메소드를 호출할 때, 컴파일러는 참조 변수의 타입만을 확인하여
- 해당 메소드가 존재하는지*를 확인. 그러나 실제로 어떤 메소드가 실행될지는 런타임에 결정
이 과정이 동적 메소드 디스패치 또는 런타임 다형성이라고 불림.- 컴파일 시점: 참조 변수의 타입(
Animal
)을 보고 해당 메소드(sound()
)가 존재하는지 확인. - 런타임 시점: 실제 객체(
Dog
)의 타입을 기준으로 메소드(Dog
클래스의sound()
)가 호출.
- 컴파일 시점: 참조 변수의 타입(
- 상위 클래스와 하위 클래스의 관계
- 상위 클래스는 하위 클래스의 일반적인 특성을 정의하고, 하위 클래스는 이 특성을 구체화함.
- 하위 클래스가 상위 클래스를 상속받으면, 하위 클래스는 상위 클래스의 속성과 메소드를
- 상속*받게 되며, 이를 기반으로 자신만의 고유한 기능을 추가하거나 기존 기능을 오버라이드함.
- 결론
- 객체는 실제로 하위 클래스의 인스턴스이기 때문에, 상위 클래스 타입의 참조 변수를 사용하더라도 하위 클래스의 메소드가 호출.
- 상위 클래스 생성자와 하위 클래스 생성자 모두 호출되는 이유는 객체가 생성될 때
상위 클래스 부분도 초기화되어야 하기 때문.
하지만 메소드 호출 시점에는 실제 객체의 타입을 기준으로 메소드가 선택되기 때문에, 하위 클래스에서 오버라이드된 메소드가 실행.
- 객체의 생성과정
예시
class Animal { void sound() { System.out.println("Some generic animal sound"); } } class Pet extends Animal { @Override void sound() { System.out.println("Some pet sound"); } void play() { System.out.println("Pet is playing"); } } class Cat extends Pet { @Override void sound() { System.out.println("Meow"); } void purr() { System.out.println("Cat is purring"); } } public class Main { public static void main(String[] args) { // Animal 타입의 참조 변수로 Cat 객체 참조 Animal myCat = new Cat(); // 참조 변수에 저장된 객체에 따라 메소드가 호출됨 myCat.sound(); // "Meow" 출력 (Cat 클래스의 sound() 호출) // myCat.play(); // 컴파일 오류 (Animal 타입에는 play() 메소드가 없음) // myCat.purr(); // 컴파일 오류 (Animal 타입에는 purr() 메소드가 없음) // 캐스팅을 통해 하위 클래스의 메소드 호출 가능 Pet petCat = (Pet) myCat; petCat.play(); // "Pet is playing" 출력 Cat realCat = (Cat) myCat; realCat.purr(); // "Cat is purring" 출력 } }
자바에서 상위 클래스 타입의 참조 변수로 하위 클래스 객체를 참조할 때, 다음과 같은 규칙이 적용
상위 클래스에 정의된 메소드는 상위 클래스 타입의 참조 변수를 통해 호출할 수 있음.
하위 클래스에서 오버라이딩된 메소드는 상위 클래스 타입의 참조 변수로 호출하면 하위 클래스의 오버라이딩된 메소드가 실행.
하위 클래스에만 정의된 메소드는 상위 클래스 타입의 참조 변수로는 호출할 수 없음. 해당 메소드를 호출하려면 다운캐스팅이 필요.
예시
class Animal { void sound() { System.out.println("Some generic animal sound"); } void sleep() { System.out.println("Animal is sleeping"); } } class Dog extends Animal { @Override void sound() { System.out.println("Bark"); } void wagTail() { System.out.println("Dog is wagging its tail"); } } public class Main { public static void main(String[] args) { Animal myDog = new Dog(); // Animal 클래스에 정의된 메소드 호출 가능 myDog.sound(); // Dog 클래스의 오버라이딩된 메소드가 호출됨 -> "Bark" myDog.sleep(); // Animal 클래스의 메소드가 호출됨 -> "Animal is sleeping" // Dog 클래스에만 있는 메소드는 호출 불가 // myDog.wagTail(); // 컴파일 오류: Animal 타입에 wagTail() 메소드가 정의되어 있지 않음 // wagTail() 메소드를 호출하려면 다운캐스팅 필요 Dog realDog = (Dog) myDog; realDog.wagTail(); // "Dog is wagging its tail" } }
'Programming > JAVA' 카테고리의 다른 글
객체지향 프로그래밍(추상 메서드, 인터페이스 정리) (0) | 2024.10.08 |
---|---|
객체지향 프로그래밍(인터페이스) (0) | 2024.10.08 |
객체지향 프로그래밍(추상 클래스) (0) | 2024.10.08 |
참조 자료형 (0) | 2024.10.08 |
객체지향 프로그래밍에 대해 간단하게 개념 정리 (0) | 2024.10.08 |