[Java-29] 자바 패키지, 접근제한자 & 클래스패스
Java Package, 접근제한자, classPath, 환경 변수
package 와 접근제한자, 클래스패스 에 관하여
우리가 실제로 개발하면서, 정말 많은 클래스를 만들거나, 이미 만들어져 있는 클래스를 사용할 수 있다. 패키지란 이 클래스들을 정리하는 폴더 라고 생각하면 된다. 뒤죽박죽 되어 있는 파일들을 정리하기 위해서 폴더가 필요하 듯이, 정리가 되어있지 않은 클래스들을 정리하기 위해서는 패키지 를 사용하여야 한다.
단 한가지 다른점이 있다면, 패키지는 폴더 그 이상의 역할을 한다. 모든 자바의 클래스는, package 이름.class이름 이다.
package org.example.test1; //tset1 패키지
public class Foo { }
package org.example.test2; //tset2 패키지
public class Foo { }
@Test
public void testClassName(){
Foo foo1 = new Foo();
org.example.test1.Foo foo2 = new org.example.test1.Foo();
System.out.println(foo1.getClass().getName() + " " + foo2.getClass().getName());
assertEquals(foo1.getClass().getName(), foo2.getClass().getName());
}
//Expected :org.example.test2.Foo
//Actual :org.example.test1.Foo
다음과 같은 클래스 2개가 있다고 가정하자, 전부 Foo 라는 동일한 이름을 가진 클래스 이다. 자/바에서 이 2개를 어떻게 서로 다른 클래스라고 생각을 할까? 바로 클래스 이름에 패키지 이름도 포함되어있기 때문이다.
그러면 클래스를 복사해서 사용하는건 가능할까?
만약에 test2 패키지 선언된 Apple.java 가 있다고 가정하자, 이 클래스를 그대로 복사해서, test1 패키지 Apple.java에 복사한다고 가정할 때 빨간줄 에러가 발생할 것이다.
package org.example.test2; // 빨간줄
public class Apple {
}
### pacakge 키워드
* 패키지는 클래스를 컴파일 하는 과정에서 자동적으로 생성되는 폴더이지만, 컴파일러는 클래스파일을 패키지 선언을 보고 그 경로에 따라 클래스 파일이 파일시스템을 통해 생성된다.

