Ch 2.1 변수란? (Variables)
수학에서는 변수(variable)를 '변하는 수'라고 정의하지만, 프로그래밍에서는 그 의미가 조금 다릅니다.
1. 변수의 정의
프로그래밍에서 변수(variable) 란, "단 하나의 값을 저장할 수 있는 메모리 공간" 을 의미합니다.
메모리와 변수의 관계
컴퓨터의 메모리(RAM)는 수많은 칸으로 나뉜 거대한 서랍장과 같습니다. 각 칸에는 고유한 주소(메모리 주소)가 붙어 있고, 우리는 이 칸에 데이터를 저장합니다. 하지만 직접 메모리 주소를 다루는 것은 매우 번거롭습니다(0x7FFEEFBFF5A0 같은 주소를 기억하기 어렵죠). 그래서 자바는 변수명 이라는 이름표를 통해 메모리 공간을 편하게 다룰 수 있게 해줍니다.
메모리 (RAM)
┌─────────────────────────────────┐
│ 주소: 0x100 │ 값: 25 ← age │
│ 주소: 0x104 │ 값: 0 │
│ 주소: 0x108 │ 값: 175.5 ← height │
│ ... │ ... │
└─────────────────────────────────┘
박스 비유: 변수는 내용물을 담아두는 박스입니다. 박스 겉면에 이름표(age, height)를 붙여두면, 나중에 그 이름만 불러서 안에 든 값을 꺼내거나 바꿀 수 있습니다.
변수는 하나의 값만 저장합니다. 새 값을 넣으면 기존 값은 사라집니다.
2. 변수의 선언과 초기화
변수를 사용하려면 먼저 어떤 종류인지(타입)와 어떤 이름인지(변수명)를 결정하여 '선언'해야 합니다.
변수 선언 문법
// 문법: 타입 변수명;
int age; // 정수(int) 타입의 변수 age 선언
double height; // 실수(double) 타입의 변수 height 선언
String name; // 문자열(String) 타입의 변수 name 선언
변수를 선언하면 메모리의 빈 공간에 '변수 타입'에 알맞은 크기의 저장공간이 확보되고, 이 저장공간은 '변수 이름'을 통해 사용할 수 있게 됩니다.
변수의 초기화
변수를 선언한 이후부터는 변수를 사용할 수 있으나, 그 전에 반드시 변수를 '초기화(initialization)' 해야 합니다. 초기화란 변수를 사용하기 전에 처음으로 값을 저장하는 것을 말합니다.
int age; // 1단계: 변수 선언 (메모리 공간 확보)
age = 25; // 2단계: 변수 초기화 (메모리에 값 저장)
int year = 2024; // 선언과 초기화를 한 줄에 동시에!
자바에서 지역변수(메서드 안에서 선언된 변수)는 초기화하지 않으면 컴파일 에러가 납니다!
int score;
System.out.println(score); // 에러: variable score might not have been initialized
여러 변수를 한 줄에 선언하기
같은 타입의 변수는 쉼표로 구분하여 한 줄에 선언할 수 있습니다.
int x, y, z; // 선언만
int a = 1, b = 2, c = 3; // 선언 + 초기화 동시에
3. 변수의 종류: 위치에 따른 분류
변수는 선언된 위치 에 따라 크게 세 가지로 나뉩니다.
| 종류 | 선언 위치 | 생명주기 | 기본값 |
|---|---|---|---|
| 지역변수 (Local) | 메서드 내부 | 메서드 시작~종료 | 없음 (직접 초기화 필수) |
| 인스턴스변수 (Instance) | 클래스 내부, 메서드 외부 | 객체 생성~소멸 | 타입별 기본값 |
| 클래스변수 (Static) | static 키워드와 함께 선언 | 프로그램 시작~종료 | 타입별 기본값 |
public class Student {
// 인스턴스변수: 각 Student 객체마다 독립적으로 존재
String name; // 기본값: null
int grade; // 기본값: 0
double gpa; // 기본값: 0.0
// 클래스변수: 모든 Student 객체가 공유하는 하나의 변수
static int totalStudents = 0;
public void study() {
// 지역변수: study() 메서드 안에서만 사용 가능
int studyHours = 3; // 반드시 초기화해야 함
System.out.println(name + "이(가) " + studyHours + "시간 공부했습니다.");
}
}
4. 변수의 스코프(Scope)와 생명주기(Lifecycle)
스코프 란 변수를 사용할 수 있는 유효 범위입니다. 변수는 선언된 블록({...}) 안에서만 사용할 수 있습니다.
public class ScopeExample {
static int classVar = 10; // 클래스 스코프: 어디서나 접근 가능
public static void main(String[] args) {
int outerVar = 20; // main 메서드 스코프
if (outerVar > 10) {
int innerVar = 30; // if 블록 스코프
System.out.println(classVar); // OK: 10
System.out.println(outerVar); // OK: 20
System.out.println(innerVar); // OK: 30
}
System.out.println(classVar); // OK: 10
System.out.println(outerVar); // OK: 20
// System.out.println(innerVar); // 에러! if 블록 밖에서 접근 불가
}
}
변수는 가능한 좁은 스코프에서 선언하는 것이 좋습니다. 필요한 곳에서만 선언하면 코드 가독성이 높아지고 실수를 줄일 수 있습니다.
5. 상수 (Constant) - final 키워드
값을 한 번 저장하면 프로그램이 종료될 때까지 절대 변경할 수 없는 변수 를 '상수'라고 합니다. 변수의 타입 앞에 final 키워드를 붙여 선언하며, 관례적으로 이름을 대문자와 언더바 로 작성합니다.
public class ConstantExample {
public static void main(String[] args) {
final double PI = 3.14159265358979;
final int MAX_SCORE = 100;
final String APP_NAME = "MyJavaApp";
System.out.println("원주율: " + PI);
System.out.println("최고 점수: " + MAX_SCORE);
System.out.println("앱 이름: " + APP_NAME);
// PI = 3.14; // 컴파일 에러! 상수는 값을 변경할 수 없습니다.
}
}
상수를 사용하는 이유:
- 실수 방지: 중요한 값을 실수로 바꾸지 못하게 보호
- 가독성 향상:
3.14159...보다PI가 훨씬 이해하기 쉬움 - 유지보수: 한 곳에서 값을 바꾸면 모든 곳에 반영됨
6. var 키워드 (Java 10+) - 타입 추론
Java 10부터는 var 키워드를 사용하면 컴파일러가 오른쪽 값을 보고 타입을 자동으로 추론합니다. 단, 지역변수에만 사용 가능합니다.
public class VarExample {
public static void main(String[] args) {
var age = 25; // 컴파일러가 int로 추론
var name = "Alice"; // 컴파일러가 String으로 추론
var pi = 3.14159; // 컴파일러가 double로 추론
var isStudent = true; // 컴파일러가 boolean으로 추론
System.out.println(age); // 25
System.out.println(name); // Alice
System.out.println(pi); // 3.14159
System.out.println(isStudent); // true
// var는 선언과 동시에 초기화해야 함
// var x; // 에러! 초기화 없이 var 사용 불가
}
}
var를 쓴다고 자바가 동적 타입 언어가 되는 것은 아닙니다. 여전히 강타입 언어이며, 컴파일 시점에 타입이 결정됩니다.
7. 변수 명명 규칙과 예약어
필수 규칙 (위반 시 컴파일 에러)
- 대소문자가 구분된다.(
Age와age는 서로 다른 변수) - 예약어(Keyword)를 사용할 수 없다.(
int,class,for등) - 숫자로 시작할 수 없다.(
age10은 OK,10age는 에러) - 특수문자는
_와$만 허용된다.
권장 규칙 (Java 개발자 관례)
| 대상 | 규칙 | 예시 |
|---|---|---|
| 변수, 메서드 | 소문자 시작, camelCase | studentName, totalScore |
| 클래스 | 대문자 시작, PascalCase | StudentRecord, MathUtil |
| 상수 | 대문자, 언더바 구분 | MAX_VALUE, PI |
| 패키지 | 모두 소문자 | com.example.app |
Java 예약어 목록
abstract assert boolean break byte
case catch char class const
continue default do double else
enum extends final finally float
for goto if implements import
instanceof int interface long native
new package private protected public
return short static strictfp super
switch synchronized this throw throws
transient try void volatile while
true, false, null은 예약어는 아니지만 리터럴로 취급되어 변수명으로 사용할 수 없습니다.
8. 변수 오버플로우(Overflow)와 언더플로우(Underflow)
각 변수 타입에는 저장할 수 있는 값의 범위가 정해져 있습니다. 이 범위를 넘으면 오버플로우, 아래로 넘으면 언더플로우 가 발생합니다. 오류(에러)가 발생하는 것이 아니라, 조용히 값이 반대편으로 돌아갑니다!
public class OverflowExample {
public static void main(String[] args) {
// int의 최대값과 최소값 확인
System.out.println("int 최대값: " + Integer.MAX_VALUE); // 2147483647
System.out.println("int 최소값: " + Integer.MIN_VALUE); // -2147483648
// 오버플로우: 최대값에서 1 더하기
int maxVal = Integer.MAX_VALUE;
System.out.println("최대값 + 1 = " + (maxVal + 1)); // -2147483648 (최소값으로 순환!)
// 언더플로우: 최소값에서 1 빼기
int minVal = Integer.MIN_VALUE;
System.out.println("최소값 - 1 = " + (minVal - 1)); // 2147483647 (최대값으로 순환!)
// byte 오버플로우
byte b = 127; // byte 최대값
b++; // byte b = (byte)(127 + 1) = -128
System.out.println("byte 127 + 1 = " + b); // -128
}
}
byte 범위 시각화:
... -130 -129 [-128 ......... 127] 128 129 ...
↑ ↑
MIN MAX
└────── 오버플로우 시 최소값으로 순환 ──────┘
금융 계산처럼 정확성이 중요한 경우 오버플로우에 특히 주의해야 합니다. long이나 BigInteger를 사용하는 것이 안전합니다.
9. 실전 예제: 학생 성적 관리 변수 선언
public class StudentGrade {
// 상수: 변하지 않는 값
static final int MAX_SCORE = 100;
static final int SUBJECT_COUNT = 5;
static final String SCHOOL_NAME = "한국자바학교";
// 클래스변수: 전체 학생 수
static int totalStudents = 0;
public static void main(String[] args) {
// 학생 기본 정보
String studentName = "김자바";
int studentAge = 20;
char grade = 'A'; // 학점 (A, B, C, D, F)
boolean isEnrolled = true; // 재학 여부
// 과목별 점수
int koreanScore = 95;
int mathScore = 88;
int englishScore = 92;
int scienceScore = 78;
int historyScore = 85;
// 총점과 평균 계산
int totalScore = koreanScore + mathScore + englishScore
+ scienceScore + historyScore;
double average = (double) totalScore / SUBJECT_COUNT; // 형변환 필요!
totalStudents++; // 클래스변수 업데이트
// 결과 출력
System.out.println("=== " + SCHOOL_NAME + " 성적표 ===");
System.out.println("이름: " + studentName);
System.out.println("나이: " + studentAge + "세");
System.out.println("학점: " + grade);
System.out.println("재학 여부: " + isEnrolled);
System.out.printf("총점: %d / %d%n", totalScore, MAX_SCORE * SUBJECT_COUNT);
System.out.printf("평균: %.2f점%n", average);
System.out.println("전체 학생 수: " + totalStudents + "명");
}
}
출력 결과:
=== 한국자바학교 성적표 ===
이름: 김자바
나이: 20세
학점: A
재학 여부: true
총점: 438 / 500
평균: 87.60점
전체 학생 수: 1명
10. 두 변수의 값 교환하기 (Swap)
프로그래밍에서 자주 쓰이는 패턴입니다. 임시 변수(temp)를 활용합니다.
public class SwapExample {
public static void main(String[] args) {
int x = 10;
int y = 20;
System.out.println("교환 전: x=" + x + ", y=" + y); // x=10, y=20
// 임시 변수를 활용한 교환
int temp = x; // temp = 10
x = y; // x = 20
y = temp; // y = 10
System.out.println("교환 후: x=" + x + ", y=" + y); // x=20, y=10
// 산술 연산을 이용한 교환 (temp 없이 - 정수형에서만 가능)
int a = 100, b = 200;
a = a + b; // a = 300
b = a - b; // b = 100
a = a - b; // a = 200
System.out.println("산술 교환: a=" + a + ", b=" + b); // a=200, b=100
}
}
고수 팁: XOR 비트 연산으로도 swap이 가능하지만, 가독성이 떨어지므로 실무에서는 temp 변수를 사용하는 방식이 표준입니다.
정리
- 변수 는 값을 저장하는 메모리 공간에 붙인 이름표
- 선언:
타입 변수명;/ 초기화:변수명 = 값; - 변수 종류: 지역변수(메서드 내), 인스턴스변수(클래스 내), 클래스변수(static)
- 스코프: 변수가 선언된 블록
{...}안에서만 유효 - final: 상수 선언, 값 변경 불가
- var: Java 10+, 지역변수 타입 추론
- 오버플로우: 범위 초과 시 반대편 값으로 순환 (에러가 아님!)