[Java] Static 사용법(feat. 메모리 영역, @Test, 특징, 문제 출제)

cielo ru·2024년 8월 27일
0

Spring

목록 보기
8/9
post-thumbnail

➰ 개요

자바를 사용하면 static 변수, static 클래스를 자주 볼 수 있다.

예를 들어 메소드 호출 시 에러가 나면 static 변수를 추가하라는 메시지 창이 나와서 에러를 고치기 위해서 단순히 static 을 추가해 코드를 작성했었다.

Cannot make a static reference to the non-static method info() from the type BasicSyntax3

Static 이 '정적' 라는 의미는 알고 있었지만 정확히 어떤 의미를 가지고 있는지 모르고 사용했었다. 에러가 나는데 왜 static 클래스로 만들어서 불러야 하는건지, 왜 메모리 에러가 나는지 몰랐다.

그래서 static 이 뭔데? 왜 써야하는건데?😂

늦었지만 이제라도 의미를 알고 제대로 활용하고자 static 에 대해서 정리하고, static 관련 문제도 직접 출제하고 풀어봄으로써 완벽하게 이해해보고자 한다.

➰ Static이란?

정적(static)은 '정적인, 고정된'이라는 뜻을 가지고 있다.

Static이라는 키워드를 사용하여 Static변수와 Static 메소드를 만들 수 있는데, 이들은 객체에 소속되는 것이 아닌 클래스에 고정되어 있다.

그렇기에 클래스 로더가 클래스 실행 명령어 시작하자마자 class byte code 메모리에 저장 (적재, 로딩)되고, 클래스별로 관리된다. 따라서 클래스의 로딩이 끝나는 즉시 바로 사용할 수 있다.

➿ Static 특징

  1. 메모리에 고정적으로 할당된다.

  2. 객체 생성 없이 사용할 수 있다.

    • 인스턴스 생성 후 호출도 가능하지만 지양하고 있다.
  3. 유틸리티 관련 함수를 만드는데 유용하게 사용된다.

  4. 프로그램이 시작되면 메모리의 static 영역에 적재되고, 프로그램이 종료될 때 해제된다.

  5. Static 메서드 내에서는 인스턴스 변수를 사용할 수 없다.

➰ Static 생성시점 및 메모리 할당

Static 키워드를 통해 생성된 정적멤버들은 Heap영역이 아닌 Static영역에 할당된다.

🌱 메모리 영역?
메모리 공간은 코드(code) 영역, 데이터(data) 영역, 힙(heap) 영역, 스택(stack) 영역으로 구성되어 있다. Static 키워드를 통해 생성되지 않은 변수와 메소드들은 객체가 생성될 때마다 각각의 객체마다 독립적으로 존재해야 하기 때문에 Heap 영역에 할당된다.
Static으로 선언된 것들은 클래스 로드 시점에 메모리에 한 번만 할당되면 되기 때문에 데이터 영역(static 영역이 속해있음)에 할당된다.

static 단점

Static 영역에 할당된 메모리는 모든 객체가 공유하므로, 어느 객체에서나 동일한 값을 참조할 수 있다.
하지만 이 메모리는 Garbage Collector의 관리 대상이 아니기 때문에, 프로그램이 종료될 때까지 해제되지 않고 계속 유지된다. 그래서 Static을 너무 많이 사용하면 메모리가 낭비되어 성능이 저하될 수 있다.
Static은 필요할 때만 신중하게 사용하는 것이 중요하다.

Garbage Collector(GC)?
GC는 사용하지 않는 메모리를 자동으로 정리해주는 기능이다.
일반적으로 객체를 생성할 때 메모리가 할당되는데, 더 이상 그 객체를 참조하지 않거나 필요하지 않을 때도 메모리는 계속 점유될 수 있다. 이런 불필요한 메모리(가비지)를 자동으로 해제해주는 것이 바로 Garbage Collector의 역할이다.
GC 덕분에 개발자가 직접 메모리 관리를 하지 않아도 된다.

*** Static 변수는 클래스와 함께 메모리에 할당되어 프로그램이 종료될 때까지 유지되므로, GC의 관리 대상이 아니다.

