자바와 객체지향
01. 사람을 사랑한 기술
자바 - 진정한 객체 지향 언어
객체 지향 언어의 중심에는 클래스(class)가 있다(객체[object]라고 해도 된다). 자바에서는 클래스를 떠나 존재할 수 있는 것은 아무 것도 없다. 심지어 프로그램의 시작점인 main() 메서드마저도 클래스 외부가 아닌 내부에 존재해야 한다. 그리고 모든 메서드도 클래스, 메서드명() 또는 객체.메소드명()으로 접근해야만 한다.
스프링 프레임워크는 사상이면서 또 단일 제품이다
스프링 프레임워크에도 끈이름의 끈처럼 스프링을 지배하는 근원적인 요소가 있는데 바로 스프링 삼각형이라고 하는 IoC/DI, AOP, PSA이다.
스프링은 POJO(Plain Old Java Object)에 세 가지 유형의 진동을 줌으로써 거대한 프레임워크를 완성해냈다.
스프링 프레임워크의 또 다른 아름다움은 ORM, OXM, JMS, AOP, CoC 등 엔터프라이즈 애플리케이션을 구현하는 데 필요한 거의 모든 서비스를 지원해준다는 것이다.
스프링은 PSA(일관성 있는 추상화) 기법을 통해 중구난방으로 구현된 다양한 기술을 표준화된 방식으로 사용할 수 있게 지원해준다. 스프링을 도입하면 엔터프라이즈 애플리케이션을 더 쉽고 편하게, 그리고 더 안정적으로 개발할 수 있다.
02. 자바와 절차적/구조적 프로그래밍
자바 프로그램의 개발과 구동
JDK는 자바 소스 컴파일러인 javac.exe를 포함하고 있고, JRE는 자바 프로그램 실행기인 java.exe를 포함하고 있다.
자바가 이런 구조를 택한 이유는 기존 언어로 작성한 프로그램은 윈도우 95용, 윈도우 XP용, 윈도우 7용, 윈도우 8용, 리눅스용, 애플 맥 OS X용 등 각 플랫폼 용으로 배포되는 설치파을 따로 준비해야 했던 불편함을 없애기 위해서다.
자바 개발자는 본인이 사용 중인 플랫폼에 설치된 JVM용으로 프로그램을 작성하고 배포하면 각 플랫폼에 맞는 JVM이 중재자로서 각 플랫폼에서 프로그램을 구동하는 데 아무 문제가 없게끔 만들어주는 것이다.
- JDK: Java Development Kit / 자바 개발 도구
- JRE: Java Runtime Environment / 자바 실행 환경
- JVM: Java Virtual Machine / 자바 가상 기계
프로그램이 메모리를 사용하는 방식
코드 실행 영역 | 데이터 저장 영역 |
객체 지향 프로그램의 메모리 사용 방식
T 메모리 구조
코드 실행 영역 | 스테틱(Static) 영역 | |
스택(Stack)영역 | 힙(Heap)영역 |
자바에 존재하는 절차적/구조적 프로그래밍의 유산
객체 지향 프로그래밍은 절차적/구조적 프로그램의 어깨를 딛고 있다. 따라서 객체 지향 언어를 이해하는 데 절차적/구조적 프로그래밍을 아는 것은 큰 도움이 된다.
- 절차적 프로그래밍은 한마디로 표현하자면 goto를 쓰지 말라는 것이다. goto를 사용하게 되면 프로그램의 실행 순서가 인간이 이해하기에 너무 복잡해질 가능성이 있기 때문이다.
- 구조적 프로그래밍은 함수를 쓰라는 것이다. 함수를 쓰면 좋은 이유는 중복 코드를 한 곳에 모아서 관리할 수 있고, 논리를 함수 단위로 분리해서 이해하기 쉬운 코드를 작성할 수 있기 떄문이다. 여기에 더해 구조적 프로그래밍의 지침 중에는 공유 사용 시 문제가 발생하기 쉬운 전역 변수 보다는 지역 변수를 쓰라는 것도 있다.
main() 메서드: 메서드 스택 프레임
JRE는 먼저 프로그램 안에 main() 메서드가 있는지 확인한다. main() 메서드의 존재가 확인되면 JRE는 프로그램 실행을 위한 사전 준비에 착수한다. 가상의 기계인 JVM에 전원을 넣어 부팅하는 것이다.
JVM은 가장 먼저 java.lang 패키지를 T 메모리의 스태틱 영역에 가져다 놓는다. java.lang 패키지가 있기에 System.out.println() 같은 메서드를 쓸 수 있게 되는 것이다.
다음으로 JVM은 개발자가 작성한 모든 클래스와 임포트 패키지 역시 스태틱 영역에 가져다 놓는다.
main() 메서드가 싫행되기 전 JVM에서 수행하는 전처리 작업들
- java.lang 패키지를 T 메모리의 스태틱 영역에 배치한다.
- import된 패키지를 T apahfldml 스태틱 영역에 배치한다.
- 프로그램 상의 모든 클래스를 T 메모리의 스태틱 영역에 배치한다.
main() 메서드가 끝나면 JRE는 JVM을 종료하고 JRE 자체도 운영체제 상의 메모리에서 사라진다.
지역 변수와 메모리
- 지역 변수는 스택 영역에서 일생을 보낸다. 그것도 스택 프레임 안에서 일생을 보내게 된다. 따라서 스택 프레임이 사라지면 함께 사라진다.
- 클래스 멤버 변수는 스태틱 영역에서 일생을 보낸다. 스태틱 영역에 한번 자리 잡으면 JVM이 종료될 때까지 고정된(static) 상태로 그 자리를 지킨다.
- 객체 멤버 변수는 힙에서 일생을 보낸다. 객체 멤버 변수들은 객체와 함께 가비지 컬렉터라고 하는 힙 메모리 회수기에 의해 일생을 마치게 된다.
외부 스택 프레임에서 내부 스택 프레임의 변수에 접근하는 것은 불가능하나 그 역은 가능하다.
전역 변수와 메모리
두 메서드 사이에 값을 전달하는 방법은 메서드를 호출할 때 메서드의 인자를 이용하는 방법과 메서드를 종료할 때 반환값을 넘겨주는 방법이 있다. 또한 전역 변수를 사용할 수도 있다.
전역 변수는 코드 어느 곳에서나 사용한다고 해서 공유 변수라고도 한다.
왜 전역 변수를 쓰지 말라고 할까?
프로젝트 규모에 따라 코드가 커지면서 여러 메서드에서 전역 변수의 값을 변경하기 시작하면 T 메모리로 추적하지 않는 이상 전역 변수에 저장돼 있는 값을 파악하기 쉽지 않기 때문이다.
결론적으로 전역 변수는 피할 수 있다면 피해야 하는 존재이다. 다만 읽기 전용으로 값을 공유해서 전역 상수로 쓰는 것은 괜찮다. 가장 대표적인 전역 상수 후보로는 원주율을 나타내는 PI 값 등이 있다.
멀티 스레드 / 멀티 프로세스의 이해
- 멀티 스레드의 T 메모리 모델은 스택 영역을 스레드 개수만큼 분할해서 쓰는 것이다.
- 멀티 프로세스는 다수의 데이터 저장 영역, 즉 다수의 T 메모리를 갖는 구조다.
멀티 프로세스는 각 프로세스마다 각자의 T 메모리가 있고 각자 고유의 공간이므로 서로 참조할 수 없다. 그에 반해 멀티 스레드는 하나의 T 메모리만 사용하는데 스택 영역만 분할해서 사용하는 구조다.
멀티 스레드는 하나의 T 메모리 안에서 스택 영역만 분할한 것이기 때문에 하나의 스레드에서 다른 스레드의 스택 영역에는 접근할 수 없지만 스태틱 영역과 힙 영역은 공유해서 사용하는 구조다. 따라서 멀티 프로세스 대비 메모리를 적게 사용할 수 있는 구조다.
멀티 스레드에서 전역 변수 사용의 문제점
쓰기 가능한 전역 변수를 사용하게 되면 스레드 안정성이 깨진다고 표현한다. 물론 이를 보완하는 방법으로 락(lock)을 거는 방법이 있기는 하다. 하지만 락을 거는 순간 멀티 스레드의 장점은 버린 것과 같다.
03. 자바와 객체 지향
객체 지향은 인간 지향이다
사물을 객체, 영어로는 Object라고 한다. 그리하여 “우리가 주변에서 사물을 인지하는 방시대로 프로그래밍할 수 있지 않겠는가”하는 것이 바로 객체 지향의 출발이다. 0과 1로 대변되는 기계(컴퓨터)에 맞춰 사고하던 방식을 버리고 현실세계를 인지하는 방식으로 프로그램을 만들자는 것이다. 그래서 객체 지향은 직관적이다.
객체 지향은 인간의 인지 및 사고 방식까지 프로그래밍에 접목하는 인간(개발자) 지향을 실천하고 있는 것이다.
객체 지향의 4대 특성
- 캡슐화(Encapsulation): 정보 은닉(information hiding)
- 상속(inheritance): 재사용
- 추상화(Abstraction): 모델링
- 다형성(Polymorphism): 사용 편의
추상화: 모델링
추상화란 구체적인 것을 분해해서 관찰자가 관심 있는 특성만 가지고 재조합하는 것이라고 정리할 수 있다.
=> 추상화란 구체적인 것을 분해해서 관심 영역(애플리케이션 경계, Application Boundary)에 있는 특성만 가지고 재조합 하는 것 = 모델링
모델은 추상화를 통해 실제 사물을 단순하게 묘사하는 것이다. 이런 모델링(추상화)은 객체 지향에서 클래스를 설계할 때 필요한 기법이고 또한 데이터베이스의 테이블을 설계할 때 필요한 기법이다.
- OOP의 추상화는 모델링이다.
- 클래스:객체=펭귄:뽀로로
- 클래스 설계에서 추상화가 사용된다.
- 클래스 설계를 위해서는 애플리케이션 경계부터 정해야 한다.
- 객체 지향에서 추상화의 결과는 클래스다.
- 추상화 = 모델링 = 자바의 class 키워드
- 클래스 객체_참조_변수 = new 클래스();
클래스 멤버 vs. 객체 멤버 = static 멤버 vs. 인스턴스 멤버
클래스 멤버들은 static 키워드와 함께 사용되고 또 T 메모리의 static 영역에 상주하게 되므로 static(정적) 멤버라고도 한다. 객체 멤버들은 객체가 클래스의 인스턴스이므로 인스턴스 멤버라고도 한다.
- 클래스 멤버 = static 멤버 = 정적 멤버
- 객체 멤버 = 인스턴스 멤버
정적 속성은 해당 클래스의 모든 객체가 같은 값을 가질 때 사용하는 것이 기본이다. 물론 이외의 경우에도 쓸 수는 있겠지만 그때는 정당한 논리를 가지고 써야 한다.
정적 메서드는 언제 사용하는 것이 좋을까
정적 메서드는 객체들의 존재 여부에 관계없이 쓸 수 있는 메서드다. 정적 멤버들은 객체가 아닌 클래스에 속해 있으며, 클래스는 JVM 구동 시 T 메모리의 스태틱 영역에 바로 배치되기 때문에 객체의 존재 여부에 관계 없이 쓸 수 있다.
main() 메서드는 당연히 정적 메서드여야 한다.
T 메모리가 초기화된 순간 객체는 하나도 존재하지 않기 때문에 객체 멤버 메서드를 바로 실행할 수는 없다. 따라서 main() 메서드는 정적 메서드여야 한다.
실무에서는 클래스의 인스턴스를 만들지 않고 사용하게 되는 유틸리티성 메서드를 주로 정적 메서드로 구성한다. 예) Math 클래스
이름 | 다른 이름 | 사는 곳(T 메모리) |
---|---|---|
static 변수 | 클래스[멤버] 속성, 정적 변수, 정적 속성 | 스태틱 영역 |
인스턴스 변수 | 객체[멤버] 속성, 객체 변수 | 힙 영역 |
local 변수 | 지역 변수 | 스택 영역(스택 프레임 내부) |
상속: 재사용 + 확장
객체 지향에서의 상속은 상위 클래스의 특성을 하위 클래스에서 상속(특성 상속)하고 거기에 더해 필요한 특성을 추가, 즉 확장해서 사용할 수 있다는 의미다.
상속을 부모 클래스 - 자식 클래스라는 표현 보다는 상위 클래스 - 하위 클래스 또는 슈퍼 클래스 - 서브 클래스라고 표현하자.
- 하위 클래스는 상위 클래스다.
- 포유류는 동물이다.
- 고래는 표유류다.
- 고래는 동물이다.
상속의 강력함
public class 동물{
String myClass;
동물(){
myClass = "동물";
}
void showMe(){
System.out.println(myClass);
}
}
public class 포유류 extends 동물{
포유류(){
myClass = "포유류";
}
}
public class 조류 extends 동물{
조류(){
myClass = "조류";
}
}
public class Driver01{
public static void main(String[] args){
동물 animal = new 동물();
포유류 mammalia = new 포유류();
조류 bird = new 조류();
animal.showMe();
mammalia.showMe();
bird.showMe();
}
}
상위 클래스에서만 showMe() 메서드를 구현했지만 모든 하위 클래스의 객체에서 showMe() 메서드를 사용할 수 있다. 상속한다는 것이 이렇게 상위 클래스의 특성을 상속한다는 의미이지 부모-자식 관계는 아니다.
클래스 상속 구조에서 최상위 클래스는 Object다. 그래서 모든 클래스는 결국 Object의 특성을 물려받는다. 그래서 어떤 클래스의 인스턴스이든 상관없이 개발자는 toString() 메서드를 사용할 수 있는 것이다.
다중 상속과 자바
왜 자바는 다중 상속을 지원하지 않는가?
ex) 인어가 사람과 물고기를 상속 한다면
사람도 수영할 수 있고, 물고기도 수영할 수 있는데 인어가 수영을 한다면 사람처럼 팔과 다리를 저어 수영해야 할까? 아니면 물고기처럼 가슴, 등, 꼬리 지느러미로 헤엄쳐야 할까? 이와 같은 문제를 다중 상속의 다이아몬드 문제라고 한다. 결국 다중 상속은 득실 관계에서 실이 더 많았기에 자바와 C#은 과감히 다중 상속을 포기했다.
대신 자바에서는 C++에는 없는 인터페이스를 도입해 다중 상속의 득은 취하고 실은 과감히 버렸다.
상속과 인터페이스
- 상속 관계: 하위 클래스 is a kind of 상위 클래스
- 해석: 하위 클래스는 상위 클래스의 한 분류다.
- 예제: 고래는 동물의 한 분류다.
- 인터페이스: 구현 클래스 is able to 인터페이스
- 해석: 구현 클래스는 인터페이스할 수 있다.
- 예제: 고래는 헤엄칠 수 있다.
상위 클래스는 하위 클래스에게 특성(속성과 메서드)을 상속해 주고, 인터페이스는 클래스가 ‘무엇을 할 수 있다’라고 하는 기능을 구현하도록 강제하게 된다.
상위 클래스는 물려줄 특성이 풍성할수록 좋고, 인터페이스는 구현을 강제할 메서드의 개수가 적을수록 좋다.
클래스가 풍성할수록 좋은 이유는 LSP(리스코프 치환 원칙)에 따른 이유라고 할 수 있다. 인터페이스에 메서드가 적을수록 좋은 이유는 ISP(인터페이스 분할 원칙)에 따른 이유라고 할 수 있다.
다형성: 사용편의성
객체 지향에서 다형성이라고 하면 오버라이딩(overriding)과 오버로딩(overloading)이라고 할 수 있다.
- 오버라이딩: 같은 메서드 이름. 같은 인자 목록으로 상위 클래스의 메서드를 재정의
- 오버로딩: 같은 메서드 이름. 다른 인자 목록으로 다수의 메서드를 중복 정의
상위 클래스 타입의 객체 참조 변수를 사용하더라도 하위 클래스에서 오버라이딩(재정의)한 메서드가 호출된다는 사실을 기억하자.
오버로딩은 함수명 하나를 가지고 인자 목록만 달리하면 됨으로 사용하기 편리하다. 특히 자바 5에서 추가된 제네릭을 이용하면 하나의 함수만 구현해도 다수의 함수를 구현한 효과를 낼 수 있다.
오버라이딩은 하위 클래스가 재정의한 메서드를 알아서 호출해 줌으로써 형변환이나 instanceof 연산자를 써서 하위 클래스가 무엇인지 신경 쓰지 않아도 된다. 상위 클래스 타입의 객체 참조 변수에서 하위 클래스가 오버라이딩한 메서드를 자동으로 호출해 줌으로써 깔끔한 코드를 유지할 수 있게 된다.
캡슐화: 정보 은닉
자바에서 정보은닉 이라고 하면 접근 제어자인 private, [default], protected, public이 생각날 것이다.
객체 멤버의 접근 제어자
- 상속을 받지 않았다면 객체 멤버는 객체를 생성한 후 객체 참조 변수를 이용해 접근해야 한다.
- 정적 멤버는 클래스명.정적멤버 형식으로 접근하는 것을 권장한다.
참조 변수의 복사
- 기본 자료형 변수를 복사하는 경우 Call By Value(값에 의한 호출)에 의해 그 값이 복사되며 두 개의 변수는 서로에게 영향을 주지 않는다.
- 기본 자료형이 아닌 객체를 저장하고 있는 객체 참조 변수를 복사하는 경우는 Call By Reference(참조에 의한 호출) 또는 Call By Address(주소에 의한 호출)이라고 한다.
기본 자료형 변수는 저장하고 있는 값을 그 값 자체로 해석하는 반면, 객체 참조 변수는 저장하고 있는 값을 주소로 해석한다는 차이가 있다.
- 기본 자료형 변수는 값을 값 자체로 판단한다.
- 참조 자료형 변수는 값을 주소, 즉 포인터로 판단한다.
- 기본 자료형 변수를 복사할 때, 참조 자료형 변수를 복사할 때 일어나는 일은 같다. 즉 가지고 있는 값을 그대로 복사해서 넘겨 준다.