본문 바로가기

Java 기본 문법 - 참조 서적 [이것이 자바다 - 한빛미디어]/4. 객체지향 프로그래밍

7. Java 자바 - 정적 멤버와 static, 싱글톤(singleton)

정적 멤버와 static

 

정적 (static) : '고정된' , 정적 멤버는 클래스에 고정된 멤버

 

객체를 생성하지 않고 사용할 수 있는 필드와 메소드를 말한다.

(각각 정적 필드, 정적 메소드라고 부른다.)

 

정적 멤버는 객체(인스턴스)에 속한 멤버가 아니라 클래스에 소속된 멤버이기 때문에

클래스 맴버이라고도 한다.

 

1. 정적 멤버 선언

 

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

 

public class 클래스 {
    //정적 필드
    static 타입 필드 [= 초기값]
    
    //정적 메소드
    static 리턴타입 메소드( 매개변수선언1, . . . ) { . . .  } 

 

정적 필드와 정적 메소드는 클래스에 고정된 멤버이기 때문에,

클래스 로더가 클래스(바이트 코드)를 로딩해서 메소드 메모리 영역에 적재할 때, 클래스 별로 관리된다.

따라서 클래스의 로딩이 끝나면 바로 사용할 수 있다.

 

정적 필드, 정적 메소드는 클래스 로딩이 끝나면 바로 사용 가능

 

 

필드를 선언 시 인스턴스 필드 / 정적 필드 선언 판단 기준

 

- 객체마다 가지고 있어야할 데이터 : 인스턴스 필드  (각 객체마다 색깔 값 등..)

- 객체마다 가지고 있을 필요성 없는 공용적인 데이터 : 정적 필드

 

ex) Calculator 클래스의 파이 (공용 데이터)

 

public class Calculator {
    String color;                 // 계산기 별 다른 색깔 가능
    static double pi = 3.14159;   // 계산기에서 사용하는 파이값은 모두 동일
}

 

 

메소드 선언 시 인스턴스 메소드 / 정적 메소드 선언 판단 기준

 

- 인스턴스 필드를 이용해서 실행 : 인스턴스 메소드

- 인스턴스 필드 이용하지 않음 : 정적 메소드

 

ex) Calculator 클래스의 덧셈, 뺄셈 기능

외부에서 주어지는 매개값을 가지고 수행하므로 정적 메소드가 좋다.

 

인스턴스 필드인 색깔 변경 메소드 : 인스턴스 메소드로 선언 한다.

 

public class Calculator {
    String color;   // 인스턴스 필드
    void setColor(String color) { this.color = color; }  // 인스턴스 메소드
    static int plus(int x, int y) { return x + y; }      // 정적 메소드
    static int minus(int x, int y) { return x - y; }     // 정적 메소드
}

 

매개 변수에 따라 객체들이 다른 값을 갖는 필드 및 메소드는 : 인스턴스

매개 변수에 큰 영향을 받지 않는다 : 정적

 

 

2. 정적 멤버 사용

 

클래스가 메모리로 로딩되면 정적 멤버를 바로 사용할 수 있다.

 

클래스 이름과 함께 도트 ( . ) 연산자로 접근할 수 있다. (인스턴스는 객체 참조 변수 이름)

 

 

클래스.필드;

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

 

ex) Calculator 클래스

 

public class Calculator {
    static double pi = 3.14159;
    static int plus(int x, int y) { . . . }
    static int minus(int x, int y) { . . . }
}

 

정적 필드 pi, 정적 메소드 plus( ), minus( ) 는 아래와 같이 사용할 수 있다.

 

double result1 = 10 * 10 * Calculator.pi;
int result2 = Calculator.plus(10, 5);
int result3 = Calculator.minus(10, 5);

 

 

정적 필드와 정적 메소드는 원칙적으로 클래스 이름으로 접근해야 하지만,

 

아래와 같이 객체 참조 변수 이름으로도 접근이 가능하다.

 

