과학

자바로 프로그래밍에 입문할래요: 2.1. 정적 메소드 (1)

이제 2장이네. 2장에서는 함수와 라이브러리에 대해서 집중할 거야.

지금부터는 객체지향의 개념이 본문에 스며드는 단계야.

 

1.5. 입출력 (2)

1.5. 입출력 (1)

1.4. 배열 (3)

1.4. 배열 (2)

1.4 배열 (1)

1.3. 조건문과 반복문 (2)

1.3. 조건문과 반복문 (1)

1.2. 내장 자료형 (2)

1.2. 내장 자료형 (1)

0.0. 여는 글, 1.1. 첫 프로그램 만들기

 

================================

 

2. 함수와 모듈(Functions and Modules)

  2장에서는 조건문이나 반복문과 같이 제어 흐름에 깊은 영향을 준 구조에 대해 집중해볼 것입니다. 바로 함수입니다. 함수란, 서로 다른 코드 조각 사이에서 제어 흐름을 왕복할 수 있는 것입니다. 함수(자바에선 정적 메소드)는 작업을 명확하게 구분한다는 점, 또한 일반적으로 코드를 재사용 할 수 있다는 점에서 중요합니다.

 

  우리는 우리가 독립적으로 컴파일 할 수 있는 모듈로 기능을 함께 묶을 것입니다. 전산 작업을 합리적인 크기의 하위 작업들로 쪼개는 것이죠. 우리는 이 챕터를 통해 모듈화 프로그래밍(Modular programming)이라는 프로그래밍 스타일에서 나만의 모듈을 어떻게 구성하고 사용하는지에 대해 배워볼 것입니다.

 

  몇몇 모듈은 다른 많은 프로그램에서 코드를 재사용 하기 위한 의도로 개발됩니다. 우리는 사실 수학적 계산을 하기 위해, 자바의 라이브러리라는 모듈을 사용해왔었습니다. 특히 이 챕터에서 난수를 생성하고, 자료를 분석하고, 입출력을 하는 라이브러리들에 대해 자세히 알아볼 것입니다. 라이브러리는 우리가 프로그램에서 사용하기 위한 연산들의 확장입니다.

 

  제어권을 자기 스스로에게 다시 한 번 넘기는 함수도 있습니다. 이런 처리를 재귀라고 합니다. 재귀는 별로 직관적이진 않지만, 다루기 복잡하고 어려운 것들을 가끔은 손쉽게 처리할 수 있게 만듭니다.

 

  "프로그램에서 작업의 단위를 나눌 수 있다면, 그렇게 해야 합니다." 우리는 이 구절을 계속 반복할 것이며, 아주 복잡한 프로그램이 어떤 하위 작업들로 분할되어 있는지, 독립적으로 개발된 모듈들이 각 하위 작업들과 어떻게 소통하는지에 대해 알게 될 것입니다.

 

 

정적 메소드(Static methods)

  함수를 구현하기 위해 사용할 수 있는 자바 문법은 정적 메소드입니다. 제어자(modifier) static은 정적이라는 의미이며, '정적'의 자세한 의미는 나중에 다루도록 하겠습니다. 여러분들은 이미 정적 메소드들을 많이 사용해왔습니다. 예를 들면 System.out.println()이나 수학 함수들인 Math.abs(), Math.sqrt()와 같은 것들이죠. StdIn, StdOut, StdDraw, StdAudio에 있는 메소드들 또한 정적 메소드들입니다. 마지막으로 모든 자바 프로그램은 main() 이라는 정적 메소드를 갖고 있습니다.

 

   수학에서 함수란 명시된 영역(정의역)의 값과, 다른 명시된 영역(치역)의 값이 대응하는 것입니다. 예를 들어, f(x) = x^2 의 경우 2는 4에 대응합니다. 3은 9에, 4는 16에 대응하게 됩니다. 우리는 수학 함수를 구현한 정적 메소드를 공부할 것입니다. 여러분들과 친숙할 것이기 때문입니다.

 

  이후에 우리는 수학 함수를 구현한 것보다 더 많은 정적 메소드들에 대해 배워볼 것입니다. 정적 메소드는 숫자뿐만 아니라 문자열이나 다른 자료형들을 정의역과 치역으로 활용할 수 있습니다. 심지어 치역이 아예 없는 경우도 있죠. 또한 프로그램을 정리하고, 복잡한 프로그래밍 작업을 간단화하는 데에 정적 메소드를 어떻게 사용할 수 있는지도 알아볼 것입니다.

 

  "프로그램에서 작업의 단위를 나눌 수 있다면, 그렇게 해야 합니다." 정적 메소드는 이 부분에서 핵심적인 해답을 제시합니다. 여러분이 수필을 쓴다면, 문단을 나눠야 합니다. 마찬가지로 프로그램을 작성할 때에는, 메소드를 나눠야 합니다. 큰 작업을 작은 작업으로 나누는 것은 프로그래밍에서 굉장히 중요합니다. 이는 오류를 쉽게 제거할 수 있게 하고, 유지보수와 재사용 등 좋은 소프트웨어를 개발하는데 중요한 것들을 가능케 하기 떄문입니다.

 

 

