6.2 변수와 메서드 (Variables and Methods)
객체지향 패러다임에서 변수와 메서드는 클래스의 핵심 구성 요소입니다. 선언 위치에 따라 변수의 종류가 달라지며, 메서드는 특정 기능을 담당하는 코드 블록입니다.
1. 선언 위치에 따른 변수의 종류
변수는 선언된 위치에 따라 클래스 변수, 인스턴스 변수, 지역 변수 세 가지로 나뉩니다.
1.1 클래스 변수 (Class Variable / Static Variable)
- 선언: 클래스 영역에서
static키워드와 함께 선언 - 특징: 모든 인스턴스가 하나의 저장 공간을 공유
- 접근:
클래스명.변수명으로 인스턴스 없이도 접근 가능 - 생존: 프로그램 시작 시 생성 → 프로그램 종료 시 소멸
- 용도: 모든 객체가 공유해야 하는 값 (예: 이자율, 카드 크기)
1.2 인스턴스 변수 (Instance Variable)
- 선언: 클래스 영역에서
static없이 선언 - 특징: 인스턴스가 생성될 때마다 각각 독립적 으로 생성
- 접근:
참조변수.변수명으로 접근 - 생존: 인스턴스 생성 시 → 인스턴스 소멸(GC) 시
- 용도: 각 객체마다 다른 고유한 값 (예: 카드 무늬, 숫자)
1.3 지역 변수 (Local Variable)
- 선언: 메서드, 생성자, 블록(
{}) 내부에서 선언 - 특징: 해당 블록 내에서만 유효하고 블록 종료 시 자동 소멸
- 기본값: 자동 초기화 없음 — 반드시 직접 초기화 후 사용
- 저장: 스택(Stack) 메모리
public class Card {
// === 인스턴스 변수 (카드마다 다른 값) ===
String kind; // 무늬 (♠ ♥ ♦ ♣)
int number; // 숫자 (1~13)
// === 클래스 변수 (모든 카드에서 공유하는 값) ===
static int width = 100; // 폭 (mm)
static int height = 250; // 높이 (mm)
void printCard() {
// === 지역 변수 (이 메서드 안에서만 유효) ===
String display = kind + number;
System.out.println("카드: " + display);
// display는 메서드 종료와 함께 사라짐
}
}
public class CardTest {
public static void main(String[] args) {
// 클래스 변수: 인스턴스 없이도 클래스명으로 접근
System.out.println("카드 폭: " + Card.width); // 100
System.out.println("카드 높이: " + Card.height); // 250
Card c1 = new Card();
Card c2 = new Card();
c1.kind = "♠"; c1.number = 1;
c2.kind = "♥"; c2.number = 13;
// 인스턴스 변수: 각 객체마다 독립적
System.out.println(c1.kind + c1.number); // ♠1
System.out.println(c2.kind + c2.number); // ♥13
// 클래스 변수 변경 → 모든 인스턴스에 반영
Card.width = 120;
System.out.println(c1.width); // 120 (공유!)
System.out.println(c2.width); // 120 (공유!)
}
}
2. 메서드(Method)란?
메서드는 특정 작업을 수행하는 코드 블록 으로, 이름을 붙여 언제든지 재사용할 수 있습니다.
메서드를 사용하는 이유
- 재사용성: 한 번 작성한 코드를 여러 곳에서 반복 사용
- 중복 제거: 동일한 로직을 여러 번 작성하지 않아도 됨
- 구조화:
main메서드를 깔끔하게 유지하고 논리를 분리 - 유지보수: 한 메서드만 수정하면 해당 기능 전체가 갱신
3. 메서드 선언 문법
접근제어자 반환타입 메서드명(매개변수1, 매개변수2, ...) {
// 메서드 바디 (실행할 코드)
return 반환값; // 반환타입이 void면 생략 가능
}
// void 메서드: 반환값 없음
public void printGreeting(String name) {
System.out.println("안녕하세요, " + name + "님!");
// return; // void는 return 생략 가능 (또는 빈 return으로 조기 종료)
}
// 반환값이 있는 메서드
public int add(int a, int b) {
return a + b; // 반드시 int 값을 반환해야 함
}
// 조건부 반환 (모든 경로에 return이 있어야 함)
public String grade(int score) {
if (score >= 90) return "A";
else if (score >= 80) return "B";
else if (score >= 70) return "C";
else return "F";
}
4. 매개변수(Parameter) vs 인수(Argument)
두 용어는 종종 혼용되지만 엄밀히 다릅니다.
| 구분 | 설명 | 예시 |
|---|---|---|
| 매개변수 (Parameter) | 메서드 선언 시 괄호 안에 정의 | int add(int a, int b) — a, b가 매개변수 |
| 인수 (Argument) | 메서드 호출 시 전달하는 실제 값 | add(3, 5) — 3, 5가 인수 |
// 매개변수: x, y
public double calculateDistance(double x, double y) {
return Math.sqrt(x * x + y * y);
}
// 인수: 3.0, 4.0
double distance = calculateDistance(3.0, 4.0); // 5.0
5. 값에 의한 전달 (Call by Value)
자바는 항상 값에 의한 전달(Call by Value) 만 지원합니다. 단, 기본형과 참조형의 동작 방식이 다릅니다.
기본형(Primitive): 복사본 전달
public class CallByValueExample {
static void doubleValue(int x) {
x = x * 2; // 복사본을 변경 → 원본에 영향 없음
System.out.println("메서드 내부 x: " + x); // 20
}
public static void main(String[] args) {
int num = 10;
doubleValue(num);
System.out.println("메서드 호출 후 num: " + num); // 10 (변경 안 됨!)
}
}
참조형(Reference): 주소값 복사 전달
public class CallByReferenceExample {
static void changeBrand(Car car) {
car.brand = "기아"; // 주소를 통해 같은 객체의 필드 변경 → 원본에 영향!
}
static void replaceCar(Car car) {
car = new Car(); // 지역 변수 car가 새 객체를 가리킴 → 원본 변수에 영향 없음
car.brand = "BMW";
}
public static void main(String[] args) {
Car myCar = new Car();
myCar.brand = "현대";
changeBrand(myCar);
System.out.println(myCar.brand); // 기아 (필드 값이 변경됨)
replaceCar(myCar);
System.out.println(myCar.brand); // 기아 (참조 자체는 변경 안 됨)
}
}
참조형 전달 주의
참조형 인수를 메서드에 전달할 때, 메서드 내에서 객체의 필드를 변경하면 원본에 영향 을 줍니다. 하지만 참조 변수 자체를 다른 객체로 교체해도 원본 참조는 바뀌지 않습니다.
6. 재귀 메서드 (Recursive Method)
메서드가 자기 자신을 호출하는 방식을 재귀(Recursion) 라고 합니다.
팩토리얼 (Factorial)
public class RecursionExample {
// n! = n × (n-1) × ... × 2 × 1
static long factorial(int n) {
if (n <= 1) return 1; // 기저 조건 (Base Case) — 반드시 필요!
return n * factorial(n - 1); // 재귀 호출
}
// 피보나치 수열 (단순 재귀 — 성능 나쁨, 개념 이해용)
static int fibonacci(int n) {
if (n <= 1) return n;
return fibonacci(n - 1) + fibonacci(n - 2);
}
public static void main(String[] args) {
System.out.println("5! = " + factorial(5)); // 120
System.out.println("10! = " + factorial(10)); // 3628800
System.out.print("피보나치 수열: ");
for (int i = 0; i < 10; i++) {
System.out.print(fibonacci(i) + " ");
}
// 0 1 1 2 3 5 8 13 21 34
}
}
스택 오버플로우(StackOverflowError)
재귀 메서드는 호출할 때마다 스택 프레임이 쌓입니다. 기저 조건(Base Case) 이 없거나 잘못되면 무한 재귀가 발생하여 StackOverflowError가 던져집니다. 항상 종료 조건을 명확히 설정하세요.
재귀 vs 반복문
// 재귀 방식 (직관적이지만 메모리 사용 多)
static long factorialRecursive(int n) {
if (n <= 1) return 1;
return n * factorialRecursive(n - 1);
}
// 반복문 방식 (성능 우수, 대용량 데이터에 적합)
static long factorialIterative(int n) {
long result = 1;
for (int i = 2; i <= n; i++) {
result *= i;
}
return result;
}
7. static 메서드 vs 인스턴스 메서드
| 구분 | static 메서드 | 인스턴스 메서드 |
|---|---|---|
| 호출 방법 | 클래스명.메서드명() | 참조변수.메서드명() |
| 인스턴스 필요 | 불필요 | 필요 |
| 멤버 변수 접근 | 클래스 변수만 접근 가능 | 모든 멤버 변수 접근 가능 |
this 사용 | 사용 불가 | 사용 가능 |
| 용도 | 유틸리티, 변환, 계산 메서드 | 객체 상태를 사용/변경하는 메서드 |
public class MathUtils {
// static 메서드: 인스턴스 없이 호출 가능
public static int max(int a, int b) {
return a > b ? a : b;
}
public static double circleArea(double radius) {
return Math.PI * radius * radius;
}
}
public class Counter {
private int count = 0; // 인스턴스 변수
// 인스턴스 메서드: 인스턴스 변수를 사용
public void increment() {
count++;
}
public int getCount() {
return count;
}
}
public class MethodTypeExample {
public static void main(String[] args) {
// static 메서드: 인스턴스 없이 바로 호출
System.out.println(MathUtils.max(10, 20)); // 20
System.out.println(MathUtils.circleArea(5.0)); // 78.53...
// 인스턴스 메서드: 반드시 객체 생성 후 호출
Counter counter = new Counter();
counter.increment();
counter.increment();
counter.increment();
System.out.println(counter.getCount()); // 3
}
}
8. 실전 예제: 계산기 클래스
완전한 계산기 클래스를 구현합니다.
public class Calculator {
// 마지막 계산 결과를 기억 (인스턴스 변수)
private double lastResult;
// 연산 횟수 (클래스 변수 — 모든 Calculator 인스턴스가 공유)
private static int operationCount = 0;
// 더하기
public double add(double a, double b) {
lastResult = a + b;
operationCount++;
return lastResult;
}
// 빼기
public double subtract(double a, double b) {
lastResult = a - b;
operationCount++;
return lastResult;
}
// 곱하기
public double multiply(double a, double b) {
lastResult = a * b;
operationCount++;
return lastResult;
}
// 나누기
public double divide(double a, double b) {
if (b == 0) {
System.out.println("오류: 0으로 나눌 수 없습니다!");
return Double.NaN;
}
lastResult = a / b;
operationCount++;
return lastResult;
}
// 나머지
public double modulo(double a, double b) {
if (b == 0) {
System.out.println("오류: 0으로 나눌 수 없습니다!");
return Double.NaN;
}
lastResult = a % b;
operationCount++;
return lastResult;
}
// 거듭제곱
public double power(double base, double exponent) {
lastResult = Math.pow(base, exponent);
operationCount++;
return lastResult;
}
// 제곱근
public double sqrt(double a) {
if (a < 0) {
System.out.println("오류: 음수의 제곱근은 계산할 수 없습니다!");
return Double.NaN;
}
lastResult = Math.sqrt(a);
operationCount++;
return lastResult;
}
// 마지막 결과 반환
public double getLastResult() {
return lastResult;
}
// 총 연산 횟수 반환 (static 메서드)
public static int getOperationCount() {
return operationCount;
}
// 계산기 초기화
public void reset() {
lastResult = 0;
System.out.println("계산기가 초기화되었습니다.");
}
}
public class CalculatorTest {
public static void main(String[] args) {
Calculator calc = new Calculator();
System.out.println("=== 계산기 테스트 ===");
System.out.printf("10 + 5 = %.1f%n", calc.add(10, 5)); // 15.0
System.out.printf("10 - 3 = %.1f%n", calc.subtract(10, 3)); // 7.0
System.out.printf("4 × 6 = %.1f%n", calc.multiply(4, 6)); // 24.0
System.out.printf("15 ÷ 4 = %.2f%n", calc.divide(15, 4)); // 3.75
System.out.printf("17 %% 5 = %.1f%n", calc.modulo(17, 5)); // 2.0
System.out.printf("2^10 = %.0f%n", calc.power(2, 10)); // 1024
System.out.printf("√144 = %.1f%n", calc.sqrt(144)); // 12.0
System.out.println("마지막 결과: " + calc.getLastResult()); // 12.0
System.out.println("총 연산 횟수: " + Calculator.getOperationCount()); // 7
// 예외 상황 테스트
calc.divide(10, 0); // 오류 메시지 출력
}
}
실행 결과:
=== 계산기 테스트 ===
10 + 5 = 15.0
10 - 3 = 7.0
4 × 6 = 24.0
15 ÷ 4 = 3.75
17 % 5 = 2.0
2^10 = 1024
√144 = 12.0
마지막 결과: 12.0
총 연산 횟수: 7
오류: 0으로 나눌 수 없습니다!
9. 메서드 작성 원칙
좋은 메서드 작성 가이드
- 단일 책임: 하나의 메서드는 하나의 일만 합니다.
- 짧게: 가능하면 20줄 이내로 작성합니다.
- 명확한 이름: 동사로 시작하고 기능이 명확히 드러나게 짓습니다. (
calculateTax(),getUserById()) - 매개변수 최소화: 매개변수가 많으면 객체로 묶는 것을 고려합니다.
- 예외 처리: 입력값 유효성을 검사하여 안전하게 처리합니다.
요약
| 개념 | 핵심 내용 |
|---|---|
| 클래스 변수 | static 선언, 모든 인스턴스가 공유 |
| 인스턴스 변수 | 각 객체별 독립적, 힙에 저장 |
| 지역 변수 | 메서드/블록 내 선언, 자동 초기화 없음 |
| void 메서드 | 반환값 없음 |
| Call by Value | 기본형은 값 복사, 참조형은 주소 복사 |
| 재귀 | 기저 조건 필수, 스택 오버플로우 주의 |
| static 메서드 | 인스턴스 없이 호출 가능, 클래스 변수만 접근 |
| 인스턴스 메서드 | 객체 생성 후 호출, 모든 멤버 변수 접근 |