Backend/Java

[Java] 객체지향 프로그래밍 1(Object-oriented Programming 1) - TIL 221214

짜잉이 2022. 12. 14. 23:14

📖 자바의 정석 Chapter 06 참고


3. 변수와 메서드

3.7 JVM의 메모리 구조

  • 응용프로그램이 실행되면, JVM은 시스템으로부터 프로그램을 수행하는데 필요한 메모리를 할당받는다.
  • JVM은 이 메모리를 용도에 따라 여러 영역으로 나누어 관리하는 그 중 3가지 주요 영역은 method area, call stack, heap이다.

(~~~p.261 jvm 메모리 구조 이미지 넣기)

 

1. 메서드 영역(method area)

  • 프로그램 실행 중 어떤 클래스가 사용되면, JVM은 해당 클래스의 클래스파일(*.class)을 읽어서 분석하여 클래스에 대한 정보(클래스 데이터)를 이 곳에 저장한다.
  • 이 때, 그 클래스의 클래스 변수(class variable)도 이 영역에 함께 생성된다.

2. 힙(heap)

  • 인스턴스변수(instance variable)들이 생성되는 공간이다.
  • 프로그램 실행 중 생성되는 인스턴스는 모두 이 곳에 생성된다.

3. 호출스택(call stack 또는 execution stack: 실행스택)

  • 호출스택은 메서드의 작업에 필요한 메모리 공간을 제공한다.
  • 메서드가 호출되면, 호출스택에 호출된 메서드를 위한 메모리가 할당되며, 이 메모리는 메서드가 작업을 수행하는 동안 지역변수(매개변수 포함)들과 연산의 중간결과 등을 저장하는데 사용된다.
  • 그리고 메서드가 작업을 마치면 할당되었던 메모리공간은 반환되어 비워진다.

호출스택의 특징

- 메서드가 호출되면 수행에 필요한 만큼의 메모리를 스택에 할당받는다.
- 메서드가 수행을 마치고나면 사용했던 메모리를 반환하고 스택에서 제거된다.
- 호출스택의 제일 위에 있는 메서드현재 실행중 메서드이다.
- 아래에 있는 메서드바로 위의 메서드를 호출한 메서드이다.

 

3.8 기본형 매개변수와 참조형 매개변수

매개변수의 타입
기본형(primitive type) - 기본형 값이 복사된다.
참조형(reference type) - 인스턴스의 주소가 복사된다.

기본형 매개변수 - 변수의 값을 읽기만 할 수 있다. (read only)
참조형 매개변수 - 변수의 값을 읽고 변경할 수 있다. (read & write)
  • 배열도 객체와 같이 참조변수를 통해 데이터가 저장된 공간에 접근하기 때문에 참조형 매개변수의 경우와 같은 결과를 얻는다. (매개변수의 타입배열이면 이것도 참조형 매개변수)
  • 임시적으로 간단히 처리할 때는 별도의 클래스를 선언하는 것보다 배열을 이용할 수도 있다.

 

3.9 참조형 반환타입

  • 매개변수 뿐만 아니라 반환타입(반환하는 값의 타입)도 참조형이 될 수 있다.
"반환타입이 '참조형'이라는 것은 메서드가 '객체의 주소'를 반환한다는 것을 의미한다."

(~~~ 설명 이미지 넣고 예시코드 설명 좀 더 추가)

 

3.10 재귀호출(recursive call)

void method() {
    method(); // 재귀호출. 메서드 자신을 호출한다.
}


void method(int n) {
    if(n == 0) return;
    
    System.out.println(n);
    method(--n); // 재귀호출
}

// 위의 코드를 반복문으로 작성하면 아래의 코드와 같다.
void method(int n) {
    while(n != 0) {
    	System.out.println(n--);
    }
}
  • 재귀호출(recursive call): 매서드의 내부에서 메서드 자신을 다시 호출하는 것.
  • 재귀 메서드: 재귀호출을 하는 메서드.
  • 호출된 메서드는 '값에 의한 호출(call by value)'을 통해, 원래의 값이 아닌 복사된 값으로 작업하기 때문에 호출한 메서드와 관계없이 독립적인 작업수행이 가능하다.
  • 무한반복문이 조건문과 함께 사용되어야하는 것처럼, 재귀호출도 조건문이 필수적으로 따라다닌다.
  • 재귀호출은 반복문과 유사한 점이 많다. 대부분의 재귀호출은 반복문으로 작성하는 것이 가능하다.
  • 반복적인 작업을 처리해야 한다면, 먼저 반복문으로 작성해보고 재귀호출로 간단히 할 수 없는지 고민해보자.
  • 재귀호출은 비효율적이므로 재귀호출에 드는 비용보다 재귀호출의 논리적 간결함이 주는 이득이 충분히 큰 경우에만 사용해야 한다. 

