패키지와 접근자

패키지는 말 그대로 꾸러미이다. 쓰레기 분류 수거할 때에 사용하는 꾸러미를 생각해 보라. 자바는 비슷한 기능이나 비슷한 성격을 가진 클래스들을 관리하기 위해서 패키지를 제공한다. 자바 API는 모두 특정 패키지에 속해있다.(주로 java로 시작하는 이름의 패키지이다.) 이제 우리가 만드는 클래스가 특정 패키지에 속하게 하는 방법을 알아본다.

Account.java
package javabank;

public class Account {
	private String accountNum;
	private double balance;
	
	public void deposit(double amount) {
		balance = balance + amount;
	}
	
	public void withdraw(double amount) {
		balance = balance - amount;
	}
	
	public long getBalance() {
		return balance;
	}
	
	public static void main(String[] args) {
		System.out.println("패키지 테스트");
	}
}

첫번째 라인의 package javabank; 의해서 Account 클래스는 javabank패키지에 속하게 된다.
패키지를 선언한 자바 소스를 컴파일 할 때는 반드시 -d 다음에 한칸 띄고 바이트 코드가 생성될 디렉토리를 지정해 주어야 한다. 만일 -d 옵션을 생략한다면 바이트 코드는 패키지가 적용되지 않는다.
예제를 실행하기전에 아래 그림과 같은 디렉토리 구조를 갖도록 하고 Account.java소스 파일도 아래처럼 C:/javaApp/bank/src/javabank 디렉토리에 생성한다.
패키지 예제 디렉토리 구조 bin디렉토리는 바이트 코드가 위치할 디렉토리이다. src디렉토리는 자바 소스 파일이 위치할 디렉토리인데 만약 클래스에 패키지가 선언되어 있다면 src에 패키지명으로 서브 디렉토리를 만들고 해당 소스를 그곳에 위치하도록 한다.(대부분 이렇게 소스를 관리한다.)

패키지 이름은 일반적으로 도메인 이름의 역순으로 짓는다.
대개 패키지 이름은 도메인 이름의 역순으로 짓는다.
또한, 패키지 이름에 .(도트)가 2개 이상 포함할 것을 권장한다.
패키지가 적용된 자바 소스 파일을 파일 시스템에서 제대로 관리하려면 어떻게 해야 할까?
만일 Log라는 클래스에 net.java_school.commons 이라는 패키지가 적용됬다면 src디렉토리 아래 .(도트)마다 서브 디렉토리를 만들고 마지막 서브 디렉토리 commons에 Log.java 소스를 위치시키면 된다.
javaApp-Log.java

다시 Account.java로 돌아오자. 모든 준비가 되었다면 소스 파일이 있는 C:/javaApp/bank/src/javabank로 이동한 후 아래와 같이 컴파일한다.

C:\ Command Prompt
C:\javaApp\bank\src\javabank>javac -d C:/javaApp/bank/bin Account.java

-d 옵션다음의 경로 구분자는 윈도우 시스템이라도 /(슬래시)를 사용할 수 있다. 물론 윈도우 시스템의 경로 구분자 \(역슬래시)도 사용할 수 있다. 컴파일 후 C:/javaApp/bank/bin 디렉토리로 이동해서 Account의 바이트 코드가 생겼는지 확인한다.

C:\ Command Prompt
C:\javaApp\bank\bin>dir
2008-03-07  오후 12:06    <DIR>          javabank

bin 디렉토리에는 Account.class 파일이 보이지 않고, 대신 javabank 디렉토리가 보인다.
Account.class 파일은 javabank 서브 디렉토리에 있다.
Accont.java를 컴파일할 때 -d 옵션으로 C:/javaApp/bank/bin 디렉토리를 지정하게 되면 C:\javaApp\bank\bin 디렉토리에는 패키지로 설정한 이름과 같은 서브 디렉토리(javabank)가 생기고 그 안에 바이트 코드(Account.class)가 생성된다.
Account.class and Account.java

JVM은 C:/javaApp/bank/bin디렉토리에 javabank.Account클래스가 있다고 이해한다. 패키지가 적용된 클래스는 패키지명.클래스명이 완전한 클래스명(Fully Qualified Class Name:FQCN)이다. ("길동"이 아닌 "홍길동"이 완전한 이름이듯이) 다른 클래스에서 Account 클래스를 사용하려면 코드에서 javabank.Account로 써야 한다. (자바 문서에서 "클래스"란 용어는 문맥에 따라 바이트 코드를 의미할 때가 있고 클래스 소스 파일을 의미할 때가 있다.) 클래스를 실행할 때도 마찬가지이다. Account 클래스를 실행하기 위해서는 C:/javaApp/javabank/bin 디렉토리에서 java javabank.Account로 실행해야 한다. 다시 한번 강조해서, JVM은 bin 디렉토리에 javabank.Accont 클래스가 있다고 이해하기 때문이다.

