본문 바로가기

컴퓨터언어(Computer Language)/자바(Java)

[JAVA] 함수(Function)란?

함수(function) 란 무엇일까 ?

우선 수학에서 함수는 첫 번째 집합의 임의의 한 원소를 두 번째 집합의 오직 한 원소에 대응시키는 대응 관계라고 얘기하고 있다.

임의의 한 원소는 주어진 입력(input) X 으로  볼 수 있고 대응되는 두 번 째 집합을 출력(ouput) Y로 본다면 대응 관계를 아래 그림 처럼 나타낼 수 있고 수학적 표기는  f : X -> Y 로 한다. 

f가 정의역 X, 공역 Y를 갖는 함수라는 뜻이고  f : x -> y 는 f(x) = y 처럼 표기할 수도 있다.

 

 

프로그래밍에서도 함수는 수학적인 개념과 어찌 보면 비슷하다고 볼수 있다.

프로그래밍에서 함수는 최종적으로 수행해야 할 목표를 위해  다양한 문장(Statement), 수식(expression), 연산자(operator) 를 하나로 묶어(grouped together) 작성하는 것이다.   

우리는 지금까지 알게 모르게 이럼 함수들을  많이 사용하고 있었는데 그 중 문자열을 출력하는데 사용했던 System.out.println() 도 함수의 한 종류이다. 

함수를 자바에서 만들기위한 syntax 는 다음과 같다.

 

[ syntax ] 
제어자 리턴(=출력)타입  함수이름(파라미터...) { // function header
    return; // function body
}

함수정의(function definiation)는 함수헤더(function header) + 함수바디(function body)로 구성되는데 함수 바디( { } )에 함수의 목적을 달성하기 위해 필요한 것 다양한 문장(Statement), 수식(expression), 연산자(operator)를 사용하여 작성하면 된다.

함수 헤더는 제어자, 리턴타입, 함수이름, 파라미터로 구성된다.

  • 제어자 (Modifiers) 는 접근제어자(access modifiers) or 비접근 제어자(Non-Access Modifiers)가 올 수 있다.
    • 접근 제어자(access modifiers)   : public, protected, default, private
    • 비접근 제어자(non-access modifiers) :  final, static, abstract, transient, synchronized, volatile
  • 리턴타입은 기본형(primitive types) or 참조형(reference types) or void 키워드(keyword) 가 올 수 있다. 
    • 기본형 (primitive types) : boolean, short, int, long, float, double, char
    • 참조형 (reference types) : 클래스(class), 인터페이스(interface), 열거형(enum), 배열(array) 등
    • void 키워드(keyword) : 아무것도 출력하고 리턴(출력)하고 싶지 않는 함수인 경우 void 키워드를 사용하면 된다. 
  • 함수이름(function name)은  함수가 하는 일이 무엇인지 잘 나타낼수는 것으로 작명하면 되고 동사형(verb)으로  작명 하는 것을 권장한다.  ex) get, find, search, put
  • 프로그래밍에서 함수는 입력 x 가  1개 이상이 올 수도 있고 0개일 수도 있는데 이 x 를 파라미터라고 부른다.
    파라미터의 타입은 리턴타입처럼 기본형(primitive types) or 참조형(reference types)이 가능하다. 

이제 본격적으로 함수를 하나 만들어 보자.

첫번째로 만들어볼 함수의 목적은 입력한 파라미터 k 까지 총 합(sum)을 구하는 것이고 코드는 아래와 같다.

 

1
2
3
4
5
6
7
public int sum(int k) {
    int sum = 0;
    for (int i = 1; i <= k; i++) {
        sum += i;
    }
    return sum;
}
Colored by Color Scripter

 

1라인에 함수를 선언하였는데 sytax 기준으로 하나씩 살펴보면 

제어자는 다른 클래스, 패키지에도 함수가 사용될 수 있도록 public 이 되어야 하고 리턴타입은 정수형 int 이며 함수명은 입력한 k 까지 총 합을 구하는것이 잘 표현되도록 sum (verb : 합계하여 […이] 되다) 으로 하였고 입력 파라미터는 int 타입을 받는 함수라고 선언하였다.

우리는 이제 함수선언(function declaration)문만 보면 대충 어떻게 사용해야하고 어떤 일을 하는지 파악할 수 있어야 한다. 

 

두번째 함수는 배열에서 i, j 번째 인덱스에 있는 값들을 서로 스왑(swap)하는 것을 만들어 보자.

함수명은 swap 이 될 것이고 파라미터는 배열과 서로 스왑할 int i, int j 가 필요해 보인다.

swap 함수는 리턴할 값이 필요 없으므로 리턴 타입은 void 가 적당하다.

 

1
2
3
4
5
public void swap(int[] a, int i, int j) {
    int t = a[i];
    a[i] = a[j];
    a[j] = t;
}
Colored by Color Scripter

 

생성한 함수는 문법에 맞게 호출(call)하여 사용하면 된다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
 
public class Main {
 
    public static void main(String[] args) {
 
        int s10 = sum(10);
        int s100 = sum(100);
        int s1000 = sum(10000);
 
        System.out.println(s10);
        System.out.println(s100);
        System.out.println(s1000);
 
        int a[] = { 1234};
        swap(a, 0,1);
        swap(a 2,3);
    }
 
}
 
