ch5_배열
통합 문서입니다.
1. 메소드와 스코프
메소드와 스코프
학습 목표
- 메소드 선언/호출 구조를 정확히 이해하고, 코드를 기능 단위로 분리할 수 있다.
- 매개변수 전달 방식과 반환 규칙을 이해해 부작용을 줄이는 코드를 작성할 수 있다.
- 스코프/생명주기/가시성의 차이를 설명하고, 변수 설계를 안정적으로 할 수 있다.
1. 왜 메소드가 중요한가
메소드는 단순 문법이 아니라, 코드 구조를 결정하는 핵심 단위다.
- 중복 제거
- 책임 분리
- 테스트 용이성 향상
- 변경 파급 범위 축소
좋은 메소드의 기준:
- 한 가지 책임만 가진다.
- 이름만 보고 동작을 예측할 수 있다.
- 입력(매개변수)과 출력(반환값)이 명확하다.
2. 메소드 선언 구조
[접근제어자] [static 여부] 반환타입 메소드명(매개변수 목록) {
// 본문
}
예:
public static int add(int a, int b) {
return a + b;
}
구성 요소 의미:
- 접근제어자: 외부에서 접근 가능한 범위
static: 객체 없이 호출 가능한지 여부- 반환타입: 호출 결과 타입 (
void면 반환값 없음) - 매개변수: 호출자가 제공해야 하는 입력값
3. 호출 스택과 실행 흐름
메소드를 호출하면 JVM은 새로운 스택 프레임을 만든다.
- 매개변수/지역변수 저장 공간 생성
- 메소드 본문 실행
return시 프레임 제거- 호출 지점으로 복귀
실무 포인트:
- 깊은 재귀나 과도한 중첩 호출은 스택 사용량을 키운다.
- 상태를 많이 공유하는 대신, 입력/출력을 명확히 하는 함수형 스타일이 디버깅에 유리하다.
4. 매개변수 전달: Java는 항상 Pass-by-Value
Java는 항상 값 복사로 인자를 전달한다.
4.1 primitive 전달
static void increase(int x) {
x++;
}
int n = 10;
increase(n);
System.out.println(n); // 10
n의 값이 복사되어 x로 들어가므로 원본은 바뀌지 않는다.
4.2 reference 전달
static void rename(User u) {
u.setName("Lee");
}
참조값 자체가 복사된다.
복사된 참조가 같은 객체를 가리키므로 객체 내부 상태는 바뀔 수 있다.
하지만 메소드 안에서 참조를 재대입해도 호출자 변수가 바뀌진 않는다.
5. 반환값 설계
5.1 void vs 값 반환
void: 동작 수행 자체가 목적(로그 출력, 저장 등)- 값 반환: 계산/판단 결과를 호출자에게 전달
5.2 다중 결과가 필요할 때
- 작은 전용 클래스/레코드 반환
- 컬렉션 반환
- 출력 파라미터 패턴 지양(가독성/안정성 저하)
5.3 조기 반환(guard clause)
public int divide(int a, int b) {
if (b == 0) {
throw new IllegalArgumentException("b must not be zero");
}
return a / b;
}
조건 예외를 빨리 반환하면 중첩을 줄일 수 있다.
6. 메소드 오버로딩(Overloading)
같은 이름의 메소드를 매개변수 시그니처(개수/타입/순서)로 구분해 여러 개 정의할 수 있다.
int sum(int a, int b) { ... }
int sum(int a, int b, int c) { ... }
double sum(double a, double b) { ... }
주의:
- 반환타입만 다른 오버로딩은 불가
- 과도한 오버로딩은 호출 의도를 흐릴 수 있음
7. 스코프(scope)와 생명주기(lifetime)
스코프는 “어디서 보이는가”, 생명주기는 “언제까지 살아있는가”다.
7.1 지역 변수
- 선언된 블록 내부에서만 유효
- 자동 초기화되지 않음
int x;
// System.out.println(x); // 컴파일 오류(초기화 전 사용)
7.2 매개변수
- 메소드 스코프 내부에서만 유효
- 호출 시점에 값이 전달되어 초기화됨
7.3 필드(인스턴스 변수)
- 객체 생명주기 동안 존재
- 기본값 자동 초기화
7.4 static 필드
- 클래스 단위로 하나
- 클래스 로딩 후 프로그램 종료 시점까지 유지
8. 변수 가림(Shadowing)
지역 변수/매개변수가 필드와 같은 이름을 가지면 필드를 가릴 수 있다.
class User {
private String name;
void setName(String name) {
this.name = name;
}
}
this.name으로 필드를 명시하지 않으면 의도 혼동이 발생한다.
9. 재귀 메소드와 종료 조건
재귀는 자기 자신을 호출하는 방식이다.
static int factorial(int n) {
if (n <= 1) return 1;
return n * factorial(n - 1);
}
반드시 종료 조건(base case)이 필요하다.
종료 조건이 없으면 StackOverflowError가 발생할 수 있다.
10. 실무에서 자주 하는 실수
- 메소드 하나에 비즈니스 로직, 검증, 출력을 모두 넣음
- 이름이 모호한 메소드(
doProcess,handleData) - 매개변수가 너무 많아 호출부 가독성이 떨어짐
- 메소드가 외부 상태를 과도하게 변경(부작용 증가)
- 스코프를 넓게 잡아 디버깅 난이도 상승
11. 메소드 설계 체크리스트
- 이름이 동작을 명확히 설명하는가?
- 입력/출력이 최소한으로 설계되었는가?
- 예외 조건을 초기에 배제했는가?
- 부작용(상태 변경)이 필요한가?
- 단위 테스트를 작성하기 쉬운가?
12. 정리
- 메소드는 코드를 실행하는 단위이자 설계를 표현하는 단위다.
- 스코프를 좁히고, 입력/출력을 명확히 하면 버그가 줄어든다.
- Java는 항상 pass-by-value이며, reference 전달 오해를 정확히 정리해야 한다.
2. 배열
배열 (Array)
학습 목표
- 배열의 메모리 모델과 인덱스 접근 방식을 이해할 수 있다.
- 1차원/다차원 배열의 선언, 순회, 복사, 정렬 기본기를 익힐 수 있다.
- 경계값/얕은복사/성능 문제를 실무 수준으로 점검할 수 있다.
1. 배열의 본질
배열은 같은 타입의 데이터를 고정 길이로 저장하는 참조형 객체다.
int[] scores = new int[5];
핵심 특성:
- 길이 고정(
scores.length) - 인덱스 기반 랜덤 접근 O(1)
- 요소 타입이 모두 동일
2. 선언, 생성, 초기화
2.1 선언과 생성 분리
int[] arr;
arr = new int[3];
2.2 선언+생성
int[] arr = new int[3];
2.3 리터럴 초기화
int[] arr = {10, 20, 30};
주의:
- 배열 길이는 생성 후 변경 불가
- 요소는 타입별 기본값으로 초기화됨
3. 배열 기본값
int[]->0double[]->0.0boolean[]->falseString[]->null
기본값 자동 초기화는 “배열 요소”에만 해당한다.
지역 변수 자체는 자동 초기화되지 않는다.
4. 순회 패턴
4.1 인덱스 순회
for (int i = 0; i < arr.length; i++) {
System.out.println(arr[i]);
}
장점:
- 위치 기반 연산 가능(교환, 부분구간 처리)
4.2 향상된 for
for (int value : arr) {
System.out.println(value);
}
장점:
- 읽기 중심 코드 간결
제약:
- 인덱스 접근이 필요하면 일반 for가 필요
5. 다차원 배열
Java의 2차원 배열은 “배열의 배열”이다.
int[][] board = new int[3][4];
가변 행(jagged array)도 가능:
int[][] jagged = {
{1, 2},
{3, 4, 5},
{6}
};
각 행 길이가 다를 수 있으므로 내부 루프에서 arr[i].length를 사용해야 한다.
6. 배열 복사: 참조 복사 vs 실제 복사
6.1 참조 복사
int[] a = {1, 2, 3};
int[] b = a;
b[0] = 99;
System.out.println(a[0]); // 99
6.2 실제 복사
int[] c1 = a.clone();
int[] c2 = java.util.Arrays.copyOf(a, a.length);
int[] c3 = new int[a.length];
System.arraycopy(a, 0, c3, 0, a.length);
참조형 배열의 경우 요소 객체까지 깊게 복사되지 않을 수 있다.
7. 예외와 경계값 처리
7.1 ArrayIndexOutOfBoundsException
int[] arr = new int[3];
arr[3] = 1; // 범위 초과
배열 인덱스 유효 범위는 항상 0 <= i < length.
7.2 NullPointerException
int[] arr = null;
System.out.println(arr.length); // NPE
실무에서는 입력 배열 null 가능성을 먼저 검사한다.
8. Arrays 유틸리티 실전
자주 쓰는 API:
Arrays.toString(arr);
Arrays.sort(arr);
Arrays.binarySearch(arr, key);
Arrays.equals(a, b);
Arrays.fill(arr, 0);
binarySearch는 정렬된 배열 전제가 필요하다.
9. 알고리즘 관점 기초
배열 연산의 시간복잡도:
- 인덱스 접근: O(1)
- 끝에 쓰기(범위 내): O(1)
- 중간 삽입/삭제(수동 이동): O(n)
- 전체 탐색: O(n)
배열은 읽기와 인덱스 접근이 빠르며,
삽입/삭제가 많은 경우는 컬렉션(List) 검토가 필요하다.
10. 실무 패턴
- 입력 데이터 검증: null, length, 범위
- 불변 보장이 필요하면 방어적 복사
- 대량 반복 처리 시 인덱스 계산/분기 최소화
- 매직 넘버 대신 상수 사용 (
MAX_SIZE) - 테스트는 빈 배열, 1개, 경계값 배열 포함
11. 정리
- 배열은 단순하지만 성능/안정성의 기반이 되는 자료구조다.
- 참조 복사와 실제 복사를 구분하지 못하면 치명적 버그가 발생한다.
- 경계 조건과 null 처리 습관이 코드 품질을 크게 좌우한다.
3. 문제
문제
ch5 범위(메소드/스코프/배열) 문제입니다.
A. 메소드 기초
- 두 정수의 합/차/곱/몫을 각각 반환하는 메소드를 작성하시오.
- 문자열 길이가 8 이상이면
true를 반환하는 메소드를 작성하시오. - 배열 평균을 계산하는 메소드를 작성하시오 (
double반환). - 매개변수 검증(예: null, 길이 0) 후 계산하는 메소드로 개선하시오.
B. 스코프/호출 스택
- 지역 변수와 필드의 이름이 같은 경우
this를 사용해 구분하는 예제를 작성하시오. - 블록 내부에서 선언한 변수를 블록 밖에서 사용하려 할 때 발생하는 컴파일 오류를 재현하시오.
- 재귀 팩토리얼 메소드를 작성하고 종료 조건이 없을 때 어떤 문제가 생기는지 설명하시오.
- 메소드 호출 순서(메인 -> A -> B)를 출력해 호출 스택 흐름을 확인하시오.
C. 배열 기초
- 정수 배열의 최댓값, 최솟값, 합계, 평균을 구하시오.
- 배열을 역순으로 출력하시오.
- 배열 요소 중 짝수 개수와 홀수 개수를 구하시오.
- 배열에서 특정 값의 첫 인덱스를 찾는 메소드를 작성하시오(없으면 -1).
D. 배열 응용
- 1~45 사이 중복 없는 로또 번호 6개를 생성하시오.
- 2차원 배열의 행별 합계와 전체 합계를 출력하시오.
- 가변 행(jagged) 2차원 배열을 만들고 모든 값을 순회하시오.
- 배열 정렬 후 이진 탐색으로 특정 값을 찾으시오.
E. 복사/참조 문제
b = a와a.clone()결과 차이를 코드로 비교하시오.Arrays.copyOf,System.arraycopy를 각각 사용해 복사하시오.- 참조형 배열(객체 배열)에서 얕은 복사가 만드는 문제를 예제로 설명하시오.
- 방어적 복사가 필요한 시나리오(생성자/게터)를 구현하시오.
F. 챌린지
- 학생 성적 배열을 받아 등급 분포(A/B/C/D/F)를 계산하는 메소드를 작성하시오.
- 회문(palindrome)인지 검사하는 문자열 메소드를 작성하시오(문자 배열 활용).
- 행렬 덧셈 메소드를 작성하시오(차원 검증 포함).
제출 체크리스트
- 메소드 이름이 역할을 정확히 설명하는가?
- null/길이 0/경계 인덱스 검증을 했는가?
- 참조 복사와 실제 복사를 구분했는가?
- 반복 로직에서 오프바이원 오류(
<= length)가 없는가?