[Java] Ch06_3. 변수와 메서드

토끼는 개발개발·2022년 1월 16일
0

Java

목록 보기
21/33
post-thumbnail

Chapter06. 객체지향 프로그래밍Ⅰ


✏️ 3. 변수와 메서드


3.1 선언위치에 따른 변수의 종류

변수는 클래스변수, 인스턴스변수, 지역변수 모두 세 종류가 있다.

변수의 종류를 결정짓는 중요한 요소는 '변수의 선언된 위치'이므로 변수의 종류를 파악하기 위해서는 변수가 어느 영역에 선언되었는지 확인하는 것이 중요하다. 멤버변수를 제외한 나머지 변수들은 모두 지역변수이며, 멤버변수 중 static이 붙은 것은 클래스 변수, 붙지 않은 것은 인스턴스 변수이다.

Class Variables       // [클래스 영역]
{
	int iv;       // 인스턴스변수
    	static int cv;    // 클래스변수(static변수, 공유변수)
    
	    void method()     // [메서드 영역]
    {                                 
    		int lv = 0;   // 지역변수
    }
}

▶ 인스턴스변수(instance variable)

클래스 영역에 선언되며, 클래스의 인스턴스를 생성할 때 만들어진다. 그렇기 때문에 인스턴스 변수의 값을 읽어 오거나 저장하기 위해서는 먼저 인스턴스를 생성해야 한다. 인스턴스는 독립적인 저장공간을 가지므로 서로 다른 값을 가질 수 있다. 인스턴스마다 고유한 상태를 유지해야하는 속성의 경우, 인스턴스 변수로 선언한다.


▶ 클래스변수(class variable)

클래스 변수를 선언하는 방법은 인스턴스변수 앞에 static을 붙이기만 하면 된다. 인스턴스마다 독립적인 저장 공간을 갖는 인스턴스변수와는 달리, 클래스변수는 모든 인스턴스가 공통된 저장공간(변수)을 공유하게 된다. 한 클래스의 모든 인스턴스들이 공통적인 값을 유지해야하는 속성의 경우, 클래스변수로 선언해야 한다.

클래스변수는 인스턴스변수와 달리 인스턴스를 생성하지 않고도 언제라도 바로 사용할 수 있다는 특징이 있으며, '클래스이름.클래스변수'와 같은 형식으로 사용한다. 예를 들어 Variables클래스의 클래스변수 cv를 사용하려면 'Variables.cv'와 같이 하면 된다.

클래스가 메모리에 '로딩'될 때 생성되어 프로그램이 종료될 때 까지 유지되며, public을 앞에 붙이면 같은 프로그램 내에서 어디서나 접근할 수 있는 '전역변수(global variable)'의 성격을 갖는다.


▶ 지역변수(local variable)

메서드 내에 선언되어 메서드 내에서만 사용 가능하며, 메서드가 종료되면 소멸되어 사용할 수 없게 된다. for문 또는 while문의 블럭 내에 선언된 지역변수는, 지역변수가 선언된 블럭{} 내에서만 사용 가능하며, 블럭{}을 벗어나면 소멸되어 사용할 수 없게 된다.




3.2 클래스변수와 인스턴스변수

인스턴스변수는 인스턴스가 생성될 때 마다 생성되므로 인스턴스마다 각기 다른 값을 유지할 수 있지만, 클래스 변수는 모든 인스턴스가 하나의 저장공간을 공유하므로, 항상 공통된 값을 갖는다.

예를 들어, 카드게임의 카드 클래스를 작성한다고 생각해보자.
카드의 속성에는 카드의 무늬, 숫자, 폭, 높이 정도가 있을 것이다.

Class card {
	String kind;  // 무늬         인스턴스변수
    	int number;   // 숫자         인스턴스변수
        
        static int weight = 100;  // 폭    클래스변수
        static int height = 250;  // 높이  클래스변수
}

