TOP
본문 바로가기
프로그래밍 및 기타/Java

[java] 정적 멤버와 Static, 싱글톤

by 기록자_Recordian 2025. 4. 4.
728x90
반응형
이전 내용
 

[java] java 예제 (메소드 오버로딩)

이전 내용 [java] 메소드 선언 : 가변 인수 모를 때 (feat. enhanced for문)이전 내용 [java] java - 클래스, this() 코드이전 내용 [java] 예제 및 풀이 (Switch, 배열 + 반복문)이전 내용 [java] java 예제 및 풀이(s

puppy-foot-it.tistory.com


정적 멤버와 Static

 

정적 멤버: 클래스에 고정된 멤버로서 객체를 생성하지 않고 사용할 수 있는 필드(정적 필드)와 메소드(정적 메소드).

 

정적 필드와 정적 메소드를 선언하려면 필드와 메소드 선언 시 static 키워드를 추가적으로 붙이면 된다.

// 정적 필드
static 타입 필드 [= 초기값];

// 정적 메소드
static 리턴 타입 메소드 (매개변수선언, ...) {...}

 

필드 선언 시에는 인스턴스 필드로 선언할 것인가 아니면 정적 필드로 선언할 것인가의 판단 기준이 필요한데,

  • 객체마다 가지고 있어야 할 데이터 ▶ 인스턴스 필드로 선언
  • 객체마다 가지고 있을 필요가 없는 공용 데이터 ▶ 정적 필드로 선언 

메소드 역시 인스턴스 메소드, 정적 메소드 선언의 판단 기준이 필요하다

  • 인스턴스 필드 포함 ▶ 인스턴스 메소드로 선언
  • 인스턴스 필드 미포함 ▶ 정적 메소드로 선언

 

◆ 정적 멤버 사용

클래스가 메모리로 로딩되면 정적 멤버를 바로 사용할 수 있는데, 클래스 이름과 함께 도트(,) 연산자로 접근

클래스.필드;
클래스.메소드 (매개값, ...);

 

◆ static의 간단한 예

예를 들어, 아래와 같이 String 타입의 name 변수와, int 타입의 member 변수가 있다고 하자.

또한, Person 이라는 생성자가 있는데, 이름을 매개변수로 하고, 이로인해 객체가 생성될 때마다 member가 1씩 추가 된다.

public class Person {
	String name;
	int member;
	
	Person (String name) {
		this.name = name;
		member++;
	}
}

 

그리고 이 생성자를 새로운 인스턴스로 생성하여 멤버수를 출력하면

public class Test {
	public static void main(String[] args) {
		Person p1 = new Person("A1");
		System.out.println(p1.member);
		
		Person p2 = new Person("B1");
		System.out.println(p2.member);
		
	}
}

분명 코드에서는 1씩 증가하도록 했는데, 두 개의 인스턴스가 생겼음에도 1이다. 이럴 때 사용하는 게 바로 static 키워드이다.

우리가 원하는 대로 인스턴스가 추가될 때마다 멤버수가 1씩 추가 되게 하려면 아래와 같이 member를 static 변수로 선언하여 클래스 내의 모든 변수들끼리 공유되도록 하면 된다.

public class Person {
	String name;
	static int member;
	
	Person (String name) {
		this.name = name;
		member++;
	}
}

 

그리고, 마찬가지로 Test 클래스에서 member 변수에 접근하는 코드도 변경해야 하는데, 앞서 접근 방식은 개별 인스턴스에 접근하는 방식으로, member 변수가 static으로 선언되면, 이 변수는 클래스 수준에서 공유되는 속성이기 되기 때문에 클래스의 모든 인스턴스가 동일한 member 변수에 접근해야 한다.

따라서 static 변수에 접근하기 위해서는 클래스 이름을 사용해야 한다.

public class Test {
	public static void main(String[] args) {
		Person p1 = new Person("A1");
		System.out.println(Person.member);
		
		Person p2 = new Person("B1");
		System.out.println(Person.member);
			}
}

[Java의 static 변수 vs 파이썬의 클래스 변수]
Java의 static 변수와 파이썬의 클래스 변수는 모든 인스턴스가 공유하는 변수라는 점에서 유사하다. 접근 방식에서도 유사하나, Java는 static을 명시적으로 선언해야 하고, 클래스 이름으로 접근하는 것이 일반적이다. 반면, 파이썬에서는 클래스 변수를 정의할 때 특별한 키워드를 사용하지 않는다.

 

public class CalStatic {
	static double pi = 3.14159;

	static int plus(int x, int y) {
		return x + y;
	}

	static int minus(int x, int y) {
		return x - y;
	}
}

 

public class CalStaticExam {

	public static void main(String[] args) {
		double result1 = 10 * 10 * CalStatic.pi;
		int result2 = CalStatic.plus(10, 5);
		int result3 = CalStatic.minus(10, 5);

		System.out.println("result1: " + result1);
		System.out.println("result2: " + result2);
		System.out.println("result3: " + result3);

	}
}

 

※ 정적 메소드 선언 시 주의할 점

객체가 없어도 실행된다는 특징 때문에 정적 메소드 선언 시에는 이들 내부에 인스턴스 필드나 인스턴스 메소드를 사용할 수 없으며, this 키워드도 사용이 불가하다.

 

아래의 코드는 컴파일 에러가 발생한다.

class Example {
	int a = 5;

	static void printA() {
		System.out.println(a);
	}

	public static void main(String[] args) {
		printA();

	}
}

 

[컴파일 에러 발생 이유]

 

1. 인스턴스 변수 접근:
printA() 메소드는 static 메소드로 정의되어 있다. 정적 메소드에서는 클래스의 인스턴스(객체)를 생성하지 않고도 호출할 수 있다. 그러나 정적 메소드 내에서는 인스턴스 변수(여기서는 a)에 직접 접근할 수 없다. 이로 인해 System.out.println(a); 부분에서 컴파일 오류가 발생한다.

 

2. 정적 메소드와 인스턴스 변수:
정적 메소드는 클래스의 인스턴스에 의존하지 않기 때문에, 인스턴스 변수에 접근하기 위해서는 인스턴스를 통해 접근해야 한다.

 

이를 해결하기 위해서는 2가지 방법 중 하나를 사용하면 된다.

1. 인스턴스 메소드를 사용하여 a 변수에 접근
2. 정적 변수로 a를 선언.

 

해결1. 인스턴스 메소드를 사용하여 a 변수에 접근

class Example {
	int a = 5;

	static void printA() {
    	    Example exam1 = new Example();
	    System.out.println(exam1.a);
	}

	public static void main(String[] args) {
		printA();
	}
}

 

해결2. 정적 변수로 a 선언

class Example {
	static int a = 5;

	static void printA() {
		System.out.println(a);
	}

	public static void main(String[] args) {
		printA();

	}
}

◆ Q. 아래 코드에서 counter의 값은 어떻게 될까?

class Counter {
	static int counter = 0;

	Counter() { // 생성자
		counter++;
	}

	public static void main(String[] args) {
		Counter c1 = new Counter();
		Counter c2 = new Counter();
		Counter c3 = new Counter();
		System.out.println(Counter.counter);
	}
}

1) 0