C:\ Command Prompt
C:\javaApp\bank\bin>java javabank.Account
패키지 테스트

만약 클래스가 없는 디렉토리에서 실행하려면 어떻게 해야 할까? 이 경우 JVM에게 바이트 코드가 어디에 위치하는지 알려주어야 하는데, 자바 인터프리터(java.exe)의 classpath1옵션을 사용하면 된다. 아래는 C:/Program Files 디렉토리에서 javabank.Account를 실행하는 방법을 보여주고 있다.

C:\ Command Prompt
C:\Program Files>java -classpath C:/javaApp/bank/bin javabank.Account
패키지 테스트

-classpath다음에 한 칸 띄고 실행할 바이트 코드가 있는 곳을 절대 경로 또는 상대 경로로 지정한다. 위에서는 classpath 값을 절대 경로로 지정하고 있는데 상대 경로로 지정한다면 다음과 같다.

C:\ Command Prompt
C:\Program Files>java -classpath ../javaApp/bank/bin javabank.Account
패키지 테스트

.은 현재 디렉토리이고 ..은 한단계 위의 디렉토리를 의미한다.
자바 컴파일러인 javac.exe도 classpath2옵션은 가지고 있다.
자바 컴파일러에서 이 옵션을 사용하는 경우는 클래스(컴파일하려는 자바 소스)에서 다른 사용자가 만든 클래스(바이트 코드)를 사용하는 코드가 있는 경우이다.
만일 다른 사용자가 만든 클래스(바이트 코드)가 C:/javaApp/commons/bin에 있다면 이 경로를 자바 컴파일러에게 알려주어야 한다.
왜냐하면 자바 컴파일러는 바이트 코드를 만들기 전에 소스에서 사용하는 클래스(바이트 코드)들을 제대로 사용되고 있는지 모두 검사하기 때문이다.
위 설명에 대한 예를 만들어 보자.
새로운 클래스는 아래와 같은 디렉토리 구조를 가지도록 하겠다.
Log.class and Log.java

다음은 새로 만들 Log클래스3이다.

Log.java
package net.java_school.commons;

public class Log {
	public static void out(String msg) {
		System.out.println(new java.util.Date() + " : " + msg);
	}	
}

Log.java를 컴파일한다.

C:\ Command Prompt
C:\javaApp\commons\src\net\java_school\commons>javac -d ^
C:\javaApp\commons\bin Log.java

다음으로 기존 Accont클래스가 Log클래스를 사용하도록 수정한다. Log클래스는 net.java_school.commons 란 패키지가 적용되었으므로 FQCN은 net.java_school.commons.Log 이므로 소스에서도 그대로 써야 한다.

public void deposit(double amount) {
	balance = balance + amount;
	net.java_school.commons.Log.out(amount + "원이 입금되었습니다.");
	net.java_school.commons.Log.out("잔고는" + this.getBalance() + "원입니다.");
}

public void withdraw(double amount) {
	balance = balance - amount;
	net.java_school.commons.Log.out(amount + "원이 출금되었습니다.");
	net.java_school.commons.Log.out("잔고는 " + this.getBalance() +"원입니다.");
}

public static void main(String[] args) {
	Account hong = new Account();
	hong.deposit(10000);
	hong.withdraw(500);
}

Account.java를 수정했으니 다시 컴파일한다.

C:\ Command Prompt
C:\javaApp\bank\src\javabank>javac -d C:\javaApp\bank\bin ^
-classpath C:\javaApp\commons\bin Account.java

javabank.Account 클래스를 실행한다.

C:\ Command Prompt
C:\javaApp\bank\bin>java -classpath .;C:\javaApp\commons\bin ^
javabank.Account

우리가 만든 자바 프로그램은, javabank.Account와 net.java_school.commons.Log 클래스로 구성되어 있다. 이 두 클래스의 위치가 다르므로 자바 인터프리터의 classpath옵션으로 두 클래스의 위치를 지정해야 한다. classpath옵션이 사용되면 JVM은 관련 클래스를 classpath로 설정된 곳에서만 찾는다. 그래서 현재 디렉토리를 의미하는 .(도트)가 클래스 패스에 포함되어야 한다.

