Java 파트6-5. 인스턴스 멤버와 정적 멤버

1. 시작하기 전에


인스턴스 멤버

  • 객체 마다 가지고 있는 멤버
    • 인스턴스 필드 : 힙 영역의 객체 마다 가지고 있는 멤버, 객체마다 다른 데이터를 저장
    • 인스턴스 메소드 : 객체가 있어야 호출 가능한 메소드, 클래스 코드(메소드 영역)에 위치하지만, 이해하기 쉽도록 객체 마다 가지고 있는 메소드라고 생각해도 된다.

정적 멤버

  • 객체와 상관없이 클래스당 하나씩 생기는 멤버 -> 동일한 클래스의 모든 객체들에 의해 공유
    • 정적 필드 및 상수 : 객체 없이 클래스만으로도 사용 가능한 필드
    • 정적 메소드 : 객체가 없이 클래스만으로도 호출 가능한 메소드


2. 인스턴스 멤버


객체를 생성한 후 사용할 수 있는 필드와 메소드

// 클래스 파일
public class Car{
    // 필드
    int gas;

    // 메소드
    void setSpeed(int speed){

    }
}

// 메인 파일
Car myCar = new Car(); // 객체 생성
myCar.gas = 10; // 인스턴스 필드 사용
myCar.setSpeed(60) // 인스턴스 메소드 사용


3. 정적 멤버와 static


  • 클래스에 고정된 멤버로서 객체 생성하지 않고, 사용할 수 있는 필드와 메소드
  • static과 함께 선언
  • 메인 실행 파일에서 객체 생성 없이 도트연산자로 접근하여 사용

인스턴스 필드가 있는 클래스의 객체를 생성하면 객체마다 인스턴스 필드에 새로운 메모리를 할당한다. 하지만 static 즉 정적 필드의 경우 클래스 자체에 붙어있는 것으로 아무리 객체를 많이 생성하더라도 메모리는 한 번만 할당된다. 즉, 정적 필드의 경우 해당 클래스의 모든 객체는 단 하나의 메모리를 참조하고 있는 것이다.

// 기본
// 클래스 파일
public class 클래스명{
    // 정적 필드
    static 타입 변수명;

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

    }
}

// 예시
// 클래스 파일
public class Calculator{
    static double pi = 3.14;
    static int plus (int x, int y){

    }    
}
// 메인 파일
double result = 10 * 10 * Calculator.pi; // 객체 생성 없이 사용 가능
int result2 = Calculator.plus(10,5); // // 객체 생성 없이 사용 가능


static 활용

자바에는 어떤 변수나 함수도 클래스 바깥에 존재할 수가 없다. 자바의 캡슐화 원칙 때문이다. 따라서 모든 클래스에서 공유하는 전역 변수, 전역 함수에 대한 선언은 static을 이용한다.

public class Math{
    public static int INF = (int)1e9;
    public static int abs(int a);
}

// main에서 호출
Math.INF 
Math.abs(5)


주의점

static 메서드는 객체 없이도 존재하기 때문에 this 사용이 불가능하고 static 멤버에만 접근할 수 있다. 이러한 문제는 static메서드, 필드가 객체 없이도 사용할 수가 있어야만 하기 때문에 생기는 문제다.


4. 멤버 선택 기준


  • 객체마다 다를 수 있는 필드값 -> 인스턴스 필드로 선언
  • 객체마다 다를 필요 없는 값 -> 정적 필드로 선언
  • 메소드 블록에 인스턴스 필드 또는 인스턴스 메소드를 사용할 경우 -> 인스턴스 메소드로 선언
  • 메소드 블록에 인스턴스 멤버(필드,메소드)를 사용하지 않을 경우(매개인자, 메소드내 변수만 사용) -> 정적 메소드로 선언

정적 메소드 선언 시 주의점

  • 정적 메소드 선언 시 그 내부에 인스턴스 필드 및 메소드 사용 불가
  • 정적 메소드 선언 시 그 객체 자신 참조인 this 키워드 사용 불가
  • 정적 메소드에서 인스턴스 멤버를 사용하려는 경우 -> 객체 우선 생성 후 참조 변수로 접근
public class ClassName{
    // 인스턴스 필드와 메소드
    int field1;
    void method1(){

    }
    // 정적 필드와 메소드
    static int field2;
    static void method2(){

    }
    // 정적 메소드
    static void method3{
        this.field1 =10; // 컴파일 에러
        this.method1(); // 컴파일 에러

        field2=10; // 가능
        method2(); // 가능
    }

    // 정적 메소드에서 인스턴스 멤버 사용
    static void method3(){
        ClassName obj = new ClassName(); // 객체 우선 생성
        obj.field1 = 10; // 도트연산자로 접근해서 사용
        obj.method1();
    }
}


