Java - Static 키워드 이해하기

Java의 static keyword는 field, 메소드, 클래스에 적용할 수 있습니다.

static keyword를 사용하여 선언하였을 때 어떤 차이점이 있는지 알아보겠습니다.

static field(변수, 상수)

클래스 안에서 변수를 선언하면 멤버 변수로 선언이 됩니다. 멤버 변수에 접근하려면 car1.name처럼 객체를 통해서 접근해야 합니다.

Car car1 = new Car();
System.out.println(car1.name)

만약 static keyword를 사용하여 변수를 선언하면 멤버 변수와 다른 특성을 갖습니다. 다음과 같이 객체를 통해서 접근할 수 있지만, 클래스를 통해서도 접근할 수 있습니다.

System.out.println(Car.sNumberOfCars);

Car car1 = new Car();
System.out.println(car1.sNumberOfCars);

클래스를 통해서 접근을 하게 되면, 객체를 만들지 않고 외부에서 접근할 수 있습니다. static으로 선언된 변수는 프로그램이 실행될 때 생성 및 초기화됩니다. 그렇기 때문에 객체를 생성하지 않아도 이미 변수는 생성된 상태입니다.

다음과 같이 Car 클래스를 예로 들면, 여기에는 3개의 static filed가 있습니다.

public class Car {
    public final static String MANUFACTURE_NAME = "BMW";
    public final static String CAR_NAME = "BMW 320D";

    public static int sNumberOfCars = 0;

    public Car() {
        sNumberOfCars++;
    }
}

위의 코드에서 static으로 선언된 변수는 Car객체를 생성하지 않아도 접근이 가능합니다. 그리고 다음과 같은 차이점이 있습니다.

  • MANUFACTURE_NAMECAR_NAMEfinal static으로 선언했기 때문에 값 변경이 안되는 상수(Constant)입니다.

보통 static 상수를 쉽게 구분하기 위해 대문자와 _만을 사용하여 이름을 짓습니다.

  • sNumberOfCarsstatic으로 선언했기 때문에 언제나 값을 변경할 수 있는 변수(Variable)가 됩니다.

보통 static 변수를 쉽게 구분하기 위해 이름 앞에 s를 붙이기도 합니다.

클래스 이름을 통해서 접근

static field는 다음과 같이 객체를 생성하지 않고 클래스 이름을 통해서 접근할 수 있습니다. 아래 코드에서 sNumberOfCars는 Car 객체를 생성할 때 1이 증가되도록 구현되었고, 모든 객체들이 하나의 변수를 공유하기 때문에 3이 출력되었습니다.

Car car1 = new Car();
Car car2 = new Car();
Car car3 = new Car();

System.out.println(Car.CAR_NAME);
System.out.println(Car.MANUFACTURE_NAME);
System.out.println("Number of cars : " + Car.sNumberOfCars);

Output:

BMW 320D
BMW
Number of cars : 3

객체를 통해서 접근

물론 다음과 같이 객체를 통해서 접근할 수도 있습니다. 하지만 static field는 위와 같이 Class를 통해서 접근하는 방법을 권장합니다. 그 이유는 멤버 변수를 접근하고 있는지 static 변수를 접근하고 있는지 쉽게 구분이 안되기 때문입니다.

Car car1 = new Car();
Car car2 = new Car();
Car car3 = new Car();

System.out.println(car1.CAR_NAME);
System.out.println(car1.MANUFACTURE_NAME);
System.out.println("Number of cars : " + car1.sNumberOfCars);

Output:

BMW 320D
BMW
Number of cars : 3

접근 제한자

static field를 선언할 때 접근 제한자를 조절하여 공개하고 싶은 범위를 설정할 수 있습니다.

다음과 같이 public으로 설정하면, 누구나 접근할 수 있습니다.

public final static String MANUFACTURE_NAME = "BMW";

다음과 같이 private으로 설정하면, 클래스 내부에서만 접근할 수 있습니다.

private final static String MANUFACTURE_NAME = "BMW";

다음과 같이 package-private으로 설정하면, 동일한 package에 있는 클래스에서만 접근할 수 있습니다.

final static String MANUFACTURE_NAME = "BMW";

static field를 사용하는 이유?

