[Java]인스턴스 멤버와 정적 멤버

Devlog·2024년 3월 12일

Java

목록 보기
15/41

✔️ 인스턴스 멤버

: 인스턴스(instance)멤버란 객체(인스턴스)를 생성한 후
  사용할 수 있는 필드(인스턴스 필드)와 메소드(인스턴스 메소드)
: 인스턴스 필드와 메소드는 객체에 소속된 멤버이기 때문에
  객체 없이는 사용할 수 없음

- 인스턴스 멤버 선언

public class Car {
	//필드
	int gas;

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

→ gas 필드, setSpeed()메소드는 인스턴스 멤버이기 때문에
  외부 클래스에서 사용하기 위해서는 Car객체(인스턴스)를 생성하고 
  참조 변수 myCar 또는 yourCar로 접근해야함

Car myCar = new Car();
myCar.gas = 10;
myCar.setSpeed(60);

Car yourCar= new Car();
yourCar.gas = 20;
yourCar.setSpeed(80);

→ 인스턴스 필드 gas는 객체마다 따로 존재하고,
  인스턴스 메소드 setSpeed()는 메소드 영역에 저장되고 공유됨

Q. 인스턴스 메소드는 객체에 소속된 멤버인데,
  왜 객체 내부에 존재하지 않고 메소드 영역에 저장되고 공유된다고 했을까?
A. 메소드는 코드 블록이므로 객체마다 동일한 코드 블록을 가지고 있을 필요x

Q. 인스턴스라는 용어는 왜 붙였을까?
A. 메모드 블록 내부에 인스턴스 필드 등이 사용되는 경우가 있기 때문

→ 인스턴스 필드가 사용되면 메소드 역시 객체 없이는 실행할 수 없음


✔️ this

: 객체 내부에서도 인스턴스 멤버에 접근하기 위해 this를 사용할 수 있음
: 주로 생성자와 메소드의 매개 변수 이름이 필드와 동일한 경우,
  인스턴스 멤버인 필드임을 명시하고자 할 때 사용

Car(String model){
	this.model = model;
}

void setModel(String model){
	this.model = model;
}
👩‍💻 인스턴스 멤버와 this
class Car{
	//필드
	String model;
	int speed;
	
	//생성자
	Car(String model){
		this.model = model;
	}
	
	//메소드
	void setSpeed(int speed) {
		this.speed = speed;
	}
	
	void run() {
		for(int i=10; i <= 50; i+=10) {
			this.setSpeed(i);
			System.out.println(this.model + "가 달립니다,
            (시속:" + this.speed + "km/h)");
		}
	}
}

public class CarExample {

	public static void main(String[] args) {
		Car myCar = new Car("포르쉐");
		Car yourCar = new Car("벤츠");
		
		myCar.run();
		yourCar.run();
	}
}

💻 결과
포르쉐가 달립니다,(시속:10km/h)
포르쉐가 달립니다,(시속:20km/h)
포르쉐가 달립니다,(시속:30km/h)
포르쉐가 달립니다,(시속:40km/h)
포르쉐가 달립니다,(시속:50km/h)
벤츠가 달립니다,(시속:10km/h)
벤츠가 달립니다,(시속:20km/h)
벤츠가 달립니다,(시속:30km/h)
벤츠가 달립니다,(시속:40km/h)
벤츠가 달립니다,(시속:50km/h)

✔️ 정적 멤버와 static

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

- 정적 멤버 선언

public class 클래스 {
  //정적 필드
  static 타입 필드 [= 초기값];

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

: 정적 필드와 정적 메소드를 선언 시 static 키워드를 추가적으로 붙이면 됨
: 정적(필드, 메소드)는 클래스에 고정된 멤버이므로
  클래스 로더가 클래스(바이트 코드)를 로딩해서
  메소드 메모리 영역에 적재할 때 클래스별로 관리
  따라서 클래스의 로딩이 끝나면 바로 사용할 수 있음

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

ex)
public class Calculator { 
	String color; //계산기별로 색깔이 다를 수 있음
	static double pi = 3.14159; //계산기에서 사용하는 파이 값은 동일
}

- 인스턴스 메소드 vs 정적 메소드
: 인스턴스 필드를 포함하고 있다면 인스턴스 메소드로 선언
  인스턴스 필드를 포함하고 있지 않다면 정적 메소드로 선언

ex)
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; } //정적 메소드
}

- 정적 멤버 사용
: 클래스가 메모리로 로딩되면 정적 멤버를 바로 사용가능

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

ex)
// 클래스 작성
public class Calculator {
	static double pi = 3.14159;
	static int plus(int x, int y) { ··· }
	static int minus(int x, int y) { ··· }
}

// 정적필드, 메소드 사용
double result1 = 10 * 10 * Calculator.pi;
int result2 = Calculator.plus(10, 5);
int result3 = Calculator.minus(10, 5);

- 정적 필드와 정적 메소드는 원칙적으로 클래스 이름으로 접근해야함
  하지만 객체 참조 변수로도 접근 가능

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

// 하지만 정적 요소는 클래스 이름으로 접근하는 것이 좋음
// 그렇지 않고 객체 참조 변수로 접근했을 경우 경고 표시가 나타남

