이번 섹션에선 클래스의 생성자와 프로젝트 패키지에 대해 학습한다.
'객체 지향 프로그래밍' 섹션은 생략했다. 요약해 보면, 클래스 안에 속성(멤버 변수, 필드)과 기능(메서드)을 만들어 객체로서 온전한 역할을 하도록 하고, 객체들끼리 협력하도록 하는 게 객체 지향 프로그래밍이다. 메서드 모듈화와 클래스의 캡슐화 덕분에 유지 보수하기 쉽고 깔끔한 코드를 작성할 수 있다.
1. 생성자
객체를 생성하는 시점에 어떤 작업을 하고 싶다면 생성자(Constructor)를 이용하면 된다. 생성자는 객체 생성 직후 객체를 초기화하기 위한 특별한 메서드라고 생각하면 된다.
this 키워드
아래 Member() 코드를 보면, 메서드의 매개변수에 정의한 지역 변수와 Member의 멤버 변수 이름이 똑같다. 이때 두 변수를 구분하기 위해 사용하는 것이 this 키워드다.
- 더 세부적인 것이 우선순위를 가지기 때문에 보통 멤버 변수보다 매개변수가 우선순위를 가진다. 따라서 Member() 메서드 안에서 age라고 적으면 매개변수에 먼저 접근한다.
- 멤버 변수에 접근하려면 변수 앞에 this 키워드를 붙이면 된다. 여기서 this는 인스턴스 자신의 참조값을 가리킨다.
- ex. this.age
public class MemberInit {
String name;
int age;
int grade;
void Member(String name, int age, int grade) {
this.name = name;
this.age = age;
this.grade = grade;
}
}
참고
요즘은 IDE에서 멤버 변수와 지역 변수(매개변수)를 다른 색으로 구분해 주기 때문에 this를 필요한 경우(변수 이름 중복)에만 사용하기도 한다.
생성자 도입
대부분의 객체 지향 언어는 객체를 생성하자마자 즉시 필요한 기능을 좀 더 편리하게 수행할 수 있도록 생성자(Constructor)라는 기능을 제공한다. 생성자는 메서드와 비슷하지만 다음과 같은 차이가 있다.
- 생성자 이름 = 클래스 이름 (대문자로 시작함)
- 생성자는 반환 타입이 없음
- 나머지는 메서드와 동일함
public class MemberConstructor {
String name;
int age;
int grade;
// 생성자
MemberConstructor(String name, int age, int grade) {
System.out.println("init");
this.name = name;
this.age = age;
this.grade = grade;
}
}
참고
new 키워드를 사용해서 객체를 생성할 때 마지막에 괄호를 포함해야 하는 이유가 바로 생성자 때문이다. 객체를 생성하면서 동시에 생성자를 호출한다.
생성자를 도입하면 다양한 장점을 얻을 수 있다.
- 중복 호출 제거
- 객체 생성과 생성자 호출을 한 줄로 끝낼 수 있다.
- 생성자 호출 필수
- 객체를 생성할 때 직접 정의한 생성자가 있다면, 직접 정의한 생성자를 반드시 호출해야 한다.
- 생성자를 메서드 오버로딩처럼 여러 개 정의할 수 있는데, 이 경우엔 하나만 호출하면 된다.
- 호출하지 않는다면 컴파일 오류로 IDE에서 즉시 확인할 수 있다.
- 필수값 입력 보장
- 참고로 좋은 프로그램은 적절한 제약이 있는 프로그램이다.
기본 생성자
매개변수가 없는 생성자를 말한다. 클래스에 생성자가 하나도 없으면 자바 컴파일러는 매개변수가 없고, 작동하는 코드도 없는 기본 생성자를 자동으로 만들어 준다.
- 생성자가 하나라도 있으면, 자바는 기본 생성자를 만들지 않는다.
- 자바가 자동으로 생성해 주는 기본 생성자는 클래스와 같은 접근 제어자를 가지며, 우리 눈에 보이지 않는다.
기본 생성자를 왜 자동으로 만들어 줄까?
- 만약 자바에서 기본 생성자를 만들어 주지 않는다면, 생성자 기능이 필요하지 않은 경우에도 모든 클래스에 개발자가 직접 기본 생성자를 정의해야 한다.
오버로딩과 this()
생성자도 메서드 오버로딩처럼 매개변수만 다르게 해서 여러 생성자를 제공할 수 있다.
public class MemberConstructor {
String name;
int age;
int grade;
// 생성자
MemberConstructor(String name, int age, int grade) {
System.out.println("init");
this.name = name;
this.age = age;
this.grade = grade;
}
MemberConstructor(String name, int age) {
this.name = name;
this.age = age;
this.grade = 10;
}
}
위 코드에서 두 생성자를 비교해 보면 코드가 중복되는 부분이 있다. 이때 this()라는 기능을 사용하면 생성자 내부에서 자신의 생성자를 호출할 수 있다.
- this()는 생성자 코드의 첫 줄에만 작성할 수 있다. 규칙을 위반하는 경우 컴파일 오류가 발생한다.
public class MemberConstructor {
String name;
int age;
int grade;
// 생성자
MemberConstructor(String name, int age, int grade) {
System.out.println("init");
this.name = name;
this.age = age;
this.grade = grade;
}
MemberConstructor(String name, int age) {
// System.out.println("X"); // 규칙 위반
this(name, age, 10);
}
}
2. 패키지
개념
컴퓨터는 보통 파일을 분류하기 위해 폴더, 디렉터리라는 개념을 제공한다. 자바도 이런 개념을 제공하는데, 이것이 바로 패키지(package)이다. member, product 같은 패키지를 만들고, 해당 패키지 안에 관련된 자바 클래스들을 넣으면 된다.
- 패키지를 사용하는 경우, 항상 코드 첫 줄에 package pack;과 같이 패키지 이름을 적어줘야 한다.
- 패키지 하위에 패키지를 또 만들 수 있다.
- 같은 패키지에 소속돼 있다면 패키지 경로를 생략할 수 있다. 그러나 패키지가 다르다면 패키지 전체 경로를 포함해서 클래스를 적어줘야 한다.
package pack;
public class Product {
public Product() {
System.out.println("pack.Product");
}
}
//
package pack.age;
public class Member {
public Member() {
System.out.println("pack.age.Member");
}
}
import
다른 패키지의 클래스를 사용할 땐 import를 사용하면 패키지를 생략하고 클래스 이름만 적을 수 있다. 코드 첫 줄에는 package를 사용하고 다음 줄에 import를 사용할 수 있다.
- 특정 패키지에 포함된 모든 클래스를 포함해서 사용하고 싶다면 import 시점에 *(별표, asterisk)을 사용하면 된다. 하위 패키지는 포함되지 않는다.
package pack;
import pack.age.Member;
public class PackageMain {
public static void main(String[] args) {
Product p = new Product();
Member member = new Member(); // import 사용으로 패키지 명 생략
}
}
패키지 덕분에 클래스 이름이 같아도 패키지 이름으로 구분해서 같은 이름의 클래스를 사용할 수 있다. 이때, 같은 이름의 클래스가 있다면 import는 둘 중 하나만 선택할 수 있다. 따라서 자주 사용하는 클래스를 import 하고, 나머지는 패키지를 포함한 전체 경로를 적어주면 된다.
참고
생성자에 public이 붙어 있어야 다른 패키지에서 생성자를 호출할 수 있다.
규칙
필수 규칙
- 패키지의 이름과 위치는 폴더(디렉터리) 위치와 같아야 한다.
관례
- 패키지 이름은 모두 소문자를 사용한다.
- 패키지 이름의 앞부분에는 일반적으로 회사의 도메인 이름을 거꾸로 사용한다.
- ex. com.company.myapp
- 외부 라이브러리를 사용하는 경우, 같은 패키지에 같은 클래스 이름이 존재하는 문제를 방지할 수 있다. 따라서 오픈소스나 라이브러리를 만들어 외부에 제공한다면 꼭 지키는 것이 좋다.
- 내가 만든 애플리케이션을 다른 곳에 공유하지 않고, 직접 배포한다면 보통 문제가 되진 않는다.
참고
패키지가 계층 구조를 이루더라도, 모든 패키지는 서로 다른 패키지이다. 따라서 a 패키지의 클래스에서 a.b 패키지의 클래스가 필요하면 import 해서 사용해야 한다. 반대도 마찬가지이다.