대부분 static field는 상수를 선언할 때 많이 사용합니다.

이렇게 상수를 선언하면 다른 클래스에서도 Car.MANUFACTURE_NAME를 통해 상수에 접근할 수 있습니다.

public class Car {
  public final static String MANUFACTURE_NAME = "BMW";
  public final static String CAR_NAME = "BMW 320D";
}

멤버 변수로 선언할 수도 있지만 이렇게 구현하면 객체마다 상수들을 갖고 있기 때문에 객체의 크기가 커질 수 있습니다.

기억해둬야 할 내용

  • static filed는 프로그램이 실행될 때 생성 및 초기화가 됩니다. 그렇기 때문에 객체를 생성하지 않아도 접근할 수 있습니다.
  • static으로 상수를 선언할 때는 CAR_NAME처럼 관습적으로 대문자와 _를 이용하여 이름을 짓습니다.
  • 많은 개발자들은 static으로 변수를 선언하지 않습니다. 덜 객체지향적이고, 무분별하게 사용하면 문제가 발생했을 때 원인을 찾기 어렵기 때문입니다. static 변수를 사용했을 때 구조적으로 얻는 이점이 많고, 잘 관리할 수 있다고 생각이 된다면 사용해도 좋습니다.
  • static으로 변수를 선언할 때 멤버 변수와 쉽게 구분하기 위해(또는 사용하는데 주의하라는 메시지를 보내기 위해) 이름 앞에 s를 붙이기도 합니다.
  • static field는 클래스와 객체를 통해서 모두 접근이 가능하지만, 클래스를 통해서 접근해야 합니다.
  • static으로 상수가 아닌, 변수를 선언하는 일은 많지 않습니다. static으로 변수를 많이 선언하면 객체지향과 거리가 멀어지고, 자칫 스파게티 코드가 되기 쉬울 수 있습니다.

또한, 오류가 발생했을 때 원인을 찾기 어려울 수 있습니다.

static 메소드

static 메소드도 static field와 비슷합니다. 메소드를 선언할 때 static을 사용하면 객체를 생성하지 않아도 그 메소드를 호출할 수 있습니다.

다음 클래스를 예로 들겠습니다. Car 클래스는 printCarName()라는 static 메소드가 있습니다.

public class Car {
    public final static String MANUFACTURE_NAME = "BMW";
    public final static String CAR_NAME = "BMW 320D";

    public int year = 2018;

    public Car() {
    }

    public static void printCarName() {
        System.out.println("{ Brand : " + MANUFACTURE_NAME + ", Name : " + CAR_NAME + " }");
    }
}

printCarName()는 클래스와 객체를 통해서 모두 호출이 가능합니다. 하지만 객체의 메소드를 호출하는 것인지 static 메소드를 호출하는 것인지 구분이 안되기 때문에 클래스를 통해서만 호출되어야 합니다.

Car.printCarName();

Car car1 = new Car();
car1.printCarName();

Output:

{ Brand : BMW, Name : BMW 320D }
{ Brand : BMW, Name : BMW 320D }

주의할 점

static 메소드는 객체를 생성하지 않아도 호출할 수 있습니다. 이것은 메소드가 객체와 분리되어 있다는 의미입니다. 그래서 메소드 내부에서 super, this와 같은 키워드를 사용할 수 없고 클래스의 멤버 변수에 접근할 수도 없습니다.

예를 들어, 다음과 같이 멤버 변수 year에 접근하려고 하면 컴파일 에러가 발생합니다. 내부에 선언된 변수 외에 static field, static 메소드만 접근할 수 있기 때문입니다.

public static void printCarName() {
    // System.out.println("{ year : " + year + " }");  // compile error!
    System.out.println("{ Brand : " + MANUFACTURE_NAME + ", Name : " + CAR_NAME + " }");
}

static 메소드를 사용하는 이유?

static 메소드는 보통 Utils, Helper 클래스를 만드는데 사용합니다.

예를 들어, 다음과 같이 Math라는 Utils 클래스를 만들 수 있습니다. Math.sqrt(), Math.sum()으로 호출할 수 있는데요. 수학적인 연산과 관련된 메소드들을 Math라는 클래스 아래로 모아주는 장점이 있습니다. (grouping)

