Theme:

코드가 길어지면 자연스럽게 두 가지 고민이 생긴다. "이 로직을 어떻게 분리하지?"와 "이 데이터를 어떻게 묶지?". 메서드가 전자를, 배열이 후자를 해결해준다. 이번 글에서는 이 두 가지를 한 번에 정리한다.

메서드란?

메서드는 하나의 작업을 수행하는 코드 블록이다. 다른 언어에서는 함수(function)라고도 한다. Java에서는 모든 코드가 클래스 안에 있어야 하므로, 함수 대신 메서드라는 이름을 쓴다.

JAVA
public class Calculator {

    // 메서드 선언
    static int add(int a, int b) {
        return a + b;
    }

    public static void main(String[] args) {
        int result = add(3, 5); // 메서드 호출
        System.out.println(result); // 8
    }
}

메서드의 구조

JAVA
접근제어자 반환타입 메서드이름(매개변수) {
    // 본문
    return 결과;
}
JAVA
public static int multiply(int a, int b) {
//│      │      │    │          └── 매개변수(parameter)
//│      │      │    └── 메서드 이름
//│      │      └── 반환 타입
//│      └── static (객체 없이 호출 가능)
//└── 접근제어자
    return a * b;
}
  • 반환 타입: 메서드가 돌려주는 값의 타입. 반환값이 없으면 void
  • 매개변수: 메서드에 전달되는 입력값. 0개 이상
  • return: 값을 반환하고 메서드 종료. void이면 생략 가능

void 메서드

반환값이 없는 메서드는 void로 선언한다.

JAVA
static void greet(String name) {
    System.out.println("안녕하세요, " + name + "님!");
    // return 생략 가능
}

public static void main(String[] args) {
    greet("Alice"); // 안녕하세요, Alice님!
}

매개변수(Parameter) vs 인자(Argument)

이 두 용어는 자주 섞여 쓰이지만 정확히는 다르다.

JAVA
static int add(int a, int b) { // a, b는 매개변수(parameter)
    return a + b;
}

add(3, 5); // 3, 5는 인자(argument)
  • 매개변수: 메서드 선언부에 있는 변수
  • 인자: 메서드 호출 시 실제로 넘기는 값

메서드 오버로딩 (Overloading)

같은 이름의 메서드를 매개변수를 다르게 해서 여러 개 만들 수 있다.

JAVA
static int add(int a, int b) {
    return a + b;
}

static double add(double a, double b) {
    return a + b;
}

static int add(int a, int b, int c) {
    return a + b + c;
}
JAVA
add(3, 5);        // int add(int, int) 호출
add(3.0, 5.0);    // double add(double, double) 호출
add(1, 2, 3);     // int add(int, int, int) 호출

오버로딩의 조건

  • 매개변수의 타입, 개수, 순서 중 하나가 달라야 한다
  • 반환 타입만 다른 건 오버로딩이 아니다 (컴파일 에러)
JAVA
// 컴파일 에러 — 매개변수가 같고 반환 타입만 다름
static int calculate(int a) { return a; }
static double calculate(int a) { return a; } // 불가!

System.out.println()int, double, String, boolean 등 다양한 타입을 받을 수 있는 것도 오버로딩 덕분이다.

Call by Value — Java는 항상 값을 복사한다

Java에서 메서드에 인자를 넘기면 항상 값이 복사된다. 이걸 Call by Value라고 한다.

기본 타입의 경우

JAVA
static void changeValue(int x) {
    x = 100;
}

public static void main(String[] args) {
    int num = 10;
    changeValue(num);
    System.out.println(num); // 10 — 변하지 않음!
}

num의 값이 복사되어 x에 전달된다. x를 바꿔도 원본 num은 영향 없다.

참조 타입의 경우

JAVA
static void changeElement(int[] arr) {
    arr[0] = 99; // 배열 내용을 변경
}

public static void main(String[] args) {
    int[] numbers = {1, 2, 3};
    changeElement(numbers);
    System.out.println(numbers[0]); // 99 — 변했다!
}

"참조 타입이면 원본이 바뀌잖아요? 그러면 Call by Reference 아닌가요?"

아니다. 참조(주소) 값이 복사되는 거다. 배열의 주소가 복사되어 같은 배열을 가리키게 된 것이지, 원본 변수 자체가 넘어간 건 아니다.

JAVA
static void replaceArray(int[] arr) {
    arr = new int[]{99, 88, 77}; // 새 배열로 교체
}