대표적인 재귀호출의 예: 팩토리얼(factorial) 구하기

n!(n은 양의 정수) e.g. 5! = 5 * 4 * 3 * 2 * 1 = 120

팩토리얼을 수학적 메서드로 표현

f(n) = n * f(n-1), 단 f(1) = 1

팩토리얼 예시 코드

// static메서드이므로 인스턴스를 생성하지 않고 직접 호출할 수 있다.
static int factorial(int n) {
    // 매개변수 n의 상한값을 12로 정한 이유는 13!의 값이 매서드factorial의 반환타입인 int의 최대값(약 20억)보다 크기 때문이다.
    // 그 이상의 값을 구하고 싶으면 반환타입을 int보다 큰 타입으로 바꾸면 된다.
    if(n <= 0 || n > 12) return -1; // 매개변수 n의 유효성 검사
    if(n == 1) return 1;
    
    return n * factorial(n-1);
}


// 재귀메서드 factorial을 반복문으로 작성한 코드
int factorial(int n) {
    int result = 1;
    while(n != 0)
        result *= n--;
    return result;    
}

 

Q. p.275 예제 6-16 코드 20행
System.out.printf("%2d!=%20d%n", i, result);
전체가 2자리 수...전체가 20자리 수...  i? result? 위치??

 

 

3.11 클래스 메서드(static메서드)와 인스턴스 메서드

  • 메서드 앞static이 붙어 있으면 클래스메서드이고 붙어있지 않으면 인스턴스 메서드이다.
  • 클래스 메서드도 클래스변수처럼, 객체를 생성하지 않고도 '클래스이름.메서드이름(매개변수)' 형식으로 호출이 가능하다.
    인스턴스 메서드는 반드시 객체를 생성해야만 호출할 수 있다.
  • 인스턴스 메서드는 인스턴스 변수와 관련된 작업을 하는, 즉 메서드의 작업을 수행하는데 인스턴스 변수를 필요로 하는 메서드이다. 인스턴스 변수는 인스턴스(객체)를 생성해야만 만들어지므로 인스턴스 메서드 역시 인스턴스를 생성해야만 호출할 수 있다.
    클래스 메서드(static메서드)는 메서드 중에서 인스턴스와 관계없는(인스턴스 변수나 인스턴스 메서드를 사용하지 않는) 메서드를 말한다.
  • 멤버변수: 클래스 영역에 선언된 변수. 인스턴스변수와 static변수를 모두 통칭한다.
    클래스변수(static변수): 멤버변수 중에 static이 붙은 변수 (static O)
    인스턴스변수: 멤버변수 중에 static이 붙지 않은 변수 (static X)

 

1. 클래스를 설계할 때, 멤버변수 중 모든 인스턴스에 공통으로 사용하는 것에 static을 붙인다.

- 모든 인스턴스에서 같은 값이 유지되어야 하는 변수는 static을 붙여서 클래스 변수로 정의해야 한다.

 

2. 클래스 변수(static변수)인스턴스를 생성하지 않아도 사용할 수 있다.

- 클래스변수는 클래스가 메모리에 올라갈 때 이미 자동적으로 생성되기 때문이다.

 

3. 클래스 메서드(static메서드)인스턴스 변수를 사용할 수 없다.

- 클래스메서드는 인스턴스 생성 없이 호출가능하므로 클래스 메서드가 호출되었을 때 인스턴스가 존재하지 않을 수도 있다. 그래서 클래스 메서드에서 인스턴스변수의 사용을 금지한다.

- 반면에 인스턴스 변수나 인스턴스 메서드에서는 static이 붙은 멤버를 사용하는 것이 언제나 가능하다. 인스턴스 변수가 존재한다는 것은 static변수가 이미 메모리에 존재한다는 것을 의미하기 때문이다.

 

