static키워드는 매번 쓰지만 정확히 알지 못하고 써온 것 같다.
그래서 static이 무엇인지 자세히 알아보자!
- 클래스를 설계할 때, 멤버변수 중 모든 인스턴스에 공통적으로 사용해야하는 것에 static을 붙인다.- 인스턴스를 생성하면, 각 인스턴스들은 서로 독립적이기 때문에 서로 다른 값을 유지한다. 경우에 따라서는 각 인스턴스들이 공통적으로 같은 값이 유지되어야 하는 경우 static을 붙인다.
- static이 붙은 멤버변수는 인스턴스를 생성하기 않아도 사용할 수 있다. - static이 붙은 멤버변수(클래스변수)는 클래스가 메모리에 올라갈때 이미 자동적으로 생성되기 때문에.
- static이 붙은 메서드(함수)에서는 인스턴스 변수를 사용할 수 없다.
- static이 붙은 메서드는 인스턴스 생성 없이 호출가능한 반면, 인스턴스 변수는 인스턴스를 생성해야만 존재하기 때문에, static이 붙은 메서드를 호출 할 때 인스턴스가 생성되어있을수도 그렇지 않을 수도 있어서 static이 붙은 메서드에서 인스턴스변수의 사용을 허용하지 않는다. (반대로, 인스턴스변수나 인스턴스메서드에서는 static이 붙은 멤버들을 사용하는 것이 언제나 가능하다. 인스턴스변수가 존재한다는 것은 static이 붙은 변수가 이미 메모리에 존재한다는 것을 의미하기 때문)
- 메서드 내에서 인스턴스 변수를 사용하지 않는다면, static을 붙이는 것을 고려한다.- 메서드의 작업내용중에서 인스턴스 변수를 필요로 한다면, static을 붙일 수 없다. 반대로 인스턴스변수를 필요로 하지 않는다면, 가능하면 static을 붙이는 것이 좋다. 메서드 호출시간이 짧아지기 때문에 효율이 높아진다.(static을 안붙인 메서드는 실행시 호추로디어야할 메서드를 찾는 과정이 추가적으로 필요하기 때문에 시간이 더 걸린다.)
- 클래스 설계시 static의 사용지침
- 먼저 클래스의 멤버변수중 모든 인스턴스에 공통된 값을 유지해야 하는 것이 있는지 살펴보고 있다면 static을 붙여준다.
- 작성한 메서드 중에서 인스턴스 변수를 사용하지 않는 메서드에 대해서 static을 붙이는 것을 고려한다.
클래스변수와 인스턴스변수 차이
이를 이해하기 위해 카드 게임에 사용되는 카드 클래스로 예로 들어보자.
class Card {
String kind ; // 카드의 무늬 - 인스턴스 변수
int number; // 카드의 숫자 - 인스턴스 변수
static int width = 100 ; // 카드의 폭 - 클래스 변수
static int height = 250 ; // 카드의 높이 - 클래스 변수
}
각 Card인스턴스는 자신만의 무늬(kind)와 숫자(number)를 유지하고 있어야 하므로 이들을 인스턴스변수로 선언하였고, 각 카드들의 폭(width)과 높이(height)는 모든 인스턴스가 공통적으로 같은 값을 유지해야하므로 클래스변수로 선언하였다.
만일 카드의 폭을 변경해야할 필요가 있을 때는 모든 카드의 width값을 변경하지 않고, 한 카드의 width값만 변경해도 모든 카드의 width값이 변경되게 된다.
class CardTest{
public static void main(String args[]) {
// 클래스변수(static 변수)는 객체생성없이 '클래스이름.클래스변수'로 직접 사용 가능하다.
System.out.println("Card.width = " + Card.width);
System.out.println("Card.height = " + Card.height);
Card c1 = new Card();
c1.kind = "Heart";
c1.number = 7;
Card c2 = new Card();
c2.kind = "Spade";
c2.number = 4;
System.out.println("c1은 " + c1.kind + ", " + c1.number + "이며, 크기는 (" + c1.width + ", " + c1.height + ")" );
System.out.println("c2는 " + c2.kind + ", " + c2.number + "이며, 크기는 (" + c2.width + ", " + c2.height + ")" ); System.out.println("이제 c1의 width와 height를 각각 50, 80으로 변경합니다.");
c1.width = 50;
c1.height = 80;
System.out.println("c1은 " + c1.kind + ", " + c1.number + "이며, 크기는 (" + c1.width + ", " + c1.height + ")" );
System.out.println("c2는 " + c2.kind + ", " + c2.number + "이며, 크기는 (" + c2.width + ", " + c2.height + ")" );
}
}
class Card {
String kind ; // 카드의 무늬 - 인스턴스 변수
int number; // 카드의 숫자 - 인스턴스 변수
static int width = 100; // 카드의 폭 - 클래스 변수
static int height = 250; // 카드의 높이 - 클래스 변수
}
[실행결과]
Card.width = 100
Card.height = 250
c1은 Heart, 7이며, 크기는 (100, 250)
c2는 Spade, 4이며, 크기는 (100, 250)
이제 c1의 width와 height를 각각 50, 80으로 변경합니다.
c1은 Heart, 7이며, 크기는 (50, 80)
c2는 Spade, 4이며, 크기는 (50, 80)
Card인스턴스인 c1과 c2는 클래스 변수인 width와 height를 공유하기 때문에, c1의 width와 height를 변경하면 c2의 width와 height값도 바뀐 것과 같은 결과를 얻는다.
Card.width, c1.width, c2.width는 모두 같은 저장공간을 참조하므로 항상 같은 값을 갖게 된다.
인스턴스 변수는 인스턴스가 생성될 때 마다 생성되므로 인스턴스마다 각기 다른 값을 유지할 수 있지만, 클래스 변수는 모든 인스턴스가 하나의 저장공간을 공유하므로, 항상 공통된 값을 갖는다.
클래스메서드(static메서드)와 인스턴스메서드
변수에서와 같이 메서드 앞에 static이 붙어있으면 클래스메서드이고 아니면 인스턴스메서드이다.
클래스 메서드는 호출방법 역시 클래스변수처럼, 객체를 생성하지 않고도 '클래스이름.메서드이름(매개변수)'와 같은 식으로 호출이 가능하다.
어느 경우에 static을 사용해서 클래스메서드로 정의해야하는 것일까?
클래스는 '데이터(변수)와 데이터에 관련된 메서드의 집합'이라고 할 수 있다. 같은 클래스 내에 있는 메서드와 멤버변수는 아주 밀접한 관계가 있다. 인스턴스메서드는 인스턴스변수와 관련된 작업을 하는, 즉 메서드의 작업을 수행하는데 인스턴스변수를 필요로 하는 메서드이다.
그래서 인스턴스변수와 관계없거나(메서드 내에서 인스턴스변수를 사용하지 않거나), 클래스변수만을 사용하는 메서드들은 클래스메서드로 정의한다.
물론 인스턴스변수를 사용하지 않는다고 해서 반드시 클래스 메서드로 정의해야하는 것은 아니지만, 그렇게 하는 것이 일반적이다.
참고로 Math클래스의 모든 메서드는 클래스메서드임을 알 수 있다. Math클래스에는 인스턴스변수가 하나도 없거니와 Math클래스의 함수들은 작업을 수행하는데 필요한 값들을 모두 매개변수로 받아서 처리 하기 때문이다. 이처럼, 단순히 함수들만의 집합인 경우에는 클래스메서드로 선언한다.
class MyMath2 {
long a, b;
// 인스턴스변수 a, b를 이용한 작업을 하므로 매개변수가 필요없다.
long add() { return a + b; }
long subtract() { return a - b; }
long multiply() { return a * b; }
double divide() { return a / b; }
// 인스턴스변수와 관계없이 매개변수만으로 작업이 가능하다.
static long add(long a, long b) { return a + b; }
static long subtract(long a, long b) { return a - b; }
static long multiply(long a, long b) { return a * b; }
static double divide(double a, double b) { return a / b; }
}
class MyMathTest2 {
public static void main(String args[]) {
// 클래스메서드 호출
System.out.println(MyMath2.add(200L, 100L));
System.out.println(MyMath2.subtract(200L, 100L));
System.out.println(MyMath2.multiply(200L, 100L));
System.out.println(MyMath2.divide(200.0, 100.0));
MyMath2 mm = new MyMath2();
mm.a = 200L;
mm.b = 100L;
// 인스턴스메서드는 객체생성 후에만 호출이 가능함.
System.out.println(mm.add());
System.out.println(mm.subtract());
System.out.println(mm.multiply());
System.out.println(mm.divide());
}
[실행결과]
300
100
20000
2.0
300
100
20000
2.0
인스턴스메서드인 add(), subtract(), multiply(), divide()는 인스턴스변수인 a와 b만으로도 충분히 원하는 작업이 가능하기 때문에, 매개변수를 필요로 하지 않으므로 괄호()에 매개변수를 선언하지 않았다.
반면에 add(long a, long b), subtract(long a, long b) 등은 인스턴스변수 없이 매개변수만으로 작업을 수행하기 때문에 static을 붙여서 클래스메서드로 선언하였다. MyMathTest2의 main메서드에서 보면, 클래스메서드는 객체생성없이 바로 호출이 가능했고, 인스턴스메서드는 MyMath2클래스의 인스턴스를 생성한 후에야 호출이 가능했다.
이 예제를 통해서 어떤 경우 인스턴스메서드로, 또는 클래스메서드로 선언해야하는지, 그리고 그 차이를 이해하는 것은 매우 중요하다.
클래스멤버와 인스턴스멤버간의 참조와 호출
같은 클래스에 속한 멤버들간에는 별도의 인스턴스를 생성하지 않고도 서로 참조 또는 호출이 가능하다. 단, 클래스멤버가 인스턴스멤버를 참조 또는 호출하고자 하는 경우에는 인스턴스를 생성해야 한다.
그 이유는 인스턴스멤버가 존재하는 시점에 클래스멤버는 항상 존재하지만, 클래스멤버가 존재하는 시점에 인스턴스멤버가 항상 존재한다는 것을 보장할 수 없기 때문이다.
class MemberCall {
int iv = 10;
static int cv = 20;
int iv2 = cv;
// static int cv2 = iv; 에러. 클래스변수는 인스턴스 변수를 사용할 수 없음.
static int cv2 = new MemberCall().iv; // 굳이 사용하려면 이처럼 객체를 생성해야함.
static void classMethod1() {
System.out.println(cv);
// System.out.println(iv); 에러. 클래스메서드에서 인스턴스변수를 바로 사용할 수 없음.
MemberCall c = new MemberCall();
System.out.println(c.iv); // 객체를 생성한 후에야 인스턴스변수의 참조가 가능함.
}
void instanceMethod1() {
System.out.println(cv);
System.out.println(iv); // 인스턴스메서드에서는 인스턴스변수를 바로 사용가능.
}
static void classMethod2() {
classMethod1();
// instanceMethod1(); 에러. 클래스메서드에서는 인스턴스메서드를 바로 호출할 수 없음.
MemberCall c = new MemberCall();
c.instanceMethod1(); // 인스턴스를 생성한 후에야 인스턴스메서드를 호출할 수 있음.
}
void instanceMethod2() { // 인스턴스메서드에서는 인스턴스메서드와 클래스메서드
classMethod1(); // 모두 인스턴스생성없이 바로 호출이 가능하다.
instanceMethod1();
}
}
클래스멤버(클래스변수와 클래스메서드)는 언제나 참조 또는 호출이 가능하다.
그렇기 때문에 인스턴스멤버가 클래스멤버를 참조, 호출하는 것은 아무런 문제가 안 된다. 클래스멤버간의 참조 또는 호출 역시 아무런 문제가 없다.
그러나, 인스턴스멤버(인스턴스변수와 인스턴스메서드)는 반드시 객체를 생성한 후에만 참조 또는 호출이 가능하기 때문에 클래스멤버가 인스턴스멤버를 참조, 호출하기 위해서는 객체를 생성하여야 한다.
하지만, 인스턴스멤버간의 호출에는 아무런 문제가 없다. 하나의 인스턴스멤버가 존재한다는 것은 인스턴스가 이미 생성되어있다는 것을 의미하며, 즉 다른 인스턴스멤버들도 모두 존재하기 때문이다.
실제로는 같은 클래스 내에서 클래스멤버가 인스턴스멤버를 참조 또는 호출해야하는 경우는 드물다. 만일 그런 경우가 발생한다면, 인스턴스메서드로 작성해야할 메서드를 클래스메서드로 한 것은 아닌지 한번 더 생각해봐야 한다.
[알아두면 좋은 팁]
수학에서의 대입법처럼, c = new MemberCall()이므로 c.instanceMethod1();에서 c대신 new MemberCall()을 대입하여 사용할 수 있다.
MemberCall c = new MemberCall();
int result = c.instanceMethod1();
위의 두 줄을 다음과 같이 한 줄로 할 수 있다.
int result = new MemberCall().instanceMethod1();
대신 참조변수를 사용하지 않았기 때문에 생성된 MemeberCall인스턴스는 더 이상 사용할 수 없다
출처: https://vaert.tistory.com/101#recentEntries [Vaert Street]
static field 기억해둘 내용
- static field는 프로그램이 실행될 때 생성 및 초기화가 됩니다. 그렇기 때문에 객체를 생성하지 않아도 접근할 수 있습니다.
- static으로 상수를 선언할 때는 CAR_NAME처럼 관습적으로 대문자와 _를 이용하여 이름을 짓습니다.
- 많은 개발자들은 static으로 변수를 선언하지 않습니다. 덜 객체지향적이고, 무분별하게 사용하면 문제가 발생했을 때 원인을 찾기 어렵기 때문입니다. static 변수를 사용했을 때 구조적으로 얻는 이점이 많고, 잘 관리할 수 있다고 생각이 된다면 사용해도 좋습니다.
- static으로 변수를 선언할 때 멤버 변수와 쉽게 구분하기 위해(또는 사용하는데 주의하라는 메시지를 보내기 위해) 이름 앞에 s를 붙이기도 합니다.
- static field는 클래스와 객체를 통해서 모두 접근이 가능하지만, 클래스를 통해서 접근해야 합니다.
- static으로 상수가 아닌, 변수를 선언하는 일은 많지 않습니다. static으로 변수를 많이 선언하면 객체지향과 거리가 멀어지고, 자칫 스파게티 코드가 되기 쉬울 수 있습니다. 또한, 오류가 발생했을 때 원인을 찾기 어려울 수 있습니다.
static method 기억해둘 내용
- static 메소드는 객체 생성 없이 호출할 수 있습니다. 그렇기 때문에 멤버 변수에 접근이 안되고, static field나 static 메소드만 접근할 수 있습니다.
- static 메소드는 객체 및 클래스를 통해서 호출할 수 있습니다. 하지만 멤버 메소드와 구분하기 위해 클래스를 통해서만 호출하는 것이 좋습니다.
- static 메소드는 Utils, Helper 클래스들을 만드는데 많이 사용됩니다.
- static 메소드는 namespace가 클래스 아래에 위치하기 때문에, 클래스 속성과 연관된 메소드를 묶어주는 효과가 있습니다.(grouping)
static 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으로 선언하여 한 곳에 모을 수 있습니다.
😊출처😊
https://vaert.tistory.com/101#recentEntries [Vaert Street]