[Java] package에 대해 알아봐요.

최지수·2022년 2월 22일
0

Java

목록 보기
10/27
post-thumbnail

패키지(Package)

자바 클래스를 모아놓은 디렉토리, 즉 폴더 개념이에요. 패키지에는 클래스 또는 인터페이스를 포함시킬 수 있고, 서로 관련된 클래스들끼리 그룹 단위로 묶어 놓아 효율적으로 관리할 수 있게 해요.

같은 이름의 클래스여도 폴더로 분리하여 서로 다른 패키지에 존재하는 것이 가능해, 자신만의 패키지 체계를 유지하면서 다른 개발자가 개발한 클래스 라이브러리의 클래스와 이름이 충돌하는 것을 피할 수 있어요.

패키지 선언은 아래처럼 해주면 되요.

package {패키지, 즉 해당 소스가 속한 폴더 이름};

굳이 이 키워드를 선언해야 하냐면 답은 Yes에요. 자바에서 클래스를 선언할 때 package를 지정해주지 않으면 defaultunnamed package로 여겨져요. 이렇게 생성된 클래스는 다른 package에 있는 자바 파일에서 불러올import 수 없어요.

물론, 같은 default package에 존재하면 가능해요.

💡import
자바 패키지 내 클래스를 불러오는 키워드에요. .을 통해 하위 경로로 이동이 가능해요. 선언 방식은 아래와 같아요.

import {패키지명}[.{하위 패키지명}].클래스(또는 *);

./java/lang/Function 클래스를 불러오고 싶다면 아래처럼 선언해주시면 되요.

import java.lang.Function;
import java.lang.*; // 해당 패키지 내 모든 클래스를 불러와요.
					// 특정 클래스만 import 윗 구문과 성능상 차이는 전혀 '없어요'

주의할 점은, import 문에서 클래스 이름대신 *을 사용하는게 하위 패키지의 클래스까지 포함하는 건 아니에요.

import static이라는 문법이 존재해요. 이 방식으로 선언하면 클래스 사용할 때 따로 패키지를 기입할 필요가 없어요.

import static java.lang.System.out;

public class Example{
	public static void main(String[] args){
    	// System.out.println("Hello");
    	out.println("Hello");
    }
}

import는 C/C++ 개발한 입장에서 굳이 대입하자면, #include와 동일한 방식으로 동작한다고 이해하시면 되요.

패키지 기본 속성에 대해서 설명드렸어요. 이제 패키지 선언에 있어 여러 상황을 알아보기 위해 패키지랑 자바 코드를 아래와 같이 구성해봤어요.

package는 다중 선언이 불가능해요.

하나의 소스 파일엔 오직 하나의 package만 선언이 가능해요. package는 단순히 계층 구성을 표현하는게 아니라 컴파일 시점의 계층 구성을 정의하기 때문이에요. 그래서 package를 다중 선언하게 되면 해당 소스 파일은 다수의 폴더에 존재한다는 의미가 되요.

같은 내용과 이름은 존재할 수 있지만 그 파일 자체가 여럿 존재할 수는 없어요. 그래서 package는 소스 파일 당 하나만 선언이 가능해요.

패키지와 경로는 일치시키는 것을 '추천'해요.

예제를 통해 보여드릴게요. 저는 이 부분까지 학습하면서 위에 package의 다중 선언이 불가하다는 이유를 깨닳았어요. 이걸 보시는 여러분들도 그랬으면 좋겠네요 ㅎㅎ.

코드에 대한 계층 설명
1. Main.java : 실제 \to ./src/Main.java, 소속 패키지 \to practice
2. HelloWorld.java : 실제 \to ./src/practice/HelloWorld.java, 소속 패키지 \to practice

해당 예제는 실제 폴더 구성 경로와 패키지 구성 경로를 다르게 해봤어요. javac -d ./out Main.java를 수행하면 정상 동작해요. 다만 결과물에 대한 계층 구성이 좀 상이해요.

둘 다 ./out/practice 폴더에 구성이 되요. 이렇게 패키지 계층 구성과 실제 폴더 경로를 다르게 하면 결과가 기존 구성과 다르게 나온다는 것을 알 수 있어요.