정적 메소드의 정의 및 사용(Using and defining static methods)

  여러분들도 알다시피, 자바의 Math 라이브러리를 사용하는 방법은 쉽게 이해할 수 있습니다. 예를 들어, Math.abs(a-b) 를 사용하면, 이 코드는 어딘가에 있을 자바의 Math.abs() 메소드에 의해 계산되는 값으로 대체되는 것입니다. 이는 아주 직관적이죠. 시스템이 이 효과를 만들기 위해 어떤 일을 해야하는지에 대해 생각해본다면, 이는 프로그램의 제어 흐름 변경을 수반함을 예측할 수 있습니다. 이는 마치 조건문이나 반복문과 같죠.

 

  여러분은 main() 이외에도 다른 정적 메소드들을 정의할 수 있습니다. 다음 프로그램은 이전에 우리가 작성해보았던 <프로그램 1.3.6>을 정적 메소드와 함께 다시 한 번 작성해본 것입니다. 이 구현은 기존의 프로그램보다 훨씬 좋은데, 두 개의 작업으로 명확하게 구분되어 있기 때문입니다: 제곱근을 계산하는 것과, 입출력을 하는 것이죠. "프로그램에서 작업의 단위를 나눌 수 있다면, 그렇게 해야 합니다."

 

 

  <프로그램 2.1.1, Newton>은 <프로그램 1.3.6>과 같이 제곱근을 반환하는 프로그램입니다. 다만 다른 점이 두 가지 있습니다. 첫째, sqrt()는 인자가 음수일 때 Double.NaN을 반환합니다. 둘째, main()은 sqrt()을 단순히 한 번 호출하지 않으며, 명령행에 의해 주어진 인자들에 따라 여러 번 호출합니다.  

 

프로그램 2.1.1: Newton''s method(revisited)

 
public class Newton
{
	public static double sqrt(double c)
	{	// Compute the square root of c.
		if (c < 0)
			return Double.NaN;
		double err = 1e-15;
		double t = c;
		while (Math.abs(t - c / t) > err * t)
			t = (c / t + t) / 2.0;
		return t;
	}

	public static void main(String[] args)
	{ 	// Print square roots of arguments.
		int N = args.length;
		double[] a = new double[N];
		for (int i = 0; i < N; i++)
			a[i] = Double.parseDouble(args[i]);
		for (int i = 0; i < N; i++)
		{ // Print square root of ith argument.
			double x = sqrt(a[i]);
			StdOut.println(x);
		}
	}
}
% java Newton 1.0 2.0 3.0 1000000.1
1.0
1.414213562373095
1.7320508075688772
1000.000499999875

% java Newton NaN Infinity 0 -0 -2
NaN
Infinity
0.0
-0.0
NaN

 

  정적 메소드는 가능한 인자들에 대해 잘 정의된 값을 반환해야 합니다. 다수의 명령행 인자를 받는 것은 Newton 연산이 다양한 입력값에 대해 잘 작동하는지 확인할 수 있게 합니다.

 

 

제어 흐름(Control flow)

  <Newton>은 두 정적 메소드로 구성됩니다. sqrt()와 main()입니다. sqrt()가 코드의 앞 부분에 있지만, 처음으로 실행되는 구문은 언제나 main()의 첫 구문입니다. sqrt(a[i]) 코드가 실행된 후에, 정적 메소드인 sqrt()가 함수 호출(function call)으로써 sqrt() 메소드의 첫 줄로 제어 흐름이 변경됩니다.

 

  또한 sqrt() 안에 있는 c의 값은 main()에서 호출될 때 a[i]에 의해 초기화 됩니다. 그 후엔 sqrt() 메소드에서 return문을 만날 때까지 실행되며, return을 만나면 다시 sqrt()를 호출했던 main()의 구문으로 제어권이 되돌아가게 됩니다.

 

  더 나아가, sqrt(a[0])의 호출은 sqrt()가 호출되었을 때 반환되는 t 값과 동일한 효과를 지닙니다. 다시 말해서 sqrt(a[0]) 대신 반환되는 t 값이 그 자리에 들어가도 전혀 문제가 되지 않고, 또 그 자리에 t가 들어간 것처럼 다룰 수도 있습니다. 마치 sqrt(a[0])이 그 자체로 double형 변수인 것처럼 다룰 수 있다는 의미입니다.

  

 