5. 싱글톤


  • 전체 프로그램에서 단 하나의 객체만 만들도록 보장하는 코딩 기법
  • 작성법
    • 클래스 외부에서 new 연산자를 통해 생성자를 호출하는 것을 불가하도록 private 접근 제한자 사용
    • 자신의 타입인 정적 필드를 선언한 후 자신의 객체를 생성해 초기화
    • 외부에서 호출할 수 있는 getInstance() 선언
    • 정적 필드에서 참조하는 자신의 객체 리턴
  • 정리하자면, 싱글톤은 단 하나의 객체만 만들도록 하는 기법이므로 해당 클래스 파일에서 생성자는 private으로 해주고 정적 필드에 해당 생성자를 이용하여 객체를 만든다. 그러면 이제 객체는 단 하나만 있는 상황이다. 이때 외부에서 이 객체를 호출할 수 있도록 정적 메소드 getInstance를 선언하여 리턴값으로 해당 객체를 준다. 결과적으로 해당 클래스에 해당하는 객체는 단 하나가 되는 것이다.
// 클래스 파일
public class 클래스명{
    // 정적 필드
    // 자기 자신의 타입의 필드를 정적으로 선언하고 자기 자신의 객체를 생성
    // class 내부에서는 private으로 선언된 생성자를 이용해서 객체를 만들 수 있다. 밖에서 못 만든다는 것 기억
    private static 클래스명 변수명 = new 클래스명(); 

    // 생성자
    private 클래스() {

    }

    // 정적 메소드
    // 외부에서 호출할 수 있어야하므로 private을 붙이면 안돼
    // 자기 자신의 타입을 리턴타입으로 설정
    static 클래스명 getInstance(){
        return 변수명 // 자기 자신의 타입을 리턴해야하므로 위에 선언한 필드를 리턴
    }
}

// 메인 실행 파일
클래스명 변수1 = 클래스.getInstance();
클래스명 변수2 = 클래스.getInstance();

그림1

단 하나의 객체만 존재하므로 결국 같은 곳을 참조한다.


6. final 필드와 상수


  • 초기값이 저장되면 최종값이 되어 프로그램 실행 도중 수정이 불가능한 필드
  • final 필드의 초기값 주는 방법
    • 단순 값일 경우 필드 선언 시 초기화(주로 정적 필드(상수)일 경우)
    • 객체 생성 시 외부 데이터로 초기화 필요한 경우 생성자에서 초기화(주로 인스턴스 필드일 경우)
  • 인스턴스 final 필드
    • 객체에 한 번 초기화된 데이터를 변경 불가로 만들 경우 ex) 주민번호
    • final 타입 필드; // 주민번호 같은 경우 사람마다 다르므로 생성자에서 초기화
  • 정적 final 필드
    • static final로 선언된 필드는 대부분 상수이기 때문에 불변의 상수값을 저장하는 변수명은 관례적으로 대문자
    • 불변의 값인 상수를 만들 경우 ex) PI
    • static final 타입 상수(변수명) = 초기값;
// 예시 1

// 클래스 파일
public class Person {
	final String nation = "korean";
	final String ssn;
	final String name;
	
	
	Person(String ssn,String name){
		this.ssn = ssn;
		this.name = name;
	}
}

// 메인 실행 클래스
Person p1 = new Person("123-456","hong");		
System.out.println(p1.nation);
System.out.println(p1.ssn);

p1.nation = "USA" // 컴파일에러 // final이기 때문에

// 예시 2
// 클래스 파일
public class Earth {
	// static final로 선언된 필드는 대부분 상수이기 때문에
	// 불변의 상수 값을 저장하는 변수명은 관례적으로 대문자
	static final double EARTH_RADIUS = 6400; 	
	static final double EARTH_AREA = 4 * Math.PI*EARTH_RADIUS*EARTH_RADIUS;	
}

// 메인 실행 클래스
public static void main(String[] arg) {
		System.out.println("지구의 반지름: "+ Earth.EARTH_RADIUS + " KM");
		System.out.println("지구의 표면적: "+ Earth.EARTH_AREA + " KM^2");
}


7. 정리하기


  • 인스턴스 멤버 : 객체를 생성한 후 사용할 수 있는 필드와 메소드(인스턴스 필드와 인스턴스 메소드)
  • this : 객체 내부에서도 인스턴스 멤버에 접근하기 위해 this를 사용할 수 있다.
    • 주로 생성자와 메소드의 매개변수 이름이 필드와 동일한 경우, 인스턴스 멤버인 필드임을 명시하기 위해 사용
  • 정적 멤버 : 클래스에 고정된 멤버로서 객체를 생성하지 않고 사용할 수 있는 필드와 메소드
  • static : 정적 멤버를 선언할 때마다 사용하는 키워드
  • 싱글톤 : 전체 프로그램에서 단 하나의 객체만 만들도록 보장해야 하는 경우 사용하는 코드 패턴
  • final 필드 : 초기값이 저장되면 이것이 최종값이 되어 프로그램 실행 도중 수정할 수 없는 필드
  • 상수 : 불변의 값을 저장하는 정적 필드, final static 키워드로 선언

본 포스팅은 ‘혼자 공부하는 자바’를 읽고 공부한 내용을 바탕으로 작성하였습니다.


© 2021. By Backtony