static 키워드
『static』 이란 명칭은 메모리에서 고정되기 때문에 붙은 이름으로 모든 객체가 공유한다는 의미가 더 큽니다.
보통 모든 객체가 동일한 데이터를 참고해야 할 필요가 있는 경우에 활용하며
그렇기에 모든 객체는 데이터에 영향을 줄 수 있습니다.
형태
클래스 변수
class Calculator{
int left, right;
public void setOprands(int left, int right){
this.left = left;
this.right = right;
}
public void sum(){
System.out.println(this.left+this.right);
}
}
public class CalculatorDemo4 {
public static void main(String[] args) {
Calculator c1 = new Calculator();
c1.setOprands(10, 20);
c1.sum();
Calculator c2 = new Calculator();
c2.setOprands(20, 40);
c2.sum();
}
}
Q. left와 right 변수는 누구의 맴버일까?
A. 인스턴스의 멤버다.
▶ 클래스를 정의하고 인스턴스를 만들어야 사용할 수 있었고,
인스턴스마다 서로 다른 값을 가지고 있기 때문이다.
가령, left의 값은 인스턴스마다 달라질 수 있다.
인스턴스 변수 c1의 left 값은 10이고, c2의 left 값은 20이었다.
인스턴스의 상태인 변수의 값이 인스턴스마다 다른 값을 가질 수 있다는 점은
하나의 클래스를 여러개의 인스턴스로 만들어서 사용할 수 있다는 점에서 좋은 기능이라고 할 수 있다.
그렇다면 모든 인스턴스가 같은 값을 공유하게 하고 싶을 때가 있다.
클래스간 맴버를 가질 수 있다는 것일까? 이를 클래스 멤버라고 부르면 static을 이용해 구현
- static을 맴버(변수,메소드) 앞에 붙이면 클래스의 맴버가 된다.
- 클래스, 인스턴스로 접근이 가능하다.(굳이 인스턴스를 생성하지 않아도 접근 가능하다. ← new 연산자)
class Calculator2 {
static double PI = 3.14;
static int base = 0;
int left, right;
public void setOprands(int left, int right) {
this.left = left;
this.right = right;
}
public void sum() {
// 더하기에 base의 값을 포함시킨다.
System.out.println(this.left + this.right + base);
}
public void avg() {
// 평균치에 base의 값을 포함시킨다.
System.out.println((this.left + this.right + base) / 2);
}
}
public class CalculatorDemo2 {
public static void main(String[] args) {
Calculator2 c1 = new Calculator2();
c1.setOprands(10, 20);
c1.sum();
Calculator2 c2 = new Calculator2();
c2.setOprands(20, 40);
c2.sum();
// c1.base = 10; 혹은 c2.base = 10; 해도 가능
Calculator2.base = 10;
// 클래스 변수는 모든 인스턴스에 영향!
c1.sum();
c2.sum();
}
}
각각의 인스턴스 마다 원주율(PI)과 임의의 변수(base) 값을 별도로 가지고 있을 필요가 없다.
이런 경우 변수를 클래스의 맴버로 만들면 된다. (객체간 공유를 하고 있다.)
근데, 특정 인스턴스가 클래스 변수의 값을 변경하면 모든 인스턴스가 영향을 받게 된다.
의도치 않은 클래스 변수 값의 변경으로 공유하고 있는 모든 인스턴스가 잘못된 결과를 가져올 수 있는데,
이를 막고자 하면 final 키워드를 통해 값 변경을 제한할 수 있다. (ex. 상수 처리)
public class ConstEx {
public static final int RECTANGLE = 1;
public static final int TRIANGLE = 2;
public double getArea(int type, int width, int height) {
double area = 0.0;
if(type == RECTANGLE) {
area = width * height;
}else if(type == TRIANGLE) {
area = (width * height) / 2;
}
return area;
}
public static void main(String[] args) {
ConstEx ex = new ConstEx();
int w = 100;
int h = 50;
System.out.println(ex.getArea(ConstEx.RECTANGLE, w, h));
System.out.println(ex.getArea(ConstEx.TRIANGLE, w, h));
}
}
즉, 클래스 멤버가 필요한 경우는 아래과 같다.
- 인스턴스에 따라서 변하지 않는 값이 필요한 경우
( 이런 경우 final을 이용해서 상수로 선언하는 것이 바람직하다.)
- 인스턴스를 생성할 필요가 없는 값을 클래스에 저장하고 싶은 경우
- 값의 변경 사항을 모든 인스턴스가 공유해야 하는 경우
static 필드는 클래스의 모든 객체에 공통으로 사용되는 변수라는 특징을 생각해보면
C/C++의 전역 변수와 유사하다고 볼 수 있다.
(실제 static 멤버의 생성 시점은 자바 가상 기계에 따라 다르다.
그러나 일반적으로 static 멤버가 포함된 클래스가 로딩하는 시점에 static 멤버가 생성된다고 볼 수 있다.
public class OrderEx {
private static int count = 0;
public void pressButton() {
count++;
System.out.println("고객님의 번호는 " + count + " 입니다.");
}
public static void main(String[] args) {
OrderEx u1 = new OrderEx();
u1.pressButton();
OrderEx u2 = new OrderEx();
u2.pressButton();
}
}
클래스 메소드
Q. 클래스 변수가 있다면 클래스 메소드도 있지 않을까? → 존재합니다.
left, right와 같이 별도의 변수를 저장하지 않고 인자로 바로 전달받아 sum()을 구현할 수 있습니다.
class Calculator{
public static void sum(int left, int right){
System.out.println(left+right);
}
}
public class CalculatorDemo {
public static void main(String[] args) {
Calculator3.sum(10, 20);
Calculator3.sum(20, 40);
}
}
new 연산자를 통해 인스턴스를 생성하지 않아도 static을 통해 바로 이용할 수 있다.
이처럼 메소드가 인스턴스 변수를 참조하지 않는다면 클래스 메소드를 사용해서 불필요한 인스턴스의 생성을 막을 수 있다.
이러한 형태가 자바에서 제공하는 표준 API가 내부적으로 동작하고 있는 것이다.
ex) Integer.parseInt( ), Math.random( ) 등도 일종의 static 메소드이다. → 언제 어디서나 사용가능
import java.util.Scanner;
public class AreaEx {
public static double calcCircle(double radius) {
return (radius * radius) * Math.PI;
}
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
System.out.println("반지름을 입력해주세요.");
double r = sc.nextDouble();
double area = AreaEx.calcCircle(r);
System.out.println("넓이는 " + area);
}
}
non-static vs static
인스턴스 변수와 클래스 변수는 아래와 같이 부르기도 합니다.
- 인스턴스 변수 → Non-Static Field
- 클래스 변수 → Static Field
▶ 인스턴스 메소드는 클래스 멤버에 접근 할 수 있다.
▶ 클래스 메소드는 인스턴스 멤버에 접근 할 수 없다.
인스턴스 변수는 인스턴스가 만들어지면서 생성되는데,
클래스 메소드는 인스턴스가 생성되기 전에 만들어지기 때문에
클래스 메소드가 인스턴스 맴버에 접근하는 것은 존재하지 않는 인스턴스 변수에 접근하는 것과 같다.
▶ static 메소드에서는 this 키워드를 사용할 수 없다.
왜냐하면 static 메소드는 객체가 생성되지 않은 상황에서도 클래스 이름을 이용하여 호출이 가능하기 때문에
호출 당시 실행 중인 객체를 가리키는 this 레퍼런스를 사용할 수 없다.
단 한번만 동작하게 하는 static{ } 예제
public class BlockEx {
static {
System.out.println("static 불럭");
}
public void doA() {
System.out.println("AAAA");
}
public static void main(String[] args) {
BlockEx ex1 = new BlockEx();
BlockEx ex2 = new BlockEx();
BlockEx ex3 = new BlockEx();
ex1.doA();
ex2.doA();
ex3.doA();
}
}
Java에서 모든 영향력의 경계선이 { } 라는 것을 생각해보면
코드 상단에 『static』 키워드에 영향을 받는 것을 확인할 수 있습니다.
static이라는 키워드가 객체가 아닌 클래스와 관련이 있다는 것을 생각해보면
static { ... } 은 클래스가 메모리상에 올라가는 시점에만 동작하게 됩니다.
따라서 객체의 생성과는 관계없이 클래스가 로딩되는 시점에 단 한 번만 실행하고 싶은 처리를 하기위해서 사용
요약
- static이 붙은 변수들은 객체들이 다 같이 공유하는 데이터를 의미
- static이 붙은 메소드는 객체들의 데이터와 관계없는 완벽하게 공통적인 로직을 정의할 때 사용
(그렇지 않으면 생성된 객체들 중에서 어떤 객체의 데이터를 활용해야 할지 알 수 없게 되기 때문)
따라서 static 메소드에서는 인스턴스 변수나 객체의 메소드를 사용할 수 없다.
(왜냐하면 인스턴스 생성전에 메모리에 로딩시켜야 하기 때문.)
+) static은 속도는 빠르지만, 메모리가 회수되지 않기 때문에 주의해야 합니다.
가비지 컬렉션의 대상이 아니기 때문입니다. (메모리에서 항상 상주)
이 문제는 단순하게 개발해서 결과만 보는 동안에는 느껴지지 않고,
시스템을 오랜 시간 가동되면서 점점 느려지는 현상을 보이면 그제야 눈에 들어올 수 있습니다.
'프로그래밍 언어 > Java' 카테고리의 다른 글
[Java] 절차적 vs 함수적 vs 객체지향 프로그래밍 비교 (0) | 2021.03.02 |
---|---|
[Java] 제어문 (if, swtich, for, while) (0) | 2021.03.02 |
[Java] Map 인터페이스를 구현한 HashMap (0) | 2021.03.02 |
[Java] [예제] 사칙연산 계산기 (0) | 2021.03.02 |
[Java] 가비지 컬렉션(Garbage Collection) (0) | 2021.03.02 |
댓글