아래의 내용은 java_grammer 레파지토리 C02MethodClass 디렉터리에 저장되어있는 내용을 정리하였다.
먼저, 클래스와 객체의 관계를 간단하게 구분하자면 다음과 같다.
즉, "클래스"는 설계도, "객체"는 완성품이라고 볼 수 있다.
클래스를 정의해두면, 그 클래스를 기반으로 여러 개의 객체(인스턴스)를 만들어 각각의 고유한 데이터 공간을 사용할 수 있다.
하나의 클래스는 다음과 같은 요소들로 이루어진다.
아래 예제에서는 Calculator 클래스의 클래스 메서드를 사용했다.
int sum_result = Calculator.sum(10, 20);
System.out.println("Calculator 10 + 20 ===" + sum_result);
Calculator 클래스의 sum() 메서드는 static으로 선언되어 있기 때문에, 객체를 생성하지 않고도
Calculator.sum() 형태로 직접 호출이 가능하다.
이제 Calculator 클래스의 누적 합(sumAcc) 기능을 예시로 보자.
Calculator.sumAcc(20);
System.out.println("A부서 1월 매출 ===" + Calculator.total); // 20
Calculator.sumAcc(30);
System.out.println("A부서 2월 매출 ===" + Calculator.total); // 40
Calculator.sumAcc(40);
System.out.println("A부서 3월 매출 ===" + Calculator.total); // 90
여기까지는 문제가 없어 보이지만, B부서의 매출을 계산하려 할 때 문제가 생긴다.
Calculator.sumAcc(10);
System.out.println("B부서 1월 매출 ===" + Calculator.total); // 100 (A와 누적됨)
Calculator.sumAcc(30);
System.out.println("B부서 2월 매출 ===" + Calculator.total); // 130 (A+B 누적)
두 부서의 합산 결과가 하나로 합쳐져 버렸다.
그 이유는 Calculator.total 변수가 클래스 변수(static 변수)이기 때문이다.
클래스 변수는 클래스 전체에서 하나의 공간만 공유하기 때문에,
A부서와 B부서가 같은 total을 참조하게 된다.
결국, 클래스 변수와 클래스 메서드만으로는 부서별 고유한 데이터를 관리할 수 없다.
이 문제를 해결하기 위해 객체를 활용할 수 있다.
클래스를 기반으로 각 부서를 고유 객체로 생성하면 서로 다른 메모리 공간을 사용할 수 있다.
CalculatorForInstance calA = new CalculatorForInstance();
calA.sumAcc(20);
calA.sumAcc(30);
calA.sumAcc(40);
System.out.println("A부서 매출 누적액 ===" + calA.total);
CalculatorForInstance calB = new CalculatorForInstance();
calB.sumAcc(10);
calB.sumAcc(30);
calB.sumAcc(20);
System.out.println("B부서 매출 누적액 ===" + calB.total);
이 경우, calA와 calB는 서로 독립적인 힙 메모리 공간을 가진다.
따라서 한 쪽의 total 값이 변경되어도 다른 객체에는 전혀 영향을 주지 않는다.
| 구분 | static 키워드 | 호출 방식 | 특징 |
|---|---|---|---|
| 클래스 메서드 | 있음 | 클래스명.메서드명() | 객체 상태와 무관하게, 주로 도우미 역할 |
| 객체 메서드 | 없음 | 객체명.메서드명() | 객체 내부 상태(필드)에 접근 및 관리 |
일반적으로 데이터의 누적, 고유 상태 관리 등이 필요한 경우에는 객체 메서드를 사용하고,
단순 계산이나 유틸리티성 기능에는 클래스 메서드를 사용하는 것이 적절하다.
대표적인 클래스 메서드 예시는 Math 클래스에 있다.
System.out.println(Math.min(10, 20));
Math 클래스는 모든 메서드가 static으로 선언되어 있으며,
객체를 생성하지 않고 바로 사용할 수 있다.
이번에는 ArrayList를 예로 들어보자.
List<Integer> myList = new ArrayList<>();
myList.add(10);
myList.add(20);
System.out.println(myList);
여기서 add()는 객체 메서드다.
myList라는 객체 내에 들어있는 데이터 공간(리스트)에 직접 접근해서 값을 추가한다.
즉, 객체별로 고유의 상태를 가진다.
class Calculator {
static int total = 0; // 클래스 변수
public static int sum(int a, int b) {
return a + b;
}
public static int sumAcc(int a) {
total += a;
return total;
}
}
static이 붙은 total, sum, sumAcc라는 클래스 변수/메서드는 모두 클래스 단위에서 공유된다. class CalculatorForInstance {
int total = 0; // 객체 변수
static public int sum(int a, int b) {
return a + b;
}
public int sumAcc(int total) {
this.total += total;
return this.total;
}
}
total은 객체마다 각각의 힙 영역에 저장된다. this.total을 통해 각 객체 자신을 가리킨다. static이 붙은 클래스 변수와 메서드는 공용 공간을 사용하기 때문에 상태가 공유된다.