1. 멤버의 효율성
하나의 클래스로부터 여러개의 객체(인스턴스)가 생성될 수 있다.
만약 객체마다 필드값이 달라야 한다면, 해당 필드는 각 객체가 별도로 갖고 있는게 맞다.
그러나 모든 객체가 동일한 값을 가지는 필드라면 각 객체가 별도로 갖고 있을 필요가 없다.
오히려 이런 필드를 각 객체가 별도로 가진다면 메모리 낭비, 추가적인 작업 등 개발의 비효율을 유발할 수 있다.
그래서 자바에서는 이런 상황에서 개발의 효율성을 높이기 위해
객체마다 가지고 있는 인스턴트 멤버,
클래스에 존재하고 각 객체들이 공유하는 정적 멤버를 구분해서 선언할 수 있다.
1. 인스턴스 멤버, this
인스턴스 멤버는 객체(인스턴스)를 생성한 후 사용할 수 있는 필드와 메소드를 말한다.
각각 인스턴스 필드, 인스턴스 메소드라고 부른다.
객체가 생성될 경우,
인스턴스 객체는 각 객체마다 따로 존재하고,
인스턴스 메소드는 메소드 영역에 저장되고 공유된다.
그런데 인스턴스 메소드의 경우 각 객체마다 따로 존재하는 것이 아닌,
메소드 영역에 저장되어 각 객체가 공유하는데
왜 정적 메소드와 구분하여 인스턴스 메소드라고 부를까?
그 이유는 메소드 블록 내부에 인스턴스 필드가 사용되는 경우가 있기 때문이다.
인스턴스 필드가 사용되는 메소드는 객체 없이 사용될 수 없기 때문에
인스턴스 메소드라고 부른다.
객체 외부에서 인스턴스 멤버에 접근하기 위해 참조변수를 사용하는 것 처럼,
객체 내부에서도 인스턴스 멤버에 접근하기 위해 객체 자신을 가리키는 this를 사용할 수 있다.
this는 주로 생성자 또는 메소드의 매개변수 이름이 필드와 동일한 경우, 필드임을 명시하고자 할때 사용한다.
public 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();
}
}
2. 정적 멤버와 static
정적(static) 멤버는 클래스에 고정된 멤버로서,
객체를 생성하지 않고 클래스를 통해 바로 접근해서 사용할 수 있는 필드와 메소드를 말한다.
각각 정적 필드, 정적 메소드라고 부른다.
앞서 말했듯이 인스턴트 필드로 선언할 것인가, 정적 필드로 선언할 것인가에 기준은 필드의 값이다.
객체마다 개별적인 값을 가지고 있어야할 필드라면 인스턴트 필드로 선언해야하고,
객체마다 개별적인 값을 가질 필요가 없는 필드라면 정적 필드로 선언하는 것이 효율적이다.
인스턴트 메소드와 정적 메소드의 기준은 메소드 블록 내부가 기준이된다.
메소드 블록 내부에 인스턴트 필드가 포함되어 있다면 인스턴트 메소드로 선언하고,
포함하고 있지 않다몬 정적 메소드로 선언한다.
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) {
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);
}
}
정적 메소드를 사용할 때 주의할 점이 있다.
정적 메소드는 객체가 없어도 실행된다는 특징 때문에
객체를 통해 접근할 수 있는 인스턴스 필드, 인스턴스 메소드를 내부에서 사용할 수 없다.
또한 객체 자신을 참조하는 this 키워드도 사용할 수 없다.
만약 정적 메소드 내부에서 인스턴스 멤버를 사용하고 싶다면
정적 메소드 내부에서 객체를 생성하고, 참조변수를 통해 접근하여 사용해야한다.
public class Car {
//인스턴스 필드
int speed;
//인스턴스 메소드
void run() {
System.out.println(speed + "으로 달립니다.");
}
public static void main(String[] args) {
//정적 메소드 내부에서는 인스턴스 필드, 인스턴스 메소드를 사용할 수 없다.
//또한 객체를 참조하는 this도 사용할 수 없다.
//정적 메소드 안에서 인스턴스 필드 및 메소드를 사용하기 위해서는 객체를 먼저 생성하고 참조변수를 통해 사용해야한다.
//main도 정적 메소드 중 하나이다.
Car myCar = new Car();
myCar.speed = 60;
myCar.run();
}
}
3. 싱글톤(Singleton)
전체 프로그램에서 가끔 단 하나의 객체만 생성해야할 경우가 있다.
단 하나만 생성된다고 해서 이 객체를 싱글톤(Singleton)이라고 한다.
싱글톤을 생성하려면 클래스 외부에서 new 연산자를 통해 생성자를 호출할 수 없도록 막아야한다.
생성자를 외부에서 호출할 수 없도록 하려면 생성자 압ㅍ에 private 접근 제한자를 붙여주면 된다.
그 후 자신 타입인 정적 필드를 하나 선언하고, 자신의 객체를 생성해 초기화 한다.
이때 정적 필드의 이름이 singleton 이다.
또한 해당 필드도 private 접근제한자를 통해 외부에서 필드값을 변경하지 못하도록 막는다.
대신 외부에서 호출할 수 있는 정적 메소드인 getInstance() 메소드를 선언하고, 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(); //getIntance() 메소드를 통해 클래스 외부에서 객체를 생성할 수 있다.
if(obj1 == obj2) {
System.out.println("같은 Singleton 객체입니다."); // getInstance() 메소드는 하나의 객체만 리턴하기 때문에, obj1과 obj2는 동일한 객체를 공유한다.
} else {
System.out.println("다른 Singleton 객체입니다.");
}
}
}
4. 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", "kmk");
System.out.println(p1.nation);
System.out.println(p1.ssn);
System.out.println(p1.name);
//p1.nation = "usa"; final 키워드가 선언된 필드이므로, 초기화 후에 필드 값 변경이 불가능하다.
//p1.ssn = "654321-7654321"; 마찬가지로 불가능하다.
p1.name = "홍삼원"; //final 키워드가 적용되지 않은 필드이므로, 초기화 후에 필드 값 변경이 가능하다.
System.out.println(p1.nation);
System.out.println(p1.ssn);
System.out.println(p1.name);
}
}
final 필드에서 더 확장되어 상수(static final)라는 개념이 있다.
상수는 불변의 값을 말한다.
final 필드는 한번 초기화될 경우, 값을 변경할 수 없다.
어찌보면 final 필드는 상수와 매우 유사한 특성을 갖고 있지만, final 필드 자체를 상수라고 부르지는 않는다.
앞서 말했듯이 상수는 불변의 값이다.
불변의 값은 공용성을 띄고 있으며, 여러 가지 값으로 초기화 될 수 없다.
그러나 final 필드는 객체마다 가질 수 있으며, 생성자를 통해 객체마다 다른 값을 가질 수 있기 때문이다.
이러한 상수로서 부족한 특성을 채워줄 수 있는 것이 static 키워드이다.
즉 필드에 staitc final 키워드를 사용하여 해당 클래스의 모든 객체가 공용하고 값이 불변하는 필드,
즉 상수를 생성할 수 있다.
아래의 예시코드 처럼 상수 이름은 모두 대문자로 작성하는 것이 관례이다.
여러 단어가 혼합된 이름인 경우, 각 단어를 _ 로 이어준다.
public class Earth {
//초기화된 값이 변하지 않고(final), 전체에 대한 공용성(static)을 띄는 것. 상수이다.
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");
}
}
출처: 혼자 공부하는 자바(신용권)