Do You Coding?
[Java] Java 기본 문법 - 변수, 타입, 배열, 문자열 본문
1. 변수와 데이터 타입
1-1. 변수(Variable)란?
변수는 데이터를 저장하기 위한 메모리 공간입니다.
Java에서는 변수를 선언할 때 반드시 타입을 명시해야 합니다.
변수의 크기는 저장된 값이 아니라 타입에 의해 결정됩니다.
1-2. 기본형과 참조형
Java의 데이터 타입은 크게 기본형(Primitive Type)과 참조형(Reference Type)으로 나뉩니다.
| 구분 | 특징 | 예시 |
|---|---|---|
| 기본형 | 크기가 미리 정해진 데이터 표현. 실제 값을 직접 저장 | int, double, boolean 등 |
| 참조형 | 크기가 미리 정해질 수 없는 데이터 표현. 실제 값이 저장된 메모리 주소(참조값)를 저장 | String, 배열, 객체 등 |
변수는 내부적으로 타입 — 이름 — 값(또는 참조값)의 구조를 가집니다.
기본형은 값 자체를, 참조형은 Heap 메모리에 있는 객체의 주소를 저장합니다.
1-3. 기본형 타입 상세
| 분류 | 타입 | 크기 | 기본값 | 비고 |
|---|---|---|---|---|
| 논리형 | boolean | 1 byte | false | |
| 정수형 | byte | 1 byte | 0 | |
| short | 2 byte | 0 | ||
| int | 4 byte | 0 | 정수형 기본 타입 | |
| long | 8 byte | 0L | 리터럴 끝에 L 붙임 | |
| 실수형 | float | 4 byte | 0.0f | 리터럴 끝에 F 붙임 |
| double | 8 byte | 0.0 | 실수형 기본 타입. float 대비 소수부 정밀도 2배 | |
| 문자형 | char | 2 byte | '\u0000' | null 문자 |
리터럴에 아무 접미사를 붙이지 않으면, 정수는
int, 실수는double로 인식됩니다.
예:3→ int,2.213→ double
실수 연산의 정밀도 문제
float이나 double은 근사값으로 계산하기 때문에 정확한 값이 나오지 않을 수 있습니다.
정확한 계산이 필요하면 정수로 변환 후 계산하고, 다시 실수로 전환하는 방법을 사용합니다.
또는 유효 자릿수를 이용한 반올림 처리를 합니다.
오버플로우
MAX_VALUE + 1처럼 표현 범위를 초과하면 오버플로우가 발생합니다.
Java에서는 오버플로우가 에러 없이 정상 처리되므로, 개발자가 직접 주의해야 합니다.
1-4. var 키워드 (Java 10+)
Java 10부터 var 키워드로 로컬 변수를 선언할 수 있습니다.
값을 할당하는 시점에 타입이 자동으로 결정됩니다.
var name = "Hello"; // String으로 추론
var count = 10; // int로 추론
제약사항: 지역 변수에서만 사용 가능하며, 선언과 동시에 값을 할당해야 합니다.
초보자에게는 타입을 명시적으로 쓰는 것이 권장됩니다.
2. String 문자열
2-1. String 생성 방식과 String Pool
String은 기본형이 아닌 참조형입니다.
생성 방식에 따라 메모리에 저장되는 위치가 다릅니다.
| 방식 | 코드 | 저장 위치 |
|---|---|---|
| 리터럴 할당 | String str = "Hello"; | String Pool |
| 생성자 이용 | String str = new String("Hello"); | Heap 영역 (새 객체 생성) |
리터럴 방식은 String Pool에 같은 문자열이 이미 있으면 기존 객체를 재사용합니다.
new로 생성하면 같은 내용이라도 매번 새로운 객체를 만듭니다.
String a = "Java"; // String Pool에 저장
String b = "Java"; // Pool에서 같은 객체를 참조
System.out.println(b == a); // true (같은 객체)
String bb = new String("Java"); // Heap에 새 객체 생성
String aa = "Java"; // Pool의 기존 객체 참조
System.out.println(bb == aa); // false (다른 객체)
System.out.println(b.equals("Java")); // true (내용 비교)
==는 같은 객체인지(참조 비교),equals()는 같은 내용인지(값 비교)를 판단합니다.
null 안전한 문자열 비교
메서드 반환값이 null일 수 있다면, equals() 호출 시 NullPointerException이 발생할 수 있습니다.
이를 방지하려면 리터럴 쪽에서 equals()를 호출하는 것이 안전합니다.
// 위험: getA()가 null이면 NullPointerException 발생
System.out.println((getA()).equals("Java"));
// 안전: null 여부를 먼저 체크
System.out.println(getA() != null && (getA()).equals("Java"));
// 가장 권장: 리터럴 쪽에서 호출하면 null이어도 안전
System.out.println("Java".equals(getA()));
2-2. String 주요 메서드
String 객체는 불변(immutable)입니다.
메서드를 호출해도 원본 문자열은 변하지 않고, 새로운 문자열 객체가 반환됩니다.
| 메서드 | 설명 | 예시 |
|---|---|---|
length() | 문자열 길이 반환 | "Java".length() → 4 |
charAt(int index) | 특정 위치의 문자 반환 (char) | "Java".charAt(1) → 'a' |
substring(int from, int to) | from 이상 to 미만 범위의 부분 문자열 반환 | "Java".substring(0, 2) → "Ja" |
split(String regex) | 정규표현식 기준으로 분리 후 String[] 반환 | "a b c".split(" ") |
toCharArray() | char 배열로 변환 | "Java".toCharArray() |
toUpperCase() | 대문자로 변환 (원본 불변) | "Java".toUpperCase() → "JAVA" |
replace(old, new) | 문자 또는 문자열 치환 | "Java".replace('v', 'a') → "Jaaa" |
replaceAll(regex, new) | 정규표현식 기반 치환 | "Java".replaceAll("a", "k") → "Jkvk" |
concat(String s) | 문자열 이어 붙이기 | "java".concat("algo") → "javaalgo" |
repeat(int n) | 문자열 n번 반복 | "ab".repeat(3) → "ababab" |
문자열 → 숫자, 숫자 → 문자열 변환
// 숫자 → 문자열
int i1 = 10;
String x = String.valueOf(i1); // 권장 방법
String y = i1 + ""; // 편법 (내부적으로 StringBuilder 동작)
// 문자열 → 숫자
int num = Integer.parseInt("42");
문자열 연결 시 주의할 점
String k1 = 1 + 2 + ""; // "3" (숫자 연산이 먼저 수행)
String k2 = "" + 1 + 2; // "12" (문자열 결합이 먼저 수행)
+연산자로 문자열을 연결하면, 내부적으로StringBuilder가 생성됩니다.
반복적인 문자열 결합은 불필요한 객체를 많이 만들어 메모리 낭비가 됩니다.
2-3. StringBuilder
StringBuilder는 가변(mutable) 문자열 객체입니다.
String과 달리 내용을 직접 수정할 수 있어, 반복적인 문자열 조작에 효율적입니다.
StringBuilder sb = new StringBuilder("java");
sb.append("algo"); // 원본 객체가 직접 변경됨
sb.append(true).append(3.4); // 메서드 체이닝 가능
String result = sb.toString(); // String으로 변환
String vs StringBuilder: 메서드에서의 차이
public static void main(String[] args) {
String s = "java";
StringBuilder sb = new StringBuilder("java");
m1(s); // String은 불변이므로 원본 변경 불가
m1(sb); // StringBuilder는 가변이므로 원본 변경됨
System.out.println("s : " + s); // s : java (변화 없음)
System.out.println("sb : " + sb); // sb : javagumi (변경됨)
}
static void m1(String s) {
s = s.concat("algo"); // 새 객체가 생성될 뿐, 원본은 변하지 않음
System.out.println("m1 : " + s); // m1 : javaalgo
}
static void m1(StringBuilder sb) {
sb.append("gumi"); // 원본 객체가 직접 변경됨
System.out.println("m1 : " + sb); // m1 : javagumi
}
String은 불변이기 때문에 메서드 안에서 변경해도 호출자의 변수에는 영향이 없습니다.
원본을 변경하고 싶다면 메서드의 반환값을 다시 할당하거나,StringBuilder를 사용해야 합니다.
2-4. Text Block (Java 15+)
Text Block을 사용하면 여러 줄에 걸친 문자열을 쉽게 작성할 수 있습니다.
자동 개행이 적용되어 \n을 직접 넣을 필요가 없습니다.
String html = """
<html>
<body>
<p>Hello</p>
</body>
</html>
""";
3. 형변환(Type Casting)
3-1. 묵시적 형변환과 명시적 형변환
| 구분 | 방향 | 값 손실 | 코드 예시 |
|---|---|---|---|
| 묵시적(자동) 형변환 | 작은 타입 → 큰 타입 | 없음 | int i = b; (byte → int) |
| 명시적(강제) 형변환 | 큰 타입 → 작은 타입 | 발생 가능 | int result = (int) d; (double → int) |
// 묵시적 형변환: 값 손실 없이 자동 변환
byte b = 10;
int i = b; // byte → int 자동 변환
// 명시적 형변환: 값 손실 가능, 캐스팅 연산자 필요
double d = 100.5;
int result = (int) d; // 100 (소수부 손실)
묵시적 형변환 방향
타입의 표현 범위가 커지는 방향으로 할당하면 자동 형변환이 일어납니다.
byte → short → int → long → float → double
char → int (char는 short를 거치지 않고 바로 int로 변환)
3-2. 산술 연산 시 자동 형변환
산술 이항 연산자는 연산 전에 피연산자의 타입을 일치시킵니다.
두 가지 규칙이 적용됩니다.
- 피연산자의 크기가 4byte(int) 미만이면 int로 변환 후 연산
- 두 피연산자 중 큰 타입으로 변환 후 연산
byte b1 = 10;
byte b2 = 20;
// byte b3 = b1 + b2; // 컴파일 에러! b1, b2가 int로 변환되어 연산됨
int b3 = b1 + b2; // OK: 결과는 int
int i1 = 10;
long l1 = 20;
// int i2 = i1 + l1; // 컴파일 에러! long이 더 크므로 long으로 연산
long i2 = i1 + l1; // OK: 결과는 long
// float f1 = 10.0; // 컴파일 에러! 10.0은 double 리터럴
float f1 = 10.0f; // OK: float 리터럴에는 f 접미사 필요
3-3. 기본형과 객체형의 형변환 (Boxing / Unboxing)
기본형은 값만 저장하지만, 객체형(Wrapper 클래스)은 추가적인 속성과 메서드를 포함합니다.
Java 5부터 autoboxing과 unboxing으로 자동 변환됩니다.
| 기본형 | Wrapper 클래스 |
|---|---|
| byte | Byte |
| short | Short |
| int | Integer |
| long | Long |
| float | Float |
| double | Double |
| char | Character |
| boolean | Boolean |
int i = 10;
// Boxing: 기본형 → 객체형
Integer iw = Integer.valueOf(i); // 명시적 boxing
Integer iw2 = i; // autoboxing
// Unboxing: 객체형 → 기본형
int i2 = iw.intValue(); // 명시적 unboxing
int i3 = iw; // auto unboxing
// 기본형 ↔ 문자열 변환
int i4 = Integer.parseInt("15"); // 문자열 → int
String s1 = String.valueOf(i); // int → 문자열 (권장)
String s3 = Integer.toString(i); // int → 문자열
String s4 = "" + i; // 편법
4. 조건문
4-1. if문
if문의 조건에는 boolean으로 평가되는 식만 들어갈 수 있습니다.
int score = 85;
if (score >= 90) {
System.out.println("A");
} else if (score >= 80) {
System.out.println("B");
} else {
System.out.println("C");
}
4-2. switch문
switch문의 조건에 들어갈 수 있는 타입은 byte, short, char, int, String 등입니다.
long은 사용할 수 없습니다.
각 case 끝에 break를 빠뜨리면 다음 case로 계속 실행(fall-through)되므로 주의해야 합니다.
int month = 3;
int day = -1;
switch (month) {
case 2:
day = 29;
break;
case 4:
case 6:
case 9:
case 11:
day = 30; // 4, 6, 9, 11월은 모두 30일
break;
default:
day = 31;
}
4-3. Switch Expression (JDK 14+)
Switch Expression은 표현식 자체가 값을 반환할 수 있습니다.
: 대신 ->를 사용하며, 자동 break가 적용됩니다.
중괄호 블록이 필요할 경우 yield 키워드로 값을 반환합니다.
int month = 3;
int day = switch (month) {
case 2 -> 29;
case 4, 6, 9, 11 -> 30;
default -> {
yield 31; // 중괄호 블록에서는 yield로 반환
}
};
5. 반복문
5-1. for문과 while문
for문은 반복 횟수가 정해진 경우에, while문은 조건 기반 반복에 주로 사용합니다.
// for문: 1부터 5까지 출력
for (int i = 1; i <= 5; i++) {
System.out.println(i);
}
// while문: 조건이 false가 될 때까지 반복
int count = 0;
while (count < 5) {
System.out.println(count);
count++;
}
6. 배열
6-1. 배열의 정의와 생성
배열은 동일한 타입의 데이터 0개 이상을 연속된 메모리 공간에서 관리하는 자료구조입니다.
배열 변수는 Heap 메모리에 생성된 배열을 가리키는 참조형 변수입니다.
// 크기만 지정하여 생성 (기본값으로 초기화)
int[] points = new int[3];
// 생성과 동시에 값 초기화
int[] b = new int[]{1, 3, 5, 6, 8};
int[] c = {1, 3, 5, 6, 8}; // new int[] 생략 가능 (선언과 동시에 할당할 때만)
주의: 선언과 할당을 분리할 때는
new int[]를 반드시 명시해야 합니다.
int[] points;
// points = {1, 3, 5, 6, 8}; // 컴파일 오류!
points = new int[]{1, 3, 5, 6, 8}; // OK: 선언과 할당이 분리될 때는 new 필수
6-2. 배열 요소의 기본값
배열을 생성하면 각 요소는 해당 자료형의 기본값으로 자동 초기화됩니다.
| 자료형 | 기본값 | 비고 |
|---|---|---|
| boolean | false | |
| char | '\u0000' | null 문자 |
| byte, short, int | 0 | |
| long | 0L | |
| float | 0.0f | |
| double | 0.0 | |
| 참조형 변수 | null | 아무것도 참조하지 않음 |
문자열에서 문자 다루기
String str = "Hello123World";
for (int i = 0; i < str.length(); i++) {
char ch = str.charAt(i); // 각 문자에 접근
System.out.println(ch);
}
// 문자열에서 숫자 추출
String numStr = "42";
int num = Integer.parseInt(numStr); // 문자열 → 정수 변환
6-3. for-each문 (향상된 for문)
인덱스 없이 배열의 각 요소에 직접 접근할 수 있는 반복문입니다.
가독성이 좋고 배열 및 Collection에서 사용할 수 있습니다.
int[] intArray = {1, 3, 5, 7, 9};
for (int x : intArray) {
System.out.println(x); // 1, 3, 5, 7, 9 순서대로 출력
}
주의: for-each문은 Read Only입니다.
반복 변수x의 값을 변경해도 원본 배열에는 영향이 없습니다.
요소를 수정해야 하면 인덱스 기반 for문을 사용해야 합니다.
6-4. 배열 복사
배열의 크기는 최초 할당 후 변경 불가합니다.
개별 요소의 값은 바꿀 수 있지만, 요소를 추가하거나 삭제할 수는 없습니다.
크기를 변경하려면 새 배열을 만들고 복사해야 합니다.
System.arraycopy
// System.arraycopy(원본배열, 원본시작인덱스, 대상배열, 대상시작인덱스, 복사할길이)
int[] src = {1, 2, 3, 4, 5};
int[] dest = new int[10];
System.arraycopy(src, 0, dest, 0, src.length);
Arrays.copyOf
// Arrays.copyOf(원본배열, 새배열길이)
int[] original = {1, 2, 3, 4, 5};
int[] copied = Arrays.copyOf(original, 10); // 길이 10인 새 배열로 복사
6-5. 다차원 배열
다차원 배열이라고 해서 특별한 것은 없습니다.
어떤 타입의 데이터를 저장하는가의 차이일 뿐입니다.
| 선언 | 타입 | 저장 대상 |
|---|---|---|
int a | int | 정수 값 |
int[] arr | int[] | int 값들 |
int[][] arr2 | int[][] | int[] 배열들 |
// 4행 크기만 지정 (열은 나중에 결정 가능)
int[][] intArray = new int[4][];
// int[]를 담을 4개의 공간만 생성됨. 각 행의 길이를 다르게 설정 가능
intArray[0] = new int[]{1, 2}; // 2열
intArray[1] = new int[]{3, 4, 5}; // 3열
intArray[2] = new int[]{6}; // 1열
intArray[3] = new int[]{7, 8, 9, 10}; // 4열
7. 값 전달과 참조 전달
7-1. 기본형 전달 vs 참조형 전달
Java에서 메서드에 인자를 전달할 때, 기본형은 값이 복사되고 참조형은 참조값(주소)이 복사됩니다.
따라서 기본형은 메서드 안에서 변경해도 원본에 영향이 없지만,
참조형은 같은 객체를 가리키므로 객체의 내부 상태가 변경됩니다.
public static void main(String[] args) {
int a = 10;
aa(a);
System.out.println("main a = " + a); // main a = 10 (변화 없음)
Data d = new Data();
d.x = 10;
aa(d);
System.out.println("main d.x = " + d.x); // main d.x = 20 (변경됨!)
}
static void aa(int a) {
a = 20; // 복사된 값만 변경 → 원본에 영향 없음
System.out.println("aa a = " + a); // aa a = 20
}
static void aa(Data d) {
d.x = 20; // 같은 객체를 가리키므로 원본도 변경됨
System.out.println("Data x = " + d.x); // Data x = 20
}
class Data {
int x, y;
}
실행 결과:
aa a = 20
main a = 10
Data x = 20
main d.x = 20
8. 보충 팁
8-1. printf에서 \n과 %n의 차이
\n은 운영체제에 따라 줄바꿈 문자가 다를 수 있습니다.
%n은 운영체제에 맞는 줄바꿈 문자를 자동으로 적용합니다.
Java는 OS에 종속되지 않는 것이 원칙이므로, printf 사용 시 %n이 더 적합합니다.
참고로
printf의 f는 format의 약자입니다.
8-2. Wrapper 클래스의 필요성
기본형은 객체가 아니기 때문에, 객체가 필요한 상황에서는 Wrapper 클래스로 감싸야 합니다.
예를 들어 List<int>는 불가능하지만, List<Integer>는 가능합니다.
핵심 정리
- 변수: 타입에 의해 크기가 결정된다. 기본형은 값을 직접, 참조형은 주소를 저장한다.
- String: 참조형이며 불변 객체다. 리터럴은 String Pool을 공유하고,
new는 매번 새 객체를 만든다. - 문자열 비교:
==는 참조 비교,equals()는 값 비교. null 안전을 위해"리터럴".equals(변수)패턴을 사용한다. - StringBuilder: 가변 문자열. 반복적인 문자열 조작 시
String보다 효율적이다. - 형변환: 작은 타입 → 큰 타입은 자동, 큰 타입 → 작은 타입은 명시적 캐스팅이 필요하다.
- 산술 연산: 4byte 미만은 int로 승격, 두 피연산자 중 큰 타입으로 자동 변환된다.
- Boxing/Unboxing: 기본형과 Wrapper 클래스 간 자동 변환이 가능하다.
- switch: long은 사용 불가. JDK 14+의 Switch Expression은 값을 반환하고 자동 break된다.
- 배열: 크기 불변. 동일 타입의 연속된 메모리. 복사는
System.arraycopy또는Arrays.copyOf를 사용한다. - for-each: 읽기 전용. 인덱스 없이 요소에 직접 접근하며, 배열 및 Collection에서 사용한다.
- 메서드 인자 전달: 기본형은 값 복사(원본 불변), 참조형은 주소 복사(객체 내부 변경 가능).
'Backend > Java' 카테고리의 다른 글
| [Java] 상속 (Inheritance) (0) | 2026.03.18 |
|---|---|
| [Java] 추상화 (Abstraction) (0) | 2026.03.17 |