1회차에서 서술했듯 객체는 클래스를 통해 생성해낼 수 있다. 클래스를 생성한다는 것은 클래스를 실제로 사용할 수 있도록 메모리 공간을 할당받는다는 것이다.
이때 사용하는 메모리 공간은 힙 메모리로, 메모리 주소만 알고있다면 생성된 객체를 참조하여 사용할 수 있다.
프로그램에 실체화된 클래스를 객체 또는 인스턴스라고 하며, 인스턴스는 클래스가 메모리 공간에 생성된 상태를 일컫기도 한다.
인스턴스의 생성은 클래스 명으로 이루어진 생성자
와 new
예약어를 통해 이루어진다.
Student
클래스의 인스턴스를 생성하는 코드new Student();
Student()
: Student
클래스의 디폴트 생성자생성자는 객체 생성시 자동으로 호출되는 특수한 메서드이다. 멤버 변수를 초기화 하는 역할을 하며, 클래스를 생성할 때 딱 한 번만 호출된다.
생성자의 이름은 클래스의 이름과 동일하며, 반환값은 없다. 예를 들어 Student
클래스의 생성자 이름은 Student()
이고, 매개변수는 프로그래머의 설정에 따른다.
만약 클래스 내에 생성자를 선언하지 않았다면 디폴트 생성자가 자동으로 생성되는데, 이때 디폴트 생성자는 매개변수가 없다.
하지만 클래스 내에 어떤 형태의 생성자라도 하나 이상 선언되어 있다면 디폴트 생성자는 만들어지지 않으므로, 언제나 매개변수가 없는 디폴트 생성자를 사용할 수 있는 것은 아니다.
필요에 따라 매개변수가 다른 생성자를 여러개 만들 수 있는데, 이처럼 생성자가 2개 이상 제공되는 것을 생성자 오버로딩이라고 한다.
오버로드(overload)
객체 지향 프로그램에서 메서드 이름이 같고 매개변수만 다른 경우
Person
클래스
package classpart;
public class Person {
String name;
int height;
double weight;
char gender;
boolean married;
int child;
int age;
/* 생성자 1 */
public Person() {};
/* 생성자 2 */
public Person(String name) {
this.name = name;
}; // name 멤버변수를 초기화
/* 생성자 3 */
public Person(String name, int age) {
this.name = name;
this.age = age;
}; // name과 age 멤버변수를 초기화
}
this
: 생성된 인스턴스 스스로를 가리키는 예약어, 다음 회차에 좀 더 자세히 다룰 예정이다.생성된 인스턴스를 사용하려면 인스턴스가 가지고 있는 메모리 주소를 알아야한다. 그래서 생성되는 순간 인스턴스의 메모리 주소를 변수에 저장해두어야 한다.
[클래스 형] [참조변수] = new [생성자];
특정 클래스 자료형으로 선언된 변수는 인스턴스가 생성된 메모리를 가르킬 수 있게 된다. 인스턴스의 메모리 주소를 참조값
, 참조값을 담는 변수를 참조변수
라고 한다.
Student
클래스의 인스턴스를 생성한다면Student jwoo = new Student();
new
예약어와 Student
클래스의 생성자 Student()
를 이용하여, 학생 인스턴스(또는 학생 객체)를 생성한다.Student
클래스 자료형으로 선언한 jwoo
라는 변수는 이 학생 인스턴스의 메모리 주소를 가르키고 있다.Person
클래스 만들기package classpart;
public class Person {
String name;
int height;
double weight;
char gender;
boolean married;
int child;
int age;
/* 생성자 1 */
public Person() {};
/* 생성자 2 */
public Person(String name) {
this.name = name;
};
/* 생성자 3 */
public Person(String name, int age) {
this.name = name;
this.age = age;
};
Person returnItSelf() {
return this;
} // 자기 자신을 반환하는 메서드
// 반환값을 출력해보면 인스턴스의 메모리 주소가 담겨있음
public void setPersonInfo(String name, int age) {
this.name = name;
this.age = age;
} // 오버로드
public void setPersonInfo(int height, double weight) {
this.height = height;
this.weight = weight;
} // 오버로드
// 바로 위 메서드와 메서드 명은 동일하고, 매개변수만 다름
public void setPersonFamily(boolean married, int child) {
this.married = married;
this.child = child;
}
public void printPersonInfo() {
System.out.println("age : " + this.age);
System.out.println("name : " + this.name);
System.out.println("married : " + this.married);
System.out.println("child : " + this.child);
}
}
Student student1 = new Student();
Student student2 = new Student();
Person jwoo = new Person();
Person jiwon = new Person("jiwon");
Person jiwonWoo = new Person("jiwon woo", 25);
student1
, student2
는 모두 동일한 클래스, 동일한 생성자로부터 생성되었지만, 멤버 변수의 값은 모두 다를 수 있다.jwoo
, jiwon
, jiwonWoo
는 모두 동일한 클래스로부터 생성되었지만, 멤버 변수의 값은 모두 다르게 설정되었다.package classpart;
public class MainTest {
public static void main(String[] args) {
Person james = new Person("James", 40);
Person man = new Person("Man");
james.setPersonFamily(true, 3);
man.setPersonFamily(false, 1);
james.age = 30;
System.out.println("----------------");
james.printPersonInfo();
System.out.println("----------------");
man.printPersonInfo();
System.out.println("----------------");
System.out.println(man.returnItSelf());
System.out.println(james.returnItSelf());
}
}
MainTest
출력해보기----------------
age : 30
name : James
married : true
child : 3
----------------
age : 0
name : Man
married : false
child : 1
----------------
classpart.Person@7dc5e7b4
classpart.Person@1ee0005
returnItSelf()
메서드의 반환값인 자기자신의 주소, 즉 인스턴스의 메모리 주소를 출력한 것이다.man.returnItSelf()
는 man
이라는 참조변수에 담긴 인스턴스의 주소를, james.returnItSelf()
는 james
이라는 참조변수에 담긴 인스턴스 주소를 반환한다.접근 제어자는 변수나 메서드, 생성자에 대한 접근 권한을 지정해줄 수 있는 예약어로 접근 지정자로도 불린다. 위의 Person
클래스에서 생성자 명 앞에 붙은 public
이 접근 제어자의 한 종류이다.
객체 지향 언어에서 접근 제어자를 사용하는 이유는 객체를 캡슐화하기 때문에 어떤 객체가 다른 객체에 접근하는 것을 허용할지 말지 지정할 필요가 있기 때문이다.
접근 제어자 | 설명 |
---|---|
public | 패키지에 상관없이 외부/내부 클래스 어디에서나 접근 가능 |
protected | 동일 패키지의 클래스와 자식 클래스에서만 접근 가능 |
접근 제어자 생략 | 디폴트 접근 지정. 같은 패키지 내에 있는 클래스에서만 접근 가능 |
private | 오직 클래스 내부에서만 접근 가능 |
protected
: 자식 클래스인 경우 동일한 패키지가 아니더라도 접근이 가능하다. 이 접근 제어자는 상속을 다루며 더 자세히 다룰 예정이다.클래스 내부에서 사용할 변수와 메서드를 private
으로 선언하여 외부에서의 접근을 막는 것을 정보은닉이라고 한다.
해당 클래스 내부에서만 사용하여 문제가 없는 경우도 있지만, 외부에서 private
변수의 값을 얻어오거나 다시 세팅해줘야할 필요가 있는 경우도 있다. 이럴 때는 private
변수를 사용할 수 있도록 public
메서드를 제공해야한다.
지난 회차에서 만들었던 Student
클래스의 getStudentName()
과 setStudentName()
메서드, 위에서 만들었던 Person
클래스의 setPersonInfo()
과 setPersonFamily()
메서드가 이러한 기능을 하는 메서드이다.
일반적으로 이런 메서드들을 get()
, set()
메서드라고 하는데, getter
, setter
메서드라고도 부른다. 메서드의 이름에서도 알 수 있듯, get()
메서드는 값을 얻기 위한 메서드이고, set()
메서드는 값을 설정해주는 메서드다.
getter
& setter
그렇다면 변수를 public
으로 선언하여 외부에서 접근 가능하게 하는 것과, private
변수로 선언한다음 public
함수로 접근하는 것은 어떤 차이가 있을까?
결론부터 말하자면, get()
, set()
메서드를 이용할 경우 정보 오류가 발생할 확률을 낮출 수 있다.
예를 들어 Person
클래스의 age
멤버 변수를 public
으로 선언했다고 가정해보자. 그러면 외부에서도 age
변수에 마음대로 접근하여 값을 얻거나 바꿀 수 있을 것이다.
그런데 만약 값을 바꾸는 사람이 age
변수에 음수를 대입했다면 어떨까? 음수의 나이를 갖는 것은 있을 수 없는 일이다.
반면 age
변수를 private
으로 선언하고 public
set()
함수를 사용한다면, 이렇게 정보의 오류가 발생할 수 있는 경우의 수를 아래와 같은 방법으로 방지할 수 있게된다.
set()
예제public class Person {
private int age;
public void setAge(int age) {
if (age < 0) {
System.out.println("Error");
}
else {
this.age = age;
}
}
}
캡슐화는 객체의 속성과 기능(연관있는 변수와 메서드)를 하나의 클래스로 묶는 작업을 말한다.
자바는 객체지향 언어의 캡슐화 원칙을 철저히 지켜, 변수나 메서드는 반드시 클래스 내에 구현한다. 클래스에 속하지 않은 변수나 메서드는 존재하지 않는다.
그리고 캡슐화는 단순히 클래스를 만드는 작업을 넘어 실제 구현내용을 외부에 감추는 의미까지 포함하고 있는 개념이다.
외부 객체는 객체의 내부 구조를 알 수 없으며, 오로지 객체가 노출하기로 결정한 변수와 메소드만 이용할 수 있다. 어떤 것을 어떤 범위까지 노출할 지는 접근 제어자를 활용하여 결정한다.
캡슐화 된 객체는 정보 은닉되어 있기 때문에 위에서 getter
, setter
메서드에 대해 설명한 것 처럼 외부에 의해 정보의 변경이 발생할 때 오류가 발생할 확률이 낮아진다.