함수 호출 추적 테이블(Function call trace)

  함수 호출의 제어 흐름을 따라가는 방법으로, 각 함수가 자신이 호출됐을 때 자신의 이름과 인자를 화면에 출력하는 것을 상상해볼 수 있습니다. 또한 값을 반환할 때 반환한 값을 함께 명시하죠. 또한 들여쓰기로 제어의 흐름을 드러내고, 각 함수가 예상대로 흘러가는지 확인 할 수 있게 돕습니다.

 


 

 

 

 

정적 메소드의 구조

 

 

 

 

  제곱근 함수는 음수가 아닌 실수 두 개가 서로 대응하는 함수입니다. <Newton>의 sqrt() 정적 메소드는 double과 double이 대응하죠. 정적 메소드의 첫 줄은 메소드의 시그니처(signature)라고 하며, 메소드의 정보를 담습니다. 

 

  정의역은 함수 이름 뒤에 있는 괄호에 이름을 덧붙여 나타냅니다. 이는 우리가 인자를 참조할 때 사용하며, 매개변수(argument variable, 인자 변수)라고 합니다. 치역은 함수 이름 앞에 나타냅니다. 우리는 이 함수를 호출하는 코드를, 해당하는 타입의 표현이 들어갈 수 있는 곳이라면 어디든 넣을 수 있습니다. 예를 들어 double형 변수가 들어갈 수 있는 자리라면, 언제든지 그 자리를 sqrt(a) 따위로 대체할 수 있습니다. sqrt()의 치역이 double이기 때문이죠.

 

  (우리는 public과 static의 의미를 챕터3에서 제대로 다뤄볼 것입니다. 엄밀히 따지면 사실 자바의 시그니처는 이런 키워드들이나 반환 자료형을 포함하지 않습니다만, 우리는 전문가가 아니니 넘어가도록 합시다)

 

  시그니처 다음에는 중괄호로 둘러쌓인 메소드의 바디(body)가 옵니다. 어떤 인자의 값이 주어지든 간에, 메소드는 반드시 반환 값을 계산해야 합니다. 메소드의 바디에서 선언되고 사용된 변수들을 지역 변수(local variables)라고 합니다. 예를 들어 위 <Harmonic>의 경우 sum이라는 지역 변수가 있습니다. 이 변수들은 메소드 바깥에서 사용할 수 없습니다.

 

 

정적 메소드의 속성

  이 절의 나머지 부분에서는 정적 메소드를 만들고 사용하는 데 집중할 것입니다:

 

 

용어

  우리가 여지껏 해왔던 것처럼, 추상적 개념과 그것을 실제로 구현하는 자바 메커니즘의 차이를 도출해보는 것은 유용하죠. 예를 들어 조건문의 개념을 자바에선 if문으로 구현하고 있고, 반복문의 개념을 자바에선 while문으로 구현하고 있습니다. 다음을 참고해보세요.

 

 

 

 

 

스코프(Scope, 범위)

  변수의 스코프는, 곧 이를 참조할 수 있는 구문들의 집합입니다. 쉽게 말해 그 변수를 사용할 수 있는 영역이죠. 자바의 일반적인 규칙은, 중괄호 블록 안에 있는 변수의 스코프는 그 블록 안의 구문들에서만 유효하다는 것입니다. 특히 정적 메소드에 있는 변수들의 스코프 역시 메소드의 바디로 제한됩니다. 따라서, 여러분들은 어떤 정적 메소드에서 다른 정적 메소드에 선언된 변수들을 참조할 수 없습니다. 마치 if문이나 for문 안에서 선언된 변수들은 해당 조건문이나 반복문이 끝나면 더 이상 사용할 수 없는 것과 같죠.

 

  이 때문에, 변수 이름이 같음에도 불구하고 여러 코드 블록에서 각각 독립적으로 사용할 수 있게 됩니다.

 


  

 