➰ 코드 풀이

static 문제를 풀면서 잘 이해했는지 확인해봤다.

➿ 문제

package step01;

public class BasicSyntax3{
	static String name ="원우";
	int age =21;
	
	static int no1 = 0;
	int no2 = 0;
	
	BasicSyntax3(){
		no1 = no1+1;
		no2 = no2+1;
	}
	
	static void info() {
		String name2 = "호시";
		int age2 = 22;
		System.out.println(name+" "+name2);
	}

	
	public static void main(String[] args) {
		// 1. 현 코드가 구현된 클래스 내에서 static 메소드 검색해서 호출 
		info(); 
		
		// 2. 외부에서 static 메소드 호출하는 문법
		BasicSyntax3.info();
		
		//3. static 변수와 non-static변수 차이점 확인
		BasicSyntax3 bs1 = new BasicSyntax3(); 
		BasicSyntax3 bs2 = new BasicSyntax3(); 
		System.out.println(bs1.no1 + " "+ no1); 
		System.out.println(bs2.no1 + " "+ no1);
		System.out.println(bs1.no2 + " " +bs2.no2);
		
		//4. 객체를 통해서 멤버변수 및 메소드 호출
		BasicSyntax3 c = new BasicSyntax3();
		System.out.println(c.name);
		c.info() ;
		
        //5. Static 으로 선언된 변수 확인
		name = "fisaman";
		System.out.println(c);
		System.out.println(c.name);
		
		//6. Math 함수
		int v1 = Math.max(10, 20);
		System.out.println(v1);
	}
}

➿ 정답

➿ 풀이

1. 현 코드가 구현된 클래스 내에서 static 메소드 검색해서 호출

info(); 
  • static 메소드인 info()는 객체 생성 없이 클래스 내에서 static 메소드를 직접 호출할 수 있다.

  • static String name = "원우";: 클래스가 로딩될 때 메모리에 할당되며, 모든 객체가 공유한다.

-> 출력 결과: 원우 호시

2. 외부에서 static 메소드 호출하는 문법

BasicSyntax3.info();
  • 객체를 생성하지 않고 클래스명으로 접근하여 static 메소드를 호출한다.

-> 출력 결과: 원우 호시

3. static 변수와 non-static 변수 차이점 확인

BasicSyntax3 bs1 = new BasicSyntax3(); 
BasicSyntax3 bs2 = new BasicSyntax3(); 
System.out.println(bs1.no1 + " " + no1); 
System.out.println(bs2.no1 + " " + no1);
System.out.println(bs1.no2 + " " + bs2.no2);
  • no1 (static 변수): 클래스 로딩 시 메모리에 할당되어, 모든 객체가 동일한 값을 공유한다. 생성자 호출 시 no1이 두 번 증가하므로 최종값은 2이다.
    ** 📍 이부분을 많이 틀렸을거 같다.

  • no2 (non-static 변수): 객체마다 독립적으로 존재하며, 각 객체 생성 시 1로 증가한다. 두 객체의 no2는 모두 1이다.

-> 출력 결과: 2 2, 2 2, 1 1

4. 객체를 통해 멤버변수 및 메소드 호출

BasicSyntax3 c = new BasicSyntax3();
System.out.println(c.name);
c.info();
  • name 변수는 static이므로 클래스명으로 접근 가능하지만, 객체를 통해서도 접근할 수 있다. (권장되는 방법은 아니다)

  • info() 메소드는 static이지만 객체를 통해 호출할 수 있다.

-> 출력 결과: 원우, 원우 호시

5. Static으로 선언된 변수 확인

name = "fisaman";
System.out.println(c);
System.out.println(c.name);
  • name 변수는 static이기 때문에 모든 객체에서 동일한 값을 참조한다.
  • name 값을 "fisaman"으로 변경하면, 모든 인스턴스가 이를 공유한다.

-> 출력 결과: 객체의 메모리 주소(c.toString()의 결과), fisaman

6. Math 함수