외부 자바 라이브러리를 자신의 프로그램에 추가하는 방법

대부분 외부 자바 클래스는 jar형태의 압축파일로 제공된다. Log클래스를 다른 자바 프로그래머가 사용하도록 배포하려면 jar.exe 를 이용한다. Log바이트 코드가 있는 디렉토리로 이동하여 다음 멸령을 수행한다.

C:\ Command Prompt
C:\javaApp\commons\bin>jar cvf java-school-commons.jar .

탐색기를 이용하여 commons에 생성된 java-school-commons.jar파일을 잘라내기 하여 C:\devLibs에 붙여넣는다. javabank.Account클래스를 실행하는데 이번에는 C:\devLibs에 있는 java-school-commons.jar파일속에 있는 Log클래스가 이용될 수 있도록 실행한다.

C:\ Command Prompt
C:\javaApp\bank\bin>java -classpath ^
.;C:\devLibs\java-school-commons.jar javabank.Account

이전과는 달리 jar파일명까지의 경로를 classpath에 지정해줘야 한다.

접근자

접근자는 외부에서 접근할 수 있는지 여부를 결정한다. 접근자는 2단계 접근 제어를 제공한다. 1단계 접근 제어는 접근자가 클래스 선언에 쓰일 때이다.

public 접근자가 Account의 클래스 선언에 쓰인 경우

package javabank;

public class Account {
	// 구현부
} 

package private 접근자가 Account의 클래스 선언에 쓰인 경우

package javabank;

class Account {
	// 구현
} 
1단계 접근 제어 : 접근자가 클래스 선언에 쓰일 경우
public 모든 패키지의 클래스에서 참조할 수 있다.
package private 같은 패키지에 있는 클래스에서만 참조할 수 있다.

public 접근자가 적용된 Account의 클래스를 패키지가 다른 클래스에서 Account 를 참조한 예이다.

package example;//Account에 적용된 javabank와 다른 패키지

public class BankSystem {

	//javabank.Account 타입의 파라미터를 선언할 수 있다.
	public void deposit(javabank.Account account, double amount) {
		account.deposit(amount);//deposit()메소드 선언에 적용된 접근자가 적용된다.
	}

}

아래 그림은 javabank 패키지에 있는 클래스가 모두 클래스 선언에 public으로 접근자가 지정되어 있는 경우다. (그림에서 +는 public을 의미한다.) PUBLIC 접근자 그림 (Bank, Customer는 아직 없지만 Account와 같이 javabank란 이름의 패키지로 만들었다고 가정하자.) Bank, Customer, Account는 어떤 패키지에 속한 클래스라도 참조할 수 있다. 그림에서 example 패키지에 있는 BankSystem는 javabank패키지의 모든 클래스를 접근할 수 있다. 이 말은 위의 BankSystem소스에서 볼수 있듯이 javabank.Account account;와 같이 변수 선언의 데이터 타입으로 사용할 수 있고 파라미터 타입으로 javabank.Account를 쓸 수 있다는 의미이다. import문을 이용하면 클래스 바디에서 javabank.Account를 클래스 바디에서 Account로 줄여 쓸 수 있다.

package example;

import javabank.Account;

public class BankSystem {

	public void deposit(Account account, double amount) {
		account.deposit(amount);
	}

}

이제는 Bank, Customer, Account의 클래스 선언에 package private 접근자로 설정된 경우를 가정해 보자. package private 접근자 그림 이 경우 다른 패키지에 있는 BankSystem클래스에서는 Bank, Customer, Account를 사용할 수 없다. 그림처럼 보이지 않는다고 생각하면 된다. BankSystem에서 Bank, Customer, Account클래스를 참조하려 하면 컴파일 에러가 난다.

2단계 접근 제어 : 접근자가 필드나 메소드에 쓰일 경우
public 모든 패키지에서 접근 가능하다.
protected 같은 패키지에서만 접근 가능하다. 단, 다른 패키지에 속해있는 자식 클래스에서 부모클래스의 protected접근자로 지정된 멤버는 접근 가능하다.
package private 같은 패키지에서만 접근 가능하다.
private 외부에서 접근할 수 없다.

