아래의 내용은 java_grammer 레파지토리 C01Basic 디렉터리에 저장되어있는 내용을 정리하였다.
자바의 데이터 타입은 크게 기본형(Primitive Type) 과 참조형(Reference Type) 으로 나뉨.
| 구분 | 예시 | 저장 위치(개념적으로) | 비교 방식 | 저장되는 것 |
|---|---|---|---|---|
| 기본형 (Primitive Type) | int, char, boolean 등 | 스택 프레임에 값 자체 저장 | == 로 값 비교 | 실제 값 |
| 참조형 (Reference Type) | String, 배열, 클래스 등 | 스택에는 주소, 실제 데이터는 힙(Heap) | equals() 로 값 비교 | 객체(주소) |
== 비교 시 true가 나옴.== 로 비교하면 주소 비교, equals() 를 써야 내부 값 비교가 됨.int a = 10;
int b = 10;
System.out.println(a == b); // true
String s1 = new String("hello");
String s2 = new String("hello");
System.out.println(s1 == s2); // false
System.out.println(s1.equals(s2)); // true
실제 JVM 구현에서는 “무조건 기본형=스택, 참조형=힙”이라고 딱 잘라 말할 수는 없지만, 학습 단계에서는 위와 같은 그림으로 이해하고 가면 됨.
String 은 참조 자료형이고, 내부적으로는 불변(immutable) 객체 로 설계되어 있음.
문자열을 생성하는 방식은 크게 두 가지가 있음.
String st1 = new String("Hello world"); // new 사용
String st2 = new String("Hello world");
String st3 = "Hello world"; // 리터럴
String st4 = "Hello world";
new String("...") : 호출할 때마다 새로운 String 객체를 힙에 생성함."Hello world" 리터럴 : String Pool(상수 풀) 에 저장되고, 같은 리터럴을 다시 쓰면 기존 풀에 있는 객체를 재사용함그래서:
System.out.println(st1 == st2); // false (서로 다른 객체)
System.out.println(st3 == st4); // true (String Pool의 같은 객체 참조)
System.out.println(st1 == st3); // false
System.out.println(st1.equals(st3)); // true (문자열 내용은 같음)
일반적으로는 new 보다는 리터럴 방식이 메모리 재사용 측면에서 더 권장되는 패턴임.
Wrapper 클래스 는 기본형 타입을 객체 형태로 감싼 참조형 타입임.
| 기본형 | Wrapper |
|---|---|
| byte | Byte |
| short | Short |
| int | Integer |
| long | Long |
| float | Float |
| double | Double |
| char | Character |
| boolean | Boolean |
Wrapper를 쓰는 대표적인 이유는 다음과 같음.
List, Set, Map) 에는 기본형을 직접 넣을 수 없고, 참조형만 저장 가능 Integer.parseInt, Double.parseDouble 등)자바는 기본형과 Wrapper 사이의 변환을 자동으로 해주는 기능을 제공함
int i1 = 10;
Integer i2 = i1; // 오토박싱
int i3 = i2; // 오토언박싱
String s = Integer.toString(i1); // int → String
int n = Integer.parseInt(s); // String → int
컬렉션과 함께 쓰일 때 특히 자주 보게 됨.
List<Integer> list = new ArrayList<>();
list.add(10); // int가 자동으로 Integer로 박싱되어 저장됨
배열에는 예외적으로 기본형을 직접 넣을 수 있지만, List 같은 컬렉션에는 반드시 Wrapper 타입을 사용해야 함.[3]
String 은 불변 객체라, 한 번 만들어진 값이 바뀌지 않고, 수정처럼 보이는 연산은 항상 새로운 String 객체 를 만들어냄.
자주 사용하는 주요 메서드의 “개념적인 용도”만 정리하면 아래와 같음.
| 메서드 | 역할 |
|---|---|
equals() | 문자열 내용이 같은지 비교 |
equalsIgnoreCase() | 대소문자 구분 없이 내용 비교 |
length() | 문자열 길이(문자 개수) 반환 |
charAt(index) | 특정 인덱스 위치의 문자 반환 |
toCharArray() | 문자열을 char[] 배열로 변환 |
indexOf(str) | 앞에서부터 str 이 처음 나타나는 위치 반환, 없으면 -1 |
lastIndexOf(str) | 뒤에서부터 찾았을 때 위치 반환 |
contains(str) | 해당 문자열 포함 여부 boolean 반환 |
substring(begin, end) | [begin, end) 구간의 부분 문자열 반환 |
trim() / strip() | 양쪽 끝 공백 제거 (Java 11부터 strip 권장) |
toUpperCase() / toLowerCase() | 대문자/소문자로 변환 (원본은 그대로) |
replace(a, b) | a 문자열을 b 문자열로 치환 |
replaceAll(regex, b) | 정규표현식으로 매칭되는 부분을 b로 치환 |
split(delimiter) | 구분자를 기준으로 문자열을 잘라 String[] 반환 |
정규식 패턴 ([가-힣], [A-Za-z], \\s+ 등) 은 문자열 필터링/검증에서 자주 쓰이므로, 개념만이라도 익혀두면 좋음.
입력값 검증에서 자주 헷갈리는 세 가지 개념임.
null : 아무 객체도 가리키지 않는 상태 (참조 자체가 없음) "" : 길이가 0인 문자열 (빈 문자열) " " : 공백 문자 하나를 포함한 문자열null 인 참조에 대해 메서드를 호출하면 NullPointerException 이 발생하지만, 빈 문자열/공백 문자열은 정상적인 String 객체라 메서드 호출이 가능함.
String case1 = null; // null은 문자열 아님
String case2 = ""; // 빈 문자열은 문자열
String case3 = " "; // 공백
System.out.println("null과 빈문자열 비교 : " + case1 == case2); // false
System.out.println("null 체크 : " + case1 == null); // true
// System.out.println(case1.isEmpty()); // error : NullPointerException
System.out.println("빈 문자열의 빈문자열 체크 : " + case2.isEmpty()); // true
System.out.println("공백의 빈문자열 체크 : " + case3.isEmpty()); // false(공백이 차있는 것)
System.out.println("빈문자열의 빈문자열 체크 : " + case3.isBlank()); // true
String 은 불변이라 += 로 여러 번 이어 붙이면 매번 새로운 객체가 만들어져 성능이 나빠질 수 있음.
이 문제를 해결하기 위해 가변(mutable)한 문자열 버퍼 인 StringBuffer, StringBuilder 를 사용함.
StringBuffer StringBuilder 공통적으로 다음과 같은 메서드를 제공함.
append() : 문자열/문자/숫자를 뒤에 이어 붙임 insert(index, str) : 특정 위치에 문자열 삽입 deleteCharAt(index) : 특정 위치 문자 삭제 substring() : String과 비슷한 방식으로 부분 문자열 추출 reverse() : 문자열을 뒤집음 실제로 콘솔에 여러 줄 출력하기보다, StringBuilder 나 StringBuffer 에 문자열을 모아서 한 번에 출력하는 게 I/O 호출 횟수도 줄고 성능도 더 좋음.
String 은 Comparable<String> 을 구현하고 있어서 compareTo() 로 사전순 비교가 가능함.
내부적으로는 각 문자의 유니코드 값을 순서대로 비교해서 차이를 계산하는 방식이라, 정렬 구현이나 사전순 정렬 시에 활용할 수 있음.
String st1 = "hello";
String st2 = "dello";
String st3 = "hello";
String st4 = "hfllo";
System.out.println("compareTo 1->2 : "+st1.compareTo(st2)); //4
System.out.println("compareTo 2->1 : "+st2.compareTo(st1)); //-4
System.out.println("compareTo 1->3 : "+st1.compareTo(st3)); //0
System.out.println("compareTo 4->3 : "+st4.compareTo(st3)); //1
| 비교 | 다른 위치 | 문자1 | 문자2 | ASCII 차이 | 결과 |
|---|---|---|---|---|---|
| st1 vs st2 | 0 | h(104) | d(100) | 104-100 | +4 |
| st2 vs st1 | 0 | d(100) | h(104) | 100-104 | -4 |
| st1 vs st3 | 전체 동일 | - | - | - | 0 |
| st4 vs st3 | 1 | f(102) | e(101) | 102-101 | +1 |
참조자료형 = 클래스기반이다를 기억하자