자바의 메모리 구조는 크게 메서드 영역, 스택 영역, 힙 영역 3개로 나눌 수 있다.
<JavaMemoryMain1.java>
package memory;
public class JavaMemoryMain1 {
public static void main(String[] args) {
System.out.println("main start");
method1(10);
System.out.println("main end");
}
static void method1(int m1){
System.out.println("method1 start");
int cal = m1 * 2 ;
method2(cal);
System.out.println("method1 end");
}
static void method2(int m2){
System.out.println("method2 start");
System.out.println("method2 end");
}
}
-> 스택 구조
- 자바는 스택 영역을 사용해서 메서드 호출과 지역 변수(매개변수 포함)를 관리한다.
- 메서드를 계속 호출하면 스택 프레임이 계속 쌓인다.
- 지역 변수(매개변수 포함)는 스택 영역에서 관리한다.
- 스택 프레임이 종료되면 지역 변수도 함께 제거된다.
- 스택 프레임이 모두 제거되면 프로그램도 종료된다.
<Data.java>
package memory;
public class Data {
private int value;
public Data(int value){
this.value = value;
}
public int getValue(){
return value;
}
}
<JavaMemoryMain2.java>
package memory;
public class JavaMemoryMain2 {
public static void main(String[] args) {
System.out.println("main start");
method1();
System.out.println("main end");
}
static void method1(){
System.out.println("method1 start");
Data data1 = new Data(10);
method2(data1);
System.out.println("method1 end");
}
static void method2(Data data2){
System.out.println("method2 start");
System.out.println("data.value = "+ data2.getValue());
System.out.println("method2 end");
}
}
지역 변수는 스택 영역에, 객체(인스턴스)는 힙 영역에 관리되는 것을 확인했다. 이제 나머지 하나가 남았다. 바로 메서드 영역이다. 메서드 영역이 관리하는 변수도 있다. 이것을 이해하기 위해서는 먼저 static 키워드를 알아야 한다. static 키워드는 메서드 영역과 밀접한 연관이 있다.
생성된 데이터들의 갯수세기
<Data1.java>
package static1;
public class Data1 {
public String name;
public int count;
public Data1(String name){
this.name = name;
count++;
}
}
<DataCountMain1.java>
package static1;
public class DataCountMain1 {
public static void main(String[] args) {
Data1 data1 = new Data1("A");
System.out.println("A count=" + data1.count);
Data1 data2 = new Data1("B");
System.out.println("B count=" + data2.count);
Data1 data3 = new Data1("C");
System.out.println("C count=" + data3.count);
}
}
-> 이 프로그램은 당연히 기대한 대로 작동하지 않는다. 객체를 생성할 때 마다 Data1 인스턴스는 새로 만들어진다. 그리고 인스턴스에 포함된 count 변수도 새로 만들어지기 때문이다.(count 는 계속 초기화된다.)
=>인스턴스에 사용되는 멤버 변수 count 값은 인스턴스끼리 서로 공유되지 않는다. 따라서 원하는 답을 구할 수 없다. 이 문제를 해결하려면 변수를 서로 공유해야 한다.
<Counter.java>
package static1;
public class Counter {
public int count;
}
<Data2.java>
package static1;
public class Data2 {
public String name;
public Data2(String name, Counter counter){
this.name=name;
counter.count++;
}
}
<DataCountMain2.java>
package static1;
public class DataCountMain2 {
public static void main(String[] args) {
Counter counter = new Counter();
Data2 data1 = new Data2("A",counter);
System.out.println("A count="+ counter.count);
Data2 data2 = new Data2("B",counter);
System.out.println("B count="+ counter.count);
Data2 data3 = new Data2("C",counter);
System.out.println("C count="+ counter.count);
}
}
-> Data2의 인스턴스는 3개가 생성
=> 하지만 Data2 클래스와 관련된 일인데, Counter 라는 별도의 클래스를 추가로 사용해야 하고, 생성자 매개변수 추가로 생성자와 생성자 호출부분이 복잡해진다.
=> 외부의 도움을 받지 않는 방법?(Counter 메서드를 따로 만들지 않는 법?) =static
특정 클래스에서 공용으로 함께 사용할 수 있는 변수를 만들 수 있다면 편리할 것이다.
static 키워드를 사용하면 공용으로 함께 사용하는 변수를 만들 수 있다.
<Data3.java>
package static1;
public class Data3 {
public String name;
public static int count;
public Data3(String name){
this.name = name;
count++;
}
}
멤버 변수에 static 을 붙이면 static변수, 정적변수 또는 클래스변수라 한다.
- 객체가 생성되면 생성자에서 정적 변수 count 의 값을 하나 증가시킨다.
<DataCountMain3.java>
package static1;
public class DataCountMain3 {
public static void main(String[] args) {
Data3 data1 = new Data3("A");
System.out.println("A count="+ Data3.count);
Data3 data2 = new Data3("B");
System.out.println("B count="+ Data3.count);
Data3 data3 = new Data3("C");
System.out.println("C count="+ Data3.count);
}
}
-> Data3.count 는 마치 클래스에 직접 접근하는 것 처럼 느껴짐
=>static 변수는 쉽게 이야기해서 클래스인 붕어빵 틀이 특별히 관리하는 변수이다. 붕어빵 틀은 1개이므로 클래스 변수도 하나만 존재한다. 반면에 인스턴스 변수는 붕어빵인 인스턴스의 수 만큼 존재한다.
public class Data3 {
public String name;
public static int count;
public Data3(String name){
this.name = name;
count++;
}
}
멤버변수는 static의 유무로 두가지로 나뉨
static 은 정적변수다. 왜냐하면 인스턴스변수는 힙영역에서 동적으로 제거되고 생성되지만 static은 프로그램이 시작하면서 종료될때까지 존재하기 때문이다.
static 변수는 클래스를 통해 바로 접근할 수도 있고, 인스턴스를 통해서도 접근할 수 있다.
<DataCountMain3.java>
package static1;
public class DataCountMain3 {
public static void main(String[] args) {
Data3 data1 = new Data3("A");
System.out.println("A count="+ Data3.count);
Data3 data2 = new Data3("B");
System.out.println("B count="+ Data3.count);
Data3 data3 = new Data3("C");
System.out.println("C count="+ Data3.count);
//추가
//인스턴스를 통한 접근
Data3 data4 = new Data3("D");
System.out.println(data4.count);
//클래스를 통한 접근
System.out.println(Data3.count);
}
}
인스턴스를 통한 접근은 권장하지 않는다.
왜냐? 가져다 쓰는 입장에서 data4.count는 인스턴스변수인가? 하는 의문이 들 수 있지만 Data3.count는 누가봐도 static 이네!가 가능해서..
<DecoUtil1.java>
package static2;
public class DecoUtil1 {
public String deco(String str){ //deco 메서드는 멤버변수도 없고 단순히 기능만 제공
String result = "*" + str + "*";
return result;
}
}
<DecoMain1.java>
package static2;
public class DecoMain1 {
public static void main(String[] args) {
String s = "hello JAVA";
DecoUtil1 utils = new DecoUtil1();
String deco = utils.deco(s);
System.out.println("before: "+s);
System.out.println("after: "+deco);
}
}
위의 코드에서 deco() 메서드는 멤버변수도 없고 기능만 제공하는 메서드이다. 그런데 굳이 Main 코드에서 정의하고 호출까지 해야할까?
<DecoUtil2.java>
package static2;
public class DecoUtil2 {
public static String deco(String str){ //정적 메서드 : 인스턴스 생성 없이 클래스로 바로 호출 가능
String result = "*" + str + "*";
return result;
}
}
<DecoMain2.java>
package static2;
public class DecoMain2 {
public static void main(String[] args) {
String s = "hello JAVA";
String deco = DecoUtil2.deco(s); //static 때문에 바로 호출 가능
System.out.println("before: "+s);
System.out.println("after: "+deco);
}
}
메서드에 static이 붙음으로써 정적메서드가 된다. 이렇게 하면 인스턴스 생성없이 클래스명으로 바로 호출이 가능하다. -> 불필요한 객체생성이 없어짐
<DecoData.java>
package static2;
public class DecoData {
private int instanceValue;
private static int staticValue;
public static void staticCall(){
// instanceValue ++; // 인스턴스 변수 접근 - 불가능
// instanceMethod(); // 인스턴스 메서드 접근 - 불가능
staticValue++; //정적 변수에 접근, 호출 가능
staticMethod();
//static 은 static만 호출가능
}
public void instanceCall(){
instanceValue++;
instanceMethod();
staticValue++;
staticMethod();
//instance는 instance, static 호출가능
}
public static void staticCall(DecoData data){
data.instanceValue++;
data.instanceMethod();
//static에서 instance 호출 가능하지만 여기 있는 data는 외부에 생긴 새로운 참조값
}
private void instanceMethod(){
System.out.println("instanceValue=" + instanceValue);
}
private static void staticMethod(){
System.out.println("staticValue=" + staticValue);
}
}
<DecoDataMain.java>
package static2;
public class DecoDataMain {
public static void main(String[] args) {
System.out.println("1.정적 호출");
DecoData.staticCall();
System.out.println("2.인스턴스 호출-인스턴스");
DecoData decoData = new DecoData();
decoData.instanceCall();
System.out.println("3.인스턴스 호출-정적");
DecoData.staticCall();
System.out.println("3.정적 호출-외부참조");
DecoData.staticCall(decoData);
}
}
객체 생성이 필요 없이 메서드 호출만으로 필요한 기능을 수행할 때 사용, 유틸리티성 메서드
둘다 결과적으로는 정적 메서드에 접근하지만 위의 것은 코드를 읽을 때 마치 인스턴스메서드에 접근하는 것처럼 오해 할 수 있음
정적메서드를 자주 사용한다면 import 기능 사용
Alt+Enter
import static static2.DecoData.*; // 모든 정적메서드에 적용
정적메서드인 main()메서드가 같은 클래스에서 호출하는 메서드도 정적메서드로 선언해야함.
package static2;
import oop1.ValueData;
public class ValueDataMain {
public static void main(String[] args) { //static이기 때문에
ValueData valueData = new ValueData();
add(valueData);
}
static void add(ValueData valueData) {//static 으로 선언
valueData.value++;
System.out.println("숫자 증가 value=" + valueData.value);
}
}
final은 이름 그대로 끝! 이라는 뜻, 변경이 불가능
package final1;
public class FinalLocalMain {
public static void main(String[] args) {
//final 지역변수
final int data1;
data1 = 10; //최초 한번만 할당 가능
// data2 = 20; //컴파일 오류
//final 지역변수2
final int data2=10;
// data2 =20;//컴파일 오류
method(10);
}
static void method(final int parameter){
// parameter = 10; //컴파일 오류 , 메서드 내에서 매개변수를 줄 수 없다.
}
}
package final1;
public class ConstructInit {
final int value; // 이것만 하면 에러 남 -> 아래처럼 생성자를 통해서 넣어야함
public ConstructInit(int value){
this.value = value;
}
}
package final1;
public class FieldInit {
static final int CONST_VALUE = 10; //static final 대문자 -> 관례
// final int value = 10; // 초기 값을 경우에는 아래처럼 생성자를 통해서 할당이 불가
// public FieldInit(int value){
// this.value = value;
// }
final int value = 10;
}