인스턴스 멤버 vs 정적멤버
- 메모리 낭비를 줄이기위해 필드를 각 객체마다 갖게 하는 것이 아니라 한 곳에 모아놓고 객체들이 공유하도록 한다.
- 인스턴스 멤버 : 객체마다 가지고 있는 멤버
- 정적 멤버 : 클래스에 위치하며 객체들이 공유하는 멤버
인스턴스 멤버
- 인스턴스 멤버란, 객체(인스턴스)를 생성하고 나서 사용할 수 있는 필드와 메소드를 말함
- 각각 인스턴스 필드와 인스턴스 메소드라고 불림 (이전까지 배운 것들이 모두 인스턴스 멤버임)
- 인스턴스 필드와 인스턴스 메소드는 모두 객체에 소속된 멤버이므로 객체가 있어야만 사용이 가능함
인스턴스 멤버 선언 및 호출
public class Car4 {
// 인스턴스 필드 선언
int gas;
// 인스턴스 메소드 선언
void setSpeed(int speed) {
System.out.println("speed : " + speed);
}
}
public class Car4Example {
public static void main(String[] args) {
Car4 myCar = new Car4();
// 인스턴스 멤버인 gas필드와 setSpeed() 메소드를 사용하려면 반드시 객체(인스턴스)를 먼저 생성해주어야한다.
myCar.gas = 10; // 그 다음에 참조 변수 myCar로 접근해야한다.
myCar.setSpeed(60);
Car4 yourCar = new Car4();
yourCar.gas = 20;
yourCar.setSpeed(80);
}
}
- 인스턴스 필드 gas는 객체마다 따로 존재하지만 인스턴스 메소드 setSpeed()는 메소드 영역에 저장되어 사용할 때마다 공유된다.
- 인스턴스 메소드는 인스턴스라는 이름이 붙었긴 하지만 메소드 자체는 객체마다 동일한 메소드 코드 블록이 실행되므로 굳이 객체마다 하나씩 가지고 있을 필요가 없다.
- 그럼에도 인스턴스라는 단어가 붙은 이유는 인스턴스 필드가 블록 내부에서 사용되기 때문이다.
- 인스턴스 메소드 내에 인스턴스 필드가 사용될 경우 객체 없이는 메소드가 실행되지 않는다.
this
- 생성자와 메소드의 매개변수 이름이 필드 이름과 동일한 경우 필드 이름임을 명시하고자 필드명 앞에 this를 붙인다.
this 예제
public class Car5 {
// 필드
String model;
int speed;
// 생성자
Car5(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 Car5Example {
public static void main(String[] args) {
Car5 myCar = new Car5("포르쉐");
Car5 yourCar = new Car5("벤츠");
myCar.run();
yourCar.run();
}
}
정적(static) 멤버
- '고정된' 이라는 의미로 정적 멤버는 클래스 안에서 고정된 멤버로 객체를 생성하지 않아도 사용이 가능한 필드와 메소드를 말함
- 각각 정적 필드와 정적 메소드라고 부름
정적 멤버 선언
- 정적 멤버를 선언할 때는 인스턴스 멤버와 다똑같지만 맨앞에 static을 붙여준다는 특징이 있다.
public class Car {
// 정적 필드
static int speed;
// 정적 메소드
static void setSpeed(int speed) { }
}
정적 멤버의 메모리 구조
- 클래스 로더가 클래스의 바이트 코드를 읽는다.
- 바이트 코드를 로딩해서 메소드 메모리 영역에서 클래스별로 관리한다.
- 클래스 로딩이 끝나면 바로 사용 가능하다.
인스턴스 필드와 정적 필드의 판단 기준
public class Calculator {
String color; // 객체별로 서로 다른 색깔을 지정해줄거라면 인스턴스 필드로 선언
static double pi = 3.14; // 파이값은 객체마다 변하지 않는 공용 데이터라서 정적 필드
}
인스턴스 메소드와 정적 메소드의 판단 기준
public class Calculator {
String color; // 인스턴스 필드
void setColor() { // 인스턴스 필드의 값(색깔)을 변경하는 메소드는 인스턴스 메소드로 선언
this.color = color;
}
static int plus(int x, int y) { // 외부에서 매개값을 받아서 수행하는 메소드는 정적 메소드로 선언
int result = x + y;
return result;
}
}
정적 멤버 사용
- 클래스이름.필드 형태로 정적 필드를 사용할 수 있다.
- 클래스이름.메소드 형태로 정적 메소드를 사용할 수 있다.
- 원칙적으로는 클래스 이름으로 접근해야하지만 객체 참조 변수로도 접근이 가능하다.
- 그 경우에는 이클립스에서 경고 표시가 뜬다.
정적 멤버 사용 예제
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;
}
}
public class CalculatorExample {
public static void main(String[] args) {
// static 메소드를 실행시킬때 원칙적으로 클래스 이름으로 접근해야한다.
double result1 = 10*10*Calculator.pi;
int result2 = Calculator.plus(10,5);
int result3 = Calculator.minus(10,5);
// 하지만 객체 참조 변수로도 접근은 가능하다. 경고 표시가 뜨긴 함
Calculator myCalc = new Calculator();
double result4 = 10*10*myCalc.pi;
int result5 = myCalc.plus(10, 5);
int result6 = myCalc.minus(10,5);
System.out.println("result1 : " + result1); // result1 : 314.159
System.out.println("result2 : " + result2); // result2 : 15
System.out.println("result3 : " + result3); // result3 : 5
System.out.println("result4 : " + result4); // result4 : 314.159
System.out.println("result5 : " + result5); // result5 : 15
System.out.println("result6 : " + result6); // result6 : 5
}
}
-> static 멤버의 사용시 클래스이름으로 접근하는게 원칙인데 객체 참조 변수로 접근해서 경고가 뜨는 것이다.
정적 메소드 내에서 인스턴스 멤버 사용하고 싶을 때
- 객체가 없어도 실행이 되는 정적 메소드의 특징 때문에 정적 메소드 내에서는 인스턴스 멤버를 사용할 수 없다.
- 우선 정적 메소드 안에서 정적 필드나 정적 메소드를 사용하는건 그냥 이름만 적어도 사용이 가능하다.
- 정적 메소드 안에서 인스턴스 멤버를 사용하기위해 this 접근 연산자를 사용하면 컴파일 에러가 난다.
- 그럼 정적 메소드 안에서 인스턴스 멤버를 사용하고 싶다면??
- 먼저 객체를 생성하고 참조 변수를 통해 접근하면 된다.
public class ClassName {
// 인스턴스 멤버 선언
int field1;
void method1() {};
// 정적 멤버 선언
static int field2;
static void method2() {};
// 정적 메소드 안에서 필드와 메소드 사용
static void method3() {
// this.field1 = 10; //인스턴스 필드를 this 연산자로 접근시 컴파일에러
// this.method1(); //인스턴스 메소드를 this 연산자로 접근시 컴파일에러
field2 = 10; //정적 필드는 그냥 사용할 수 있음
method2(); //정적 메소드도 그냥 사용할 수 있음
ClassName obj = new ClassName(); // 인스턴스 멤버 사용을 위해 먼저 객체를 생성
obj.field1 = 10; // 인스턴스 필드를 사용하기 위해 참조 변수로 접근
obj.method1(); // 인스턴스 메소드를 사용하기 위해 참조 변수로 접근
}
}
main 메소드 안에서 인스턴스 메소드 사용
- main 메소드 형태 : public static void main(String[] args) {
- main 메소드도 정적 메소드 이므로 객체 생성을 해야 인스턴스 필드와 인스턴스 메소드를 사용할 수 있다.
public class Car4 {
// 필드
int speed;
// 메소드
void run() {
System.out.println(speed + "km/hour로 달립니다.");
}
// main 메소드
public static void main(String[] args) {
Car4 myCar = new Car4(); // 객체 생성
myCar.speed = 60; // 참조 변수로 접근 가능
myCar.run(); // 60km/hour로 달립니다.
}
}
싱글톤
- 싱글톤은 항상 private을 떠올리도록 하자
- 객체를 딱 하나만 생성해야하는 경우가 있는데 이 경우에 유일하게 생성된 그 객체를 바로 싱글톤이라고 부른다.
- 싱글톤을 만들기 위해서는 객체를 생성하지 못하게 막아야한다.
- 우리는 new 연산자를 통해 생성자를 호출하여 객체를 생성하는 방식을 사용한다.
- 그렇다면 생성자 호출을 막으면 객체는 더이상 생성될 수가 없게 된다.
- 생성자 호출을 막기위해서는 클래스 이름 앞에 private 접근 제한자를 붙여주면 된다.
싱글톤 만드는 코드
public class Singleton {
// 정적 필드
// 타입이 자기자신(클래스)인 정적 필드 선언하고 객체 생성하여 초기화
private static Singleton singleton = new Singleton();
// 생성자
// 다른 클래스에서 객체를 추가로 생성하지 못하도록 private을 붙임
private Singleton() {}
// 정적 메소드
// 타입이 클래스 자기 자신이고 return값으로는 참조하고 있는 자신의 객체를 리턴해준다.
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(); // getInstance()메소드를 호출하는 것 뿐이다.
if(obj1==obj2) { // obj1과 obj2는 동일한 객체를 참조하므로 같다.
System.out.println("같은 Singleton 객체입니다.");
} else {
System.out.println("다른 Singleton 객체입니다.");
}
}
}
final 필드 선언
- final 필드는 최종적인 필드를 의미하며 초기값이 한 번 저장되면 이게 최종 값이 되어 수정이 불가능한 것을 말한다.
- 필드 선언 형태 : final 타입 필드;
- 필드 선언 형태 (초기화까지) : final 타입 필드 = 초기값;
final 필드 초기화하는 방법 2가지
1. 필드 선언 시 초기화하기 : 단순 값이거나 항상 고정된 값일때 이용
2. 생성자에서 초기값 주기 : 초기화 코드가 복잡하거나 외부 데이터로 초기화해야 하는 경우 이용
-> final 필드로 선언시 최종 초기화를 마쳐야하며 초기화 되지 않은 채로 final 필드 선언만 있다면 컴파일 에러 발생
final 필드 선언과 초기화 예시
public class Person {
// 필드
final String nation = "korea";
final String ssn;
String name;
// 생성자
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("nation: " + p1.nation);
System.out.println("ssn: " + p1.ssn);
System.out.println("name: " + p1.name);
// p1.nation = "usa"; // final 필드 값 변경 시 에러 발생
// p1.ssn = "654321-7654321"; // final 필드 값 변경 시 에러 발생
p1.name = "홍길동";
}
}
상수
- 상수란, 원주율 파이, 지구의 무게 등 변하지 않는 값을 말한다. constant 라고 불린다.
상수와 final 필드의 차이
- 우선 final 필드는 한 번 초기화되면 수정할 수 없는 필드이다.
- 상수는 객체마다 저장할 필요가 없는 공용의 것(=static)이지만 final 필드는 객체마다 저장이 된다.
- 상수는 여러 가지 값으로 초기화될 수 없지만 final 필드는 생성자의 매개값을 통해서 여러 가지 값을 가질 수 있음
- 따라서 상수는 final 이면서 static 이기도 해야함
- static final 필드는 객체마다 존재하지 않고 클래스에만 존재한다. 또한 초기값 저장 후에는 변경할 수 없다.
상수 선언 및 사용 예제
public class Earth {
static final double EARTH_RADIUS = 6400;
static final double EARTH_AREA = 4 * Math.PI * Math.pow(EARTH_RADIUS, 2);
}
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
'👨💻 2. 웹개발_Back end > 2-1 Java' 카테고리의 다른 글
[JAVA] 07-1 상속 (0) | 2021.08.04 |
---|---|
[JAVA] 06-6 패키지와 접근 제한자 (0) | 2021.08.04 |
[JAVA] 06-4 메소드 (0) | 2021.08.02 |
[JAVA] 06-3 생성자 (0) | 2021.08.02 |
[JAVA] 06-2 필드 (0) | 2021.08.02 |
댓글