객체 지향
- 객체 지향은 '인간' 지향 언어이다.
- 객체 지향은 현실 세계를 반영한다.
- 기계에 맞춰서 프로그래밍하던 방식(절차 지향 프로그래밍)을 버리고 인간이 사물을 인지하는 방식대로 프로그래밍하고자 만들고자 객체 지향 프로그래밍 언어가 탄생했다.
- 클래스와 객체의 관계는 붕어빵틀과 붕어빵이라기보다는 분류와 실체의 개념에 가깝다.
- 클래스는 분류, 집합, 같은 속성과 기능을 가진 객체를 총칭하는 개념이다.
객체지향의 대표적인 특성
- 캡슐화(정보 은닉)
- 추상화(모델링)
- 상속(재사용)
- 다형성(사용 편의)
추상화 = 모델링
- Object 의 의미는 '객체'보다는 '개체'라는 뜻에 더 가깝다.
- 객체는 세상에 존재하는 유일무이한 사물을 말한다. (By 저자)
- 객체를 특성(속성, 기능)에 따라 분류해보니 객체를 통칭할 수 있는 집합적인 개념인 "클래스"가 등장한다.
- 어떤 클래스를 이용해서 Object를 만들었다는 점을 강조할 때 "인스턴스" 라는 표현을 쓴다.
- 애플리케이션 경계: "컨텍스트"와 같다.
- 추상화: 구체적인 것을 분해해서 관심 영역에 있는 특성만 가지고 재조합(모델링)하는 것을 말한다.
- 모델링: 추상화란 구체적인 것을 분해해서 '관심 영역'(애플리케이션 경계)에 있는 특성만 가지고 재조합하는 것을 말한다.
- 추상화 = 모델링 = 자바의 class 키워드
- 추상화를 넓게 보면 상속을 통한 추상화, 인터페이스를 통한 추상화, 다형성을 통한 추상화를 모두 포함한다.
- 클래스 멤버 = static 멤버 = 정적 멤버
- 객체 멤버 = 인스턴스 멤버
- 정적 속성은 모든 객체가 같은 값을 가질 때 사용하는 게 기본이다. 이외에도 사용 가능하지만 정단한 이유가 있어야한다.
Java의 main 메서드가 static 메서드인 이유
- Java로 작성된 프로그램을 실행하기 위해서는 main() 메서드가 필요하다.
- 그런데 이 메서드가 static인 이유는 뭘까?
- T 메모리가 초기화된 순간, 객체는 하나도 존재하지 않는다. 따라서, 객체 멤버 메서드를 바로 실행할 수 없다.
- main 메서드는 반드시 static이어야만 한다.
지역변수는 초기화하지만 전역 변수는 초기화하지 않아도 되는 이유
- 지역변수는 개발자가 필수적으로 초기화해야하지만 클래스 속성과 객체 속성은 컴파일러가 자동으로 초기화해준다.
- 클래스 변수는 프로그램 어디에서든 접근 가능한 변수이다. 그렇다면 이걸 어느 클래스에서 초기화해야한다고 규정할 수 있을까?
- 없다. 따라서 기본값으로 자동 초기화된다.
- 지역변수는 초기화하지 않으면 쓰레기 값을 가진다.
클래스와 인터페이스
- 상위 클래스는 하위 클래스에 물려줄 특성이 많을수록 좋고 인터페이스는 구현을 강제할 메서드가 적어야 좋다.
- 상위 클래스는 하위 클래스에 물려줄 특성이 많아야 좋은 이유: LSP(리스코프 치환 법칙)
- 인터페이스는 구현을 강제할 메서드가 적어야 좋은 이유: ISP(인터페이스 분리 원칙)
상속과 T 메모리
- 다음 예제를 실행할 때 T 메모리가 어떻게 구성되는지 살펴보자.
Ex 1)
- 펭귄 클래스
<hide/>
public class Penguin extends Animal{
public String habitat;
public void showHabitat() {
System.out.printf("%s는 %s에 살아\n", name, habitat);
}
}
- 동물 클래스
<hide/>
public class Animal {
public String name;
public void showName(){
System.out.printf("안녕 나는 %s야. 반가워\n", name);
}
}
- Driver 클래스
<hide/>
public class Driver {
public static void main(String[] args) {
Penguin pororo = new Penguin();
pororo.name = "뽀로로";
pororo.habitat = "남극";
pororo.showName();
pororo.showHabitat();
Animal pingu = new Penguin();
pingu.name = "핑구";
// pingu.habitat = "EBS"; 컴파일 에러
pingu.showName();
// pingu.showHabitat(); 컴파일 에러
// Penguin happyfeet = new Animal(); 컴파일 에러
}
}
Note) 실행 결과
- static 영역
- 세 개의 클래스 - Penguin, Animal, Driver
- main() 메서드
- stack 영역
- showName()
- showHabitat()
- 지역 변수: pororo, pingu 는 각각 heap 영역의 인스턴스를 가리킨다.
- heap 영역
- 인스턴스 - Penguin, Animal
- 인스턴스 변수- habitat, name
1) Penguin pororo = new Penguin(); 실행 후, T메모리 구조
- Penguin 의 상위 클래스인 Animal 도 heap에 생성된다. (깊게 따져보면 Object 클래스의 인스턴스도 생성된다. )
2) Animal pingu = new Penguin(); 실행 후, T 메모리 구조
- static 영역의 pororo는 heap 영역의 Penguin 을 가리키고, pingu는 Penguin이 아닌 Animal 을 가리킨다.
- 따라서, 자식 클래스인 Penguin 클래스에 정의된 메서드를 사용할 수 없다.
- 묵시적 형변환이 일어난다.
다형성: 사용 편의
- 오버로딩, 오버라이딩
- 앞에서 본 예제에 코드를 추가해서 T 메모리 구조를 살펴보려한다.
Ex 2) 다형성
- Penguin
- showName() 메서드를 오버라이드했다.
<hide/>
public class Penguin extends Animal{
public String habitat;
public void showHabitat() {
System.out.printf("%s는 %s에 살아\n", name, habitat);
}
/*** 여기서부터 추가 */
@Override
public void showName() {
System.out.println("어머 내이름은 알아서 뭐하게요?");
}
public void showName(String yourName) {
System.out.printf("%s, 안녕 나는 %s 라고 해\n", yourName, super.name);
}
}
- Animal
<hide/>
public String name;
public void showName(){
System.out.printf("안녕 나는 %s야. 반가워\n", name);
}
- Driver
<hide/>
public static void main(String[] args) {
Penguin pororo = new Penguin();
pororo.name = "뽀로로";
pororo.habitat = "남극";
pororo.showName();
pororo.showName("초보람보");
pororo.showHabitat();
Animal pingu = new Penguin();
pingu.name = "핑구";
pingu.showName();
}
Note) 실행 결과
- Penguin 타입 참조 변수로 선언한 pororo와 Animal 타입 참조변수로 선언한 pingu 에 대해서 showName()를 실행하면 하위 클래스에서 오버라이드한 메서드가 호출된다. 상위 클래스에서 정의한 showName()은 무시되고 있다.
- 결론: 객체 참조 변수를 사용해도 하위 클래스에서 오버라이딩한 메서드가 호출된다.
Ex 2 - 1) pororo 생성 시, T 메모리 구조
- showName() 을 호출하면 하위 클래스에 재정의된 showName()이 호출된다.
Ex 2 - 2) pingu 생성 시, T 메모리 구조
- showName()을 호출하면 Animal로 선언한 변수 pingu임에도 불구하고 하위 클래스에서 오버라이드한 메서드가 호출된다.
Ex) 다형성과 제네릭
- 만약 다형성이 지원되지 않는다면 오버로딩이 불가능할것이다.
- 두 개의 값을 매개변수를 받아서 더하는 add()라는 함수를 만든다고 가정하자.
- add() 의 매개변수로 어떤 정수형(byte, short, int, long, char) 이나 실수형(double, float)이 들어가도 덧셈이 가능하도록 만들어야한다.
- 위의 7개의 자료형에 대해 add() 를 오버로딩하면 ( 7 * 7 ) - 7 = 42 개의 add() 메서드가 필요하다.
- 그런데 여기서 제네릭을 이용하면 add 메서드는 딱 하나만 있어도 위의 42개의 메서드를 구현한 것과 같은 효과를 낼 수 있다.
- 위와 같이 선언하면 printValue()안에 어떤 데이터 타입이든 받아서 출력 가능하다.
<hide/>
public class GenericExample {
public static <T> void printValue(T value) {
System.out.println(value);
}
public static void main(String[] args) {
printValue(10); // 정수 출력
printValue(3.14); // 실수 출력
printValue("Hello"); // 문자열 출력
}
}
캡슐화 : 정보 은닉
- 접근 제어자가 캡슐화의 대표적인 예시이다.
- 접근 제어자에 대한 UML 표기법
- - : private
- ~: default
- #: protected
- +: public
- _: 속성이나 메서드 아래에 언더바를 쓰면 static 을 의미한다.
title
- con
- con
알아보기
- 클래스에는 물려줄 속성이 많을수록 인터페이스에는 구현할 메서드가 적어야 좋은 이유
- 명시적 형변환, 묵시적 형변환
출처 - 「 스프링 입문을 위한 자바 객체 지향의 원리와 이해 - 김종민 」
'Java > 스프링 입문을 위한 자바 객체 지향의 원리와 이해' 카테고리의 다른 글
Ch 05. 객체 지향 설계 5원칙 SOLID (0) | 2023.12.01 |
---|---|
Ch 02. Java와 절차적/구조적 프로그래밍 (1) | 2023.11.26 |