int v1 = Math.max(10, 20);
System.out.println(v1);
  • Math.max는 static 메소드이기 때문에 객체를 생성하지 않고 호출할 수 있다.
    • Math 내 데이터가 중요한 것이 아닌 max값 산출하는 메서드 기능이 중요!
      API 설계자도 사용자들이 주는 값 받고 비교하고 반환하고 번거로웠다.
      즉, Math 객체 생성할 이유와 필요가 없어서 static으로 제시

-> 출력 결과: 20

➰ Static 문제1

Static 변수 이해하고자 직접 낸 문제입니다.

package step01;

public class Airplane {
	String name;
	static int total = 0;

	public Airplane(String name) {
		this.name = name;
		total = total+1;
	}

	static void showTotalCars() {
		System.out.println("구매한 비행기 수: " + total);

	}

	public static void main(String[] args) {
		Airplane airplane1 = new Airplane("boeing486");
		System.out.println("비행기이름 : " + airplane1.name);

		Airplane.total = 2938;
		System.out.println(airplane1.name + "의 total : " + airplane1.total);

		Airplane airplane2 = new Airplane("airbus127");
		System.out.println("비행기이름 : " + airplane2.name);

		Airplane airplane3 = new Airplane("wooriair98");
		System.out.println("비행기이름 : " + airplane3.name);

		Airplane.showTotalCars();
	}

}

결과

비행기이름 : boeing486
boeing486의 total : 2938
비행기이름 : airbus127
비행기이름 : wooriair98
구매한 비행기 수: 2940

➰ Static 문제2

문제와 정답이 코드 주석에 모두 있습니다.

package step01;

/*
 * 1. 코드를 확인하여 문법적으로 오류가 있는 부분을 모두 찾아 주석처리하고 그 이유를 작성하시오
 * 2. 코드의 실행 결과를 작성하시오
 */

public class PaperTest {
	
	int A;
	static int B;
	int C;
	String S = "hi";
	
	public PaperTest() {
		A++;
		B++;
		--C;
		++B;
	};
	
	static void func1() {
		System.out.println("func1 is called!");
	}
	
	void func2() {
		System.out.println("func2 is called!");
	}

	public static void main(String[] args) {
		
		PaperTest p1 = new PaperTest();
		System.out.println("B is " + B); //B is 2
		System.out.println("d1.C is " + p1.C); //d1.C is -1
		
		PaperTest p2 = new PaperTest();
		System.out.println("B is " + B); //B is 4
		System.out.println("d2.A is " + p2.A);//d2.A is 1
		
		PaperTest p3 = new PaperTest();
		System.out.println("d3.B is " + p3.B);//d3.B is 6
		
//		System.out.println(S); 멤버변수
		System.out.println(p3.S);
		
		func1();
		p1.func1();
//		func2(); static 필요
		p3.func2();
		int result = p1.A + p2.B + p3.C;
		System.out.println("A + d2.B + d3.C = " + result); //A + d2.B + d3.C = 6
		
	}

}

➰ (추가) Test 시 static 변수

@Test 사용 시 static 없이도 함수 호출이 가능하다.

위에서 실행한 코드를 보면 당연히 되지 않아야 하는게 맞는데 @Test 에서는 함수 호출이 가능했다. 이게 가능한 일인가?🤔

코드를 보면 running()은 static으로 선언되어 있지 않고, 테스트 코드에서 해당 함수를 객체 생성도 안하고 호출이 가능했다.

혼란스러웠지만 이유를 찾았다.

💡 왜 가능할까?

@Test 사용시 static 없이도 함수 호출이 가능한 이유는 JUnit은 테스트 메소드를 실행하기 전에 매번 새로운 인스턴스를 생성하여 메소드를 호출하기 때문이다. 좀 더 쉽게 말하면 테스트의 독립성과 상태 격리를 보장하며, 개발 과정에서 효율적인 테스트 작성이 가능하도록 설계된 예외적인 기능이다.

즉, 위와 같은 이유로 @Test 사용 시 예외적으로 static 없이도 함수 호출만 가능할 수 있도록 하였다. 또한 Test 애노테이션은 개발단계에서만 사용하기 때문에 이러한 호출을 예외적으로 허용하도록 되었다.

➰ 참고

profile
Cloud Engineer & BackEnd Developer

0개의 댓글