본문 바로가기

Backend/Java

[JAVA] static과 인스턴스(instance)

 

 

 


 

클래스로 알아보는 스태틱(static)과 인스턴스(instance)

국비지원수업 13일차

 


 

 

1. static 키워드


static

- 멤버변수, 멤버 메소드에 사용할 수 있는 키워드로

- 정적변수(클래스의 변수), 정적 메소드(클래스의 메소드)를 선언할 때 사용한다.

 

 

자바 프로그램의 구동 과정

JVM(Java Vertual Machine): 어디서든 자바 프로그램이 잘 실행되도록 하는 가상머신

 

JVM에서 프로그램이 시작되면...

1. 실행할 모든 클래스 중 static이 붙은 변수와 메소드를 수집한다.

2. 해당 변수와 메소드들은 프로그램 시작 전에 메모리에 적재한다.

static이 붙지 않은 변수와 메소드는 생성 명령이 실행될 때 메모리에 적재된다.

3. 메모리에 로딩된 static 메소드 중, main 이름을 검색한다.

4. main 메소드가 검색되었다면 검색된 main 메소드를 호출하여 프로그램을 시작한다.

 

 

 

2. static과 인스턴스



2-1. static과 인스턴스의 특징

앞서 기술한 static의 특징에 따라 static과 인스턴스를 비교하자면....

 

static 변수와 메소드의 특징

객체 생성과 무관하게 프로그램 시작 전부터 메모리에 자리를 확보하고 실행되기를 기다린다.

static int number = 200; // 스태틱 변수

 

 

static이 아닌 변수와 메소드의 특징 (=인스턴스)

Student std = new Student() 과 같은 객체 생성이 있어야(객체 생성에 의존) 그 안에 static이 아닌 변수와 메소드가 생성된다.

int a; // 이 명령이 실행될 때 변수가 생성되고 메모리에 적재 (동적변수)

 

라고 할 수 있다.

 

 


2-2. static 변수와 인스턴스 변수의 사용법

class StaticA{
    int num = 100; // 인스턴스 변수 : 인스턴스가 생성돼야 덩달아 생성되는 변수 (동적변수)
    static int number = 200; // 스태틱 변수
    private static int numVar = 300;
}

StaticA 클래스

 

1. 인스턴스 변수 num

2. static 변수 number

3. private로 보호된 static 변수 numVar

 

위와 같은 StaticA 클래스가 있다고 했을 때, main에서는...

 

 

1. static 멤버변수는 객체 생성과 무관하게 변수를 사용할 수 있다.

System.out.println("staticA 클래스의 static 변수 number : " + StaticA.number);

 

멤버변수는 변수 앞에 객체이름과 .(점)을 붙여서 사용해야하지만 ( class.number )

number는 static 변수이기에 객체를 생성하지 않아도 메모리에 적재되어 있다.

(객체를 생성하지 않고도 사용할 수 있는 클래스의 멤버변수다.)

따라서 붙여줄 객체가 없으므로 대신 클래스 이름을 써준다.

 

덧붙이자면,

객체 생성과 무관하게 생성된 변수이기 때문에 객체가 몇개 생성되는 static으로 만들어진 변수는 딱 하나만 존재한다.

 

 

2. 인스턴스 멤버 변수를 사용하기 위해서는 객체 생성이 반드시 필요하다.

System.out.println("StaticA 클래스의 인스턴스 멤버변수 num : " + num); // 에러  num cannot be resolved to a variable

 

static 변수처럼 사용하려고 한다면, 애초에 '선언되지 않았다'는 오류가 뜬다.

 

StaticA s1 = new StaticA();
s1.num = 100;
System.out.println("s1 인스턴스 멤버변수 num : " + s1.num);
System.out.println("---------------------------------");

StaticA s2 = new StaticA();
s2.num = 200;
System.out.println("s2 인스턴스 멤버변수 num : " + s2.num);
System.out.println("---------------------------------");

