포스트

(Day16) - OOP, 객체지향 프로그래밍 상세 [3] 클래스의 사용법

이 글은 제가 교육을 수강하며 기록하고 추가한 내용입니다. 강사님과 무관하게 잘못된 내용이 있을 수 있습니다.


클라우드 기반 웹 데브옵스 프로젝트 개발자 교육 과정 (5기)

  • 비트캠프 엄진영 강사님 (https://github.com/eomjinyoung/)
  • 훈련기관 : 네이버클라우드주식회사
  • 기간: 2023-11-14 ~ 2024-5-22
  • 남은 일자 : 112 일 ( 17/129 )

본 내용은 제가 교육을 수강하며 기록한 내용으로, 빠른 템포를 따라가며 기록하고 제가 공부한 내용을 추가하므로 내용의 정확성이 부족할 수 있는 점 양해 부탁드립니다.

17일(2023-12-06, 수)

학습 - 객체지향

클래스의 주요 사용 방법 2가지?

사용자 정의 데이터 타입을 생성하여 데이터 관리

이것은 데이터를 다루는 본인이 그 데이터를 가지고 있어야 한다는 객체지향 프로그래밍의 권장사항에도 해당

기능의 분리

스태틱 변수(클래스 변수)의 생명 주기는?

클래스변수는 클래스가 로딩되는 시점에 메모리에 생긴다. JVM이 종료되는 시점에 같이 삭제된다. 아주 많은 클래스를 로딩하는 경우도 마찬가지다.

클래스는 명령어다. 메서드를 호출하거나 변수를 사용하려면 명령어를 메모리에 올려놓고 봐야 한다. 보조기억장치에서 RAM에 올라와야 한다.

클래스와 그 내부의 메서드는 사용 여부와 관계없이 로딩한다면 모두 메모리에 올라가야 한다.

프로그램의 분리
모든 프로그램이 한 파일에 있던 상황을 생각해보자. 여러 기능들이 메서드로 나뉘어져 있을 것이다. 그럼 이 메서드들 중 관련도가 높은 것을 클래스 하나로 분리한다. 그 작업을 반복하면 여러 클래스 파일로 이뤄진 프로그램으로 분리된다.

관련 글

로딩(Loading)
보조기억장치에 있는 class (바이트코드 파일)을 메모리에 올리는 동작을 말하는 용어이다. 클래스가 사용될 때 스태틱 변수는 method area에 생성되며, 프로그램이 종료될 때 해제된다. 클래스는 사용하는 그 시점에 한번 로딩되며 일단 한번 로딩되면 프로그램이 종료되기 전 까지 메모리에서 계속 존재하고 있다. 클래스 로딩 시점부터 명령어가 로딩하고 계속 적재되어 있는 것이다.

클래스 로딩에 대한 의문이 있다면, 클래스의 목적 자체가 쪼개서 메모리를 아끼고 유지보수를 편하게 하려는 것임에 이해가 부족한 것인지 생각해보아야 한다. 물론 클래스를 로딩한 이후에 사용하지 않을 것이라는 판단이 섰을 때 로딩을 해제하는 것을 생각해 볼 수는 있겠지만…

프로그램을 실행한다는 것은 보조기억장치의 명령어를 메모리에 올리고 메모리 컨트롤러를 통해 CPU가 해당 명령어들을 실행한다는 것이며, 그 과정에서 L3 쉐어드 캐시를 쓰고 코어별로 있는 레벨2, 레벨1 캐시도 쓴다.

비유하면 보조기억장치 : 부엌 메모리: 테이블 L3캐시: 자기 앞자리 L2캐시: 개인 식판 L1캐시: 숟가락 CPU: 입

실행할 명령어가 들어있는 조각인 클래스를 메모리에 올린다. 먹으려는 걸 전부 테이블에 올릴 수 없으니, 부엌에서 쪼개둔 클래스를 먹으려는 것만 테이블에 올린다. 자주 먹는 건 앞자리에 올리고 식판에 올리고 숟가락에 올려둔다. 이런 비유와 추상화는 전혀 다른 두 현상들에게서 공통점을 느껴지게 한다.

GC는 오직 힙 영역에 있는 가비지만 (더이상 사용되지 않는 인스턴스) 청소한다. Method Area, Stack 은 당연히 건드려서도 안되고 건드리지 않는다.

스태틱 변수의 한계와 극복 방법은?

스태틱 변수는 해당 클래스가 로딩될 때 스택 영역에 해당 클래스의 프레임 내에 단 한번 생성되기 때문에 단 하나만 쓸 수 있다. 그것이 한계이다. 이 한계를 극복할 필요가 있을 때 (개별적으로 여러개를 사용해야 할 때) 인스턴스 변수를 쓰면 된다.

인스턴스 변수의 생명주기?

new 명령어를 통해서 인스턴스 변수가 메모리에 생성되며, 어떤 레퍼런스도 그 인스턴스의 주소를 가지고 있지 않아서 GC가 청소할 때 죽는다.

인스턴스 메서드를 설명할 수 있는가.

static 키워드를 안쓰면 인스턴스 메서드로 선언된 것이다. this 내장변수를 가지고 있다.

this 변수에 대해 설명할 수 있는가?

호출한 인스턴스의 주소를 가리키는 내장변수이다.

스태틱 메서드와 인스턴스 메서드를 각각 언제 써야 하는지?

this 내장변수의 필요성이 있는지를 따져보면 된다. this 내장변수의 필요성이 있다면 인스턴스를 쓰는 것이다. 인스턴스의 메서드나 변수를 써야 하는 경우는 인스턴스 메서드로 만들어줘야 한다. 그럴 필요가 없다면 스태틱 메서드로 만들어도 된다.

생성자를 설명할 수 있는가?

생성자는 인스턴스를 new 명령어로 생성할 때 자동으로 호출되는 메서드를 말한다. new 명령어로 생성할 때 단 한번만 무조건 실행된다. 생성자를 정의하지 않아도 기본생성자가 생성된다. (파라미터 안 받고 아무 명령도 실행하지 않는다.) 생성자는 보통 인스턴스 메서드의 변수를 초기화하는 용도로 사용된다.


C++ VS Java

객체지향 프로그래밍? 한 줄로 핵심만 말하면.
소스를 클래스 단위로 잘게 쪼개서 관리하자. (함수로 쪼갰다가, 함수를 클래스로 그룹핑)

C++는 OOP를 구현하기 위해 소스를 클래스들로 잘게 쪼갠다. 근데 C++는 컴파일 할 때 쪼개진걸 다시 묶어서 실행파일로 만든다.

C++ 컴파일 결과인 실행파일을 CPU로 실행할 때는 이 실행 파일이 통쨰로 RAM에 올라간다.

Java또한 OOP를 구현하기 위해 소스를 클래스 단위로 잘게 쪼갠다. Java는 소스를 컴파일하면 각각의 클래스 파일로 쪼개져서 실행파일(정확히는 중간파일)이 만들어진다.

Java 바이트코드를 JVM으로 CPU에서 실행할 때는 필요할 때 RAM에 로딩된다.

그래서 메모리를 Java에서 더 효율적으로 쓴다. (결론은 아니다. 이 스토리의 흐름을 위한 단편적인 문장이다.)

DLL의 등장(Dynamic Linking Library)

C++ 은 클래스를 쪼개도 컴파일 하면 하나의 파일이다. 그래서 하나의 프로그램을 컴파일하여 커다란 실행 파일이 나오면 그 전체를 RAM에 로딩해야 한다. (실행하려면) 이것은 메모리를 비효율적으로 사용하는 것이라 해결 방법이 등장한다. 그것이 DLL이다. 동적으로 링크하는 라이브러리 라는 의미다. 컴파일하는 실행 파일들을 쪼개서 컴파일한 것이다. DLL단위로 메모리에 올릴 수 있게 한 것이다. DLL은 클래스들의 묶음을 컴파일한 통짜 결과이다.

인스턴스 필드

인스턴스 변수보다 인스턴스 필드라는 용어가 더 자연스럽다.

관습적 표현: 언어의 경제성

인스턴스의 주소를 가리키는 레퍼런스. 레퍼런스는 주소를 담은 변수다. 그런데 그냥 레퍼런스를 객체라고 부른다.

ClassName obj = new ClassName(); obj는 레퍼런스다.

obj가 가리키는 인스턴스를 아래와 같이 표현한다.

  • obj 레퍼런스가 가리키는 인스턴스
  • obj가 가리키는 인스턴스
  • obj 인스턴스
  • obj 객체

레퍼런스 배열

변수는 두 가지를 저장한다. 가장 기초가 되는, 원시의 값(Primitive value)을 저장하거나, 값을 저장하고 있는 주소(Reference)를 저장한다. 변수는 정수, 부동소수점, 논리값, 문자를 제외하면 다 레퍼런스다.

new 명령어로 인스턴스를 생성할 때, 레퍼런스를 배열로 생성할 수도 있다. 이를 레퍼런스 배열이라고 한다. (Reference Array)

과제에 관한 변수와 메서드를 Assignment 라는 클래스로 쪼개 둔 상황이라고 하자. 각 사용자(a~z)별로 Assignment 인스턴스를 만들어주고 싶을 때

1
2
3
4
5
Assignment a = new Assignment();
Assignment b = new Assignment();
Assignment c = new Assignment();
...
Assignment z = new Assignment();

라고 하기는 너무 힘드니까, 당연히 배열을 사용하자는 생각을 하게 된다. 그래서 아래와 같은 문법을 만들게 된다.

1
Assignment[] assignment = new Assignment[100];

참고로 인스턴스는 JVM이 관리하므로 인스턴스의 배열을 만드는 방법은 없다. C++같은 언어는 힙 영역에 배열을 만들 수 있으나 Java는 인스턴스의 배열을 만드는 방법이 없다. C++ 에서 Assignment a[10]; 하면 실제로 메모리에 배열이 생긴다. 근데 힙 영역이 아닌 스택 영역에 생긴다. 힙에 만드려면 아래와 같이 해야 한다.

Assignment p;
p = new Assignment[10];
...
// 메모리 회수는 수동이다.
delete p;

레퍼런스 배열과 인스턴스

Assignment[] assignments = new Asignment[3]; 이렇게 레퍼런스 배열을 생성하면…

  1. 인스턴스가 하나 생성된다. 레퍼런스의 배열 형태이며 3개이다.
  2. assignments 라는 레퍼런스에 레퍼런스 배열의 시작 주소가 저장된다.

인스턴스 내에 있는 레퍼런스의 배열은 현재 인스턴스의 주소를 저장하고 있지 않다. 각각의 레퍼런스에 대하여 인스턴스는 생성되어 있지 않다.

뭔가 만들 때…

스케치 하고 지우고 다시 스케치하고 선 잡고 명암 잡고 채색 하는 것처럼… 프로그램도 한번에 땅 하구 작성하는 게 아니다

예외처리나 리팩터링 등은 천천히 변경시켜가면서 만들어가는거지 처음 작성할 때부터 뙁 하는 것 아니다.

라면을 끓일 때도 물끓이고 스프 넣고 물 넣고 파 넣고 계란 넣고… 하나를 하기까지 과정이 있다. 두려워마라.

코드 문해력 : Code Literacy

작가가 되고 싶다면. 먼저 필사를 하면서 따라가야 할 것이다.

코드도 남의 코드를 따라 쳐 봐야 한다. 가독성이 좋고 이해하기 쉽고 하나의 흐름인 것처럼 읽기 편하고 이해하기 쉬우면 난해하지 않고 실력있는 프로그래머의 코드다. 이런 코드를 자주 치면 그 사람의 문체, 코딩 스타일이 나에게도 배게 된다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
if (length == assignments.length) {  
  //System.out.println("용량 초과로 과제 더 이상 입력 불가함");  
 //배열 크기 부족하면, 크기 50% 증가시킨 새 배열 만들어서 옮기기  
  int oldSize = assignments.length;  
  int newSize = oldSize + oldSize / 2;  
  Assignment[] arr = new Assignment[newSize];  
  for (int i = 0; i < oldSize; i++) {  
    arr[i] = assignments[i];  
  }  
  assignments = arr;  
  //여기서 레퍼런스가 assignments가 가리키던 기존의 배열(인스턴스)는 가비지가 된다.  
  
  return;  
}

위 코드가 배열 길이 초과하면 기존배열크기를 50% 증가시킨 새 배열을 만들고 기존 배열을 새 배열에 복사한 다음 기존에 쓰던 레퍼런스를 새 배열로 가리키는 것이라고 해석되는가?

static 변수의 초기화

스태틱 변수는 자동으로 초기화되지만 초보때는 직접 초기화 해 줘라.

이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.