그럼 여기서, CLI 환경에서 Main을 실행시키려면 어떻게 해야할까요? 참고로 java -cp ./out/practice Main은 안되요.

정답은 바로 java -cp ./out practice.Main이에요. 이유는 아래에 있어요(그니까 좀 더 읽어줘요 ㅎㅎ).

자바에선 같은 이름의 클래스가 존재할 수 있어요.

C++에선 클래스를 단순히 이름으로만 구분했어요. 그래서 다른 경로나 소스 파일에 존재하더라도, 클래스 이름이 같으면 컴파일 에러를 발생시켜요. 자바는 이름이 같더라도 클래스의 실제 이름Full name은 패키지 명도 포함되어 구분되요. ./Main의 클래스는 Main이고, ./MyFunction/Main의 클래스의 실제 이름은 MyFunction.Main인 거에요. 그래서 같은 이름의 클래스가 있어도 컴파일이 잘되요.

따라서 이렇게 같은 이름의 클래스가 존재해도 다른 패키지경로에 있다면 정상적으로 컴파일되요.

위에 예제에선 java를 통해 실행할 때 실제 이름Full name으로 해줘야 하는 이유도 이것이에요.

💡참고로, 자바 언어 자체의 패키지들은 java.javax.로 시작해요.

내용을 정리하자면,

package 특성
1. package는 계층 구조로 클래스 라이브러리를 관리하기 위한 컴파일러 단에서 정의하기 위한 계층 구조 정의 키워드에요.
2. package는 소스 당 하나만 선언 가능해요. 왜냐하면 파일은 오직 하나의 계층 구조를 가지기 때문이에요.
3. 그래서 유지보수를 위해서라도 package을 선언할 때 실제 폴더 구성 경로와 일치시켜야 해요.
4. 클래스의 실제 이름은 {package 구성}.{클래스 이름}이에요.

패키지 명명 국룰

오라클에서 제공하는 내용을 기반으로 말씀드릴게요. 모든 패키지 이름은 클래스나 인터페이스와 혼동을 주지 않기 위해 소문자lower case로 지정해요. 그리고 일반적인 기업에서는 com.회사이름.패키지 이름식으로 명명해서 해당 패키지는 회사.com의 개발자가 이 패키지를 만들었다고 명시해요. 특수적으로 지역도 표기하고 싶은 경우에는 com.회사이름.지역.패키지 이름으로 하는 방법도 있어요.

이 방식이 항상 잘되란 법은 없어요. 만약 회사 이름에 -이 포함되거나, 앞 자리가 숫자이거나, 또는 자바의 고유 keyword일 수도 있죠(ex. int, boolean(이런 회사가 있나?)). 각 상황에 대한 대처 방식은 아래와 같아요.

패키지의 종류

크게 2가지로 나뉘어요.

  1. 빌트인Built-in 패키지
  • 자바 프로그래밍에 가장 기본이 되는 클래스들을 포함하고 있어요.
  • 따로 import를 선언하지 않아도 사용할 수 있어요.
  • 대표적인 예로 java.lang 패키지가 있어요.
  1. 유저 정의User-defined 패키지
  • 개발자가 직접 만든 패키지에요.

빌트인(Built-in) 패키지를 조금 깊게 알아봐요.

기본적인 자바 클래스 라이브러리들을 포함하는 빌트인 패키지인, java.lang에 있는 클래스들을 나열해봤어요. 보다 자세한건 Oracle Java Reference를 참조하면서 알아가면 좋아요(저도 이렇게 알아가도록 할게요!).

Object

모든 클래스의 최고 조상격인 클래스에요. 클래스를 선언하면 자동으로 상속되요. 그래서 Object 고유의 멤버들은 모든 클래스에서 사용이 가능해요.

String

문자열을 저장하고 이를 다루는데 필요한 메서드를 제공해요. C/C++을 해보신 분이라면 기존 문자열을 char 또는 string 같은 타입으로 문자열을 선언하는데, 자바는 String이라는 클래스로 문자열을 다뤄요.