2) 1

3) 2

4) 3 

 

정답은 4번 3이다.

  • static int counter = 0;는 클래스에 속하는 정적 변수로, 이 변수는 Counter 클래스의 모든 인스턴스가 공유한다.
  • Counter 인스턴스가 생성될 때마다 counter의 값이 1 증가합니다.
  • Counter c1 = new Counter();를 실행하면 counter는 0에서 1로 증가.
  • Counter c2 = new Counter();를 실행하면 counter는 1에서 2로 증가.
  • Counter c3 = new Counter();를 실행하면 counter는 2에서 3으로 증가.

▶ Counter 클래스의 생성자가 3번 호출되었기 때문에, counter의 값이 3으로 증가


싱글톤

 

싱글톤은 "하나만 존재하는" 특별한 객체(인스턴스)를 만드는 방법이다. 예를 들어, 학교에 한 명의 교장이 있는 것과 비슷한데, 교장은 한 명만 있을 수 있다.

이 특별한 객체는 프로그램의 어디에서든지 사용할 수 있다. 예를 들어, 교장이 언제든지 학생들과 교사들에게 도움을 줄 수 있는 것과 같다.

  • 하나의 객체: 싱글톤 패턴을 사용하면 하나의 객체만 생성되어 사용.
  • 전역 접근: 언제 어디서든 해당 객체에 쉽게 접근해서 사용.

▶ 싱글톤 패턴은 일반적으로 생성자를 private로 설정하여 외부에서 직접 인스턴스를 생성할 수 없게 하고, 내부에서 자신을 생성하여 반환하는 정적 메소드를 제공한다.


◆ 싱글톤 패턴이 필요한 이유

 

1. 유일한 인스턴스 보장
자원 절약: 시스템 내에서 하나의 인스턴스만 필요할 때, 싱글톤 패턴은 그 인스턴스를 한 번만 생성하도록 보장한다. 예를 들어, 데이터베이스 연결 객체나 설정 파일을 관리하는 클래스를 만들 때 유용하다.

 