#### pacakge 선언하는 방법과 주의할점
* 패키지를 선언하는 방법은 간단하다. **package org.example.xxx** 과 같이 자바 코드 내에서 다음과 같이 선언해주면 된다.
```java
package org.example.test2;
하지만 패키지를 선언하면서 주의할 점이 있다.
- 숫자로 시작하면 안됨, _. $를 제외한 특수 문자를 사용하면 안된다.
- 만약 org.example.123test 라는 패키지를 만든 다음에 이 패키지 안에다가 자바 파일을 옮기려고 한다면, 다음과 같은 에러가 발생할 것이다 .
- java로 시작하는 패키지는 자바 표준 API에서만 사용하므로 사용해서는 안된다.
- 자바 패키지 안에 작성하는게 가능은 하지만, 사용하지 않는걸 권장한다.
- 모두 소문자로 작성하는 것이 관례이다.
패키지 선언이 포함된 클래스를 컴파일 하는 방법
javac Apple.java 현재 클래스에서 App.class를 생성함 (pacakge org.example을 신경쓰지 않음)
javac -d . Apple.java 현재 폴더안에서 package.org.xxx 폴더를 직접 만들어서 넣어줌
javac -d ..\bin Apple.java 현재 폴더와 같은 위치의 bin 폴더에 생성
javac -d C:\test Apple.java 원하는 곳에 패키지 생성
import 키워드
우리가 개발을 하면서 다른 클래스를 사용할 때, 2가지 방법을 사용할 수 있다. 예를들어
package org.example.test2;
public class Apple {
}
import org.example.test2.Apple;
public static void main(String args[]){
Apple apple = new Apple(); // 1. import 키워드를 사용
org.example.test2.Apple apple2 = new Apple(); // 2. 패키지와 클래스를 모두 기술
}
@Test
public void test1(){
Apple useImport = new Apple();
org.example.test2.Apple usePath = new Apple();
System.out.println(useImport.getClass().getName());
System.out.println( usePath.getClass().getName());
assertEquals(useImport.getClass().getName(),usePath.getClass().getName());
}
이렇게 2가지 방법이 있다. 물론 우리는 이 2가지 방법중에 첫번째 방법인 import 방법을 주로 사용할 것이다. intellij 나 eclipse 를 사용하면 자동으로 package 패스를 작성해주긴 하지만, import 문을 직접 작성 하려면 어떻게 작성해야 하는지 알아보자,
import 패키지명.클래스이름
이런식으로 작성이 가능하다.
- import 문이 작성되는 위치는 패키지 선언가 클래스 선언의 사이이다.
- 만약 그 패키지안에 있는 모든 클래스를 한거번에 import 해서 사용하고 싶다면 ' * ' 를 사용하도록 하자 .
package org.example; //패키지 선언
import org.example.test2.Apple; // import 선언 Apple 클래스 사용
import org.example.test2.*; // import 선언 test2 안에 있는 모든 클래스 사용
public class App // 클래스 선언
{
Apple apple = new Apple();
}
접근 제한자
우리가 main() 메소드가 있지 않은 대부분의 클래스는 라이브러리의 역할로, 외부 클래스를 끌어다 사용할 목적으로 만들어질 것이다. 이런 외부클래스를 설계할 때에는 클래스의 필드, 메소드 의 사용을 제한하고, 이를 설계할 필요가 있다.
우리가 클래스를 선언할 때, 고려할 사항은 같은 패키지 내에서만 사용할 것인지, 아니면 다른 패키지에서도 사용할 수 있도록 할 것인지에 대한 결정을 해야한다.
접근제한자는 크게 4가지가 있는데 public, protected, default, private 가 있다.
접근 제한 | 적용 대상 | 접근 할 수 없는 클래스 |
---|---|---|
public | 클래스, 필드, 생성자, 메소드 | 없음 |
protected | 필드,생성자,메소드 | 자식 클래스가 아닌 다른 패키지에 소속된 클래스 |
default | 클래스, 필드, 생성자, 메소드 | 다른 패키지에 소속됟 클래스 |
private | 필드, 생성자, 메소드 | 모든 외부 클래스 |
defualt 접근 제한
- 클래스를 선언할 때, 기본적으로 public class 키워드를 붙여주지만, 만약 *public 이 없다면 어떻게 될까? *
package org.example.test2;
class Apple{ // default 생략
}
package org.example;
import org.example.test2.Apple;
public class App
{
public static void main(String[] args) {
Apple apple = new Apple();
}
}
java: org.example.test2.Apple is not public in org.example.test2; cannot be accessed from outside package
이는 default 가 생략 되어있는 상태로, 다른 패키지에 소속된 클래스가 Apple 클래스를 사용할 수 없다.
public 접근 제한
- 보통 라이브러리를 개발한다라고 했을 때, 대두분의 라이브러리 클래스들의 접근제한은 public일 가능성이 높다. public 클래스는 ㅍ프로젝트 내에서 , 언제든지 그 클래스를 끌어다 사용할 수 있다.
package org.example.test2;
public class Apple {
}
package org.example;
import org.example.test2.Apple;
public class App
{
public static void main(String[] args) {
Apple apple = new Apple();
}
}
Getter 와 Setter 가 필요한 이유
우리가 클래스를 작성을 하면, 또는 깃헙이나 다른 분들이 작성된 자바코드를 보면, 외부 클래스를 작성할 때, 필드(맴버 변수)에 대부분의 접근제한자가 private 를 붙이고는 한다. 이는 해당 클래스를 제외하고는 클래스의 맴버변수에 접근할 수 없다. 라는 것을 의미한다. 그리고 이런 필드에 접근하기 위해서, Getter, Setter의 public한 메소드를 이용해서 맴버변수에 접근한다.
그럼 왜 클래스 변수 에다가 귀찮게 private이라는 접근제한자를 작성하는가?
- 객체의 무결성 : 클래스를 맘대로 사용할 수 있다고 한들, 객체의 값들을 함부로 집어넣는 특히 행위는 객체를 무결성을 깨트릴 수 있다. 예를들어보자
사람(Person) 이라는 클래스 내에서, 그사람의 신체 정보를 담고 있는 객체를 생성한다고 가정해보자,
package org.example.test2;
public class Person {
public String name; // 성함
public Gender gender; // 성별
public int weight; // 몸무게
public int height; // 키
public int age; // 나이
enum Gender{
MALE,FEMALE;
}
}
다음과 같은 클래스가 존재한다고 가정을 해보자
public static void main(String[] args) {
Person person1 = new Person();
person1.name = "이마루";
person1.gender = Gender.MALE;
person1.weight = 70;
person1.height = 177;
person1.age = 25;
}
우리가 다음과 같이 클래스를 사용한다면 더할 나위 없이 좋지만, 만약 양의 정수가 들어가야 하는 weight, height, age 에 음의 중수가 들어가면 어떨까?
public static void main(String[] args) {
Person person1 = new Person();
person1.name = "이마루";
person1.gender = Gender.MALE;
person1.weight = -70;
person1.height = -177;
person1.age = -25;
}
다음과 같이, 몸무게, 키, 나이가 음의 정수인 사람은 아무도 없을 것이다. 이런 무분별하게 클래스 맴버변수에 접근을 하게 되면, 다음과 같은 데이터의 무결성이 깨질 수 있을 것이다. 그래서 먼저 클래스의 맴버변수들을 private 으로 지정한다.
그런다음 각자 저번에 블로그에 포스팅한 this. 키워드를 이용해서 setter 를 작성하도록 한다.
public class Person {
private String name;
private Gender gender;
private int weight;
private int height;
private int age;
enum Gender{
MALE,FEMALE;
}
public void setName(String name) {
this.name = name;
}
public void setGender(Gender gender) {
this.gender = gender;
}
public void setWeight(int weight) {
this.weight = weight;
}
public void setHeight(int height) {
this.height = height;
}
public void setAge(int age) {
this.age = age;
}
}
public static void main(String[] args) {
Person person1 = new Person();
person1.setName("이마루");
person1.setGender(Gender.MALE);
person1.setAge(25);
person1.setHeight(177);
person1.setWeight(70);
}
만약 여기서 음의 정수를 받지 않는 Setter 를 만들고 싶다면, 다음과 같이 작성할 수 있을 것이다.
public void setAge(int age) {
if(age < 0){
System.out.println("0보다 적은 나이를 입력할 수 없습니다");
age = 0;
//또는 오류를 발생시키는 것도 좋은 방법이다.
}
this.age = age;
}
클래스 패스
- 클래스 패스를 알아보기 전에 다음 코드를 먼저 보도록 하자. Apple 이라는
package org.example.test2;
public class Apple {
public static void main(String[] args) {
System.out.println("Hello Apple");
}
}
이 클래스를 intelliJ, eclipse 와 같은 IDE 가 아닌, 맥의 iterm, 또는 cmd 화면에서 구동을 해보자,
그냥 바탕화면에서 java Apple.java 를 진행할 경우 다음과 같은 에러가 발생하는 것이다. 얘기의 요점은 클래스패스란, 결국 자바컴파일러가 클래스를 찾기위한 경로를 말하는 것이다. 그럼 클래스패스를 옵션을 사용해서, 자바 명령어를 실행시켜 보도록하자
java -classpath ".;test2" Apple
-classpath 명령어
이 옵션은 결국에, Apple 클래스를 찾아주는 경로를 옵션으로 붙혀주는 것이다. 그럼 test2 패키지 앞에 ' . ; ' 반점과 새미콜론은 무엇을 의미하는 것일까?
- 마침표 . 는 현재의 패키지(폴더)라는 뜻이고, 새미콜론은 그리고 라는 뜻이다
- 즉, 현재폴더 안에서, 그리고 test2 라는 패키지 내부에서 Apple 클래스를 찾아서 java를 실행시켜라 라는 의미가 된다.
- -classpath 는 -cp 로도 작성이 가능하다.
- 우리가 classpath 를 계속 지정해줘야 하는 것일까?
환경 변수
앞서 클래스 패스에 대해서 알아보았다. 그런데, 이를 java 명령어를 실행시킬 때 마다 계속 진행해줘야하는 것인가? 사실 아니다. 우리가 자바를 사용하기 위해서, 환경 변수를 설정을 해 두었는데, 이를 통해서, 쉽게 해결이 가능하다.
java -classpath "something" Test // 이를 환경변수를 이용하면
java (-classpath "something") Test // 이를 클래스 패스를 생략이 가능해지는 것이다.