개요
코딩 테스트에서 자주 나오는 Java 문법들과 주의 사항들에 대해 정리하였습니다.
1. BufferedReader와 BufferedWriter를 활용한 입출력
왜 BufferedReader/BufferedWriter를 사용해야 할까?
입력에는 Scanner, 출력에는 System.out을 사용하는 것이 일반적인 입출력 방식입니다.
먼저 System.out을 여러 번 사용하게 될 경우 그 때마다 system call이 발생하게 됩니다.
System.out.println("Hello"); // 시스템 콜 발생
System.out.println("World"); // 시스템 콜 발생
그에 비해 BufferedWriter의 경우 버퍼에 문자열들을 보관해두었다가 한 번에 출력하기 때문에 시스템 콜 횟수를 줄일 수 있습니다.
I/O 작업이 cpu 작업에 비해 많은 시간이 걸린다는 것을 생각하면 여러 데이터를 출력할 때 매번 System.out을 활용하는 것보다 BufferedWriter를 사용하여 메모리에 보관해두었다가 출력하는 것이 시간을 단축할 수 있습니다.
물론 코딩 테스트에서 BufferedWriter가 아닌 System.out을 사용했다고 해서 시간 초과 문제가 발생하는 경우는 매우 드물겁니다.
보조적인 시간 단축 수단에 해당하기 때문에 최적의 알고리즘을 적용하는 것이 가장 중요합니다.
BufferedReader 사용법
코딩 테스트에서는 공백으로 구분되어 데이터가 주어지는 경우가 있습니다.
이 데이터를 활용하려면 StringTokenizer를 사용해야 합니다.
BufferedWriter 사용법
import java.io.BufferedWriter;
import java.io.OutputStreamWriter;
import java.io.IOException;
public class OutputExample {
public static void main(String[] args) throws IOException {
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(System.out));
// 문자열 출력
bw.write("Hello World");
bw.newLine(); // 줄바꿈
// 숫자 출력
int result = 42;
bw.write(String.valueOf(result));
bw.newLine();
bw.flush(); // 버퍼 비우기 (필수!)
bw.close();
}
}
- BufferedReader는 readLine()만 제공하므로 StringTokenizer나 split()과 함께 사용
- BufferedWriter는 반드시 flush()나 close() 호출
- IOException 처리 필요 (throws IOException 추가)
2. 배열 선언 시 원시 타입 vs 참조 타입 주의사항
원시 타입 배열의 기본값
원시 타입 배열을 선언하면 각 타입의 기본값으로 자동 초기화됩니다.
public class PrimitiveArrayExample {
public static void main(String[] args) {
int[] intArray = new int[5]; // 모든 원소가 0으로 초기화
boolean[] boolArray = new boolean[5]; // 모든 원소가 false로 초기화
double[] doubleArray = new double[5]; // 모든 원소가 0.0으로 초기화
System.out.println(intArray[0]); // 0 출력
System.out.println(boolArray[0]); // false 출력
}
}
참조 타입 배열 주의 사항
참조 타입 배열은 모든 원소가 null로 초기화됩니다.
즉, 따로 각 배열의 원소를 초기화 하지 않고 접근한다면 NullPointerException이 발생할 수 있습니다!
public class ReferenceArrayExample {
public static void main(String[] args) {
String[] stringArray = new String[5];
Integer[] integerArray = new Integer[5];
int[][] twoDimArray = new int[3][]; // 1차원 배열 참조들이 null!
// ❌ NullPointerException 발생!
// System.out.println(stringArray[0].length());
// System.out.println(integerArray[0] + 1);
// twoDimArray[0][0] = 1;
// 2차원 배열 올바른 초기화
for (int i = 0; i < twoDimArray.length; i++) {
twoDimArray[i] = new int[4]; // 각 행을 별도로 초기화
}
// 또는 한 번에 초기화
int[][] properArray = new int[3][4]; // 모든 원소가 0으로 초기화됨
}
}
3. Comparable과 Comparator로 정렬 기준 설정
Comparable 인터페이스
클래스 자체에 정렬 기준을 정의할 수 있습니다.
class Student implements Comparable<Student> {
String name;
int score;
public Student(String name, int score) {
this.name = name;
this.score = score;
}
@Override
public int compareTo(Student other) {
// 점수 기준 내림차순 정렬
return Integer.compare(other.score, this.score);
// 여러 조건으로 정렬하는 경우
// if (this.score != other.score) {
// return Integer.compare(other.score, this.score); // 점수 내림차순
// }
// return this.name.compareTo(other.name); // 이름 오름차순
}
}
// 사용 예시
List<Student> students = Arrays.asList(
new Student("Alice", 85),
new Student("Bob", 92),
new Student("Charlie", 78)
);
Collections.sort(students); // compareTo 메서드 사용
System.out.println(students); // [Bob(92), Alice(85), Charlie(78)]
Comparator 인터페이스
정렬 기준을 별도로 정의하거나, 여러 가지 정렬 방식을 제공할 때 사용합니다.
import java.util.Arrays;
import java.util.Comparator;
public class ComparatorExample {
public static void main(String[] args) {
int[][] points = {{1, 3}, {2, 1}, {3, 2}, {1, 1}};
// 1. 람다 표현식 사용
Arrays.sort(points, (a, b) -> {
if (a[0] != b[0]) {
return Integer.compare(a[0], b[0]); // x좌표 오름차순
}
return Integer.compare(a[1], b[1]); // y좌표 오름차순
});
// 2. 익명 클래스로 Comparator 객체 직접 생성
Arrays.sort(points, new Comparator<int[]>() {
@Override
public int compare(int[] a, int[] b) {
if (a[0] != b[0]) {
return Integer.compare(a[0], b[0]); // x좌표 오름차순
}
return Integer.compare(a[1], b[1]); // y좌표 오름차순
}
});
// 3. Comparator 메서드 체이닝
Arrays.sort(points, Comparator
.comparingInt((int[] a) -> a[0]) // x좌표 기준
.thenComparingInt(a -> a[1])); // y좌표 기준
// 4. 내림차순 정렬
Arrays.sort(points, (a, b) -> Integer.compare(b[0], a[0]));
// 5. 문자열 배열 정렬 (길이 기준, 길이 같으면 사전순)
String[] words = {"apple", "pie", "washington", "book"};
Arrays.sort(words, (a, b) -> {
if (a.length() != b.length()) {
return Integer.compare(a.length(), b.length());
}
return a.compareTo(b);
});
System.out.println(Arrays.deepToString(points));
System.out.println(Arrays.toString(words));
}
}
4. 문자열 변경 메모리 주의 사항 : String vs StringBuilder
String 연산의 메모리 비효율성
Java에서 String은 불변(immutable) 객체입니다. 따라서 문자열을 반복적으로 합치는 작업은 매번 새로운 String 객체를 생성하여 메모리 초과가 발생할 수 있습니다.
public class StringPerformanceExample {
public static void main(String[] args) {
// ❌ 비효율적인 방법
String result = "";
for (int i = 0; i < 10000; i++) {
result += "a"; // 매번 새로운 String 객체 생성
}
// 시간 복잡도: O(n²) - 매우 느림!
// ✅ 효율적인 방법
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 10000; i++) {
sb.append("a"); // 내부 배열에 문자 추가
}
String efficient = sb.toString();
// 시간 복잡도: O(n) - 빠름!
}
}
StringBuilder 사용법
import java.util.Arrays;
public class StringBuilderExample {
public static void main(String[] args) {
StringBuilder sb = new StringBuilder();
// 1. 기본 사용법
sb.append("Hello");
sb.append(" ");
sb.append("World");
System.out.println(sb.toString()); // "Hello World"
// 2. 메서드 체이닝
sb.clear(); // 초기화
String result = sb.append("Java")
.append(" is")
.append(" awesome")
.toString();
// 3. 다양한 타입 추가
sb.clear();
sb.append("Number: ").append(42);
sb.append(", Boolean: ").append(true);
sb.append(", Character: ").append('A');
// 4. 문자열 삽입과 삭제
sb.clear();
sb.append("Hello World");
sb.insert(5, " Beautiful"); // "Hello Beautiful World"
sb.delete(5, 15); // "Hello World" (5번째부터 15번째 전까지 삭제)
sb.reverse(); // "dlroW olleH"
// 5. 배열 요소를 문자열로 변환
int[] arr = {1, 2, 3, 4, 5};
sb.clear();
for (int i = 0; i < arr.length; i++) {
sb.append(arr[i]);
if (i < arr.length - 1) {
sb.append(", ");
}
}
System.out.println(sb.toString()); // "1, 2, 3, 4, 5"
}
}
5. 배열과 List의 차이점과 순회 방법
배열 vs List 비교
| 배열 (Array) | List (ArrayList) | |
| 크기 | 고정 크기 | 동적 크기 |
| 타입 | 원시 타입 + 참조 타입 | 참조 타입만 (Wrapper 클래스 사용) |
배열 활용법
public class ArrayExample {
public static void main(String[] args) {
// 1. 배열 선언과 초기화
int[] arr1 = new int[5]; // 크기 5, 모든 원소 0
int[] arr2 = {1, 2, 3, 4, 5}; // 초기값 지정
int[] arr3 = new int[]{1, 2, 3, 4, 5}; // 명시적 초기화
// 2. 배열 순회 방법들
int[] numbers = {10, 20, 30, 40, 50};
// 기본 for문 (인덱스 필요한 경우)
for (int i = 0; i < numbers.length; i++) {
System.out.println("Index " + i + ": " + numbers[i]);
}
// 향상된 for문 (Enhanced for loop)
for (int num : numbers) {
System.out.println(num);
}
}
}
List 활용법
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
public class ListExample {
public static void main(String[] args) {
// 1. List 선언과 초기화
List<Integer> list1 = new ArrayList<>();
List<Integer> list2 = Arrays.asList(1, 2, 3, 4, 5);
List<Integer> list3 = new ArrayList<>(Arrays.asList(1, 2, 3, 4, 5));
// 2. List 조작
list1.add(10);
list1.add(20);
list1.add(1, 15); // 인덱스 1에 15 삽입
list1.remove(0); // 인덱스 0 제거
list1.remove(Integer.valueOf(20)); // 값 20 제거
// 3. List 순회 방법들
List<String> fruits = Arrays.asList("apple", "banana", "cherry");
// 기본 for문
for (int i = 0; i < fruits.size(); i++) {
System.out.println("Index " + i + ": " + fruits.get(i));
}
// 향상된 for문
for (String fruit : fruits) {
System.out.println(fruit);
}
// 인덱스와 함께 순회하고 싶은 경우
for (int i = 0; i < fruits.size(); i++) {
System.out.println(i + ": " + fruits.get(i));
}
}
}
배열과 List 변환
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
public class Main {
public static void main(String[] args) {
// 1. 배열 → List
int[] arr = {1, 2, 3, 4, 5};
List<Integer> list1 = new ArrayList<>();
for (int num : arr) {
list1.add(num);
}
String[] strArr = {"a", "b", "c"};
List<String> strList = Arrays.asList(strArr);
// 2. List → 배열
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
// Integer 배열로 변환
Integer[] integerArr = numbers.toArray(new Integer[numbers.size()]);
}
}
6. 정수 overflow 주의
코테를 하다보면 java의 정수를 많이 사용하게 됩니다.
java int의 범위는 -2,147,483,648부터 2,147,483,647이기 때문에 이 범위를 넘어가면 정답을 맞추지 못 할 수 있습니다.
만약 이 범위가 넘어가는 값을 저장해야 한다면 int가 아닌 long을 활용해야 합니다.
java long의 범위는 -9,223,372,036,854,775,808부터 9,223,372,036,854,775,807로 int보다 더 넓은 범위의 정수를 저장할 수 있습니다.
한 번 15억 정수 두 개를 더해서 long에 저장해봅시다.
public class Main {
public static void main(String[] args) {
int a = 1500000000;
int b = 1500000000;
long c = a+b;
System.out.println(c);
// overflow 발생 : -1294967296
}
}
overflow가 발생해서 이상한 값이 저장됩니다.
이 현상을 해결하려면 더할 때 적어도 하나의 변수를 long으로 변환해야 합니다.
public class Main {
public static void main(String[] args) {
int a = 1500000000;
int b = 1500000000;
long c = (long)a+b;
System.out.println(c);
// 정상 실행 : 3000000000
}
}