public static void main(String[] args) {
    int[] numbers = {1, 2, 3};
    replaceArray(numbers);
    System.out.println(numbers[0]); // 1 — 변하지 않음!
}

arr이 새 배열을 가리키도록 바꿔도 원본 numbers는 그대로다. 주소 값이 복사된 것이지, 변수 자체를 넘긴 게 아니기 때문이다.

PLAINTEXT
main:  numbers → [1, 2, 3]       numbers → [1, 2, 3]  (원본 유지)

method: arr ────────┘             arr → [99, 88, 77]   (새 배열 가리킴)
         (복사된 주소)

이 차이를 이해하면 Java의 Call by Value가 명확해진다.

배열

배열 선언과 초기화

배열은 같은 타입의 데이터를 고정 크기로 모아놓은 것이다.

JAVA
// 선언 + 초기화
int[] numbers = {1, 2, 3, 4, 5};

// 크기만 지정 (기본값으로 초기화)
int[] scores = new int[5]; // [0, 0, 0, 0, 0]
String[] names = new String[3]; // [null, null, null]
boolean[] flags = new boolean[3]; // [false, false, false]

// 선언 후 나중에 초기화
int[] data;
data = new int[]{10, 20, 30};

기본 타입 배열의 기본값:

타입기본값
int, long0
double, float0.0
booleanfalse
char'\u0000'
참조 타입null

배열 접근

JAVA
int[] numbers = {10, 20, 30, 40, 50};

System.out.println(numbers[0]);     // 10 (첫 번째)
System.out.println(numbers[4]);     // 50 (마지막)
System.out.println(numbers.length); // 5 (배열 크기)

numbers[2] = 99;                    // 값 변경
System.out.println(numbers[2]);     // 99

인덱스는 0부터 시작한다. length는 5인데 마지막 인덱스는 4라는 점을 주의.

JAVA
// ArrayIndexOutOfBoundsException!
System.out.println(numbers[5]); // 인덱스 범위 초과

배열 순회

JAVA
int[] numbers = {10, 20, 30, 40, 50};

// 기본 for문 — 인덱스가 필요할 때
for (int i = 0; i < numbers.length; i++) {
    System.out.println(i + ": " + numbers[i]);
}

// for-each — 인덱스가 필요 없을 때
for (int num : numbers) {
    System.out.println(num);
}

배열 유틸리티 (java.util.Arrays)

JAVA
import java.util.Arrays;

int[] numbers = {5, 2, 8, 1, 9};

// 정렬
Arrays.sort(numbers);
System.out.println(Arrays.toString(numbers)); // [1, 2, 5, 8, 9]

// 검색 (정렬된 배열에서)
int index = Arrays.binarySearch(numbers, 5);
System.out.println(index); // 2

// 복사
int[] copy = Arrays.copyOf(numbers, numbers.length);
int[] partial = Arrays.copyOfRange(numbers, 1, 4); // [2, 5, 8]

// 채우기
int[] filled = new int[5];
Arrays.fill(filled, 7);
System.out.println(Arrays.toString(filled)); // [7, 7, 7, 7, 7]

// 비교
System.out.println(Arrays.equals(numbers, copy)); // true