Calculator myCalcu = new Calculator();  //객체 생성

double result1 = 10 * 10 * myCalcu.pi;
int result2 = myCalcu.plus(10, 5);
int result3 = myCalcu.minus(10, 5);

 

하지만, 정적 요소는 클래스 이름으로 접근하는 것이 좋다.

(이클립스에서는 객체 참조 변수로 접근 시 경고 표시가 나타난다.)

 

 

ex) 정적 멤버 사용

Calculator.java

public class Calculator {
    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;
    }
}    

 

CalculatorExample.java

public class CalculatorExample {
    public static void main(String[] args) {
        double result1 = 10 * 10 * Calculator.pi;
        int result2 = Calculator.plus(10, 5);
        int result3 = Calculator.minus(10, 5);
        
        System.out.println("result1 : "+ result1);
        System.out.println("result2 : "+ result2);
        System.out.println("result3 : "+ result3);
    }
}

 

 

3. 정적 초기화 블록

 

정적 필드는 보통 필드 선언과 동시에 초기값을 주는 것이 보통이다.

 

static double pi = 3.14159;

 

하지만, 계산이 필요한 초기화 작업이 존재할 수 있다.

 

인스턴스 필드는 생성자에서 초기화되지만, 정적 필드는 객체 생성 없이 사용해야 하므로

초기화 작업을 할 수가 없다.

(생성자는 객체 생성 시에만 실행되기 때문!)

 

따라서 정적 필드의 복잡한 초기화 작업을 위해서 정적 블록 (static block)을 제공한다.

 

정적 블록의 형태

 

static {
    . . . 
}

 

정적 블록 특징 

 

- 정적 블록은 클래스가 메모리로 로딩될 때 자동으로 실행된다.

 

- 정적 블록은 클래스 내부에 여러 개가 선언되어도 상관 없다.

 

- 클래스가 메모리로 로딩될 때 선언된 순서대로 실행된다.

 

ex) Television : 3 개의 정적 필드 가짐, company, model은 선언 시 초기값을 주었고,

info 는 초기화 하지 않았다.

info 필드는 정적 블록에서 company와 model 필드값을 서로 연결해서 초기 값으로 설정한다.

 

Television.java

public class Television {
    static String company = "Samsung";
    static String model = "LCD";
    static String info;   // 초기화 되지 않음
    
    static {
        info = company + "-" + model;   // 블록 내에서 초기화 가능
    }
}

 

TelevisionExample.java

public class TelevisionExample {
    public static void main(String[] args) {
        System.out.println(Television.info);
    }
}

 

 

정적 메소드, 정적 블록 선언 시 주의할 점

 

객체가 없어도 실행된다는 특징!

 

이들(정적 메소드, 정적 블록) 내부에 인스턴스 필드나 인스턴스 메소드는 사용할 수 없다.

(객체가 생성되지 않았기 때문!!)

 

또한 객체 자신의 참조인 this 키워드 사용도 역시 불가능하다.

 

아래 코드는 컴파일 오류가 발생한다.

 

public class ClassName {
    // 인스턴스 필드와 메소드
    int field1;
    void method1() { . . . }
    
    // 정적 필드와 메소드
    static int field2;
    static void method2() { . . . }
    
    // 정적 블록
    static {
        field1 = 10;    // 컴파일 에러 : 인스턴스 필드
        method1();      // 컴파일 에러 : 인스턴스 메소드
        field2 = 10;
        method2();
    }
    
    // 정적 메소드
    static void method3 {
        this.field1 = 10;    // 컴파일 에러 : 인스턴스 필드
        this.method1();      // 컴파일 에러 : 인스턴스 메소드
        field2 = 10;
        method2();
    }
}

 

만약 정적 메소드와 정적 블록에서 인스턴스 멤버를 사용하려면 아래와 같이

객체를 먼저 생성하고, 참조 변수로 접근하면 가능하다.

 