public class Math {
    public static double sqrt(double num) {
        // ....
    }

    public static double sum(double num1, double num2) {
        // ....
    }
}

또 다른 장점으로, static 메소드를 구현할 때 내부에서 상수만 접근하도록 만들면 입력에 따른 결과가 달라지지 않습니다. 즉, 좀 더 함수형 프로그래밍에 가까워집니다.

기억해둬야 할 내용

  • static 메소드는 객체 생성 없이 호출할 수 있습니다. 그렇기 때문에 멤버 변수에 접근이 안되고, static field나 static 메소드만 접근할 수 있습니다.
  • static 메소드는 객체 및 클래스를 통해서 호출할 수 있습니다. 하지만 멤버 메소드와 구분하기 위해 클래스를 통해서만 호출하는 것이 좋습니다.
  • static 메소드는 Utils, Helper 클래스들을 만드는데 많이 사용됩니다.
  • static 메소드는 namespace가 클래스 아래에 위치하기 때문에, 클래스 속성과 연관된 메소드를 묶어주는 효과가 있습니다.(grouping)

static class

static class도 분리라는 의미에서 위와 비슷한 특성이 있습니다.

static 키워드를 이용하여 class를 선언하면, 상위 클래스와 분리를 해 줍니다.

예를 들어, Car 클래스 하위에 Wheel 클래스를 선언하였습니다. 이런 구조로 선언된 Wheel 클래스를 Inner class라고 부릅니다. Inner class의 특징 중 하나는 상위 클래스와 연결되어 있는 것입니다. 그래서 Wheel 클래스는 상위 클래스인 Car 클래스의 멤버 변수에 접근이 가능합니다.

public class Car {
    public int year = 2018;

    public Car() {
    }

    public class Wheel {
        public Wheel() {
            year = 10;
        }
    }
}

위와 같은 특성 때문에 Inner class는 상위 클래스에서만 생성할 수 있습니다. 상위 클래스를 통하지 않고 외부에서 직접 Inner class를 생성할 수 없습니다.

만약 다음과 같이 static으로 Wheel클래스를 선언하면 Car 클래스와 분리가 됩니다. 분리되었다는 것은 Wheel 클래스에서 Car 클래스의 멤버변수에 접근할 수 없다는 의미입니다. 이렇게 static으로 선언된 하위 클래스를 Static nested class라고 합니다.

public class Car {
    public int year = 2018;

    public Car() {
    }

    public static class Wheel {
        public Wheel() {
            // year = 10; // compile error!
        }
    }
}

Static nested class는 상위 클래스가 생성되지 않아도, 외부에서 직접 객체를 생성할 수 있습니다.

예를 들어, 다음과 같이 하위 클래스를 외부에서 직접 객체를 생성할 수 있습니다.

Car.Wheel wheel = new Car.Wheel();

이것이 Inner class와의 가장 큰 차이점이라고 할 수 있습니다.

주의할 점

static class는 하위 클래스를 선언할 때만 가능합니다. 아래와 같이 상위 클래스를 static으로 선언하려고 하면 컴파일 에러가 발생합니다.

public static class Car {  // compile error!
    public int year = 2018;

    public Car() {
    }

    public class Wheel {
        public Wheel() {
            // year = 10; // compile error!
        }
    }
}

static class를 사용하는 이유?

static class로 Inner class를 생성하면 좋은 점은 grouping입니다. 어떤 클래스와 연관된 클래스들을 하위에 선언하여 관련있는 클래스들을 모아둘 수 있습니다.

public class Car {
    public Car() {
    }

    public static class Wheel {
    }
}

이런 부분을 Inner class로도 할 수 있지만, 위에서 설명한 것처럼 Inner class는 상위 클래스와 연결되어있어 독립된 클래스가 아닙니다.

정리

static 키워드의 공통점은 객체와의 분리입니다. 객체를 생성하지 않고 접근할 수 있습니다.

또한, Grouping이라는 장점이 있습니다. 어떤 클래스 하위에 이와 관련있는 메소드, 클래스 등을 static으로 선언하여 한 곳에 모을 수 있습니다.

Loading script...

Related Posts

codechachaCopyright ©2019 codechacha