ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 9. 자바 static 변수 static 메서드
    자바(Java) 강의 2019. 4. 3. 17:12

    static키워드의 의미는 무엇일까? public static void main(..){..}에서 static은 왜 필요한 걸까? 또 클래스에서 메서드를 만들 때는 왜 static을 안 써도 괜찮았을까? 이번 포스트에서는 자바가 제공하는 static 키워드와 static의 의미, 사용하는 이유에 대해서 알아보도록 하겠다. 

    참고! 이 포스트는 자바8 기준으로 작성되었으며, 지금부터 나오는 메모리에 관련된 내용은 자바가상머신(Java Virtual Machine)에 대한 내용이다.

    이전 포스트

    목표

    • static 클래스와 오브젝트
    • static 변수
    • static 메서드
    • static 정리
    • static 클래스

    static 클래스와 오브젝트

    static과 클래스/오브젝트가 무슨 관련인가? 아주 많은 관련이 있다. static은 어떤 변수나 메서드가 어디에 속하느냐에 대한 개념이기 때문이다. 지금까지 우리는 어떤 클래스에 있는 변수와 메서드를 사용하기 위해서는 그 클래스의 오브젝트를 생성해야 한다고 했다. 이것은 반은 맞고 반은 틀린 말이었다.

    public class MyClass {
        public int myVariable;
        
        public void myMethod() {
            
        }
    }

    위의 MyClass에 존재하는 myVariable과 myMethod를 사용하기 위해서는 이 클래스 형의 오브젝트를 생성한다. 그리고 그 오브젝트 안에 존재하는 myVariable과 myMethod를 사용하는 것이다.

    public class Main {
        public static void main(String[] args) {
            MyClass myObject1 = new MyClass();
            myObject1.myVariable = 1;
            myObject1.myMethod();
            
            MyClass myObject2 = new MyClass();
            myObject2.myVariable = 2;
            myObject2.myMethod();
        }
    
    }

    우리는 같은 클래스 형으로 여러 개의 오브젝트를 만들 수 있다는 것을 알고 있다. 클래스는 설계도에 비유되고 오브젝트는 설계도를 이용해 만든 실제 건물에 비유했었다. 따라서 해당 클래스의 변수와 메서드는 각각의 오브젝트가 생성될 때마다 새로 생성된다. 이를 통해 어떤 변수(멤버 변수)와 메서드는 그 클래스형의 오브젝트에 귀속된다는 사실을 알 수 있다. 

    그렇다면 static은 무엇인가? static은 오브젝트가 아닌 '클래스'에 귀속되는 것들(변수/메서드/클래스)를 명시하기 위한 키워드이다. '클래스'에 귀속된다는 것이 무슨 뜻인지 아래에서 더 자세히 알아보도록 하겠다.

    static 변수

    public class MyClass {
        static int myStaticVariable = 100;
        int myVariable = 200;
    }

    위의 예제로 각 변수가 생성되는 공간에 대해 말해보도록 하겠다.

    자바 프로그램을 시작하면 자바(JVM)가 클래스 파일을 읽어 필요한 클래스와 변수/메서드들을 메모리에 생성한다. (클래스가 로딩되는 시점은 클래스마다 다르지만 지금은 프로그램 시작시 전부 로딩된다고 생각해도 상관 없다.) 이후 코드에서 그 클래스의 형을 가진 오브젝트를 생성하면 각 오브젝트마다 myVariable을 저장할 메모리 공간이 Heap이라는 메모리 공간에 생긴다. 모든 오브젝트는 Heap이라고 부르는 메모리 공간에 생긴다.

    반면 static 변수는 Heap이 아닌 다른 공간에 생긴다. 그 공간의 이름은 MetaSpace(자바8 이전은-PermGen)이다. 위의 그림처럼 static변수는 클래스에 귀속되지 각각의 오브젝트에 귀속되지 않는다. 다시 말해, 평범한 변수는 오브젝트마다 1개 생성되므로 n개의 오브젝트가 있을 때 n개 생성된다. static변수는 오브젝트가 몇 개이건 단 1개만 생성된다. 아래의 실습을 통해 그 의미를 알아보자.

    public class Main {
        public static void main(String[] args) {
            System.out.println(MyClass.myStaticVariable);
        }
    
    }

    MyClass에 존재하는 myStaticVariable에 접근하려면 어떻게 해야 할까? 위에서도 말했듯 static변수는 '클래스'에 귀속된다고 했다. 따라서 이 변수를 사용하기 위해서는 <클래스이름>.<스태틱변수이름>으로 접근해야 한다. 클래스에 속하므로 오브젝트를 생성하지 않아도 myStaticVariable을 사용할 수 있다.

    public class Main {
        public static void main(String[] args) {
            System.out.println(new MyClass().myVariable);
        }
    
    }

    반면 멤버 변수인 myVariable은 'new MyClass()'와 같이 새 오브젝트를 생성하고 이 오브젝트를 참조(마침표를 이용해 변수에 접근)해야만 해당 변수를 사용할 수 있다. 

    public class MyClass {
        static int myStaticVariable = 100;
        int myVariable = 200;
    
        public void myMethod() {
            myStaticVariable++;
            myVariable++;
            System.out.println(myStaticVariable);
            System.out.println(myVariable);
        }
    }

    위와 같은 메서드가 있다고 치자. 이 메서드를 메인에서 실행시켜보자.

    public class Main {
        public static void main(String[] args) {
            new MyClass().myMethod();
        }
    
    }
    
    // 실행결과
    // 101
    // 201

    두 값 모두 1씩 증가한 것을 확인할 수 있다. 이제 아래의 코드를 실행시켜보자.

    public class Main {
        public static void main(String[] args) {
            new MyClass().myMethod();
            System.out.println("-----------------");
            new MyClass().myMethod();
        }
    
    }
    
    /*
    실행결과
    101
    201
    -----------------
    102
    201
    */

    두 번째 결과에서 myStaticVariable이 101이 아니라 102가 된 것을 확인할 수 있다. 어떻게 된 것인가?

    위 코드의 실행 순서 (메인메서드는 포함하지 않았다)

    처음에 말했듯 자바를 실행시키면 자바(JVM)가 static변수를 먼저 MetaSpace에 만든다. 이후 메인 메서드가 실행되면서 새 오브젝트들이 Heap공간에 생성된다. 위처럼 각 myVariable은 오브젝트 당 하나 생성되므로 각각의 값이 1씩 늘어나지만 myStaticVariable은 클래스 당 1개 존재하므로 '공유' 변수처럼 사용된다.

    static 메서드

    static 메서드도 마찬가지이다. 

    public class MyClass {
    
        static int myStaticVariable = 100;
        int myVariable = 200;
    
        public void myMethod() {
            
        }
    
        public static void myStaticMethod() {
    
        }
    }

    위의 예제 코드를 이용해 static메서드에 대해 이야기해 보도록 하겠다.

    static변수에서 했던 것처럼, static 메서드도 마찬가지로 MetaSpace에 만들어진다. 이제 한 가지 실험을 해 보겠다. 각각의 메서드에서 myStaticVariable과 myVariable을 사용해 보는 것이다.

    public class MyClass {
        static int myStaticVariable = 100;
        int myVariable = 200;
    
        public void myMethod() {
            myStaticVariable++;
            myVariable++;
            System.out.println(myStaticVariable);
            System.out.println(myVariable);
        }
    
        public static void myStaticMethod() {
            myStaticVariable = 3;
            myVariable = 4; // non-static field 'myVariable' cannot be referenced from static context
        }
    }

    위의 코드를 짜고 돌려보면 myVariable = 4;에서 신택스 에러가 날 것이다. 왜인지 알 수 있는가? 그렇다. 지금 MyClass 안에 있는 myVariable은 설계도이다. 이 뜻은 "언젠가 이 클래스를 이용해 오브젝트를 생성하게 되면 myVariable이라는 변수를 만들고 200으로 초기화해라"라는 뜻이다. 하지만 myStaticMethod는 자바 프로그램 살행 당시 자바가 클래스를 인식(Class Loading)하면서 "어? 이 클래스는 myStaticVariable과 myStaticMethod라는 static 변수/메서드가 있으니 얘네는 MetaSpace에 먼저 넣어놔야겠다."하고 클래스가 로딩되는 시점에서 만들어진다. 따라서, 위의 예제에서 myStaticMethod가 실행될 때 이 메서드에서 사용(참조)하는 myVariable은 메모리 상에 존재하지 않는다. 메모리 상에 존재하지 않으므로 사용할 수 없다. 다시 말해, 자바 프로그램을 실행시키면 맨 처음 public static void main이 실행되기 전에 아래와 같은 상황이라는 뜻이다. (메인 메서드도 MetaSpace어딘가에 존재하지만 그림에선 생략한다.)

    그리고 이후에 main메서드가 실행되고 new MyClass();를 이용해 오브젝트를 생성하면 아래와 같은 상황이 된다.

    따라서 myStaticMethod는 비록 같은 클래스 안에 정의되어 있지만, 오브젝트 생성 시 생성되는 일반적인 멤버 변수/메서드를 볼 수 없다. 반대로 말하면 myStaticVariable과 myStaticMethod보다 오브젝트가 늦게 만들어지므로 각 오브젝트는 자기 자신이 메모리에 생성되는 시점에 myStaticVariable과 myStaticMethod가 반드시 존재함을 알 수 있다. 따라서 myMethod안에서는 myStaticVariable 또는 myStaticMethod를 사용할 수 있는 것이다.

    main 메서드

    이제 왜 main앞에 static이 붙는지 알겠는가? 클래스를 로딩하면서 main메서드가 메모리에 저장되므로, 자바는 메인 메서드를 보고 "아 얘가 main이니까 얘를 제일 먼저 call 하면 되겠다."라고 이해하는 것이다. 

    참고!

    클래스는 반드시 오브젝트를 생성해 사용한다고 했었는데 우리는 main메서드가 있는 Main클래스의 오브젝트를 만든 적이 한 번도 없었다. 아래 예제를 통해 main메서드도 결국 다른 static변수/메서드와 다름이 없음을 알 수 있다. 다만 public static void main(String[] args)라는 형식을 가진 메서드를 자바가 가장 먼저 실행 시킨다는 특징이 있을 뿐이다.

    public class Main {
        public static void main(String[] args) {
            Main mainObject = new Main();
            mainObject.methodInMain();
        }
    
        public void methodInMain(){
            System.out.println("Main 클래스의 메서드입니다.");
        }
    
    }

    또 Main클래스의 오브젝트를 만들더라도 static인 main메서드를 오브젝트를 이용해 부를 수 없다. 

    System.out.println

    또한 우리는 System.out.println을 사용할 때 오브젝트를 만든 적이 없었다. 우리는 System.out.println을 하면서 한번도 new System()을 하지 않았고 그래도 out을 사용 할 수 있었다. 이를 통해 System이라는 클래스에 static으로 선언된 out이라는 변수가 있으며 이 변수가 참조하는 오브젝트에는 println이라는 메서드가 존재한다는 것을 알 수 있다.

    참고! System클래스와 out변수

    public final class System {
    // 다른 코드..
       /**
         * The "standard" output stream. This stream is already
         * open and ready to accept output data. Typically this stream
         * corresponds to display output or another output destination
         * specified by the host environment or user.
         * <p>
         * For simple stand-alone Java applications, a typical way to write
         * a line of output data is:
         * <blockquote><pre>
         *     System.out.println(data)
         * </pre></blockquote>
         * <p>
         * See the <code>println</code> methods in class <code>PrintStream</code>.
         *
         * @see     java.io.PrintStream#println()
         * @see     java.io.PrintStream#println(boolean)
         * @see     java.io.PrintStream#println(char)
         * @see     java.io.PrintStream#println(char[])
         * @see     java.io.PrintStream#println(double)
         * @see     java.io.PrintStream#println(float)
         * @see     java.io.PrintStream#println(int)
         * @see     java.io.PrintStream#println(long)
         * @see     java.io.PrintStream#println(java.lang.Object)
         * @see     java.io.PrintStream#println(java.lang.String)
         */
        public final static PrintStream out = null;
    
    // ... 다른코드
    }

    static 정리

    static 변수/메서드는 클래스당 하나 생성된다. 프로젝트가 하나의 변수를 공유해야 할 경우 static 변수를 사용한다.

    static 변수/메서드를 사용하기 위해서 오브젝트를 사용 할 필요가 없다.

    언제 사용할까?

    프로젝트가 하나의 변수를 공유해야 할 경우 static 변수를 사용한다. 

    오브젝트의 멤버 변수에 구애받지 않는 경우, 오브젝트의 생성 없이 메서드를 사용하고 싶은 경우 static메서드를 사용한다.

    static 클래스

    static은 클래스에도 사용할 수 있는데 이는 nested class(중첩 클래스)를 알아야 하므로 다른 포스트에서 다루도록 하겠다. 개념은 똑같으니 혼자 실험해 봐도 좋다.

    이번 포스트에서는 static키워드에 대해 알아보았다. static 키워드를 알기 위해서는 JVM(Java Virtual Machine)에 대한 이해가 약간 필요해 어려웠을 수 있다. 한편으로는 static을 공부하며 JVM을 조금씩 알아간다고 생각하는 것도 나쁘지 않은 것 같다. 다음 포스트부터는 자바와 OOP개념인 추상 클래스, 인터페이스에 대해 이야기하도록 하겠다.

    다음 포스트: 10. 자바 상속 (Inheritance)

    댓글

f.software engineer @ All Right Reserved