클래스는 객체를 만들기 위한 설계도 입니다. 쉽게 말해서, 클래스는 프로그램에서 사용할 객체의 속성과 기능을 미리 만들어 놓은 틀이라고 할 수 있습니다.
객체는 설계도인 클래스를 바탕으로 실제 메모리에 만들어진 실체 입니다.
인스턴스는 객체와 거의 같은 말입니다. 단지 클래스로부터 만들어진 객체를 인스턴스 라고 지칭할 뿐입니다.
객체를 만들 때는 new 라는 키워드를 사용합니다.
생성된 객체의 속성이나 기능에 접근할 때는 객체이름.속성, 객체이름.메서드() 형식으로 사용합니다.
객체가 가지는 특징을 의미합니다. 변수로 표현이 됩니다.
객체를 만들 때 사용합니다. 어떤 식으로 만들지 (각 속성에 어떤 값을 넣을지?)등을 정의하는 부분이라고 생각하면 됩니다.
객체의 동작을 의미합니다. 메서드로 표현이 됩니다.
메서드는 다른 언어(c, c++ 등)에서는 보통 함수라고 부릅니다. 어떤 작업을 할 때 필요한 동작들을 한 곳에 모아둔 것이라고 할 수 있습니다.
왜 필요한가?
같은 기능을 하는 부분이 있다면 한 부분 한 부분 똑같이 작성을 해주어야 합니다. 여기까지는 사실 '조금 귀찮기만 하지 굳이 메서드까지 만들어야 하나?' 싶기는 합니다.
하지만, 만약 그 기능에서 어떤 부분을 수정해야 한다면, 그 부분을 수정하기 위해서 기능을 위해 작성했던 코드들을 모두 찾아서 고쳐야합니다.즉, 유지 보수에 있어서 큰 불편함이 발생합니다.
메서드를 사용한다면, 그 기능을 하는 부분을 한 덩어리로 작성할 수 있습니다. 만약, 그 기능이 필요하다면 메서드를 호출하기만 하면 됩니다. 또한 수정을 해야 하는 부분이 있다면 메서드에서 수정을 하면 모든 부분에서 수정이 된 것과 같은 효과를 얻을 수 있습니다.
int method_name (int a, int b) {
...
return k;
}
하나하나 살펴보겠습니다.
객체를 만들 때 생성자를 사용한다고 하였습니다.
생성자는 메서드로 표현이 되는데, 특징은 다음과 같습니다.
생성자로 객체를 만들 때에는 다음과 같이 만들게 됩니다.
Person person = new Person(); // Person 클래스의 새로운 인스턴스 person을 생성
// Person이라는 타입의 변수 person을 하나 선언하고 정의하고 초기화하는 것이라고도 이해하면 쉽다.
// 이렇게 만들어진 객체에서 각 속성이나 메서드에 접근하려면
// person.속성이름 또는 person.메서드이름
// 와 같은 방식으로 접근할 수 있습니다.
반환 타입이 존재하지 않으면 어떻게 person이 객체가 되나요?
new 라는 키워드로 가능합니다.
new 키워드는, 새로운 객체를 만들 때 적절한 힙(heap) 영역을 할당해줍니다.
Person()이라는 생성자가 실행되면서, new는 Person()으로 만들어지는 객체가 들어갈 공간을 힙 영역에서 할당을 해주고(즉, 객체는 힙 영역에 만들어집니다), 최종적으로 객체의 주소를 반환을 하게 됩니다.
생성자는 반환 값이 없지만, new가 객체의 주소를 반환해주기 때문에 person이라는 변수로 객체에 접근할 수 있는 것입니다.
this라는 키워드를 종종 마주치게 될 것입니다. this의 의미는 자기 자신을 가리키는 키워드입니다.
이렇게 말해서는 잘 와닿지 않을 것 같은데, 어떤 객체의 메서드를 호출하게 된다고 가정을 해보겠습니다. 그 메서드 안에서 객체의 속성 값을 사용하게 될 수도 있을 것입니다. 이 때, 자기 자신의 속성 값을 고치는 것인지 아니면, 다른 값을 고치는 지 애매할 때가 있을 것입니다. 이 때 자기 자신의 속성에 접근하는 것이라고 명시해주는 것이 this 키워드 입니다.
// 생성자에서 this
class Person {
String name; // 사람의 이름
String phoneNumber; // 전화번호
int age; // 나이
Person(String name, String phoneNumber, int age) {
this.name = name;
this.phoneNumber = phoneNumber;
this.age = age;
}
}
위 코드 예시에서, 인자로 name, phoneNumber, age를 받고 있습니다. 이 변수들은 Person의 속성 변수들과 이름이 같습니다.
그렇기 때문에 지금 호출하는 변수 name이 매개변수를 통해서 들어온 name인지, 객체가 가지고 있는 속성의 name인지 애매모호합니다.
그래서 '현재 객체의 name을 건드리고 있어요' 라는 것을 명시해주기 위해서 this라는 키워드를 사용하는 것입니다.
객체를 다룰 때, 객체 속성에 쉽게 접근을 할 수 있다면 많은 문제가 발생할 수 있습니다(속성의 값이 쉽게 바뀐다는 등).
이를 방지하기 위해서, 객체의 속성은 객체 내에서만 접근을 할 수 있도록(private) 하면서 속성을 받거나 속성의 값을 수정할 때에는 각각 Getter와 Setter를 사용하도록 하는 것입니다.
위에서 이야기했던 것처럼, 객체 속성에 무분별한 접근을 막는 것으로 Getter와 Setter의 목적은 크게 캡슐화(Encapsulation) 에 있습니다.
class Person {
private String name; // 사람의 이름, 외부에선 직접 접근할 수 없음
private String phoneNumber; // 전화번호, 외부에선 직접 접근할 수 없음
private int age; // 나이, 외부에선 직접 접근할 수 없음
...
String getName() { // name 속성의 Getter
return this.name;
}
void setName(String name) { // name 속성의 Setter
this.name = name;
}
}
코드를 예시와 같이 작성하게 된다면, person.name으로 직접 name에 접근할 수는 없고, getName을 통해서 name을 받거나, setName을 통해서 name을 수정할 수 있게 됩니다.
static 키워드는 변수나 메서드가 "클래스에 속한다" 는 뜻입니다. 즉, 객체를 만들지 않아도 클래스 이름으로 직접 접근할 수 있습니다.
클래스에 속하기 때문에, 모든 객체가 동일한 static 변수/메서드를 공유합니다.
class Person {
static String nationality = "Korea"; // 국적, 해당 클래스에서 만드는 사람들이 동일한 국적
private String name;
private int age;
}
class Main {
public static void main(String[] args) {
Person.nationality; // "Korea"
Person.name; // error
}
}
인스턴스와 상관 없기 때문에, static 변수는 클래스가 처음 로드될 때 같이 초기화가 됩니다. 즉, 명시적으로 초기화가 되지 않더라도 해당 자료형의 기본값으로 초기화가 됩니다.
위 방식처럼 static 변수를 초기화하는 방법이 있고, static 초기화 블럭을 사용하는 방법도 있습니다.
static int count;
static int current;
static { // static 초기화 블럭
count = 1;
current = 10;
}
여기서 헷갈릴 수 있는데, 생성자라는 것은, 인스턴스를 만들 때마다 실행을 시키기 때문에 인스턴스가 한 개 생길 때마다 인스턴스 변수는 각 인스턴스에 맞게 초기화가 됩니다. 하지만, static 변수는 인스턴스와 무관하기 때문에, 클래스가 처음 로드될 때 한 번만 초기화가 이루어집니다.
final은 한 번 초기화가 되면, 이 후에는 값을 변경하지 못합니다(상수).
final 키워드는 변수, 메서드, 클래스에 사용할 수 있는데, 각각이 의미하는 바는 다음과 같습니다.
class Person {
...
}
class Main {
public static void main(String[] args) {
final Person person = new Person();
person.name = "철수"; // 가능
person = new Person(); // error
}
}
위 코드에서 보이는 바와 같이, 인스턴스 안에 있는 속성 등을 변경하는 것은 가능하지만, person 자체에 새로운 인스턴스를 할당하는 것은 불가능합니다.
왜?
왜 그런지는 위에서 new 키워드에 대해 언급했던 것을 생각해보면 됩니다.
person 변수 안에는 Person()이라는 생성자를 통해 만든 객체의 주소가 담겨져 있습니다.
그렇기 때문에, 새로운 객체를 만들어서 새로 만들어진 객체의 주소를 다시 person에 담으려고 하는 것은 final 변수의 특징에 의해서 불가능합니다.하지만, person에 담긴 주소를 타고 진짜 객체가 들어있는 곳으로 가서 거기 존재하는 변수(person.name)을 수정하는 것은 person 자체의 값을 변경하는 것이 아니기 때문에 가능한 것입니다.
자료 및 코드 출처: 스파르타 코딩클럽