매개변수(Argument variables, 인자 변수)

  매개변수는 지역 변수를 쓰는 방법과 같이, 함수 바디의 어느 부분에서든 사용할 수 있습니다. 유일한 차이는, 매개변수는 해당 메소드를 호출한 부분에서 제공하는 값으로 초기화된다는 것입니다. 이런 식의 접근 방식을 pass by value라고 칭합니다. pass by value에서 메소드는 인자의 '값'을 이용해 작업하지, 인자 그 자체를 이용하진 않습니다. 따라서 해당 메소드에서 매개변수의 값을 아무렇게나 바꾸어도, 메소드를 호출한 부분에는 아무런 영향을 미치지 않습니다.

 

  다른 방법은 pass by reference라고 합니다. 호출 코드의 변수 값이 아닌 변수 그 자체를 메소드가 실제 이용하는 것이고, 이는 일부 프로그래밍 환경에서 유용하게 사용됩니다. 그리고 실제로 우리가 곧 접하게 될, 기본 타입이 아닌 인자를 받을 때 자바가 작동하는 방법과 유사합니다.

 

 

여러 개의 메소드

  여러분들은 수많은 정적 메소드들을 하나의 .java 파일에 정의할 수 있습니다. 각 바디는 일련의 구문들과 함께 중괄호로 닫혀있어야 합니다. 메소드들끼리 서로 호출할 수 있다는 점을 빼면, 전부 독립적으로 존재합니다. 어떤 순서로 작성하든지 전혀 상관 없습니다.

 

 

다른 정적 메소드를 호출하기

  앞서 보았듯이 <Newton>에서 main()은 sqrt()를 호출하고, sqrt()는 Math.abs()를 호출합니다. 같은 .java 파일 내에서는 어떤 정적 메소드이든 서로 호출할 수 있습니다. 자바 라이브러리 메소드도 언제든지 호출할 수 있습니다. 또한 다음 절에서 이야기할 것인데, 같은 디렉토리에 있는 .java 파일이라면 해당 파일에 정의된 정적 메소드도 호출할 수 있습니다. 2.3절에서는 심지어 자기 자신을 호출하는 정적 메소드에 대해서도 배워볼 것입니다.

 

 

여러 개의 인자

  자바의 정적 메소드는 여러 개의 매개변수를 받을 수 있습니다. 예를 들어 다음 메소드는 피타고라스의 정리에서 각 변의 길이 a와 b를 받아 빗변의 길이를 반환합니다.

 

 
public static double hypotenuse(double a, double b)
{ return Math.sqrt(a*a + b*b); }

 

  이 경우에는 인자의 자료형이 double형으로 동일하지만, 서로 다른 자료형 역시 가능합니다. 보다시피 함수의 시그니처에는 쉼표로 구분하여 자료형과 해당 매개변수의 이름을 선언할 수 있습니다.

 

 

오버로딩(Overloading)

  시그니처가 다른 정적 메소드들은 서로 다른 정적 메소드입니다. 메소드 시그니처는 앞서 말했다시피 메소드의 첫줄에 적는 것이라고 생각하면 됩니다. 예를 들어, 우리는 종종 서로 다른 숫자 자료형들에 대한 같은 연산들을 정의해야 할 때가 있습니다. 다음은 절대값을 계산하는 메소드들입니다.

 

 
public static int abs(int x)
{
	if (x < 0) return -x;
	else return x;
}
public static double abs(double x)
{
	if (x < 0.0) return -x;
	else return x;
}

 

  이 두 메소드는 분명 비슷합니다. 둘 다 abs라는 이름을 사용하고 있기 때문이죠. 하지만 이 둘은 반환 자료형과 매개변수의 자료형이 다르기 때문에, 서로 다른 메소드입니다. 이렇게 같은 이름을 가졌지만 시그니처가 다른 메소드들을 오버로딩했다고 합니다.

 

  오버로딩은 자바에서 굉장히 흔하게 이루어집니다. 예를 들어 자바의 Math 라이브러리의 메소드들 또한 서로 다른 숫자 자료형들에 대해 각각 구현되어 있습니다. 또한 같은 이름으로 비슷한 기능을 다루기 위해 매개변수의 개수를 달리해서 오버로딩을 활용하기도 합니다.

 

 

단일 반환 값

  수학 함수처럼, 자바의 정적 메소드는 메소드 시그니처에서 선언된 오로지 하나의 값만을 반환합니다. 이는 생각보다 제한적이지는 않습니다. 챕터 3에서 하나의 자료형에 여러 정보를 담는 것을 배우며, 이번 절에서도 배열을 이용해서 값을 반환하는 법을 배울 것입니다. 

 

 

