이 글은 2026년 2월 4일에 작성된 글입니다.
데이터 타입
자바의 데이터 타입은 크게 기본타입(원시타입 - primitive type)과 참조 타입(reference type)으로 분류된다.

- 기본타입
- byte, char, short, int, long, float, double, boolean
- 실제 값을 변수에 저장함
- 참조타입
- 배열, 열거, 클래스, 인터페이스
- 메모리의 주소를 값으로 저장함 (메모리 주소를 통해 객체를 참조)
메모리 영역
JVM 메모리 구조는 다음과 같다.

모든 구조를 정확히 알면 좋겠지만, 가장 중요하다고 느낀 JVM stack, Heap만 정리해보았다.
JVM Stack
- 쓰레드마다 존재하며, 쓰레드 생성 시 같이 생성된다.
- Stack Frame: 함수가 호출될 때, 그 함수만의 스택 영역을 구분하기 위하여 생성되는 공간. 함수 호출 시 할당되며 함수 종료되면 소멸함.
- Stack Frame 내부에는 로컬 변수 스택이 있어 여기에 변수가 push되거나 pop됨.
- 기본 타입 변수 - 스택 영역에 직접 값 가지고 있음

- 참조 타입 변수 - 힙 영역이나 메소드 영역의 객체 주소를 가지고 있음

