[Java] static
static
static이라는 의미는 ‘정적인, 움직이지 않는다.’는 뜻이다. 메모리에서 고정되기 때문에 붙은 이름이지만, 실제 소스에서 static을 사용한다는 의미는 모든 객체가 ‘공유’한다는 의미이다.
cf.) 객체지향이라는 패러다임이란 데이터와 기능(로직, 메소드)을 가진 객체들의 커뮤니케이션으로 어떤 작업을 완료하는 것을 의미한다.
cf.) 객체마다 데이터를 가져도 불편한 때도 있다
예를 들어 여러분이 어떤 쇼핑몰을 운영한다고 가정해보자. 여러분의 시스템에서 발생하는 매출 현황은 비단 여러분뿐 아니라 여러분의 회사의 모든 직원이 알아야 한다.
이 비유는 객체지향 프로그래밍에서도 마찬가지로 적용할 수 있다.
1 모든 객체가 동일한 데이터를 참고해야 할 필요가 있다.
2 모든 객체는 데이터에 영향을 줄 수 있다.
1. static 중요 법칙
1 static이 붙은 변수들은 객체들이 다 같이 공유하는 데이터를 의미한다.
2 static이 붙은 메소드들은 객체들의 데이터와 관계없는 완벽하게 공통적인 로직을 정의할 때 사용한다.
3 따라서 static 메소드에서는 인스턴스 변수나 객체의 메소드를 사용할 수 없다.
2. static을 사용하는 기준
(1) 객체가 데이터를 보관할 필요가 없다면 static 메소드로 만들어도 된다.
ex.)
Random random = new Random( ); ①
double a = Math.ramdom( ); ②
둘다 마찬가지로 임의의 소수를 발생시킨다.
①은 객체가 데이터를 가지기 때문에 동일한 메소드를 호출해도 다른 결과를 만들어 낼 수 있다는 것을 의미한다.(Random 클래스는 객체를 생성할 때 데이터를 가지게 할 수 있다.)
②Math.random( )은 특정한 데이터를 기준으로 하는 것이 아니라, 완전히 매번 동일한 방법으로 동작하게 된다. 결론적으로 어떤 메소드가 완전히 객체의 데이터와 독립적인 결과를 만들어내야 하는 상황에서는 static 메소드로 만드는 것이 더 나은 선택이다.
(2) 객체들이 공유하는 데이터를 사용할 때에도 static 메소드를 이용한다.
static이라는 키워드가 붙은 존재는 객체와 무관해지기 위해서 사용한다. 그런데 static한 메소드에서 어떤 특정한 객체의 데이터를 활용하게 되면 몇 가지 문제가 생긴다.
만일 static한 공통된 기능의 메소드에서 어떤 하나의 객체의 데이터를 사용하게 된다면 대체 어떤 객체의 데이터를 활용해야 할까?
static이 붙으면 객체와는 무관해진다. 따라서 그 안에서 특정한 하나의 객체의 데이터를 활용하게 되면 문제가 되는데, 가장 근본적인 것은 만들어진 수많은 객체 중에서 어떤 객체의 데이터를 활용해야 할지 알 수 없게 된다는 점이다. 이런 이유 때문에 static이 붙은 메소드 안에서는 인스턴스 변수를 활용할 수 없도록 컴파일러가 확인하게 된다.
3. static의 의미
(1) 공유
어떤 변수에 static을 붙이게 되면 이 변수는 모든 객체가 다 같이 공유하는 변수가 된다. (static이 붙은 변수를 사용하는 모든 객체는 공유된 데이터를 사용한다.)
위와 같은 문제에서 어떤 데이터를 모든 객체가 같이 사용하게 하고 싶다면 static이라는 키워드가 바로 방법이다.
ex.) 은행에 있는 여러개의 번호표 → 각 기계(객체)마다 각각의 번호표를 사용한다면 1번 번호표를 가진 고객들은 서로 자신이 1번이라고 주장하는 웃지 못할 상황이 발생한다.
이 문제의 근본적인 문제는 각 객체가 자신만의 데이터를 갖는다는 것에 있다. 이 문제를 해결하기 위한 가장 손쉬운 해결책 역시 모든 객체가 같은 데이터를 공유할 수 있게 하면 된다.
→ static은 어떤 변수를 모든 객체가 공유하는 값으로 변경해버리기 때문에 아무리 많은 객체가 있더라도 같은 데이터를 사용하게 하는 공유의 개념을 완성할 수 있다.
(2) 객체와 무관한 메소드와 함수라는 개념
static이 붙은 메소드는 모든 객체가 공유하는 메소드이다.(static이 붙은 변수도 모든 객체가 공유)
모든 객체가 자신만의 데이터를 가지고 있으니 발생되는 문제처럼 메소드 역시 객체의 데이터의 의해서 좌우되는 상황을 벗어나기 위해서 Java에서의 static은 메소드에서도 적용할 수 있게 된다.
1) static 메소드는 결과적으로 모든 객체가 완벽하게 똑같이 동작하는 메소드를 위해서 사용
클래스에서 나온 객체의 데이터에 영향받지 않고 완벽히 동일하게 동작하는 메소드를 만들고 싶다면 static을 이용해서 객체의 메소드를 처리하면 된다. 메소드 앞에 static이 붙으면 모든 객체가 클래스에 정의된 방법대로만 사용하게 된다. 이 문제는 좀 더 현실적인 해결책으로 생각해보면 객체가 가진 데이터의 의해서 좌우되고 싶지 않다는 뜻이다.
객체가 어떤 데이터를 가지든 객체의 데이터의 영향 없이 동작하게 만든다면 언제나 완전하게 동일하게 동작하는 것을 보장해줄 수 있다.
2) Integer.parseInt( )는 static 메소드이다.
이 메소드는 언제 어디서나 동일하게 동작한다는 것이다. 이 메소드 자체가 어는 상황에서든 객체의 데이터에 의해서 영향받지 않고 같은 기능만을 제공한다.
→ static 메소드는 마치 C언어의 함수와 유사한다. 완벽하게 언제나 동일하게 동작하는 것이 보장되기 때문에 사용자는 객체의 데이터를 신경 쓰지 않아도 된다. 이런 의미에서 보면 static메소드라는 것은 객체가 가지는 메소드라기 보다는 클래스 자체의 메소드라는 의미로 생각해볼 수 있다.
(3) 단 한번만 동작하게 하는 static
static이 실행되는 시점은 클래스가 메모리상에 올라갈 때이다. 즉 우리가 프로그램을 실행하면 필요한 클래스가 메소리상에 로딩되는 과정을 거치게 된다. 그리고 한번 로딩된 클래스는 특별한 일이 발생하지 않는 이상 메모리상에서 객체를 생성할 수 있도록 메모리에 상주하게 된다.
static 은 바로 이 시점에 동작한다. 클래스가 메모리상에 올라가자마자 실행되면서 필요한 처리를 하게 된다. 따라서 static 은 객체의 생성과는 관계없이 클래스가 로딩되는 시점에 단 한 번만 실행하고 싶은 처리를 하기 위해서 사용한다.
4. static이 붙은 변수가 공유 되는 원리
cf.) static이 붙은 변수는 객체의 레퍼런스를 이용해서 사용하는 일반적인 객체지향 프로그래밍과는 달리 클래스의 변수이기 때문에 그냥 클래스 ‘이름, 클래스’변수라는 방식으로 사용하게 된다.(new해서 객체생성 불요)
(1) static이 붙은 변수를 클래스 변수라고 부른다.
모든 객체는 자신이 어디에서 생산되었는지 해당 클래스를 알 수 있다. 모든 객체가 자신이 속한 클래스를 알 수 있다는 것을 다른 말로 하면 모든 객체는 자신이 속한 클래스의 정보를 공유한다는 뜻이다.
사실 static의 비밀은 바로 이곳에 있다. static의 가장 중요한 내용은 객체와 묶이는 데이터가 아니라 클래스와 묶이는 데이터라는 것이다.
static을 사용하는 변수는 객체가 아닌 클래스의 변수이기 때문에 별도의 사용할 필요 없이 그냥 사용할 수 있다.
(2) JVM의 구조(java의 메모리 구조)
1) Heap 영역(객체 영역)
객체의 영역이란 객체들이 만들어지고 살다가 죽는 영역이다. 이 영역에서 가장 중요한 존재는 다름 아닌 가비지 컬렉터이다.
2) Method Area(비객체 영역)
비객체 영역인 Method Area는 클래스가 메모리상에 올라가는 영역이다. 이 영역은 가비지 컬렉터의 영향을 받지 않고 메모리에 상주한다. 간단히 생각해보면 모든 객체는 클래스에서 나오는데 이 클래스가 나도 모르게 메소리상에서 지워지면 난감한 일이 발생할 수 있다. 비객체 영역은 가비지 컬렉터의 영향으로부터 자유롭고, 메모리에 상주하게 되어 있는데 이런 상주의 의미를 ‘static'이라는 뜻으로 사용된다.
(3) static이 붙은 부분은 클래스가 메모리상에 로딩되면서 같이 올라간다.
static 키워드가 붙으면 메소리상에서 다르게 처리된다는 것이다. static이 붙은 변수를 클래스 변수라고 하는 것은 변수가 존재하는 영역이 클래스가 존재하는 영역과 같기 때문이다.
Java에서 모든 객체는 결과적으로는 동일한 클래스에서부터 생성되기 때문에 동일한 클래스에서 나온 모든 객체는 자신이 속한 클래스에 대해서 접근할 수 있다.
static 변수는 이런 방식을 통해서 모든 객체가 동일한 데이터를 사용할 수 있는 공유의 개념을 완성시킨다.
cp.) Tip
- 객체의 영향을 받지 않기 때문에 static은 굳이 객체를 통해서 사용할 이유가 없다.
어떤 변수나 메소드 앞에 static이 나온다는 의미는 결과적으로 객체의 데이터나 메소드의 영향을 받지 않고 완벽하게 동일한 공유의 개념을 완성하는 것이다.
따라서 굳이 객페의 레퍼런스를 이용해야 하는 필요성이 떨어진다. 어차피 객체보다는 클래스와 더 가깝기 때문이다.
→ static 클래스 이름. 함수(메소드)
static은 객체마다 다른 데이터를 가지고 동작하는 것을 막고 완벽하게 동일하게 동작하는 것을 보장하기 위해서 사용하기 때문에 굳이 객체를 사용해야 하는 이유가 없으므로 바로 ‘클래스 이름. 변수’나 ‘클래스 이름. 메소드’를 사용할 수 있다.
굳이 객체를 사용하지 않아도 된다는 의미는 객체를 사용하는 기존의 방식도 가능하기는 하다는 뜻이다.
- static이 붙은 메소드를 보면 무조건 ‘객체와 무관하다.’혹은 ‘객체의 영향 받지 않는 메소드’이다.라고 생각하자.
모든 경우에 완벽하게 동일하게 동작해주어야만 한다. 이런 상황에서는 static을 이용해서 경우에 따라서 다르게 동작할 수 없게 하는 것이 정상적인 static의 사용법이다.
→ 실무에서 static을 이용할 것인지 아닌지를 판단하는 데 있어서 가장 중요한 것은 객체마다 데이터를 활용해야 하는 경우인가. 그렇지 않은가 이다.
Integer.parseInt(),Math.random()을 보면 객체가 가진 데이터와는 완전히 무관하다. 그냥 단순히 로직을 묶어둔 존재에 가깝다.
- static은 속도는 빠르지만, 회수되지 않기 때문에 주의 요한다.
static이라는 키워드가 붙은 변수는 가비지 컬렉션의 대상이 아니라는 데에서 시작한다.
static을 클래스 변수라고 말하듯이 static이 붙은 변수는 클래스와 같은 영역에 생기고, 클래스와 동일하게 메모리에서 항상 상주하게 된다. 이 때문에 static으로 어떤 데이터나 객체를 연결해서 사용하게 되면 메모리 회수가 안된다. 실제로 시스템이 가동되면서 점점 느려진다.
5. 예제
class Exam_02_Sub {
private String name;
private double don = 1000.0;
private static float iyul = 0.05f;
// static 초기화 구문(static 초기화: 객체 발생 이전)
static {
iyul = 0.05f;
// 보통 생성자를 선언하여 초기화 하는 것과 같은 구문이다.
// iyul이라는 공간을 0.05f로 초기화 및 선언한다.
// 이렇게 static 선언하면 객체 생성을 하지 않고도 iyul을 사용할 수 있다.
}
// static 메서드: static 필드 컨트롤 목적 즉, static 필드 초기화 및 수정하게 할 목적인 것이다.
public static void setIyul(float iyul) {
Exam_02_Sub.iyul = iyul;
// this.iyul=iyul; this를 넣을 수 없다. static에는 0번째 매개변수가 없다.
// 왜냐하면 static메서드는 모든 객체가 공유하는 메서드이므로 자기자신이라는 것은 있을 수 없다.
// static 메서드는 this.disp();를 할 수 없다. -->this들어가는 것은 static에서 무조건 쓸 수 없다.
// static 메서드 안에서 일반 메서드를 호출하려면 객체 생성를 해야 한다. 바로 다이렉트로 일반 메서드를 사용할 수 없다.
Exam_02_Sub es = new Exam_02_Sub("C", 1000.0, 0.05f);
es.disp();
}
public Exam_02_Sub(String name, double don, float iyul) {
this.name = name;
this.don = don;
Exam_02_Sub.iyul = iyul;
// 같은 클래스 내부에서는 일반 메서드끼리는 호출이 가능하다.
// this.disp();
}
public void disp() {
System.out.println("name : " + this.name);
System.out.println("don : " + this.don);
// 일반 메서드에서 static을 다이렉트로 호출이 가능하다.
System.out.println("iyul : " + Exam_02_Sub.iyul);
}
}
public class Exam_02 {
// 일반 메서드를 main 메서드 안에서 호출하려면 static 메서드로 만들어야 한다. 그렇지 않으면 객체생성후 메서드 호출 요한다.
// 일반 메서드로 만들면 this 때문에 static 메서드에서 호출할 수 없다.
public static void main(String[] ar) {
Exam_02_Sub es = new Exam_02_Sub("A", 1000.0, 0.07f);
es.disp();
System.out.println();
Exam_02_Sub es1 = new Exam_02_Sub("B", 1000.0, 0.03f);
es.disp();
es1.disp();
System.out.println();
Exam_02_Sub es2 = new Exam_02_Sub("C", 1000.0, 0.05f);
es.disp();
es1.disp();
es2.disp();
}
}
class Exam_02_Sub_01 {
private String name;
private double don = 1000.0;
private static float iyul = 0.05f;
//private float iyul = 0.05f;
public Exam_02_Sub_01(String name, double don, float iyul) {
this.name = name;
this.don = don;
Exam_02_Sub_01.iyul = iyul;
}
public void disp() {
System.out.println("name : " + this.name);
System.out.println("don : " + this.don);
System.out.println("iyul : " + Exam_02_Sub_01.iyul);
}
}
public class Exam_02_01 {
public static void main(String[] ar) {
Exam_02_Sub_01 es = new Exam_02_Sub_01("A", 1000.0, 0.07f);
es.disp();
System.out.println("-------------");
Exam_02_Sub_01 es1 = new Exam_02_Sub_01("B", 1000.0, 0.03f);
es.disp();
es1.disp();
System.out.println("-------------");
Exam_02_Sub_01 es2 = new Exam_02_Sub_01("C", 1000.0, 0.05f);
es.disp();
es1.disp();
es2.disp();
}
}
출처: https://devbox.tistory.com/entry/Java-static?category=574549 [장인개발자를 꿈꾸는 :: 기록하는 공간]
'JAVA > Java' 카테고리의 다른 글
[Java] 바이트 기반의 스트림 (0) | 2020.02.18 |
---|---|
[Java] 파일I/O 개요 (0) | 2020.02.18 |
[Java] 제네릭 (0) | 2020.02.18 |
[Java] 컬렉션 프레임워크 (0) | 2020.02.18 |
[Java] 클래스 메서드와 인스턴스 메서드 (0) | 2020.02.12 |
[Java] 클래스멤버와 인스턴스멤버간의 참조와 호출 (0) | 2020.02.12 |
[Java] 변수의 종류 (0) | 2020.02.12 |
[Java] abstract (0) | 2020.02.12 |