객체들의 상호작용으로 만들어지는 프로그램
객체(Object) == 인스턴스(Instance)
수정이 쉬워야 한다: 버그를 잡거나 로직을 변경할 때.
확장이 쉬워야 한다: 새로운 기능을 추가할 때, 기존 코드를 최소한으로 고쳐야 함.
객체의 데이터(필드)와 기능(메소드)을 하나로 묶고, 내부 데이터를 외부로부터 보호하여 숨기는 것
외부에 공개된 메소드(Public Method)를 통해서만 데이터의 접근 및 수정이 가능하도록 허용.
클래스
class Animal{
String name;
int age;
void eat(){...};
void speak(){...};
}
객체
//field
String name lion;
int age = 4;
//method
void eat();
void speak();
정의: 기존의 상위 클래스(부모 클래스)가 가진 필드와 메소드를 하위 클래스(자식 클래스)가 물려받아 그대로 사용하거나 확장하는 것.
목적: 코드의 재사용성을 높이고 확장을 용이하게 함. (예: Point 클래스를 만들어 두면, ColorPoint 클래스는 color 관련 기능만 추가하면 됨)
용어:
구현: extends 키워드를 사용. (예: class Student extends Person { ... })
객체 지향에서는 '자식(서브 클래스)'이 더 큼.
이유: 자식 클래스 = 부모의 모든 기능 + 자신만의 추가적인(확장된) 기능을 더 가지고 있기 때문.
// 상위 클래스 (부모 클래스, Superclass)
class Animal {
String name;
void eat() {
System.out.println("먹이를 먹습니다.");
}
}
// 하위 클래스 (자식 클래스, Subclass)
class Human extends Animal { // Animal 클래스를 상속
String job;
void work() {
System.out.println("일을 합니다.");
}
// 상속받은 eat() 메소드를 그대로 사용 가능
}
public class Main {
public static void main(String[] args) {
Human person = new Human();
person.name = "홍길동"; // Animal로부터 상속받은 필드
person.job = "개발자"; // Human 클래스 고유의 필드
person.eat(); // Animal로부터 상속받은 메소드
person.work(); // Human 클래스 고유의 메소드
}
}
자바 클래스는 오직 하나의 슈퍼 클래스만 extends 할 수 있음. (class A extends B, C 형태 불가능)
상속의 깊이(A→B→C)는 무제한이지만, 횡으로 늘리는 것은 불가능.
대안: 다중 상속이 필요할 경우, 클래스가 아닌 인터페이스(Interface)를 사용. (인터페이스는 다중 상속 가능)
상속 시, 부모의 멤버에 자식이 접근할 수 있는지를 결정.
private: 오직 자기 클래스 내부에서만 접근 가능. (자식 클래스조차 접근 불가능)
default (아무것도 안 쓴 경우): 같은 패키지 안에서만 접근 가능.
public: 어디서든(모든 패키지) 접근 가능.
protected: default의 기능(같은 패키지) + 다른 패키지라도 상속받은 자식 클래스라면 접근을 허용.
자식 클래스(Sub Class)의 객체를 생성할 때(new) 생성자 호출 순서가 매우 중요.
(1) 생성자 호출 순서
결론: 부모와 자식 생성자 둘 다 실행됨.
순서: 슈퍼 클래스(부모) 생성자 실행 → 서브 클래스(자식) 생성자 실행
이유: 논리적으로 부모의 멤버(필드)가 먼저 초기화되어야, 자식이 그것을 기반으로 자신의 멤버를 초기화할 수 있기 때문.
연쇄 호출: C가 B를 상속하고 B가 A를 상속하면, new C() 호출 시 실행 순서는 A → B → C.
(2) super(): 부모 생성자 명시적 호출
기본 규칙: 자식 생성자에서 부모 생성자를 명시적으로 부르지 않으면, 컴파일러는 자동으로 부모의 '기본 생성자'(인자 없는 생성자)를 호출.
문제 상황 (컴파일 오류): 만약 부모 클래스에 기본 생성자가 없고, 인자가 있는 생성자만 정의되어 있다면, 자바는 자동으로 호출할 기본 생성자를 찾지 못해 컴파일 오류(Undefined constructor)가 발생.
해결책: 자식 생성자에서 super()를 사용해 부모 생성자를 직접 선택해야 함.
super(); // 부모의 기본 생성자 호출
super(10); // 부모의 인자(int 1개)가 있는 생성자 호출
super()는 "반드시 서브 클래스 생성자 코드의 첫 라인(first line)"에 와야 함.
이유: 자식 멤버가 초기화되기 전에 부모 멤버가 반드시 먼저 초기화되어야 하는 논리적 순서를 강제하기 위함.
정의: 객체의 공통적이고 본질적인 특징만 추출하고, 복잡하고 불필요한 세부 사항은 숨기는 것.
구체적인 객체들(Student, Worker)을 생각했다면, 반드시 다시 추상화하는 생각(Person)을 먼저 해야 함.
설계: Person이라는 슈퍼 클래스에 공통 속성(이름, 나이)과 메서드(말하기)를 정의.
효과: 새로운 Visitor 클래스를 추가할 때 Person을 상속받으면 '말하기' 기능을 또 만들 필요가 없음. (확장 용이)
자바에서는 추상 클래스와 인터페이스를 사용해 추상화를 구현.
정의: abstract 키워드가 붙은 클래스. "구현이 되어있지 않은", "불완전한" 클래스.
객체 생성 불가 (불완전하므로 new로 인스턴스를 만들 수 없음.)
추상 메서드: abstract가 붙고 몸체({})가 없는 메서드를 가질 수 있음.
목적:
"오버라이딩 강제": 부모(추상 클래스)가 draw()를 추상 메서드로 정의하면, 자식은 반드시 draw()를 오버라이딩하여 구현해야만 함. ("구현을 강제")
"설계와 구현의 분리": 부모는 "이런 기능이 있어야 한다"는 "틀(설계)"만 잡고, 실제 "어떻게 동작할지(구현)"는 자식에게 위임.
정의: 추상 클래스보다 더 엄격한 규격(Specification) 또는 설계도
목적: 서로 다른 클래스들이 "이 규격(뼈대)에 맞춰서 구현해라"라고 기능의 뼈대를 강제하여 호환성을 맞추기 위함.
class 대신 interface 키워드를 씀.
변수(필드)를 가질 수 없고, 상수(Constant)만 가질 수 있음.
모든 메서드는 자동으로 public abstract (뼈대만 존재, 구현 불가).
상속 시 extends(확장)가 아닌 implements(구현) 키워드를 씀.
가장 큰 차이점 : 클래스는 단일 상속만 가능하지만, 인터페이스는 다중 상속(구현)이 가능.
// 클래스는 하나만 extends
// 인터페이스는 여러 개 implements 가능
class SmartPhone extends Phone implements MP3Interface, CameraInterface {
// ... MP3Interface와 CameraInterface의 모든 추상 메서드를 구현해야 함
}
다형성(Polymorphism): 하나의 코드(인터페이스)가 여러 가지 다른 실제 동작(구현)을 가지는 특성을 말함.
업캐스팅(Upcasting), 메소드 오버라이딩(Method Overriding), 동적 바인딩(Dynamic Binding)
다형성의 전제 조건
Student is a Person) // Student(자식) 객체를 Person(부모) 타입 변수에 할당
Person p = new Student();
p는 Person 타입(그릇)이므로, Person 클래스에 정의된 멤버(메소드, 변수)에만 접근할 수 있음. Student가 가진 확장된 기능(예: study())은 p를 통해서는 바로 사용할 수 없음.다형성이 동작하는 방식
다형성이 실행되는 시점임.
Shape s = new Line(); // 1. 업캐스팅 (s는 Shape 타입, 실제 객체는 Line)
s.draw(); // 2. 메서드 호출s가 Shape 타입이라 Shape의 draw()를 부르는 것처럼 보임.s가 실제 가리키는 객체인 Line의 오버라이딩된 draw()가 호출(바인딩)됨.이 모든 것을 하는 이유
paint 메서드):Shape(부모), Circle, Line, Rectangle(자식들)이 있고, 모든 자식이 draw()를 오버라이딩했다고 가정.paint라는 메서드를 단 하나만 만들 수 있음. // 1. 매개변수를 부모 타입(Shape)으로 받음
void paint(Shape p) {
// 2. p가 실제 가리키는 객체의 draw()를 호출 (동적 바인딩)
p.draw();
}
효과: 이 paint 메서드 하나로 모든 자식 객체를 처리할 수 있음.
paint(new Circle()) → Circle의 draw()가 실행됨paint(new Line()) → Line의 draw()가 실행됨나중에 Triangle(삼각형)이라는 새로운 클래스를 추가하더라도, Triangle이 Shape을 상속하고 draw()를 오버라이딩하기만 하면, 기존의 paint 메서드는 단 한 줄도 고칠 필요가 없음. (paint(new Triangle())이 알아서 동작함)
이것이 객체 지향의 목표인 수정 없이 확장을 가능하게 하는 핵심 원리
java 공부 좀 하다 갈께요