Heap
- 클래스의 인스턴스 (객체)와 배열이 할당되는 영역
- 힙 영역에서 생성된 객체와 배열은 스택 영역의 변수나 다른 객체의 필드에서 참조함 (위 사진 참고).
- Heap과 Method area는 전체 쓰레드가 공유하는 데이터 영역이며 JVM이 시작될 때 생성되고 종료되면 해제된다.
String
String은 기본타입이 아닌 String 클래스의 객체이기 때문에 참조타입이다. 따라서 String을 이용해 문자열을 선언하는 경우, 그 변수에 값이 직접 저장되는 게 아니라 String 객체의 주소가 저장된다.
String 변수 선언 방법
1. 문자열 리터럴
자바는 문자열 리터럴이 동일하면 String 객체를 공유한다. 즉 같은 주소를 사용하는 것.
public class StringCompare {
public static void main(String[] args) {
String name1 = "kim";
String name2 = "kim";
if (name1 == name2) // 주소를 비교함
System.out.println("name1과 name2는 참조가 같다"); // 출력됨
else
System.out.println("name1과 name2는 참조가 다르다");
if (name1.equals(name2)) // equals는 단순 문자열 비교
System.out.println("name1과 name2는 문자열이 같다");
}
}
2. new 연산자 사용
new 연산자를 사용하여 직접 String 객체를 사용하면 새로운 객체를 생성하게 된다.
public class StringCompare {
public static void main(String[] args) {
String name3 = new String("kim");
String name4 = new String("kim");
if (name3 == name4)
System.out.println("name3과 name4는 참조가 같다");
else
System.out.println("name3과 name4는 참조가 다르다"); // 출력됨
if (name3.equals(name4))
System.out.println("name3과 name4는 문자열이 같다");
}
}
불변(immutable) 자료형
기본적으로 자바에서 String 객체의 값은 변경할 수 없다.
String name = "hello";
name = name + " java";
위 코드의 경우 name 변수의 값을 업데이트 한 것처럼 보이지만, 실제로 메모리에는 새로 “hello java” 값을 저장한 영역을 따로 만들어서 담고 새로 참조하는 형태로 변경된다.
StringBuffer와 StringBuilder
그래서 문자열을 수정하고 싶을 때 쓰는 것이 바로 StringBuffer, StringBuilder 클래스다.
문자열 데이터를 다룬다는 점에서 String 객체와 같지만, 객체의 공간이 부족해지는 경우 버퍼의 크기를 유연하게 늘려주어 가변적이라는 차이점이 있다.
append(), delete() 등을 이용해 문자열 크기를 변경할 수 있다. 따라서 문자열의 추가, 수정, 삭제가 빈번하게 발생하는 경우 String보다 StringBuffer 혹은 StringBuilder를 사용하는 것이 더 낫다.
StringBuffer
- String과는 달리 문자열 수정 가능.
- 빠르게 문자열 다룰 수 있음.
(안드로이드 같은 메모리 자원이 부족한 모바일 환경에서 문자열을 자주 변경할 경우 많이 사용됨) - 멀티 스레드에 동기화 기능 제공.
StringBuilder
- StringBuffer와 동일한 기능
- 다른 점은 멀티 스레드에 동기화 기능 제공되지 않는다는 것.
- 멀티 스레드 환경이 아니라면 StringBuilder를 사용하는 것이 좋음.
오늘의 문제 해결
🔴 문제
예제 문제를 풀어보던 중 실행은 잘 되지만 원하는 대로 결과가 안 나오는 문제가 생겼다.
평균 점수를 가지고 학점을 구하는 문제인데 Scanner로 input을 받아서 “keep” 이면 학점을 구하고 "quit" 이면 반복문을 종료하는 문제였다.
package basic;
import java.util.Scanner;
public class Ex12 {
public static void main(String[] args) {
double average = 40;
Scanner input = new Scanner(System.in);
for (;;) {
String text = input.next();
if (text=="keep"){
System.out.println(average + "점의 학점을 구합니다.");
} else if (text=="quit") {
break;
} else {
System.out.println("keep, quit 중 하나를 입력하세요.");
continue;
}
if (average >= 95) {
System.out.println("학점은 A+입니다.");
} else if (average >= 90) {
System.out.println("학점은 A입니다.");
} else if (average >= 85) {
System.out.println("학점은 B+입니다.");
} else if (average >= 80) {
System.out.println("학점은 B입니다.");
} else if (average >= 70) {
System.out.println("학점은 C입니다.");
} else if (average >= 60) {
System.out.println("학점은 D입니다.");
} else
System.out.println("학점은 F입니다.");
average += 20;
}
input.close();
}
}
처음엔 이렇게 짰는데 아무리 keep이나 quit를 입력해도 계속 else문으로 넘어가서 keep, quit 중 하나를 입력하세요.가 출력되었다.
🔎 시도
사실 이 문제는 String에 대해 정확히 알지 못한 상태에서 풀었기 때문에 생긴 오류였다.
String은 new 생성자를 사용해 객체를 생성하면 힙 영역에 공간이 따로 할당하는 특징이 있었다. 그런데 Scanner를 사용해 문자열을 입력 받을 때도 마찬가지였다.
Scanner를 사용해 입력을 받을 때도 new 생성자로 생성한 객체와 같이 따로 힙 영역에 객체 공간을 할당하기 때문에 text=="keep" 이라는 코드에서 text가 뭐든 당연히 false 가 나올 수 밖에 없었던 것이다. 그냥 문자열 리터럴 “keep”과 new String(”keep”)은 다르기 때문이다.
✅ 해결
그래서 == 비교 연산이 아닌 문자열 자체를 비교하는 equals() 를 사용하기로 했다. 혹시나 대문자로 입력할 경우를 대비해서 equalsIgnoreCase() 메서드를 사용하였다.
import java.util.Scanner;
public class Ex12 {
public static void main(String[] args) {
double average = 40;
Scanner input = new Scanner(System.in);
for (;;) {
String text = input.next();
if (text.equalsIgnoreCase("keep")){
System.out.println(average + "점의 학점을 구합니다.");
} else if (text.equalsIgnoreCase("quit")) {
break;
} else {
System.out.println("keep, quit 중 하나를 입력하세요.");
continue;
}
if (average >= 95) {
System.out.println("학점은 A+입니다.");
} else if (average >= 90) {
System.out.println("학점은 A입니다.");
} else if (average >= 85) {
System.out.println("학점은 B+입니다.");
} else if (average >= 80) {
System.out.println("학점은 B입니다.");
} else if (average >= 70) {
System.out.println("학점은 C입니다.");
} else if (average >= 60) {
System.out.println("학점은 D입니다.");
} else
System.out.println("학점은 F입니다.");
average += 20;
}
input.close();
}
}
💡 알게된 점
확실히 기본기가 중요하다는 걸 느꼈다. 대충 알고 있는 지식으로는 뜻하지 않는 부분에서 오류가 나기 마련이다. 코딩테스트 문제를 잘 풀기 위해서 자바 기초부터 차근차근 공부해야겠다고 생각했다.
'Java' 카테고리의 다른 글
| [TIL-260209] 자바 기초: static, 상속, 추상 클래스 (0) | 2026.02.17 |
|---|---|
| [TIL-260206] 자바 기초: 메소드와 생성자 (0) | 2026.02.17 |
| [TIL 260205] 자바 기초: 배열과 ArrayList, Map, 그리고 클래스 (0) | 2026.02.17 |
| [TIL-260203] 자바 기초: 조건문과 반복문 (0) | 2026.02.16 |
| [TIL-260202] 클린 코드와 리팩토링 (0) | 2026.02.16 |