StaticA s3 = new StaticA();
s3.num = 200;
System.out.println("s3 인스턴스 멤버변수 num : " + s3.num);
System.out.println("---------------------------------");

 

따라서 StaticA의 객체를 생성해야 (객체이름).(변수이름) 의 형태로 값을 참조할 수 있다.

 

 

3. static 변수값을 변경할 수도 있다.

System.out.println("StaticA 클래스의 static 변수 number : " + StaticA.number);
StaticA.number = StaticA.number + 50; // 스태틱 변수값의 변경
System.out.println("StaticA 클래스의 static 멤버변수 값 : " + StaticA.number);
System.out.println("---------------------------------");

 

 

4. 만약 static 변수가 private로 보호된다면?

(클래스이름).(변수이름) 으로 값을 불러올 수 있는 static 변수가 외부 클래스에서 private로 보호된다면 앞선 방법을 이용했을 때 오류가 발생한다.

// StaticA.numVar = 500; // The field StaticA.numVar is not visible

 

이처럼 private로 보호된 static 변수는 getter와 setter 메소드를 public static으로 따로 제작해서 값을 저장하거나 얻어야 한다.

 

private static int numVar = 300;

public static void setNumVar(int n) {
    numVar = n;
}

public static int getNumVar() {
    return numVar;
}

StaticA 클래스

StaticA.setNumVar(800); // private static 변수에 값을 저장하는 static 메소드 호출
int numVarValue = StaticA.getNumVar();
System.out.println("StaticA 클래스의 private static 멤버변수 값 : " + numVarValue);

main 클래스

 

 

 


2-3. 객체 생성에 따른 변수 생성

앞서 잠깐 언급했지만..

static 변수는 객체 생성과 무관하게 생성된 변수이기 때문에 객체가 몇개 생성되는 static으로 만들어진 변수는 딱 하나만 존재한다.

 

간단한 코드를 통해 알아보겠다.

 

class StaticB{
    int num; // StaticB의 객체를 만들때마다 인스턴스 변수는 매번 새로 변수가 생긴다고 보면 됨
    static int count = 0; // 한 공간에서 일관되게 변경

    StaticB(){
        count++;
        num = count;
    }
}

StaticB 클래스

 

staticB 클래스에 인스턴스 변수 num과 static 변수 count를 선언하고, 생성자에서 count값을 증가시키기로 했다.

 

count가 증가했다는 말은 '생성자가 호출' 즉, 객체가 생성되었음을 의미하고, 이를 num값에 대입함으로서

1. static 변수인 count는 한번만 생성되고

2. 프로그램 종료시까지 변수가 갖는 값이 일관되게 유지, 관리 변경됨을 나타낸다.

 

public static void main(String[] args) {

    StaticB b1 = new StaticB();
    System.out.printf("b1.num = %d, StaticB.count = %d\n", b1.num, StaticB.count);

    StaticB b2 = new StaticB();
    System.out.printf("b2.num = %d, StaticB.count = %d\n", b2.num, StaticB.count);

    StaticB b3 = new StaticB();
    System.out.printf("b3.num = %d, StaticB.count = %d\n", b3.num, StaticB.count);

    System.out.println("현재 생성된 객체의 갯수 : " + StaticB.count);
}

 

 

참고로, 인스턴스 변수는 객체를 생성할 때마다 만들어진다고 생각하면 된다.

 

 

 

3. static과 인스턴스의 관계



3-1. 관계

static과 인스턴스 변수/메소드의 관계성을 모두 이야기 하자면

 

  O X
static 변수 static 변수값으로 초기화 인스턴스 변수값으로 초기화
인스턴스 변수 static 변수값으로 초기화
인스턴스 변수값으로 초기화
 
static 메소드 static 변수 사용 인스턴스 변수 사용
인스턴스 메소드 static 변수 사용
인스턴스 변수 사용
 
static 메소드 안에 static 메소드 호출 인스턴스 메소드 호출
인스턴스 메소드 안에 static 메소드 호출
인스턴스 메소드 호출
 

 

다음 표와 같이 정리할 수 있다.

 

