클래스(Class)는 객체 지향 프로그래밍(OOP)에서 특정 객체를 생성하기 위해 변수와 메서드를 정의하는 일종의 틀이다.
Java에서는 클래스를 class 키워드를 사용하여 정의하며 클래스에는 필드(변수), 메소드(함수), 생성자 등을 포함할 수 있다.
class 클래스이름 {
// 필드
타입 변수이름;
// 메소드
반환타입 메소드이름(매개변수) {
// 실행할 코드
}
}
class Person {
String name;
int age;
void introduce() {
System.out.println("안녕하세요, 제 이름은 " + name + "이고, 나이는 " + age + "살입니다.");
}
}
클래스는 설계도일 뿐 실제 사용하려면 객체를 생성해야한다. Java에서는 new 키워드를 사용하여 객체를 생성한다.
-> 클래스이름 객체이름 = new 클래스이름();
Person person = new Person();
person.name = "홍길동";
person.age = 25;
person.introduce();
new 키워드를 이용하여 Person 객체를 생성한 것이다. 여기서 new 키워드는 내가 만든 객체(class)를 생성하여 메모리(Heap영역)에 데이터를 저장할 공간을 할당하고, 저장한 공간의 주소를 객체에게 반환한다.
Person person1 = new Person();
Person person2 = new Person();
만약 위와 같이 객체를 생성하였다면, person1과 person2의 주소값은 다르다는 것이다.
메소드는 특정 작업을 수행하기 위한 코드 블록이다. 위에서 객체를 생성할 때는 Person클래스의 필드인 name과 age가 접근 제한자를 지정하지 않았기 때문에 기본 접근 제안자(pakage-private)가 적용되었고, 이는 같은 패키지 내에서 접근이 가능하여 클래스 외부에서 필드에 값을 넣었다.
하지만 일반적으로 객체지향 프로그래밍에서는 클래스의 필드를 private로 제한한다. 그럼 클래스 외부에서 접근하여 필드값을 바꾸는 것은 불가능한데 어떻게 할까❓
class Person {
private String name; // private으로 필드 제한
private int age;
// Getter 메서드
public String getName() {
return name;
}
public int getAge() {
return age;
}
// Setter 메서드
public void setName(String name) {
this.name = name;
}
public void setAge(int age) {
if (age < 0) { // 유효성 검사
System.out.println("나이는 0 이상이어야 합니다.");
return;
}
this.age = age;
}
}
public class Main {
public static void main(String[] args) {
Person person = new Person();
// Setter 메서드로 값 설정
person.setName("홍길동");
person.setAge(25);
// Getter를 통해 값 확인
System.out.println("이름: " + person.getName());
System.out.println("나이: " + person.getAge());
person.introduce();
}
}
위 코드는 객체지향 프로그래밍 원칙에 따른 Getter/Setter 메소드이다.
객체지향의 핵심 중 하나인 캡슐화는 객체의 내부 데이터를 숨기고 외부에서 접근할 수 있는 인터페이스를 제공하여 데이터를 보호하고 노출되지 않도록 한다.
즉, 필드를 private으로 선언하고, Getter와 Setter를 통해 데이터를 간접적으로 접근하도록 강제하면
⚠️ 그런데 setter를 사용하든 필드에 접근해서 값을 바꾸든 결과적으로 객체 내부 값을 바꾼다는 점은 동일한데, 왜 객체지향적 설계 원칙에서 Setter가 더 선호될까❓
"객체지향 생활 체조 원칙을 보자"
🖥️ 직접 접근
person.name = "홍길동";
person.age = -25; // 음수 나이 입력 가능
필드를 public으로 열어두면 외부 코드가 아무런 제약 없이 내부 코드를 변경하여, 데이터의 무결성을 해칠 수 있다.
🖥️ Setter 사용
public void setAge(int age) {
if (age < 0) {
throw new IllegalArgumentException("나이는 0 이상이어야 합니다.");
}
this.age = age;
}
person.setName("홍길동");
person.setAge(-25); // Setter 내부에서 잘못된 값을 거부
💡 Setter는 유효성을 검사를 포함하거나, 추가 로직을 실행할 수 있는 기회를 제공한다.
예를 들어 위와 같이 나이가 음수로 설정되는 것을 막을 수 있다.
이런 로직 덕분에 객체는 자신이 허용하는 데이터만 저장하며 외부의 부적절한 데이터로 인해 깨지지 않도록 할 수 있는 것이다.
그럼 private 접근 생성자로 지정되면 무조건 메소드로 초기 값을 넣어주어야 하나❓ ✖️
💡생성자를 통해 초기값을 넣어줄 수 있다.
생성자는 객체를 생성할 때 호출되는 특별한 메소드이다. 생성자는 클래스와 이름이 같아야 하며, 반환타입을 명시하지 않는다.
사실 위에서 생성자는 이미 만들어졌었다. new 키워드 에서 default(기본)생성자를 사용하기 때문이다. 컴파일 시점에서 프로그래머가 따로 정의한 생성자가 없다면 자동으로 생성되어 new 키워드가 사용될 때 기본 생성자가 호출된다.
즉, new 키워드는 생성자를 이용해 객체를 인스턴스화 시킬 수 있는 것이다!!
class Person {
String name;
int age;
// 생성자 정의 -> Person person = new Person("홍길동", 25);
Person(String name, int age) {
this.name = name;
this.age = age;
}
}
생성자는 오버로딩을 통해 여러개 만드는 것도 가능하다.
(매개변수의 개수 및 데이터 타입을 다르게 함)
this는 현재 객체 자신을 가리키는 참조 변수이다. 주로 생성자나 메소드에서 필드와 매개변수를 구분하거나 다른 생성자를 호출할 때 사용된다.
➕ 필드와 매개변수 구분
this를 사용해 Person클래스의 필드인 name과 와 매개변수로 받은 name을 구분
class Person {
String name;
Person(String name) {
this.name = name;
}
}
➕ 다른 생성자 호출
class Person {
String name;
int age;
Person(String name) {
this(name, 0); // 다른 생성자 호출
}
Person(String name, int age) {
this.name = name;
this.age = age;
}
}