자바에서 필드(Fields)란 클래스 몸체에서 메소드가 아닌 모든 것을 의미한다. 멤버(members)는 객체의 속성을 저장하는 변수나 객체의 행위에 해당하는 메소드를 의미한다. 위의 표를 이해하기 쉽게 풀어 설명하도록 한다. 필드나 메소드에 적용되는 가장 쉬운 접근자는 public과 private이다. public이면 모든 패키지에서 접근할 수 있다. private이면 외부에서 접근할 수 없다. package private 접근자는 같은 패키지에서만 접근할 수 있다. protected는 package private 접근자보다 접근 허용범위가 더 넓다. (사실 protected에 대해서는 상속을 공부한 후에 아래 설명을 다시 보는 게 낫다.) 일단 package private 접근자처럼 같은 패키지에서 접근할 수 있다. 여기에 추가하여, 부모와 패키지가 다른 자식 클래스에서 부모 클래스의 protected 멤버에 접근할 수 있다. 그림에서 #은 protected를 의미한다. Protected 접근자 예 그림

캡슐화 : 객체의 자료에 접근하려면 메소드를 통해야만 접근하도록 한다.

클래스를 설계할 때는 아래 사항은 객체 지향 프로그래밍의 기본이므로 지키도록 한다. 이를 캡슐화라고 하는데 외부에서 볼 때 객체의 꼭 필요한 부분만 볼 수 있도록 해준다.

  1. 멤버변수를 private로 선언한다.
  2. private 선언된 멤버변수에 대한 setter, getter를 만든다.

(getter,setter은 아래 예제를 보고 이해하자.) 웹사이트의 회원 클래스를 캡슐화하여 구현해 보자.

User.java
package net.java_school.user;

public class User {
	private String username; //유저이름
	private String password; //패스워드
	private String fullName; //성명
	private String email; //이메일
	
	public String getUsername() {
		return username;
	}
	public void setUsername(String username) {
		this.username = username;
	}
	public String getPassword() {
		return password;
	}
	public void setPassword(String password) {
		this.password = password;
	}
	public String getFullName() {
		return FullName;
	}
	public void setFullName(String fullName) {
		FullName = fullName;
	}
	public String getEmail() {
		return email;
	}
	public void setEmail(String email) {
		this.email = email;
	}
	
}

이클립스 설치

다음 장부터 예제는 이클립스를 사용하기로 한다. 이클립스를 사용하면 위에서 실습했던 디렉토리 구조로 소스를 관리하게 되면서 클래스 패스에 대해서도 크게 신경쓰지 않아도 된다. http://www.eclipse.org/downloads/ 에서 Eclipse IDE for Java EE Developers 선택하여 다운로드한다. 압축을 풀면 생기는 eclipse 폴더를 원하는 곳에 복사하면 설치가 끝난다. 여기서는 eclipse 폴더를 C:/ 에 붙여넣기했다고 가정하고 설명한다.

실행

C:/eclipse/eclipse.exe를 더블클릭하면 이클립스가 실행된다. 실행되면 먼저 이클립스는 워크스페이스(workspace)를 어디로 할 지를 묻는다. workspace launcher 워크스페이스(workspace)는 작업장이다. 이클립스에서 워크스페이스는 하나 이상의 프로젝트들을 담는 그룻이다. 위 그림과 같이 워크스페이스를 디폴트로 보여지는 디렉토리로 선택하지 않도록 한다. 또한 Use this as the default and do not ask again 에 체크 하지 말고 그대로 둔다. 서로 다른 성격의 프로젝트를 관리하려면 워크스페이스를 여러개 두면 편리한 경우가 있을 수 있기 때문이다. 여기서는 워크스페이스를 C:/javawork 로 지정했다. 지정을 하고 OK 버튼을 클릭하면 다음과 같은 환영메시지를 볼 수 있다. welcome 환영 페이지에서 보이는 메뉴로 이클립스 소개, 튜토리얼, 샘플, 새로운 릴리즈에 추가된 내용을 볼 수 있다. 환영 페이지를 닫으면 아래와 같은 워크벤치(WorkBench)가 나타난다. workbench

이클립스 용어

워크벤치(workbench)

이클립스에서 보이는 윈도우 전체를 말한다. 윈도우는 크게 4개로 나뉘어지는데 이 분리된 영역을 뷰(view)라고 부른다.

perspective

