배열 통계를 메서드로 나누고, 같은 일을 스트림으로 써 보고, 문자열 배열과 빈 배열을 다루며, 배열을 늘리는 두 전략의 비용을 숫자로 비교합니다.
예제 1은 main 안에서 합계·최댓값·평균을 한꺼번에 구했습니다. 배열을 매개변수로 받는 작은 메서드로 나누면 다른 배열에도 그대로 쓸 수 있고, 빈 배열 처리(평균의 0 나누기)를 한 곳에서만 하면 됩니다.
public class ArrayStats {
static int sum(int[] a) {
int s = 0;
for (int v : a) s += v;
return s;
}
static int max(int[] a) {
int m = a[0];
for (int v : a) if (v > m) m = v;
return m;
}
static double average(int[] a) {
return a.length == 0 ? 0 : (double) sum(a) / a.length; // 빈 배열이면 0
}
static int countAbove(int[] a, int threshold) {
int n = 0;
for (int v : a) if (v >= threshold) n++;
return n;
}
public static void main(String[] args) {
int[] scores = {78, 92, 65, 88, 95};
int[] retake = {40, 55};
System.out.println(sum(scores) + " " + max(scores) + " " + average(scores)); // 출력: 418 95 83.6
System.out.println(sum(retake) + " " + max(retake) + " " + average(retake)); // 출력: 95 55 47.5
System.out.println(countAbove(scores, 80) + " " + countAbove(retake, 80)); // 출력: 3 0
System.out.println(average(new int[0])); // 출력: 0.0
}
}같은 통계를 스트림으로 씁니다. 반복문보다 "무엇을 구하는지"가 코드에 그대로 드러나고, 예제 1에서 두 번 순회하던 "조건에 맞는 것만 새 배열로"가 한 줄이 됩니다. 인덱스가 필요하면 IntStream.range를 씁니다. 자세한 내용은 고급 04 레슨에서 다루므로 지금은 형태만 익히세요.
import java.util.Arrays;
import java.util.stream.IntStream;
public class StreamTaste {
public static void main(String[] args) {
int[] scores = {78, 92, 65, 88, 95};
System.out.println(Arrays.stream(scores).sum()); // 출력: 418
System.out.println(Arrays.stream(scores).max().getAsInt()); // 출력: 95
System.out.println(Arrays.stream(scores).average().getAsDouble()); // 출력: 83.6
System.out.println(Arrays.stream(scores).filter(s -> s >= 80).count()); // 출력: 3
int[] passed = Arrays.stream(scores).filter(s -> s >= 80).toArray(); // 조건에 맞는 것만 새 배열로
System.out.println(Arrays.toString(passed)); // 출력: [92, 88, 95]
String[] names = {"김민수", "이서연", "박지훈", "최유진", "정우진"};
IntStream.range(0, scores.length) // 인덱스가 필요할 때
.filter(i -> scores[i] >= 90)
.forEach(i -> System.out.println(names[i] + " " + scores[i]));
// 출력: 이서연 92
// 출력: 정우진 95
int[] sorted = Arrays.stream(scores).sorted().toArray(); // 정렬된 복사본
System.out.println(Arrays.toString(sorted) + " 원본 " + Arrays.toString(scores));
// 출력: [65, 78, 88, 92, 95] 원본 [78, 92, 65, 88, 95]
}
}문자열 배열에서 조건에 맞는 것만 골라 새 배열을 만듭니다. 결과 개수를 미리 알 수 없으므로 최대 크기로 잡았다가 Arrays.copyOf로 실제 개수만큼 잘라 반환하는 것이 배열만으로 하는 표준 기법입니다. 문자열 배열도 Arrays.sort로 사전순 정렬됩니다.
import java.util.Arrays;
public class LogFiles {
static String[] endingWith(String[] files, String suffix) {
String[] tmp = new String[files.length]; // 최대 크기로 잡고
int n = 0;
for (String f : files) {
if (f.endsWith(suffix)) tmp[n++] = f;
}
return Arrays.copyOf(tmp, n); // 실제 개수만큼 잘라 반환
}
public static void main(String[] args) {
String[] files = {"app.log", "app.log.1", "error.log", "notes.txt", "access.log"};
String[] logs = endingWith(files, ".log");
System.out.println(Arrays.toString(logs)); // 출력: [app.log, error.log, access.log]
Arrays.sort(logs);
System.out.println(Arrays.toString(logs)); // 출력: [access.log, app.log, error.log]
System.out.println(Arrays.toString(endingWith(files, ".csv"))); // 출력: []
int[] lengths = new int[files.length]; // 배열 → 다른 타입 배열
for (int i = 0; i < files.length; i++) lengths[i] = files[i].length();
System.out.println(Arrays.toString(lengths)); // 출력: [7, 9, 9, 9, 10]
}
}배열을 받는 메서드는 null과 길이 0을 먼저 걸러야 합니다. 길이 1인 배열은 반복이 한 번만 돌아도 정답이 나와야 하고, copyOfRange가 범위를 넘으면 기본값으로 채워진다는 점, Arrays.equals는 null을 안전하게 처리한다는 점도 확인합니다.
import java.util.Arrays;
public class ArrayEdges {
static int safeMax(int[] a) {
if (a == null) throw new IllegalArgumentException("배열이 null");
if (a.length == 0) throw new IllegalArgumentException("빈 배열");
int m = a[0];
for (int i = 1; i < a.length; i++) if (a[i] > m) m = a[i];
return m;
}
public static void main(String[] args) {
System.out.println(safeMax(new int[]{7})); // 출력: 7
System.out.println(safeMax(new int[]{-3, -1, -2})); // 출력: -1
for (int[] bad : new int[][]{null, {}}) {
try {
safeMax(bad);
} catch (IllegalArgumentException e) {
System.out.println("거부: " + e.getMessage());
}
}
// 출력: 거부: 배열이 null
// 출력: 거부: 빈 배열
int[] a = {1, 2, 3};
System.out.println(Arrays.toString(Arrays.copyOfRange(a, 1, 3))); // 출력: [2, 3]
System.out.println(Arrays.toString(Arrays.copyOfRange(a, 2, 5))); // 출력: [3, 0, 0] (넘치는 만큼 기본값)
System.out.println(Arrays.toString(Arrays.copyOf(a, 0))); // 출력: []
System.out.println(Arrays.equals(new int[0], new int[0])); // 출력: true
System.out.println(Arrays.equals(null, new int[0])); // 출력: false
try {
System.out.println(a[a.length]); // 마지막 인덱스는 length - 1
} catch (ArrayIndexOutOfBoundsException e) {
System.out.println(e.getMessage()); // 출력: Index 3 out of bounds for length 3
}
}
}배열은 크기가 고정이라 요소를 추가하려면 새 배열로 복사해야 합니다. 매번 정확히 1칸 늘리면 복사가 n번 일어나고 옮기는 요소 수는 n²에 비례합니다. 꽉 찼을 때만 2배로 늘리면 복사는 log₂n번, 옮기는 요소는 2n 이하입니다. 대신 빈 칸이 남습니다. ArrayList가 내부적으로 후자(1.5배)를 택하는 이유입니다.
import java.util.Arrays;
public class GrowStrategy {
public static void main(String[] args) {
int n = 100_000;
// 전략 1: 매번 1칸씩 늘림
int[] a = new int[0];
int copies1 = 0;
long moved1 = 0;
for (int i = 0; i < n; i++) {
moved1 += a.length; // 이번 복사에서 옮기는 요소 수
a = Arrays.copyOf(a, a.length + 1);
copies1++;
a[i] = i;
}
// 전략 2: 꽉 차면 2배로 늘림
int[] b = new int[1];
int size = 0, copies2 = 0;
long moved2 = 0;
for (int i = 0; i < n; i++) {
if (size == b.length) {
moved2 += b.length;
b = Arrays.copyOf(b, b.length * 2);
copies2++;
}
b[size++] = i;
}
System.out.println("1칸씩: 복사 " + copies1 + "회, 옮긴 요소 " + moved1 + "개");
System.out.println("2배씩: 복사 " + copies2 + "회, 옮긴 요소 " + moved2 + "개, 남는 칸 " + (b.length - size));
// 출력: 1칸씩: 복사 100000회, 옮긴 요소 4999950000개
// 출력: 2배씩: 복사 17회, 옮긴 요소 131071개, 남는 칸 31072
}
}