Colored by Color Scripter

 

근데 이런 함수는 도대체 왜 만들까 ?

함수를 만들어야 하는 이유 중 첫번째는 코드 가독성(Code Readability)이 향상되기 때문이다. 

예를 들어 sum이라는 함수가 없으면 아래처럼 코드를 작성해야 할 것이다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public class Main {
    
    public static void main(String[] args) {
 
        int s10 = 0;
        for (int i = 1; i <= 10; i++) {
            s10 += i;
        }
        
        int s100 = 0;
        for (int i = 1; i <= 100; i++) {
            s100 += i;
        }
 
        int s1000 = 0;
        for (int i = 1; i <= 1000; i++) {
            s1000 += i;
        }
 
        System.out.println(s10);
        System.out.println(s100);
        System.out.println(s1000);
 
    }
 
}
Colored by Color Scripter

 

지금까지는 괜찮아 보인다. 

만약 숫자 1 부터 1000까지 모든 숫자에 대해 모든 sum 을 구한다고 가정해보자.

즉, 1까지 합, 2까지 합, 3까지합, 4까지 합 .... 1000까지의 합을 출력하는 프로그램을 작성한다면 

비슷한 statement 와 expression이 반복되어 매우 길게 나타날 것이며 무엇을 하는 프로그램인지 해석도 어려울 것이다.

게다가 도중에 오타라도 발생된다면 어디서 잘못된건지 파악하기도 어렵게 된다.

하지만 함수를 사용한다면 매우 간단해진다. 

1
2
3
4
5
6
7
8
9
10
11
public class Main {
 
    public static void main(String[] args) {
      
        for(int i = 1 ; i <=1000 ; i++) {
            System.out.println(sum(i));
        }
    }
 
}
Colored by Color Scripter

 

아직은 잘 모르겠지만 나중에 코드를 유지보수 쉽게 하고 확작성에 유연하도록 디자인하기위해 리팩터링(refactoring)을 공부하게 되는데 다양한 리팩터링 기법중 extract method 와 composing method는 위와 같이 코드의 가독성을 높이기 위해 긴 코드를 동일한 몇 개의 함수로 쪼개고(extract method)  원래의 긴 코드를 쪼갠 함수들로 다시 구성하는(composing method) 기법중 하나이다.

 

 

두번째로 함수를 만드는 이유는 재사용성이다. 

정수의 합을 계산하는곳이  프로그램내 다양한 곳에서 필요한 상황을 상상해 보자

sum() 이라는 함수가 없다면  여러 자바파일(.java)에  합을 구하는 비슷한 코드들을 copy & paste 하게되어 코드의 양이 길어지고 그로 인해 프로그램 코드도 커지게 된다. 

하지만 함수를 사용한다면 이런 문제점들을 완벽히 해결할 수 있다. 

함수는 비슷한 코드를 copy & paste 할 필요없이 가져다 쓰기만 하면 되기 때문에 재사용이 가능해 진다.

그리고  다른 프로그램에서 내가 만든 함수가 유용하다면 별도 라이브러리로 만들어 제공해 주는 것도 가능하다.

 

물론 함수가 장점만 있는것은 아니다.  함수는 호출하는데 비용(cost)이 든다.

특히 자바 함수 선언시 static이 아닌 함수는 가상함수(virtual function)로 간주하기 때문에 가상 함수 테이블(virual function table)을 위한 메모리 공간이 더 필요하고 함수 호출시 가상테이블을 뒤지면서 찾아야 하기 때문에 속도(퍼포먼스가)면에서 불리하다

그래서 예전에는 메모리 공간 제약이 많은 임베디드 프로그래밍 환경 or 속도가 중요한 프로그램 (ex. 동영상, 이미지 편집툴, 재생, 파일 인코딩/디코딩)을 구현할 때 함수로 만들어 사용하지 않고 일부러 풀어쓰는 기법을 쓰기도 한다. 

 

예를들어 아래와 같이 중간생략된 C++ 코드가 있다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
....
....
 
for(double dX=LEFT_MARGINE_FOR_X; dX<=RIGHT_MARGINE_FOR_X; dX+=1.0)
  for(double dY=LEFT_MARGINE_FOR_Y; dY<=RIGHT_MARGINE_FOR_Y; dY+=1.0)
    if( dMaximumValue<((dX*dX+dY*dY)/(dY*dY+dB)))
    {
      dMaximumValue=((dX*dX+dY*dY)/(dY*dY+dB));
      dMaximumX=dX;
      dMaximumY=dY;
    }
 
....
....
Colored by Color Scripter

 

 (dX*dX+dY*dY)/(dY*dY+dB)  코드가 어떤 의미인지 잘 몰라 코드 가독성(Code Readability) 을 위해 그에 맞는 함수이름으로 작명하여 함수로 빼서 호출하도록 할수 있지만 속도가 무엇보다 매우 중요하다면 그냥  풀어써버리는게 퍼포먼스면에 더 유리하게 되는것이다.

물론 여기서는  (dX*dX+dY*dY)/(dY*dY+dB) 위애 주석을 달아주면 간단해지지만 예를 위해서 설명한 것이지 그냥 넘어가자 ^^