StringBuffer와 StringBuilder

String 클래스는 인스턴스를 생성하면 지정된 문자열을 변경할 수가 없어요. 새로 초기화하던가 해야죠. StringBuffer 클래스는 이를 가능케 해요. 내부적으로 문자열 편집을 위한 버퍼buffer를 가지고 있고, 인스턴스를 생성할 때 크기를 지정할 수도 있어요.

StringBuffer는 멀티스레드에 안전Thread Safe하도록 만들어졌어요. 그래서 데이터가 안전하도록 동기화Synchronization 작업을 거쳐서, 스레드를 사용하지 않으면 불필요한 처리로 인해 효율성이 떨어져요. 그래서 StringBuilder가 나왔어요. StringBuilderStringBuffer와 완전 똑같은 기능을 제공하고, 동기화 작업을 거치지 않아서 보다 나은 성능을 보여요.

💡 여담이지만, String 클래스는 내부적으로 StringBuilder를 사용해요. 그래서 저 같은 경우 문자열 관련 처리는 StringBuilder를 사용해서 퍼포먼스를 올려요 ㅎㅎㅎ.

Math

기본적인 수학 계산에 유용한 메서드로 구성되어 있어요. 생성자 접근자modifierprivate이기 때문에 인스턴스로 생성을 할 수 없고 정적 메서드만 사용할 수 있어요. 왜냐하면 따로 인스턴스를 생성할 필요 없이 기능만 제공하기 때문이에요.

StrictMath

Math 클래스는 최대한의 성능을 위해 JVM이 설치된 운영체제의 메서드System call를 호출해서 사용해요. 즉, 운영체제에 의존적인 친구에요. 예를 들면 반올림 설정 방법이 운영체제마다 상이하기 때문에 어떠한 환경에서 구동하냐에 따라 결과가 다르게 나타날 수 있어요. 이런 차이를 없애기 위해 성능이 다소 떨어져도, 어떤 OS에서도 같은 결과를 얻도록 하는 것이 StrictMath 클래스에요.

라고 했는데, 일부 Math 클래스에선 StrictMath를 쓰더라구요...어떻게 된건지는 계속 공부해 봐야 겠어요.

Wrapper 클래스

Wrapper라는 이름을 가진 클래스가 아니에요 ㅎㅎ.

객체지향 개념에서는 모든 것을 객체로 다뤄야 하는데, 자바는 8개의 기본형을 객체로 다루지 않아요(ex. int, boolean, char,...). 이 때문에 자바는 완전한 객체지향 언어가 아니라는 이야길 들어요. 물론, 그대신 높은 성능을 얻을 수 있게 했죠(클래스랑 기본형 타입 선언 및 초기화는 속도가 다르죠).

때로는 이런 기본형 변수Primitive Variable도 객체로 다뤄야하는 경우가 있어요.

기본형 변수를 객체로 다뤄야 하는 Case
1. 매개변수로 객체를 요구
\to 자바는 기본형 변수가 call-by-value, 그 외 객체를 call-by-reference에요. 이때 필요하죠.
2. 기본형 값이 아닌 객체로 저장해야 할 때
3. 객체 간의 비교가 필요

이를 위해 사용되는 것이 Wrapper 클래스에요. 기본형 변수들을 대표하는 Wrapper 클래스가 있고, 이 클래스들로 기본형 값들을 객체로 다룰 수 있어요.

Wrapper 클래스 종류
1. Boolean \to boolean
2. Character \to char
3. Byte \to byte
4. Short \to short
5. Integer \to int
6. Long \to long
7. Float \to float
8. Double \to double

JDK1.5 이전에는 Wrapper 클래스와 기본형 변수간 연산이 불가능했지만, 그 이후 부터 가능해요.

또 없나요?

java.awt, java.io, java.net, java.sql 그리고 java.util가 있어요. 여기에 다 적기엔 방대하니 기회가 되면 정리할게요 ㅎㅎ.

참고

deftkang님 블로그
허쯔님 블로그
GYU님 블로그
Oracle Java Reference
리그캣님 블로그

profile
#행복 #도전 #지속성

0개의 댓글