📟java/개발 이론정리

레퍼런스와 스태틱

하얀성 2023. 10. 1. 16:02

레퍼런스(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에서 가져올 수 있으므로
편리한 이점이 있습니다.

대신 클래스 메소드는
객체 생성없이 클래스명을 통해 호출할 수 있는 장점이 있죠

따라서
특정 객체에 의존적인 동작은 인스턴스 메소드로,
아니라면 클래스 메소드로 만드는 것이 좋습니다.

더 자세한 내용은
“이펙티브자바”, “클린코드”, “리팩터링”
등의 서적을 학습해보세요


출처: 그림으로 배우는 자바 객체지향.