이번주가 CS 스터디의 마지막 Java 주제로, 추가적으로 알면 좋을 내용들을 알아보았다.
그럼 자바는 Call by Value일까, Call by Reference일까?
먼저 Call by Value와 Call by Reference에 대해서 알아보겠다.
메서드를 호출할 때, 인자로 전달되는 변수의 값 자체를 복사하여 전달하는 방식이다.
전달되는 변수가 int, boolean, double과 같은 기본 타입(Primitive Type)일 경우, 변수가 가진 실제 데이터 값이 복사되어 전달된다. 따라서 메서드 내에서 복사된 값(매개변수)을 변경하더라도, 원본 변수에는 아무런 영향이 없다.
public class CallByValueExample {
// 메서드: 전달받은 정수 값을 100으로 변경
public static void changeValue(int number) {
System.out.println(" [메서드 내부 시작] 매개변수 number: " + number); // 10
number = 100; // 매개변수 값 변경 (원본이 아닌 복사된 값 변경)
System.out.println(" [메서드 내부 종료] 변경된 number: " + number); // 100
}
public static void main(String[] args) {
int original = 10;
System.out.println("호출 전: original = " + original); // 10
changeValue(original); // original의 값 '10'이 복사되어 전달됨
System.out.println("호출 후: original = " + original); // 결과: 10
}
}
반면 변수가 객체와 배열 같은 참조 타입(Reference Type)일 경우, 변수가 가진 객체의 주소 값이 복사되어 전달된다. 즉, 원본 변수와 매개변수가 모두 동일한 메모리 주소를 가리키게 된다. 메서드 내에서 이 주소에 접근하여 객체의 내부 내용을 변경하면, 원본 객체의 내용도 변경된다. 하지만 매개변수에 새로운 객체를 할당하여 주소 값을 변경하더라도, 원본 변수는 여전히 기존 객체를 가리키고 있으므로 원본 변수 자체는 변경되지 않는다.
class Data {
int value;
public Data(int v) {
this.value = v;
}
}
public class CallByValueReferenceExample {
// 1. 객체의 '내용'을 변경하는 메서드 (원본 객체에 영향 O)
public static void modifyObjectContent(Data data) {
// data는 원본 객체의 '주소 복사본'을 가지고 있음
data.value = 50; // 주소를 따라가 객체 내부 필드 값 변경
}
// 2. 객체 '자체'(주소)를 변경하는 메서드 (원본 변수에 영향 X)
public static void changeObjectReference(Data data) {
data = new Data(999); // 매개변수 data에 '새로운 객체의 주소' 할당
// 이 변경은 복사된 주소 값만 변경하며, 원본 변수에는 영향을 미치지 않음
System.out.println(" [메서드 내부] 새 객체 value: " + data.value); // 999
}
public static void main(String[] args) {
Data myData = new Data(10);
System.out.println("초기 값: myData.value = " + myData.value); // 10
// Case 1: 객체 '내용' 변경 (Call by Value이지만, 복사된 주소로 객체 내용 변경)
modifyObjectContent(myData);
System.out.println("내용 변경 후: myData.value = " + myData.value); // 결과: 50 (객체 내용이 변경됨)
// Case 2: 객체 '참조(주소)' 변경 시도 (Call by Value임을 증명)
changeObjectReference(myData);
System.out.println("참조 변경 시도 후: myData.value = " + myData.value); // 결과: 50 (원본 변수는 바뀌지 않음)
}
}
메서드를 호출할 때, 인자로 전달하는 변수의 메모리 주소(참조 값) 자체를 복사하는 것이 아니라, 원본 변수 자체를 메서드에 전달하는 방식이다.
메서드의 매개변수는 인자로 전달된 원본 변수의 별칭(Alias) 또는 참조가 된다. 즉, 매개변수와 원본 변수가 동일한 메모리 공간을 공유한다.
또한 메서드 내에서 매개변수를 변경하면, 이 변경이 곧 원본 변수의 값게 직접 반영된다.
큰 데이터 구조를 복사할 필요 없이 주소만 사용하여 접근하므로, 데이터 복사 비용을 절약할 수 있어 효율적이다.
C++ 같은 언어가 Call by Reference를 사용하며, swap 함수를 예시로 들어보겠다.
a와 b의 메모리 주소를 swap 함수로 전달x와 y는 전달받은 주소에 직접 접근x와 y의 값을 바꾸는 것은 곧 원본 변수 a와 b의 메모리 공간에 있는 값 자체를 바꾸는 행위가 됨| 구분 | Call by Reference 시 동작 |
|---|---|
| 호출 전 | int a = 10;, int b = 20; |
| 함수 호출 | swap(&a, &b) (a와 b의 주소를 전달) |
| 함수 내부 | 매개변수 x와 y가 원본 a, b를 참조 |
| 함수 실행 | x와 y의 값을 교환 -> 원본 a와 b의 값이 변경됨 |
| 호출 후 | a는 20, b는 10이 됨 |
자바는 오직 Call by Value 방식만을 사용한다.
자바의 모든 인자 전달은 값의 복사를 통해 이루어진다. '값'이 기본 타입인지 참조 타입인지에 따라 동작 방식에 차이가 있어 Call by Reference와 헷갈릴 수 있다.
Call by Reference와 결정적으로 다른 점은, 자바에서는 매개변수에 새로운 객체(새로운 주소)를 할당해도 원본 변수에는 영향을 주지 않는다는 것이다.
System.currentTimeMillis() vs System.nanoTime()두 메서드는 모두 시간을 측정하는 데 사용되지만, 측정 기준과 용도, 정밀도 면에서 큰 차이가 있다.
| 특징 | System.currentTimeMillis() | System.nanoTime() |
|---|---|---|
| 단위 | 밀리초(milliseconds, ms) | 나노초(nanoseconds, ns) |
| 기준 시점 | 표준 시계 - UTC 1970년 1월 1일 00:00:00부터 경과한 시간 | 임의의 시점(JVM 시작 등) - 절대적인 날짜/시간과는 관련 없는 임의의 기준 시점 |
| 용도 | 절대적인 현재 시간이 필요할 때 예: 로그 기록, 파일 타임스탬프, UUID 생성, 실제 날짜/시간 표시 | 시간 간격 측정이 필요할 때 예: 코드 성능 측정, 벤치마킹, 경과 시간 계산 |
| 영향 | 시스템 시계(OS 시간) 변경(시간 수정, 서머타임)에 영향을 받음 값이 갑자기 점프하거나 뒤로 돌아갈 수 있음 | 시스템 시계 변경에 영향을 받지 않음 단조 증가(Monotonic)가 보장되어 성능 측정에 적합 |
| 정밀도 | 밀리초 단위의 정확도 | 밀리초보다 훨씬 높은 정밀도 |
System.currentTimeMillis()long 값으로 반환System.nanoTime()long 값으로 반환currentTimeMilis()보다 훨씬 더 정교한 시간 간격 측정에 사용됨1️⃣ 시작 시간 기록 : long startTime = System.nanoTime()
2️⃣ 작업 실행
3️⃣ 종료 시간 기록 : long endTime = System.nanoTime()
4️⃣ 경과 시간 계산 : long elapsedTime = endTime - startTime;