
생성자 오버로딩이란, 매개변수의 개수나 타입이 다른 여러 생성자를 같은 클래스 안에 정의하는 것을 말한다. 같은 클래스로 객체를 만들더라도 상황에 따라 다른 방식으로 초기화할 수 있어서 유연한 객체 생성이 가능해진다.
생성자의 특징은 다음과 같다.
예시로 Product 클래스를 생성자 오버로딩 형태로 작성하면 아래와 같다.
public class Product {
int productNum;
String productName;
public Product() {
}
public Product(int productNum) {
this.productNum = productNum;
}
public Product(String productName) {
this.productName = productName;
}
public Product(int productNum, String productName) {
this.productNum = productNum;
this.productName = productName;
}
}
같은 Product 클래스라도 번호만 초기화하거나, 이름만 초기화하거나, 둘 다 초기화하는 등 다양한 생성 방식이 가능해진다.
필드를 외부(다른 클래스)에서 직접 수정할 수 있게 두면, 잘못된 값이 들어가도 이를 통제하기가 어렵다. 예를 들어 나이(age)에 -10 같은 비정상적인 값이 들어가도 막을 방법이 없다.
public class Person {
// field 선언
String name;
int age;
}
public class PersonMain {
public static void main(String[] args) {
Person person1 = new Person(); // 객체 생성
// 외부에서 직접 값 대입 가능
person1.name = "길동";
person1.age = -10; // 비정상 값
System.out.println(person1.name); // 길동
System.out.println(person1.age); // -10
}
}
이처럼 외부에서 직접 필드값을 바꾸는 구조는 데이터 무결성이 깨질 수 있다는 문제가 있다. 이를 해결하기 위한 방법이 Getter / Setter 개념이다.
Setter는 데이터를 설정(set)하는 역할을 하는 메서드이다. 외부에서 필드를 직접 수정하지 못하도록 막고, 특정 메서드를 경유해서 값을 전달받아 초기화 또는 수정하도록 만든다. 또한 이 과정에서 유효성 검증 로직을 넣을 수 있다.
Getter는 데이터를 조회(get)하는 역할을 하는 메서드이다. 외부에서 필드를 직접 읽지 못하도록 막고, 메서드를 통해 안전하게 값을 반환(return)한다.
public void set속성명(자료형 변수명) { this.속성명 = 변수명; }
public void setName(String name) {
this.name = name;
}
public 자료형 get속성명() {
return 속성명;
}
public int getAge() { // field와 getter의 자료형이 일치해야 함
return age;
}
접근제어자는 클래스, 필드, 메서드, 변수의 접근 범위를 제어하는 키워드이다. 정보 은닉(캡슐화)을 실현하고, 코드의 보안성과 유지보수성을 높이기 위해 사용한다.
일반적으로 필드는 private로 숨기고, 외부 접근은 public getter/setter로 열어주는 방식이 많이 사용된다.
Setter에 유효성 검증을 넣어서 잘못된 데이터가 들어오면 막도록 구현했다. 또한 생성자에서도 범위를 검사해 불가능한 입력을 처리하는 예시를 만들었다.
public class UniveStudent {
String name;
int grade;
double score;
public UniveStudent() {
}
public UniveStudent(String name) {
this.name = name;
}
public UniveStudent(int grade) {
this.grade = grade;
}
public UniveStudent(String name, int grade) {
this.name = name;
this.grade = grade;
}
public UniveStudent(String name, int grade, double score) {
this.name = name;
this.grade = grade;
if(score < 0 || score > 4.5) {
System.out.println("불가능한 입력입니다.");
return;
}
this.score = score;
}
public void setName(String name) {
this.name = name;
}
public void setGrade(int grade) {
if(grade < 0 || grade > 4) {
System.out.println("불가능한 범위 입력입니다.");
return;
}
this.grade = grade;
}
public void setScore(double score) {
if(score < 0 || score > 4.5) {
System.out.println("불가능한 범위 입력입니다.");
return;
}
this.score = score;
}
public String getName() {
return name;
}
public int getGrade() {
return grade;
}
public double getScore() {
return score;
}
public void showInfo() {
System.out.println("\n이름: " + name + "\n학년: " + grade + "\n점수: " + score);
}
}
private 필드는 외부에서 직접 접근할 수 없기 때문에, getter를 통해서만 값을 확인할 수 있다.
// 이하의 클래스는 default가 생략
class AccessModifierExample { // 간판 클래스는 public이 명시돼야 함.
public int publicVar = 10;
protected int protectedVar = 20;
int defaultVar = 30;
private int privateVar = 40;
public int getPrivateVar() {
return privateVar;
}
}
public class Main {
public static void main(String[] args) {
AccessModifierExample example = new AccessModifierExample();
System.out.println(example.publicVar);
System.out.println(example.protectedVar);
System.out.println(example.defaultVar);
// System.out.println(example.privateVar); // private 접근 제어자이기 때문에 오류
System.out.println(example.getPrivateVar());
}
}
생성자 오버로딩을 통해 같은 클래스라도 다양한 상황에 맞게 객체를 초기화할 수 있다는 점이 확실히 이해됐다. 또 Getter / Setter는 단순한 문법이 아니라, 데이터를 직접 건드리지 못하게 막고 검증을 거쳐 안전하게 관리하는 구조라는 점이 핵심이었다.
접근제어자를 함께 적용하면 정보 은닉(캡슐화)이 자연스럽게 구현되고, 클래스 내부 로직을 외부에서 함부로 깨지 못하게 만들 수 있다는 점이 인상 깊었다. 결국 객체지향에서 중요한 건 “값을 아무렇게나 넣고 꺼내는 것”이 아니라, 클래스가 책임지고 데이터를 관리하도록 설계하는 것이라는 걸 체감했다.