보아하니 static에(서) 인스턴스 뭐시기를 대입하거나, 사용하거나, 호출하려고 하면 안 된다는 이야기다.

 

 

사실 이 관계성은 앞서 얘기했던 static과 인스턴스의 특징을 대입하면 아주 쉬운 문제다.

 

int iv = 10; // 인스턴스 변수 - 객체가 생성될 때 생성
static int sv = 20; // 스태틱 변수 - 프로그램이 시작되기 전에 생성

 

인스턴스는 객체가 생성될 때 같이 생성되는 존재지만 static은 프로그램이 시작하기 전에 메모리에 적재되어 있다.

 

int iv2 = sv; // 인스턴스 변수는 스태틱 변수값으로 값의 초기화가 가능
// static int sv2 = iv; // 에러  Cannot make a static reference to the non-static field iv

 

프로그램이 시작하기도 전에 존재하는 static 변수는 인스턴스 변수가 생성되는 시점보다 빠를 수 밖에 없다.

따라서 인스턴스 변수를 스태틱 변수값으로 초기화하는 것이 가능하다. 어차피 static은 선언 되어있으니까.

 

다만, static에 인스턴스 변수값을 대입하려고 하면 프로그램 시작 전에 만들어지는 static은 인스턴스 변수값이 무엇인지 알 수 없기 때문에 에러가 발생한다. 인스턴스 객체가 만들어지기도 전일테니 인스턴스 변수도 static이 정의 되는 시점에는 존재를 알 수 없다. 객체 생성 전의 인스턴스 변수는 언제 생성될지 모르는 변수인 것이다.

 

 

이 에러를 억지로 정상작동하게 하려면

static int sv2 = new MemberCall().iv; // 직접 지정

MemberCall 클래스

 

static 변수에 객체를 직접적으로 지정해서 생성해준다면 가능은 하다.

그러나 추천하지는 않는다.

 

 

 


3-2. 활용 예제

static과 인스턴스를 적절하게 사용하는 Student4 클래스 예제. 학생들의 성적표를 간단히 출력한다.

 

class Student4{
    private int num;
    private String name;
    private int[] score;
    private int total;
    private double average;

    static int count = 0;

    // 생성자. 크기 3칸짜리 score 배열을 생성하고, 번호를 증가시키며, 이름을 부여
    Student4(){
        score = new int[3];
        num = ++count; // 객체가 생성되어 생성자가 호출될 때마다 static 변수 count가 누적 증가
        name = "student" + count;
    }

    // 국어, 영어, 수학의 성적을 score 배열에 각각 대입하고, 총점과 평균을 구함
    public void inputScore(int k, int e, int m) {
        score[0] = k;
        score[1] = e;
        score[2] = m;
        for(int i = 0; i < 3; i++)
            total += score[i];
        average = total / 3.0;
    }

    // 성적표의 양식 출력
    public static void prnTitle() {
        System.out.println("\t\t\t==성적표==");
        System.out.println("--------------------------------------------------------------");
        System.out.println("번호\t이름\t\t국어\t영어\t수학\t총점\t평균");
        System.out.println("--------------------------------------------------------------");
    }

    // 번호, 이름, 과목별 점수와 총점, 평균 출력
    public void prnScore() {
        System.out.printf("%d\t%s\t", num, name);
        for(int i = 0; i < 3; i++) System.out.printf("%d\t", score[i]);
        System.out.printf("%d\t%.1f\n", total, average);
    }
}

Student4 클래스

// 3명의 학생 데이터가 저장될 공간의 주소(참조값)들을 저장할 참조변수 3개 생성
Student4[] std = new Student4[3];

for(int i = 0; i < std.length; i++) {
    std[i] = new Student4(); // std 배열에 객체 생성
    std[i].inputScore(89+i, 78+i, 85+i); // 다음 학생은 점수가 1씩 증가하게끔 (대충)설정
}

Student4.prnTitle();
for(int i = 0; i < std.length; i++)
    std[i].prnScore();

main 클래스