여러 개의 return문

  return문을 만났을 때, 값을 반환함과 동시에 제어 흐름은 다시 해당 메소드를 호출한 곳으로 되돌아가게 됩니다. 여러분은 return문을 어디든지 필요한 곳에 넣을 수 있습니다. <Newton>의 sqrt()를 참고해보세요. 몇몇 프로그래머들은 함수에 오로지 하나의 return문만 있어야 한다고 고집하지만, 우리는 이 강의에서만큼은 그렇게 엄격해지진 않기로 합시다.

 

 

부작용(Side effects)

  정적 메소드는 반환 자료형으로 void 키워드를 사용할 수 있습니다. 이는 반환값이 없음을 나타냅니다. 가끔은 return문이 필요하지 않을 때가 있습니다. void의 경우 return문을 만나지 않아도 해당 메소드의 마지막 구문이 실행되고 나면, 자연스럽게 다시 메소드를 호출했던 부분으로 제어 흐름이 되돌아갑니다. 

 

  void 정적 메소드는 부작용을 야기하는 셈입니다. 값을 반환하지 않았는데도 무언가 함수로서 일을 했다는 것은, 어떻게 보면 프로그램에 대한 부작용을 일으킨 셈이죠. 입력을 소모한다든가, 출력을 생성한다든가, 다른 부분의 시스템 상태를 변경하는 것입니다. 예를 들어 정적 메소드인 main()은 반환값이 void인데, 이는 출력을 생성하기 때문입니다. 자바에선 이런 것들 이외에도 다른 부작용을 야기하는 메소드들을 작성할 수 있는데, 3장까지는 피하도록 합시다. 

 

(계속)

4개의 댓글

l1
2021.05.28
0
2021.05.29

일단 ㅇㄷ

0

굳굳굳

0
무분별한 사용은 차단될 수 있습니다.
번호 제목 글쓴이 추천 수 날짜
516 [과학] 자바로 프로그래밍에 입문할래요: 3.2. 자료형 생성하기 (1) 3 스비니 0 19 시간 전
515 [과학] 자바로 프로그래밍에 입문할래요: 3.1. 자료형 (4) 11 스비니 3 2 일 전
514 [과학] 자바로 프로그래밍에 입문할래요: 3.1. 자료형 (3) 2 스비니 3 3 일 전
513 [과학] SF스압) 최후의질문 / 원래는..(How It Happened) 12 기타치는고라니 14 5 일 전
512 [과학] [양자역학 3부] 슈뢰딩거의 고양이에 대해서. 26 기타치는고라니 3 6 일 전
511 [과학] 자바로 프로그래밍에 입문할래요: 3.1. 자료형 (2) 2 스비니 3 7 일 전
510 [과학] (기상)우리나라 더위의 발생 형태 4가지. 51 마리괭이 20 11 일 전
509 [과학] 자바로 프로그래밍에 입문할래요: 3.1. 자료형 (1) 4 스비니 1 14 일 전
508 [과학] 자바로 프로그래밍에 입문할래요: 2.3. 재귀 (2) 24 스비니 5 22 일 전
507 [과학] 자바로 프로그래밍에 입문할래요: 2.3. 재귀 (1) 3 스비니 3 29 일 전
506 [과학] 블랙홀, 핵융합, 조류(鳥類), 의학사(醫學史) 등 과학 분야 ... 미분가능하지않은... 3 29 일 전
505 [과학] [물리학] 우리는 끝에 도달해 있는 가? 45 특이한 13 2021.07.02
504 [과학] 자바로 프로그래밍에 입문할래요: 2.2. 라이브러리와 클라이... 7 스비니 1 2021.06.28
503 [과학] 양자역학이 탄생하게된 '안'간단한 역사적 배경(2) 43 기타치는고라니 15 2021.06.24
502 [과학] 양자역학이 탄생하게된 '안'간단한 역사적 배경(1) 32 기타치는고라니 38 2021.06.24
501 [과학] 양자역학이 탄생하게 된 간단한 역사적 배경 19 Kuqi 10 2021.06.23
500 [과학] 자바로 프로그래밍에 입문할래요: 2.2. 라이브러리와 클라이... 4 스비니 0 2021.06.21
499 [과학] 자바로 프로그래밍에 입문할래요: 2.2. 라이브러리와 클라이... 4 스비니 8 2021.06.14
498 [과학] 아무도 알고싶지 않을수도 있는 이야기 - NASA의 인증을 받은... 26 Te2t1c1e 16 2021.06.11
497 [과학] 자바로 프로그래밍에 입문할래요: 2.1. 정적 메소드 (2) 8 스비니 0 2021.06.06