- 정적 메소드 선언 시 주의할 점
: 객체가 없어도 실행된다는 특징 때문에 정적 메소드를 선언할 때는
  이들 내부에 인스턴스 필드나 인스턴스 메소드를 사용할 수 없음
: 객체 자신의 참조인 this 키워드도 사용이 불가

ex)
public class ClassName { 
	//인스턴스 필드와 메소드
	int field1;
	void method1() { ··· }

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

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

- 정적 메소드에서 인스턴스 멤버를 사용하고 싶다면
  객체를 먼저 생성하고 참조 변수로 접근해야함

ex)
static void Method3(){
	ClassName obj = new ClassName();
	obj.field1 = 10;
	obj.method1();
}

- main()메소드 또한 인스턴스 필드, 메소드 바로 사용 불가

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

		// 수정
		Car myCar = new Car();
		myCar.speed = 60;
		myCar.run();
	}
}

✔️ 싱글톤

public class 클래스{
   //정적 필드
   private static 클래스 singleton = new 클래스();

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

   //정적 메소드
   static 클래스 getInstance() {
       return singleton;
   }
}

: 단 하나만 생성된다고 해서 이 객체를 싱글톤(Singleton)이라고 함
: 싱글톤을 만들려면 클래스 외부에서
  new 연산자로 생성자를 호출할 수 없도록 막아야함
  → 생성자를 호출한 만큼 객체가 생성되기 때문
: 생성자를 외부에서 호출할 수 없도록 하려면
  생성자앞에 private 접근 제한자를 붙여주면 됨
: 자신의 타입인 정적 필드를 하나 선언하고 자신의 객체를 생성해 초기화한다.
: 클래스 내부에서는 new 연산자로 생성자 호출이 가능
: 정적 필드private 접근 제한자를 붙여
  외부에서 필드값을 변경하지 못하게 막아야함
  대신 외부에서 호출할 수 있는 정적 메소드인 getInstance()를 선언하고
  정적 필드에서 참조하고 있는 자신의 객체를 리턴해줌

- 외부에서 객체를 얻는 유일한 방법은 getInstance()메소드를 호출하는 방법
: getInstance()메소드는 단 하나의 객체만 리턴함

//동일한 객체를 참조
클래스 변수1 = 클래스.getInstance();
클래스 변수2 = 클래스.getInstance();

👩‍💻 싱글톤
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 객체입니다.

✔️ final필드

final 타입 필드 [= 초기값];

: final 필드는 초기값이 저장되면 이것이 최종적인 값이 되어서
  프로그램 실행 도중에 수정할 수 없다는 것

- final 필드의 초기값을 줄 수 있는 방법
1. 필드 선언 시에 주는 방법
2. 생성자에서 주는 방법

👩‍💻 final 필드 선언과 초기화
class Person{
	final String nation = "Korea"; 
	// 생성자는 final 필드의 최종 초기화를 마쳐야 하는데, 
    // 만약 초기화되지 않은 final 필드를 그대로 남겨두면 컴파일 에러가 발생
	final String ssn; //Person 객체가 생성될 때 초기값 설정 가능
	String name;
	
	//Person 객체 생성
	public Person(String ssn, String name) {
		this.ssn = ssn;
		this.name = name;
	}
}

public class PersonExample {
	public static void main(String[] args) {
		Person p1 = new Person("123456-1234567", "홍길동");
		
		System.out.println(p1.nation);
		System.out.println(p1.ssn);
		System.out.println(p1.name);
		
		//p1.nation= "usa"; 지정된 값이라 변경 불가
		//p1.ssn = "654321-7654321"; 지정된 값이라 변경 불가
		p1.name = "홍삼원";
	}
}

💻 결과
Korea
123456-1234567
홍길동

✔️ 상수(constant)

static final 타입 상수= 초기값;

: 불변의 값을 저장하는 필드
: static이면서 final이어야함
: static final 필드는 객체마다 존재하지 않고 클래스에만 존재,
  한번 초기값이 저장되면 변경 불가

ex)
static final double PI = 3.14159;
static final double EARTH_RADIUS = 6400;
static final double EARTH_AREA = 4 * Math.PI * EARTH_RADIUS * EARTH_RADIUS;

- final vs 상수
- final: 객체마다 저장되고,
         생성자의 매개값을 통해서 여러 가지 값을 가질 수 있기 때문
- 상수: 불변의 값은 객체마다 저장할 필요가 없는 공용성을 띠고 있으며,
         여러 가지 값으로 초기화 될 수 없기 때문

👩‍💻 상수선언 및 사용
class Earth {
	static final double EARTH_RADIUS = 6400;
	static final double EARTH_AREA = 
    4 * Math.PI * EARTH_RADIUS * EARTH_RADIUS;
}

public class EarthExample {
	public static void main(String[] args) {
		System.out.println("지구의 반지름: " + Earth.EARTH_RADIUS + "km");
		System.out.println("지구의 표면적: " + Earth.EARTH_AREA + "km^2");
	}
}

💻 결과
지구의 반지름: 6400.0km
지구의 표면적: 5.147185403641517E8km^2

0개의 댓글