2. 전역 접근 제공
편리한 접근: 싱글톤 패턴을 사용하면, 해당 클래스의 인스턴스에 전역적으로 접근할 수 있다. 어떤 클래스에서든지 쉽게 참조할 수 있기 때문에, 여러 클래스 간에 공유해야 할 데이터나 기능이 있을 때 편리하다.

 

3. 상태 유지
공유 상태: 싱글톤 객체는 상태(state)를 유지할 수 있다. 예를 들어, 설정 정보나 카운터와 같이 여러 클라이언트에서 공유해야 하는 데이터가 있을 때, 싱글톤 패턴을 사용하면 객체가 가지고 있는 상태를 쉽게 관리할 수 있다.

 

4. 쓰레드 안전한 구현
동시성 문제 예방: 멀티쓰레드 환경에서는 잘 구현된 싱글톤 패턴을 통해 동시에 여러 쓰레드가 인스턴스를 생성하려고 할 때 발생할 수 있는 문제를 방지할 수 있다. 적절한 동기화(synchronization) 메커니즘을 통해 안전하게 사용할 수 있다.

 

5. 코드의 가독성 및 유지보수 향상
코드 정리: 싱글톤을 사용하면 복잡한 클래스 구조를 단순화 할 수 있다. 특정 기능이나 데이터가 한 곳에서만 관리되므로 코드의 가독성이 좋아지고 유지보수 또한 용이해진다.

 

※ 예시: 데이터베이스 연결 관리
예를 들어, 여러 부분에서 데이터베이스에 접근할 필요가 있는 애플리케이션을 생각해 보면, 데이터베이스 연결 객체를 싱글톤으로 구현하게 되면 모든 모듈이 같은 연결을 재사용하여 불필요한 연결 비용을 줄일 수 있다.


◆ 싱글톤 패턴 구현 방법

 

[싱글톤 패턴 구현 단계]

  • 정적 필드 선언: 자신의 클래스 타입의 정적 변수(인스턴스)를 선언.
  • 생성자 정의: 생성자를 private로 설정하여 외부에서 인스턴스를 만들 수 없도록 함.
  • 정적 메소드 정의: 싱글톤 인스턴스를 반환하는 public 정적 메소드를 구현. 이 메소드 내에서 인스턴스가 null인지 확인하고, null일 경우 새로운 인스턴스를 생성.
public class SchoolPrincipal {

    // 유일한 인스턴스를 저장할 정적 변수
    private static SchoolPrincipal instance;

    // 생성자를 private으로 설정하여 외부에서 인스턴스를 생성하지 못하게 함.
    private SchoolPrincipal() {
    }

    // 인스턴스를 얻는 정적 메소드
    public static SchoolPrincipal getInstance() {
        // 최초 호출 시 인스턴스를 생성.
        if (instance == null) {
            instance = new SchoolPrincipal();
        }
        // 항상 같은 인스턴스를 반환.
        return instance;
    }

    // 교장 선생님이 하는 일을 정의할 수 있는 메소드 예시
    public void manageSchool() {
        System.out.println("교장이 학교를 관리하고 있습니다.");
    }
}

 

public class Main {
    public static void main(String[] args) {
        // 교장 선생님의 인스턴스를 얻음.
        SchoolPrincipal principal1 = SchoolPrincipal.getInstance();
        principal1.manageSchool(); // 교장이 학교를 관리.

        // 다시 인스턴스를 얻으려고 시도.
        SchoolPrincipal principal2 = SchoolPrincipal.getInstance();
        
        // 두 인스턴스가 같은지 비교.
        System.out.println(principal1 == principal2); // true 출력
    }
}

 

두 singleton의 주소가 같은지 확인

public class Singleton {
	private static Singleton singleton = new Singleton();

	private Singleton() {
	}

	static Singleton getInstance() {
		return singleton;
	}
}
public class SingletonExample {
	public static void main(String[] args) {
		/*
		 Singleton obj1 = new Singleton(); // 컴파일 에러 
         Singleton obj2 = new Singleton(); // 컴파일 에러
		 */

		Singleton obj1 = Singleton.getInstance();
		Singleton obj2 = Singleton.getInstance();

		if (obj1 == obj2) {
			System.out.println("같은 Singleton 객체입니다.");
		} else {
			System.out.println("다른 Singleton 객체입니다.");
		}
	}
}
같은 Singleton 객체입니다.

[참고]

혼자 공부하는 자바

 


다음 내용

 

[java] 접근 제한자, 클래스 상속

이전 내용 [java] 정적 멤버와 Static, 싱글톤이전 내용 [java] java 예제 (메소드 오버로딩)이전 내용 [java] 메소드 선언 : 가변 인수 모를 때 (feat. enhanced for문)이전 내용 [java] java - 클래스, this() 코드

puppy-foot-it.tistory.com

728x90
반응형