각 카드 인스턴스는 자신만의 무늬와 숫자를 유지하고 있어야하므로 이들을 인스턴스변수로 선언하였고, 각 카드의 폭과 높이는 모든 인스턴스가 공통적으로 같은 값을 유지해야하므로 클래스변수로 선언했다.

카드의 폭과 넓이를 변경해야할 필요가 있을 경우, 모든 카드의 폭을 변경하지 않고 한 카드의 weight만 변경해도 모든 카드의 weight값이 변경되는 셈이다.




3.3 메서드

"메서드(method)란, 특정 작업을 수행하는 일련의 문장들을 하나로 묶은 것이다."

메서드는 기본적으로 수학의 함수와 유사하며, 어떤 값을 입력하면 이 값으로 작업을 수행해서 결과를 반환한다. 예를 들어 제곱근을 구하는 메서드 'Math.sqrt()'는 4.0을 입력하면 2.0을 결과로 반환한다. 다만, 수학의 함수와 달리 메서드는 입력값 또는 출력값(결과값)이 없을 수도 있으며, 심지어는 둘 다 없을 수도 있다.

메서드는 필요값을 넣어 원하는 결과만 얻으면 되므로 내부과정을 몰라도 된다. 그래서 메스드를 내부가 보이지 않는 '블랙박스'라고도 한다.


▶ 메서드를 사용하는 이유

1. 높은 재사용성 : 한번 만들어 놓은 메서드는 몇 번이고 호출 가능하며 다른 프로그램에서도 사용이 가능하다.
2. 중복된 코드 제거 : 반복되는 문장들을 묶어서 하나의 메서드로 작성해 놓으면, 중복을 제거할 수 있다.
3. 프로그램의 구조화 : 문장들을 작업단위로 여러 개의 메서드에 담아 프로그램의 구조를 단순화 시킬수 있다.




3.4 메서드의 선언과 구현

메서드는 '선언부(header)'와 '구현부(body)'로 이루어져 있다.