사전적 의미는 "관점"이다. 뷰를 모두 포함해서 퍼스펙티브(perspective)라고 부른다. 위 화면은 Java 퍼스펙티브이다. 이 퍼스펙티브는 자바 프로그램을 개발할 때 필요한 뷰들로 구성된다. 퍼스펙티브를 변경하려면 오른쪽 상단의 Open Perspective 메뉴바 버튼을 이용한다.

Package Explorer 뷰

왼쪽 상단의 뷰로 Java 프로젝트에 속한 리소스(패키지, 클래스, 외부 라이브러리)를 보여준다.

Hierarchy 뷰

왼쪽 상단의 뷰로 Java의 상속구조 보여준다.

Outline 뷰

오른쪽 상단의 뷰로 현재 에디터에 열려 있는 소스 파일의 구조를 보여준다.

Editor 뷰

화면중앙에 위치하는 뷰로 소스 코드를 편집하는 데 사용된다.

problems 뷰

하단에 위치한 뷰로 컴파일 에러나 경고 표시한다.

Javadoc 뷰

하단에 위치한 뷰로 Package Explorer나 Outline 뷰에서 선택한 부분에 대한 Javadoc 주석를 보여준다.

Declaraion 뷰

하단의 위치한 뷰로 에디터에서 선택된 부분이 어떻게 선언됐는지 간략히 보여준다.

뷰의 위치는 마우스로 원하는 위치로 변경할 수 있다. 하지만 익숙해 질때까지는 그대로 두는 게 좋다.

자바 예제

먼저 Java Perspective 인지 확인한다. 이클립스에서는 자바 소스는 반드시 프로젝트에 속해야만 한다. 자바 프로젝트를 생성하려면 메뉴바에서 File > New > Java Project 선택하거나 또는 아래 그림과 같은 툴바에서 가장 왼쪽을 클릭한다. toolbar 프로젝트 이름을 HelloWorld 로 지정한다. 그 외 설정은 특별히 지정하지 않아도 된다. 이클립스는 소스는 src, 컴파일된 바이너리 파일은 bin 디렉토리에 저장하여 관리한다. 입력 후 Finish 클릭하면 HelloWorld 프로젝트가 생성되고 Package Explorer 에 표시된다 project wizard 아래 툴바 메뉴에서 두번째를 클릭한다. toolbar 패키지 이름에 net.java_school.example 라고 입력하고 Finish 를 클릭한다. 이제 Package Explorer 뷰에서 패키지가 보이게 된다. package wizard 마우스로 Package Explorer 에서 net.java_school.example 패키지를 선택한 상태에서 아래 툴바 메뉴 중 오른쪽 마지막 버튼을 클릭한다. toolbar 클래스 이름으로 HelloWorld 라고 입력한다. 메인 메소드가 필요하므로 public static void main(String[] args)에 체크한다. class wizard Finish 를 클릭한 후 에디터에서 main 메소드를 아래와 같이 구현한다.

HelloWorld.java
package net.java_school.example;

public class HelloWorld {

   public static void main(String[] args) {
      System.out.println("Hello World !");
   }

}

저장하면 컴파일을 따로 할 필요가 없다. 이클립스가 백그라운드에서 계속해서 컴파일을 해주기 때문이다. 에디터에서 바로 컴파일 에러를 확인할 수 있는 이유가 여기에 있다. 실행하려면 Package Explorer 에서 HelloWorld 클래스를 선택한 상태에서 오른쪽 마우스를 클릭하고 컨텍스트 메뉴를 띄운 후 아래 그림처럼 선택한다. run console 뷰가 생기면서 Hello World !가 출력된다. console view

주석
  1. 자바 인터프리터(java)를 실행하면 클래스 로더(Class Loader)가 classpath에서 자바 프로그램을 구성하는 모든 클래스 정보를 찾아 메모리 공간에 로드(load)한다. 이때 단 하나의 클래스라도 못 찾는다면 실행에 실패하고 클래스를 못 찾았다는 에러 메시지를 출력한다. classpath 옵션를 지정하지 않으면 클래스 로더는 현재 디렉토리에서 사용자가 만든 클래스를 찾는다. java.lang.String 클래스나 java.lang.System 클래스와 같은 자바 API의 경로는 클래스 로더가 이미 알고 있으니 classpath에 지정해 주지 않는다.
  2. javac나 java의 classpath 옵션은 cp 옵션으로 대신할 수 있다.
  3. Log 클래스에 대한 설명은 따로 하지 않는다. Log 클래스의 out 메소드는 static 메소드로 static 키워드에 대한 설명은 static에서 다룬다.