static void Method3() {
    ClassName obj = new ClassName();  // 객체 생성
    obj.field1 = 10;   // 인스턴스 필드 사용 가능
    obj.method1();     // 인스턴스 메소드 사용 가능
}

 

 

* main 메소드

 

main( ) 메소드도 동일한 규칙이 적용된다.

static (정적) 메소드 이므로 객체 생성 없이

인스턴스 필드와 인스턴스 메소드는 main( ) 메소드 안에서 사용 불가 하다.

 

아래 코딩은 컴파일 에러 발생!

 

public class Car {
    int speed;
    
    void runs() { . . . }
    
    public static void main(String[] args) {
        speed = 60;   // 컴파일 에러
        run();        // 컴파일 에러
    }
}

 

아래와 같이 main( ) 메소드를 수정해야 한다. (객체를 생성해야 함)

 

public class Car {
    int speed;
    
    void run() { System.out.println(speed + " 으로 달립니다."); }
  
    public static void main(String[] args) {
        Car myCar = new Car();   // 객체 생성
        
        myCar.speed = 60;
        myCar.run();
    }
}

 


 

싱글톤

 

전체 프로그램에서 단 하나의 객체만 만들도록 보장해야 하는 경우 발생

 

객체가 단 하나만 생성된다 해서 "싱글톤(Singleton)" 이라 한다.

 

싱글톤을 만드려면 클래스 외부에서 new 연산자로 생성자를 호출할 수 없도록 막아야 한다.

(생성자를 호출한 만큼 객체가 생성되기 때문)

 

생성자를 외부에서 호출할 수 없도록 하려면 생성자 앞에 private 접근 제한자를 붙인다.

(접근 제한자는 나중에!!)

 

자신의 타입인 정적 필드를 하나 선언하고, 객체를 생성해 초기화한다.

 

클래스 내부에서는 new 연산자로 생성자 호출이 가능하다.

따라서 정적 필드도 private 접근 제한자를 붙여서 외부에서 필드값을 변경하지 못하도록 막는다.

 

대신 외부에서 호출할 수 있는 정적 메소드인 getInstance( )를 선언하고 정적 필드에서

참조하고 있는 자신의 객체를 리턴해준다.

 

ex) 싱글톤 코드

public class 클래스 {
    // 정적 필드
    private static 클래스 singleton = new 클래스();  // singleton 객체 생성 (단 하나)
    
    // 생성자
    private 클래스() { }   // 외부 클래스에서 객체 생성 불가
    
    // 정적 메소드
    static 클래스 getInstance() {
        return singleton;
    }
}

 

외부에서 객체를 얻는 유일한 방법은 getInstance( ) 메소드를 호출하는 것이다.

 

getInstance( ) 메소드는 단 하나의 객체(singleton)만 리턴하기 때문에

아래 코드에서 변수1, 변수2는 동일한 객체를 참조한다.

 

클래스 변수1 = 클래스.getInstance();
클래스 변수2 = 클래스.getInstance();

 

같은 싱글톤 객체를 참조한다.

 

ex) Singleton.java : 싱글톤 예제

public class Singleton {
    // 정적 필드
    private static Singleton singleton = new Singleton();
    
    // 생성자
    private Singleton() { }
    
    // 정적 메소드
    static Singleton getInstance() {
        return singleton;
    }
}

 

SingletonExample.java : 싱글톤 객체

public class SingletonExample {
    public static void main(String[] args) {
        /*
        Singleton obj1 = new Singleton();   // 컴파일 에러 : 객체 생성 불가
        */
        
        Singleton obj1 = Singleton.getInstance();  // 생성되어있는 객체 반환되어 참조됨
        Singleton obj2 = Singleton.getInstance();
        
        if(obj1 == obj2) {
            System.out.println("같은 Singleton 객체 입니다.");  // 같은 객체 참조된다.
        } else {
            System.out.println("다른 Singleton 객체 입니다.");
        }
    }
}