반환타입 메서드 이름 (타입 변수명, 타입 변수명, ...)     // 선언부
{ // 메서드 호출시 수행될 코드 }                       // 구현부
int add(int a, int b)                               // 선언부
{
	int result = a + b;                         // 구현부
    return result; // 호출한 메서드로 결과를 반환한다.
}

▶ 매서드 선언부(method declaration, method header)

메서드 선언부는 '메서드의 이름', '매개변수 선언', '반환타입'으로 구성되어 있다.

int add(int a, int b)     
//여기서 int는 반환타입(출력), add는 메서드 이름, (int x, int y)는 매개변수 선언(입력)이다.
  • 매개변수(parameter)는 메서드가 작업을 수행하는데 필요한 값들(입력)을 제공받기 위한 것이며, 필요한 값의 개수만큼 쉼표','를 사용해 변수를 선언한다. 이 때, 일반적인 변수선언과 달리 두 변수의 타입이 같아도 변수의 타입을 생략할 수 없다.

  • 반환타입(return type)은 메서드의 작업수행 결과(출력)인 '반환값'의 타입을 적는다. 반환값이 없는 경우 반환타입으로 'void'를 적어야 한다.


▶ 메서드의 구현부(method body)

메서드의 선언부 다음에 오는 괄호{}를 '메서드의 구현부'라 한다. 메서드 호출 시 수행될 문장들을 넣는다.

{
	int result = a + b;                         
    	return result; // 호출한 메서드로 결과를 반환한다.
}
  • return문은 메서드의 반환타입이 'void'가 아닌 경우, 구현부{}안에 반드시 'return 반환값;'이 포함되어 있어야 한다. 이 문장은 작업을 수행한 결과인 반환값을 호출한 메서드로 전달하는데, 이 값의 타입은 반환타입과 일치하거나 적어도 자동 형변환이 가능한 것이어야 한다.
    여러 개의 변수를 선언할 수 있는 매개변수와 달리 return문은 단 하나의 값만 반환할 수 있다.

▶ 지역변수(local variable)

메서드 내에 선언된 변수들은 그 메서드 내에서만 사용할 수 있으므로 서로 다른 메서드라면 같은 이름의 변수를 선언해도 된다. 이처럼 메서드 내에 선언된 변수를 '지역변수'라 한다.




3.5 메서드의 호출

메서드이름(값1, 값2, ...);    // 메서드를 호출하는 방법

print99danAll();       // void print99danAll()을 호출
int result = add(3,5)  // int add(int x, int y)를 호출하고, 결과를 result에 저장
  • 인자(argument)와 매개변수(parameter)
    : 메서드를 호출할 때 괄호()안에 지정해준 값들을 '인자' 또는 '인수'라고 하는데, 인자의 개수와 순서는 호출된 메서드에 선언된 매개변수와 일치해야 한다. 인자는 메서드가 호출되면서 매개변수에 대입되므로, 인자의 타입은 매개변수의 타입과 일치하거나 자동 형변환이 가능한 것이어야 한다.

▶ ✨ 예제 1

public class MyMathTest {

	public static void main(String[] args) {
		MyMath mm = new MyMath();
		long result1 = mm.add(5L, 3L);
		long result2 = mm.subtract(5L, 3L);
		long result3 = mm.multiply(5L, 3L);
		double result4 = mm.divide(5L, 3L);

		System.out.println("add(5L, 3L) = " + result1);
		System.out.println("substract(5L, 3L) = " + result2);
		System.out.println("muliply(5L, 3L) = " + result3);
		System.out.println("divide(5L, 3L) = " + result4);
	}

}

class MyMath{
	long add(long a, long b) {
		long result = a+b;
		return result;
// return a+b; // 위의 두 줄을 이와 같이 한 줄로 간단히 할 수 있다.
	}
	long subtract(long a, long b) { return a-b; }
	long multiply(long a, long b) { return a*b; }
	double divide(double a, double b) { return a/b; }
}



3.6 return문

return문은 현재 실행중인 메서드를 종료하고 호출한 메서드로 되돌아간다.

반환값의 유무에 관계없이 모든 메서드에는 적어도 하나의 return문이 있어야 한다. 그런데 반환타입이 void인 경우, return문을 생략해도 문제가 없는 이유는 컴파일러가 마지막에 'return;'을 자동적으로 추가해주었기 때문이다.


▶ 반환값(return value)

return문의 반환값으로 주로 변수가 오긴 하지만, 수식이 오기도 한다.
수식이 올 경우, 이 수식을 계산한 결과가 반환된다.

return x + y;

▶ 매개변수의 유효성 검사

메서드의 구현부{}를 작성할 때, 제일 먼저 해야 하는 일이 매개변수의 값이 적절한 것인지 확인하는 것이다.
적절하지 않은 값이 매개변수를 통해 넘어온다면 매개변수의 값을 보정하던가, 보정하는 것이 불가능하다면 return문을 사용해서 작업을 중단하고 호출한 메서드로 되돌아가야한다.

float divide(int x, int y){
	// 작업을 하기 전에 나누는 수(y)가 0인지 확인한다.
    if(y==0) {
    	System.out.println("0으로 나눌 수 없습니다.");
        return 0; // 매개변수가 유효하지 않으므로 메서드를 종료한다.
    }
    
    return x / (float)y;
}



3.7 JVM의 메모리 구조

응용프로그램이 실행되면, JVM은 시스템으로부터 프로그램을 수행하는데 필요한 메모리를 할당받고 JVM은 이 메모리를 용도에 따라 여러 영역으로 나누어 관리한다. 주요 영역 3가지에 대해 알아보자.

▶ 1. 메서드 영역(method area)

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

▶ 2. 힙(heap)

인스턴스가 생성되는 공간. 프로그램 실행 중 생성되는 인스턴스는 모두 이곳에 생성된다. 즉, 인스턴스변수들이 생성되는 공간이다.

▶ 3. 호출스택(call stack 또는 execution stack)

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


각 메서드를 위한 메모리상의 작업공간은 서로 구별되며, 첫 번째로 호출된 메서드를 위한 작업공간이 호출스택의 맨 밑에 마련되고, 첫 번째 메서드 수행 중에 다른 메서드를 호출하면, 첫 번째 메서드의 바로 위에 두 번째로 호출된 메서드를 위한 공간이 마련된다.

이 때 첫 번째 메서드는 수행을 멈추고, 두 번째 메서드가 수행되기 시작한다. 두 번째로 호출된 메서드가 수행을 마치게 되면, 두 번째 메서드를 위해 제공되었던 호출스택의 메모리공간이 반환되며, 첫 번째 메서드는 다시 수행을 계속하게 된다. 첫 번째 메서드가 수행을 마치면, 역시 제공되었던 메모리 공간이 호출스택에서 제거되며 호출스택은 완전히 비워지게 된다. 호출스택의 제일 상위에 위치하는 메서드가 현재 실행 중인 메서드이며, 나머지는 대기상태에 있게된다.

따라서, 호출스택을 조사해 보면 메서드 간의 호출관계와 현재 수행중인 메서드가 어느것인지 알 수 있다.


호출 스택의 특징

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




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

기본형 매개변수: 변수의 값을 읽기만 할 수 있다. (read only)
참조형 매개변수: 변수의 값을 읽고 변경할 수 있다. (read & write)

매개변수 타입이 기본형일 때는 기본형 값이 복사되고, 참조형이면 인스턴스의 주소가 복사된다.
메서드의 매개변수를 기본형으로 선언하면 단순히 저장된 값만 얻지만, 참조형으로 선언하면 값이 저장된 곳의 주소를 알 수 있기 때문에 값을 읽어오는 것은 물론 값을 변경하는 것도 가능하다.


▶ ✨ 예제 2

class Data { int x;}

public class PrimitiveParamEx {

	public static void main(String[] args) {
		Data d = new Data();
		d.x = 10;
		System.out.println("main() : x = " + d.x);
		
		change(d.x);
		System.out.println("After changed(d.x)");
		System.out.println("main() : x = " + d.x);
	}
	
	static void change(int x) { // 기본형 매개변수
		x = 1000;
		System.out.println("change() : x = " + x);
	}

}

예제를 보면, change메서드에서 main메서드로부터 넘겨받은 d.x의 값을 1000으로 변경했는데도 main메서드에서는 d.x의 값이 그대로이다. 이 결과의 과정을 살펴보자.

  1. change 메서드가 호출되면서 'd.x'가 change메서드의 매개변수 x에 복사됨.
  2. change 메서드에서 x의 값을 1000으로 변경.
  3. change 메서드가 종료되면서 매개변수 x는 스택에서 제거됨.

즉, 'd.x'의 값이 변경된 것이 아니라, change메서드의 매개변수 x의 값이 변경된 것이다. 원본이 아닌 복사본이 변경된 것이라 원본에는 아무런 영향을 미치지 못한다. 이처럼 기본형 매개변수는 변수에 저장된 값만 읽을수 있을뿐 변경할 수는 없다.


▶ ✨ 예제 3

class Data { int x; }

public class ReferenceParamEx {

	public static void main(String[] args) {
		Data d = new Data();
		d.x = 10;
		System.out.println("main() : x = " + d.x);
		
		change(d);
		System.out.println("After change(d)");
		System.out.println("main() : x = " + d.x);
	}
	
	static void change(Data d) { // 참조형 매개변수
		d.x = 1000;
		System.out.println("change() : x = " + d.x);
	}

}

예제 2번과 달리 change메서드를 호출한 후에 d.x의 값이 변경되었다. change메서드의 매개변수가 참조형이라서 값이 아니라 '값이 저장된 주소'를 change메서드에게 넘겨주었기 때문에 값을 읽어오는 것뿐만 아니라 변경하는 것도 가능하다.

  1. change메서드가 호출되면서 참조변수 d의 값(주소)이 매개변수 d에 복사됨.
    이제 매개변수 d에 저장된 주소값으로 x에 접근이 가능
  2. change메서드에서 매개변수 d로 x의 값을 1000으로 변경
  3. change메서드가 종료되면서 매개변수 d는 스택에서 제거됨



3.9 참조형 반환타입

반환타입이 '참조형'이라는 것은, 메서드가 '객체의 주소'를 반환한다는 것을 의미한다.

매개변수뿐만 아니라 반환타입도 참조형이 될 수 있다. 모든 참조형 타입은 '객체의 주소'이므로 그저 정수값이 반환되는 것일 뿐 특별할 것이 없다.


▶ ✨ 예제 4

class Data { int x; }

public class ReferenceReturnEx {

	public static void main(String[] args) {
		Data d = new Data();
		d.x = 10;
		
		Data d2 = copy(d);
		System.out.println("d.x = "+d.x);
		System.out.println("d2.x = "+d2.x);
	}
	
	static Data copy(Data d) {
		Data tmp = new Data(); //새로운 객체 tmp를 생성한다.
		tmp.x = d.x;  // d.x의 값을 tmp.x에 복사한다.
		
		return tmp; // 복사한 객체의 주소를 반환한다.
	}

}
  1. copy메서드를 호출하면서 참조변수 d의 값이 매개변수 d에 복사된다.
  2. 새로운 객체를 생성한 다음, d.x에 저장된 값을 tmp.x에 복사한다.
  3. copy메서드가 종료되면서 반환한 tmp의 값은 참조변수 d2에 저장된다.
  4. copy메서드가 종료되어 tmp가 사라졌지만, d2로 새로운 객체를 다룰 수 있다.



3.10 재귀호출(recursive call)

메서드의 내부에서 메서드 자신을 다시 호출하는 것을 '재귀호출'이라하고, 재귀호출을 하는 메서드를 '재귀메서드'라 한다.

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

▶ ✨ 예제 5

public class FactorialTest {

	public static void main(String[] args) {
		int result = factorial(4);
		
		System.out.println(result);

	}
	static int factorial(int n) {
		int result = 0;
		
		if (n==1)
			result = 1;
		else
			result = n * factorial(n-1); // 다시 메서드 자신을 호출
		
		return result;
	}
}

해당 예제는 factiorial 메서드가 static 메서드이므로 인스턴스를 생성하지 않고 직접 호출할 수 있다. 그리고 main메서드와 같은 클래스에 있기 때문에 static메서드를 호출할 때 클래스 이름을 생략하는 것이 가능하다.




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

클래스 메서드: 인스턴스와 관계없는(인스턴스 변수나 인스턴스 메서드를 사용하지 않는)메서드를 클래스 메서드
인스턴스 메서드: 메서드의 작업을 수행하는데 인스턴스 변수를 필요로하는 메서드

변수에서 그랬던 것과 같이, 메서드 앞에 static이 붙어 있으면 클래스 메서드이고 붙어있지 않으면 인스턴스 메서드이다.
클래스 메서드도 클래스변수처럼, 객체를 생성하지 않고도 '클래스이름.메서드이름(매개변수)'와 같은 식으로 호출이 가능하다.

1. 클래스를 설할 때, 멤버변수 중 모든 인스턴스에 공통으로 사용하는 것에 static을 붙인가.
2. 클래스 변수(static 변수)는 인스턴스를 생성하지 않아도 사용할 수 있다.
3. 클래스 메서드(static 메서드)는 인스턴스 변수를 사용할 수 없다.
4. 메서드 내에서 인스턴스 변수를 사용하지 않는다면, static을 붙이는 것을 고려한다.




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

같은 클래스에 속한 멤버들 간에는 별도의 인스턴스를 생성하지 않고도 서로 참조 또는 호출이 가능하다. 단, 클래스 멤버가 인스턴스 멤버를 참조 또는 호출하고자 하는 경우에는 인스턴스를 생성해야 한다.
그 이유는 인스턴스 멤버가 존재하는 시점에 클래스 멤버는 항상 존재하지만, 클래스 멤버가 존재하는 시점에 인스턴스 멤버가 존재하지 않을 수도 있기 때문이다.

class TestClass {
	    void instanceMethod() {} // 인스턴스메서드
    	    static void staticMethod() {} // static메서드
    
	    void instanceMethod2() { // 인스턴스메서드
    		instanceMethod(); // 다른 인스턴스메서드를 호출한다.
        	staticMethod();
            }
    
	    static void staticMethod2() { // static메서드
		instanceMethod(); // 에러!! 인스턴스메서드를 호출 할 수 없다.
        	staticMethod(); // static메서드는 호출 할 수 있다.
	    }
}

같은 클래스 내의 메서드는 서로 객체의 생성이나 참조변수 없이 직접 호출이 가능하지만 static메서드는 인스턴스 메서드를 호출 할 수 없다.
변수와 메서드 간의 호출 또한 인스턴스메서드는 인스턴스 변수를 사용할 수 있지만, static메서드는 인스턴스변수를 사용할 수 없다.




참고
자바의 정석(저자: 남궁성)


오늘의 한 줄

이번 챕터는 정말 한숨을 몇 번 쉬었는지 모르는 챕터였다. 양도 양이지만, 참조 변수에 관한 이해가 어려웠다.
여전히 참조변수에 대해 완벽히 이해하지 못한 듯 하며 클래스멤버와 인스턴스멤버 간의 차이와 구현도 어렵다.
정말 중요한 단원이라, 꼼꼼하게 공부하고 정리했으나 완벽히 이해하기 어려웠다.

유튜브와 다른 참고자료를 통해 추가적인 공부가 필요할 듯 하다.
어려웠던 부분은 세부적으로 다른 포스팅에서 정리해야겠다.

진짜 자바공부 화이팅이다..!!


드디어 완벽히 이해해서 추가로 적는 글-
헷갈렸던 부분이 예제2와 예제3의 차이 부분이었다.
change(d.x)와 change(Data d)의 차이인데 기본형 매개변수와 참조형 매개변수의 차이가 이해가 안됐다.
이 부분은 참조형 매개변수가 쓰이면 <주소값>을 전달받아 해당 주소에 접근해 값을 변경한다고 이해해야 한다.

예를 들어, 예제 2번에서는 change(d.x)로 호출되어 d.x를 받아 x=1000을 실행했지만, 이것은 9000번 주소를 가진 메모리에서 발생한 일이다. 즉, change 메서드가 종료되면 이 메모리는 사라지고, x=1000 메모리 또한 사라진다.

하지만, 예제 3번에서는 change(d)로 주소값을 알려주었다. 마치, "x의 원본 주소는 1004번입니다."처럼 원본으로 접근 가능한 주소를 알려줘서 값 변경이 가능하다. d.x = 1000을 실행함으로써 change메서드가 종료돼도 x의 값은 1000으로 변경된다.


3.12번은 static메서드는 인스턴스메서드를 호출할 수 없다는 것을 이해하면 된다.
그 이유는 인스턴스 멤버가 존재하는 시점에 클래스 멤버는 항상 존재하지만, 클래스 멤버가 존재하는 시점에 인스턴스 멤버가 존재하지 않을 수도 있기 때문이다.

profile
하이 이것은 나의 깨지고 부서지는 기록들입니다

0개의 댓글