레퍼런스(reference)는 자바에서 객체를 가리키는 데 사용되는 변수입니다. 레퍼런스 변수는 메모리 상의 객체의 위치를 가리키며, 이를 통해 객체에 접근하고 해당 객체의 메소드를 호출할 수 있습니다.
클래스 타입 혹은 레퍼런스 타입은 기본 타입과 다르게
앞글자를 대문자로 쓴다.
클래스 내에서 메소드의 사용 순서는 선언의 순서와 상관없이 사용 가능
class Employee {
// 필드
String name; // 이름
int[] hours; // 요일별 일한 시간
// 생성자
Employee(String str, int[] arr) {
name = str;
hours = arr;
}
// 메소드
void printTotalHours() {
System.out.printf("%s -> %d 시간\n", name, totalHours());
}
int totalHours() {
int sum = 0;
for (int i = 0; i < hours.length; i++) {
sum += hours[i];
}
return sum;
}
}
totalHours() 메소드는 printTotalHours() 메소드 내에서 호출되는데, 이는 자바에서는 메소드의 선언 순서가 호출 가능 여부에 영향을 주지 않기 때문입니다. 즉, 메소드가 클래스 내에서 어디에 선언되어 있더라도, 다른 메소드 내에서 호출할 수 있습니다.
자바 컴파일러는 클래스를 컴파일할 때, 클래스의 모든 메소드를 처리하며, 이 때 각 메소드의 시그니처(이름, 매개변수 타입, 반환 타입)를 확인합니다. 따라서 한 메소드가 다른 메소드보다 먼저 선언되어 있더라도, 컴파일러는 모든 메소드의 시그니처를 알고 있어, 어떤 메소드든지 다른 메소드 내에서 호출할 수 있습니다.
이러한 이유로, totalHours() 메소드는 printTotalHours() 메소드 내에서 호출 가능합니다.
클래스 변수 vs 인스턴스 변수
클래스 변수란, static 키워드가 필드에 적용된 것을 말합니다. 이와 반면, non-static 필드(static이 없는 일반적인 필드)는 인스턴스 변수라 합니다.
static 변수 (클래스 변수):
클래스에 속한 변수로서, 클래스의 모든 인스턴스들이 공유합니다.
객체를 생성할 때마다 static 변수의 값이 변경되면, 그 변경은 모든 객체에게 반영됩니다. 따라서, static 변수는 객체 생성시마다 값이 누적될 수 있습니다.
+ 클래스 변수 이름 그대로 객체 선언 없이 접근가능.
아래의 경우, Example 클래스의 각 인스턴스는 동일한 count 변수를 공유합니다. 따라서 객체를 생성할 때마다 count 값이 1씩 증가하게 됩니다.
Example ex1 = new Example(); // count = 1
Example ex2 = new Example(); // count = 2
Example ex3 = new Example(); // count = 3
class Example {
static int count = 0;
Example() {
count++;
}
}
static이 아닌 변수 (인스턴스 변수):
객체에 속한 변수로서, 각 인스턴스마다 독립적인 값을 가집니다.
인스턴스 변수는 객체가 생성될 때마다 초기화됩니다. 생성자에서 이 변수의 값을 변경하면, 그 변경은 해당 객체에만 영향을 줍니다.
아래의 경우, 각 객체는 독립적인 count 값을 가지게 됩니다. 객체가 생성될 때마다 생성자가 호출되어 해당 객체의 count 값이 1씩 증가하지만, 다른 객체의 count 값에는 영향을 주지 않습니다.
Example ex1 = new Example(); // ex1의 count = 1
Example ex2 = new Example(); // ex2의 count = 1
Example ex3 = new Example(); // ex3의 count = 1
class Example {
int count = 0;
Example() {
count++;
}
}
클래스 변수는 변수이름 그대로 객체 선언 없이 접근가능. (Galaxy.count)
public class GalaxyTest {
public static void main (String[] args) {
// 5칸 크기의 객체 배열 생성
Galaxy[] phones = new Galaxy[5];
// 배열 속 객체 할당
for (int i = 0; i < phones.length; i++) {
phones[i] = new Galaxy();
}
// 모든 객체 정보 출력
for (int i = 0; i < phones.length; i++) {
phones[i].print();
}
System.out.println("=========================");
/* 2. Galaxy 객체의 개수를 클래스 변수로 출력하세요.*/
System.out.printf("Galaxy 객체의 개수: %d",Galaxy.count);
}
}
// 클래스
class Galaxy {
// 필드(인스턴스 변수)
String serialNum; // 일련번호
/* 1. 해당 필드를 클래스 변수화 하세요. */
static int count = 0;
// 생성자
Galaxy() {
count++;
char c = randomAlphabet(); // A ~ Z 중 택1
serialNum = String.format("%c-%d", c, count);
}
// 메소드
char randomAlphabet() {
return (char) ('A' + Math.random() * 26); // A to Z
}
void print() {
System.out.printf("Galaxy { serialNum: %s }\n", serialNum);
}
}
질문1. 왜 Galaxy.count는 메소드도 아닌데 이렇게 사용이 가능한지?
count는 Galaxy 클래스 내에서 static 키워드로 선언된 변수입니다. static으로 선언된 변수(클래스 변수)는 해당 클래스의 모든 인스턴스(객체)에 공유되며, 클래스 이름을 통해 직접 접근할 수 있습니다. 따라서, Galaxy.count 형식으로 직접 접근하는 것이 가능합니다.
그럼 count가 인스턴스 변수면 저렇게 사용이 불가능한거야?
네, 맞습니다. 인스턴스 변수는 객체가 생성된 후에만 접근이 가능합니다. 인스턴스 변수에 접근하려면, 먼저 그 변수를 포함하고 있는 클래스의 객체를 생성해야 하며, 그 객체를 통해서만 접근할 수 있습니다.
예를 들어, count가 인스턴스 변수라면, 다음과 같이 객체를 생성한 후, 그 객체를 통해 접근해야 합니다.
Galaxy phone = new Galaxy();
System.out.println(phone.count); // 인스턴스 변수 count에 접근
아래처럼 배열인덱스마다 생성자를 할당해주면서 값을 초기화 시켜줬기 때문에
count++가 계속 발동하여, Galaxy.count 값이 5로 출력되는 것이다.
// 배열 속 객체 할당
for (int i = 0; i < phones.length; i++) {
phones[i] = new Galaxy();
}
클래스 메소드 호출
클래스 메소드 vs 인스턴스 메소드
클래스 메소드란, static 적용된 메소드입니다. 이와 반면 static이 없으면 인스턴스 메소드가 됩니다.
메소드 수행의 주체?
이 두 메소드의 차이는 “주체 객체가 있는가?”의 여부입니다.
클래스 메소드의 경우, 주체 객체 없이 클래스명으로 호출됩니다.
// Math의 클래스 메소드 random() 호출 예
double rand = Math.random();
반면 인스턴스 메소드는 먼저 주체 객체를 생성해야만 하고, 이를 통해 메소드 호출이 이루어집니다.
// 주체 객체 생성
Hero h1 = new Hero("닥터 스트레인지", 80);
// 주체 객체를 통한 인스턴스 메소드 호출
h1.teleport();
EX) 클래스 메소드 사용 코드
public class CircleTest {
public static void main(String[] args) {
// 객체 생성
Circle c1 = new Circle(0, 0, 3); // 중심(0,0) - 반지름3
Circle c2 = new Circle(2, 3, 4); // 중심(2,3) - 반지름4
/* 1. 클래스 메소드를 호출하여 원의 넓이를 구하세요. */
double area1 = Circle.area(c1);
// 주체 객체 c1 선언없이 메소드를 불러서 사용 c1.area(c1) => x
double area2 = Circle.area(c2);
// 주체 객체 c2 선언없이 메소드를 불러서 사용 c2.area(c2) => x
// 결과 출력
System.out.printf("%s => 넓이: %.2f\n", c1.toStr(), area1);
System.out.printf("%s => 넓이: %.2f\n", c2.toStr(), area2);
}
}
class Circle {
// 필드
int x; // 원의 중심 - X 좌표
int y; // 원의 중심 - Y 좌표
int r; // 반지름
// 생성자
Circle(int centerX, int centerY, int radius) {
x = centerX;
y = centerY;
r = radius;
}
// 인스턴스 메소드
String toStr() {
return String.format("Circle { 중심: (%d, %d), 반지름: %d }", x, y, r);
}
// 클래스 메소드
static double area(Circle c) {
// 원의 넓이 = 원주율 x 반지름 x 반지름
return Math.PI * c.r * c.r;
}
}
Ex2) 인스턴스 메소드 사용 코드
public class CircleTest {
public static void main(String[] args) {
// 객체 생성
Circle c1 = new Circle(0, 0, 3); // 중심(0,0) - 반지름3
Circle c2 = new Circle(2, 3, 4); // 중심(2,3) - 반지름4
/* 1. 인스턴스 메소드를 호출하여 원의 넓이를 구하세요. */
double area1 = c1.area(); // 인스턴스 메소드 호출
double area2 = c2.area(); // 인스턴스 메소드 호출
// 결과 출력
System.out.printf("%s => 넓이: %.2f\n", c1.toStr(), area1);
System.out.printf("%s => 넓이: %.2f\n", c2.toStr(), area2);
}
}
class Circle {
// 필드
int x; // 원의 중심 - X 좌표
int y; // 원의 중심 - Y 좌표
int r; // 반지름
// 생성자
Circle(int centerX, int centerY, int radius) {
x = centerX;
y = centerY;
r = radius;
}
// 인스턴스 메소드
String toStr() {
return String.format("Circle { 중심: (%d, %d), 반지름: %d }", x, y, r);
}
// 인스턴스 메소드로 변경된 area 메소드
double area() {
// 원의 넓이 = 원주율 x 반지름 x 반지름
return Math.PI * r * r; // 인스턴스 변수에 직접 접근
}
}
이 경우에는 static을 사용하는 것이 더 나을 수 있습니다. area 메소드는 Circle 클래스의 특정 인스턴스에 의존하지 않고, 전달된 Circle 객체의 반지름을 이용해 계산을 하기 때문입니다. 즉, 이 메소드는 어떤 Circle 객체에도 공통적으로 적용될 수 있으며, 인스턴스의 상태를 변경하지 않습니다.
static 메소드로 선언함으로써, Circle의 인스턴스 없이도 이 메소드를 호출할 수 있게 됩니다. 이는 코드의 명확성과 가독성 측면에서 이점이 있습니다.
하지만 둘 중 어느것이 나은지는 설계할 때의 필요성에 따라 다를 수 있습니다.
sehongpark님의 답변
네, 맞습니다.
인스턴스 메소드의 경우
주체객체 정보를 this에서 가져올 수 있으므로
편리한 이점이 있습니다.
대신 클래스 메소드는
객체 생성없이 클래스명을 통해 호출할 수 있는 장점이 있죠
따라서
특정 객체에 의존적인 동작은 인스턴스 메소드로,
아니라면 클래스 메소드로 만드는 것이 좋습니다.
더 자세한 내용은
“이펙티브자바”, “클린코드”, “리팩터링”
등의 서적을 학습해보세요
출처: 그림으로 배우는 자바 객체지향.
'📟java > 개발 이론정리' 카테고리의 다른 글
this || getter(값 불러오기. return사용), setter(값 변경 ,this사용) (0) | 2023.10.02 |
---|---|
접근제어자 (0) | 2023.10.01 |
생성자 , String.format("포맷 문자열", 인자1, 인자2, ...) (0) | 2023.09.30 |
객체지향 기초 정리. (0) | 2023.09.30 |
클론코딩과 포폴 (0) | 2023.04.02 |