4. 메서드 내에서 인스턴스 변수를 사용하지 않는다면, static을 붙이는 것을 고려한다.

- 메서드 호출시간이 짧아지므로 성능이 향상된다. static을 안 붙인 메서드(인스턴스메서드)는 실행 시 호출되어야할 메서드를 찾는 과정이 추가적으로 필요하기 때문에 시간이 더 소요된다.

 

- 클래스의 멤버변수 중 모든 인스턴스에 공통된 값을 유지해야 하는 것이 있는지 살펴보고 있으면, static을 붙여준다. 
- 작성한 메서드 중에서 인스턴스 변수나 인스턴스 메서드를 사용하지 않는 메서드에 static을 붙일 것을 고려한다.

 

3.12 클래스 멤버와 인스턴스 멤버간의 참조와 호출

  • 같은 클래스에 속한 멤버들 간에는 별도의 인스턴스를 생성하지 않고서로 참조 또는 호출이 가능하다.
  • 단, 클래스멤버가 인스턴스 멤버를 참조 또는 호출하고자 하는 경우에는 인스턴스를 생성해야 한다.
    ->인스턴스 멤버가 존재하는 시점에 클래스멤버는 항상 존재하지만, 클래스멤버가 존재하는 시점에 인스턴스 멤버가 존재하지 않을 수도 있기 때문이다.
class MemberCall {
    int iv = 10;        // 인스턴스변수
    static int cv = 20; // 클래스변수(static변수)
    
    int iv2 = cv;
//  static int cv2 = iv;                  // 에러. 클래스변수는 인스턴스 변수를 사용할 수 없음.
    static int cv2 = new MemberCall().iv; // 이처럼 객체를 생성해야 사용가능.

    /* static 메서드 */
    static void staticMethod1() {
        System.out.println(cv);     // 클래스메서드(static메서드)에서 클래스변수는 사용할 수 있다.
//      System.out.println(iv);     // 에러. 클래스메서드에서 인스턴스변수를 사용불가.
        MemberCall c = new MemberCall();
        System.out.println(c.iv);   // 객체를 생성한 후에야 인스턴스변수의 참조가능.
    }
    
    /* 인스턴스 메서드 */
    void instanceMethod1() {
        System.out.println(cv); // 인스턴스메서드에서 클래스 변수를 사용할 수 있다.
        System.out.println(iv); // 인스턴스메서드에서는 인스턴스변수를 바로 사용가능.
    }
    
    /* static 메서드 */
    static void staticMethod2() {
        staticMethod1();     // 클래스메서드에서 static메서드를 호출할 수 있다.
//      instanceMethod1();   // 에러. 클래스메서드에서는 인스턴스메서드를 호출할 수 없음.
        MemberCall c = new MemberCall();
        c.instanceMethod1(); // 인스턴스를 생성한 후에야 호출할 수 있음.
    }
    
    /* 인스턴스 메서드 */
    void instanceMethod2() {
        staticMethod1();   // 인스턴스메서드에서는 인스턴스메서드와 클래스메서드
        instanceMethod1(); // 모두 인스턴스 생성없이 바로 호출이 가능하다.
    }

}
  • 클래스멤버(클래스변수와 클래스메서드)는 언제나 참조 또는 호출이 가능하기 때문에 인스턴스멤버가 클래스멤버를 사용하는 것은 아무 문제가 없다.
  • 클래스멤버 간의 참조 또는 호출 역시 아무 문제가 없다.
  • 인스턴스멤버(인스턴스변수와 인스턴스메서드)는 반드시 객체를 생성한 후에만 참조 또는 호출이 가능하기 때문에 클래스멤버가 인스턴스멤버를 참조, 호출하기 위해서는 객체를 생성해야 한다.
  • 인스턴스멤버 간의 호출에는 아무 문제가 없다. 하나의 인스턴스 멤버가 존재한다는 것은 인스턴스가 이미 생성되어있다는 것을 의미하며, 즉 다른 인스턴스멤버들도 모두 존재하기 때문이다.

 

MemberCall c = new MemberCall();
int result = c.instanceMethod1();

// 위의 두 줄을 다음과 같이 한 줄로 나타낼 수 있다.
// 대신 참조변수를 선언하지 않았기 때문에 생성된 MemberCall인스턴스는 더 이상 사용할 수 없다.
int result = new memberCall().instanceMethod1();