Arrays.toString()은 디버깅할 때 매우 유용하다. 배열을 그냥 println()하면 [I@hashcode 같은 쓸모없는 출력이 나온다.

JAVA
int[] arr = {1, 2, 3};
System.out.println(arr);                // [I@6d06d69c (쓸모없음)
System.out.println(Arrays.toString(arr)); // [1, 2, 3] (유용!)

다차원 배열

2차원 배열

JAVA
// 3행 4열 배열
int[][] matrix = {
    {1, 2, 3, 4},
    {5, 6, 7, 8},
    {9, 10, 11, 12}
};

System.out.println(matrix[1][2]); // 7 (2행 3열)
System.out.println(matrix.length);    // 3 (행 수)
System.out.println(matrix[0].length); // 4 (열 수)

2차원 배열 순회

JAVA
for (int i = 0; i < matrix.length; i++) {
    for (int j = 0; j < matrix[i].length; j++) {
        System.out.printf("%3d", matrix[i][j]);
    }
    System.out.println();
}

또는 for-each로:

JAVA
for (int[] row : matrix) {
    for (int value : row) {
        System.out.printf("%3d", value);
    }
    System.out.println();
}

가변 길이 배열 (Ragged Array)

Java의 2차원 배열은 사실 "배열의 배열"이다. 각 행의 길이가 달라도 된다.

JAVA
int[][] ragged = new int[3][];
ragged[0] = new int[]{1, 2};
ragged[1] = new int[]{3, 4, 5};
ragged[2] = new int[]{6};

실무에서 자주 쓰이진 않지만, 자바 배열의 내부 구조를 이해하는 데 도움이 된다.

가변인자 (Varargs)

메서드에 인자 개수를 유동적으로 넘기고 싶을 때 쓴다.

JAVA
static int sum(int... numbers) {
    int total = 0;
    for (int num : numbers) {
        total += num;
    }
    return total;
}

// 호출
sum(1, 2);           // 3
sum(1, 2, 3, 4, 5);  // 15
sum();               // 0

int... numbers는 내부적으로 int[] 배열이 된다. 그래서 for-each로 순회할 수 있다.

가변인자 규칙

JAVA
// 가변인자는 매개변수 목록의 마지막에 와야 한다
static void print(String prefix, int... numbers) { // OK
    for (int num : numbers) {
        System.out.println(prefix + num);
    }
}

// static void print(int... a, int... b) {} // 컴파일 에러!
// 가변인자는 하나만 쓸 수 있다

printf, String.format 같은 메서드가 가변인자를 활용한 대표적인 예다.

JAVA
System.out.printf("이름: %s, 나이: %d%n", "Alice", 25);

메서드와 배열을 함께 쓰기

배열을 매개변수로 받기

JAVA
static double average(int[] scores) {
    int sum = 0;
    for (int score : scores) {
        sum += score;
    }
    return (double) sum / scores.length;
}

int[] scores = {85, 92, 78, 96, 88};
System.out.println(average(scores)); // 87.8

배열을 반환하기

JAVA
static int[] createRange(int start, int end) {
    int[] result = new int[end - start];
    for (int i = 0; i < result.length; i++) {
        result[i] = start + i;
    }
    return result;
}

int[] range = createRange(1, 6);
System.out.println(Arrays.toString(range)); // [1, 2, 3, 4, 5]

실전 예제: 간단한 성적 관리

JAVA
import java.util.Arrays;

public class GradeManager {

    // 평균 계산
    static double getAverage(int[] scores) {
        int sum = 0;
        for (int score : scores) {
            sum += score;
        }
        return (double) sum / scores.length;
    }

    // 최고점
    static int getMax(int[] scores) {
        int max = scores[0];
        for (int i = 1; i < scores.length; i++) {
            if (scores[i] > max) {
                max = scores[i];
            }
        }
        return max;
    }

    // 등급 판정
    static String getGrade(double average) {
        if (average >= 90) return "A";
        if (average >= 80) return "B";
        if (average >= 70) return "C";
        return "F";
    }

    public static void main(String[] args) {
        int[] scores = {85, 92, 78, 96, 88};

        System.out.println("점수: " + Arrays.toString(scores));
        System.out.println("평균: " + getAverage(scores));
        System.out.println("최고점: " + getMax(scores));
        System.out.println("등급: " + getGrade(getAverage(scores)));
    }
}

출력:

PLAINTEXT
점수: [85, 92, 78, 96, 88]
평균: 87.8
최고점: 96
등급: B

이 정도 규모에서도 메서드로 분리해두면 각 기능을 독립적으로 테스트하고 수정할 수 있다. 코드가 길어질수록 이 분리의 가치가 커진다.

TIP 이 글의 코드 예제를 직접 실행해보고 싶다면 Java 기본기 핸드북을 확인해보세요.

정리

  • 메서드: 하나의 작업을 수행하는 코드 블록. 반환타입 이름(매개변수) 형태
  • 오버로딩: 같은 이름, 다른 매개변수. 반환 타입만 다른 건 불가
  • Call by Value: Java는 항상 값을 복사. 참조 타입은 주소 값이 복사되는 것
  • 배열: 고정 크기, 같은 타입. 인덱스는 0부터 시작
  • Arrays 유틸: sort(), toString(), copyOf(), equals()
  • 가변인자: 타입... 이름 — 내부적으로 배열, 매개변수 마지막에만 가능

다음 글에서는 클래스와 객체를 다룬다. Java가 "객체지향"이라고 불리는 이유와, 클래스를 어떻게 설계하는지 살펴볼 예정이다.

댓글 로딩 중...