패키지
그렇게 중요해보이지 않은 파트이지만, classPath가 무엇인지, 어떻게 돌아가는지 어떤 용도인지에 대한 설명이 있고, 그걸 자세히 모르고 있었기 때문에 배우게 된 것도 많다.
패키지는 서로 관련된 클래스의 묶음이다. 따라서 사실 모든 클래스의 실제 이름은 패키지를 포함한 클래스 이름이다. 가령 String 클래스의 풀네임을 보면 java.lang.String 이라는 이름을 볼 수 있다. java8 버전 이하에서는 rt.jar이라는 압축 파일 내부에 java.lang 폴더 내부에 String.class가 있는 건데, jar 파일을 많이 봐왔지만, 여기서 깔끔하게 설명해준 것이 jar 압축 파일이 클래스 파일을 묶어 놓은 것이라고 한다. 이런 세세한 것도 모르고 있었다. 하지만 파일이 용량이 너무 크다는 문제가 있어서 java9 버전 이상에서는 module을 택했다고 한다.
또 신기한 부분이 있었는데, 패키지가 소스 파일의 첫 번째 문장으로 한 번만 선언된다는 사실은 익히 알고있는 내용이었다. 그러나 패키지 선언이 없을 경우에 대해서 설명을 해주셨는데, 패키지 선언이 없으면 unnamed 패키지에 속하게 된다고 한다. 이클립스에서는 unnamed 패키지가 있는 것을 확인할 수 있었는데 인텔리제이에서는 보이지가 않아서 한 번 테스트를 해봤다.
테스트 해봤을 때 src 폴더 아래에 저장이 되는 것을 볼 수 있었는데 패키지 선언이 없을 때에는 인텔리제이에서 src폴더에 저장이 되면 unnamed 패키지로 판단하는 것 같았다. 추가적으로 해당 내용을 검색하면서 다음과 같은 내용을 확인 할 수 있었다.
JLS에 따르면 default package(unnamed package)에 있는 클래스(타입)는 import할 수 없다!!
실제로도 그런가 해서 테스트를 해봤는데, 패키지 내부에 있는 다른 클래스 파일에서 src 바로 아래에 있는 클래스 파일을 import 할 수 없었다.
클래스 패스(classpath)
이어서 클래스 패스이다. 개발을 하다보면 클래스패스를 확인해보세요. 클래스 패스를 설정해보세요. 라는 말을 참 많이 봐왔는데, 어떤 내용인지에 대한 이해도도 부족했고, 무엇을 하는 것인지에 대해서도 자세히 알지 못했다. 결론부터 말하자면 클래스 패스는 클래스 파일의 위치를 알려주는 경로(path) 이다. 우리가 실제로 IDE를 사용하지 않고 컴파일을 하기 위해서는 javac 경로, 그리고 이 컴파일된 클래스를 실행하기 위해서는 java 경로 를 입력해줘야 한다는 사실은 익히 들어 알 것이다. 그런데 항상 컴파일을 할 때마다 해당 폴더로 이동을 해주거나, 해당 경로의 이름을 다 입력하기에는 너무 번거로운 작업이다. 이를 편하게 해주기 위해서 classPath 환경 변수에 해당 폴더까지의 경로를 추가함으로써 앞에 있는 루트 경로를 적지 않더라도 해당 클래스가 있는 폴더에 접근이 편리하게 가능해졌다.
Import문
import 파트에서도 자주 헷갈렸고, 왜 안되는지에 대해서 궁금했었던 부분이 있었는데 그러한 부분이 깔끔하게 해결이 됐다. 우선 우리가 원래 클래스를 호출하기 위해서는 다음과 같이 작성을 해주어야 한다.
// 1. 기존에 우리가 작성하는 코드
import java.util.Date;
public class Main {
Date d = new Date();
}
// 2. 원래 작성해야 되는 코드
public class Main {
java.util.Date d = new java.util.Date();
}
위에서 보이는 첫번째 코드가 더 익숙할 것이다. 왜냐하면 우리는 IDE를 사용해서 자동으로 임포트 되는 것에 익숙해져 있기 때문인데, 위의 패키지 파트에서 얘기했듯이, 클래스는 각각의 패키지 안에 들어가있다. 그래서 해당 클래스를 참조하기 위해서는 모든 패키지명을 적어주어야 하는데, import 문 덕분에 클래스를 사용할 때 패키지 이름을 생략할 수 있는 것이다.
하지만 단 한가지 import를 하지 않아도 되는 패키지가 있는데, 바로 java.lang 패키지에 있는 클래스들이다. 우리가 Object, String, System 등등에 대한 클래스를 사용할 때 앞에 java.lang이 붙거나, import가 되어 있지 않은 모습을 확인할 수 있었는데, 자주 사용하는 명령어이기 때문에 자바에서 기본 패키지로 지정하여 따로 import하지 않아도 가져올 수 있게 해두었다. 만약 이렇게 되지 않았다면 다음 처럼 보였을 것이다.
public class Main {
public static void main(java.lang.String[] args){
java.lang.System.out.print("..");
}
}
너무 끔찍하다. 물론 import에 import java.lang.*; 을 추가해주면 이와 같은 문제가 전부 해결될테지만 매번 꼭 사용해야 하는것들을 매번 추가하는 것도 번거로운 일이다.
import문의 선언
import문을 선언하는 방식은 두 가지가 있다. 이 부분에서 많이 헷갈리는 부분이 있었는데 살펴보자
- import 패키지명.클래스명; ex) import java.util.Date;
- import 패키지명.*; ex) import java.util.*;
위 처럼 두 가지로 명시할 수 있다. *은 와일드 카드이다. util 패키지 아래에 있는 모든 클래스를 가지고 온다는 말과 같다. IDE를 사용하게 되면 보통은 1번 같은 상황을 많이 보게 될것이다. 그러나 코딩테스트 같은 경우에는 저걸 하나 하나 입력하는 것도 굉장한 시간소모인데, 2번 처럼 작성하게 되면 시간을 많이 단축할 수 있을 것이다. 예전에 이러한 것에 대한 성능문제에 대해서 질문을 받은 적이 있는데, import는 컴파일 단계에서 이루어지는 문제이기 때문에 성능에는 전혀 영향을 끼치지 않는다.
또 주의해야 될 부분이다. 예전에 이게 왜 안되지? 라고 나 스스로에게 질문을 했던 부분이기도 하다.
import java.util.*;
import java.text.*;
import java.*;
여기서 1번과 2번은 어떤 느낌인지 알겠지만, 이렇게 생각 할 수도 있다. 아 귀찮은데 그냥 java.* 을 통해서 전부다 가지고 오면 되는거아니야? 라고 생각이 들 것이다. 물론 나도 그랬다. 하지만 이것은 전혀 다른의미이다. 간단하게 말하면 와일드카드에는 클래스 이름만 찾을 뿐 패키지까지 찾아서 그 하위 패키지를 찾지는 않는다. 자주 헷갈릴 수 있는 부분이지만 확실히 해두어야 한다. 두 번째로 왜 이렇게 되는걸까 생각했던 부분이다.
import java.sql.*;
import java.util.*;
두 패키지에는 똑같은 이름을 가진 Date 클래스가 있다. 근데 컴파일러입장에서 다음과 같은 코드를 봤다고 하자.
public class Main {
public static void main(String[] args) {
Date d = new Date(); // java.sql.date??, java.util.date??
}
}
컴파일러는 저 둘 중에 무엇을 가지고 개발자가 사용하려고 한 것 인지 이해를 할 수가 없다. 따라서 저렇게 패키지에 같은 이름이 중복되어 있을 경우에는 다음과 같이 사용해야 한다.
public class Main {
public static void main(String[] args) {
java.sql.Date d = new java.sql.Date();
java.util.Date d2 = new java.util.Date();
}
}
자기가 사용하고자 하는 패키지의 클래스를 정확하게 명시를 해주어야 컴파일러가 이를 인식할 수 있기 때문에 패키지명까지 붙여줘야 한다.
static import 문
Junit의 Assertions 클래스를 사용하면서 static import문을 많이 사용했는데, 어떻게 사용하는 것인지에 대해서 자세히 이해하고 들어가는게 더 도움이 될 것 같다. 그리고 어떻게 사용되는지도 살펴보자.
기본적인 import문과 달리 static import문은 static 멤버들에 대해서 클래스 이름을 생략하게 해준다. 인스턴스 변수들과 달리 static 변수(클래스 변수)들은 메모리가 올라갈 때 생성되기 때문에 서로 다르게 불러올 수 있도록 지정을 해준 것 같다. 사용 예시는 다음과 같다.
import static java.lang.Integer.*; // Integer 클래스에 있는 모든 static 변수와 메서드
import static java.lang.Math.random; // random() 메소드만 가지고 온다.
import static java.lang.System.out; // out만 참조 가능
이렇게만 봤을 때 클래스 멤버들을 가지고 오는 것인데, 무엇을 가지고 오는지에 대해서 좀 헷갈리는 부분이 있었다. 그래서 해당 패키지를 살펴보기로 했다. Math 클래스를 가보니 전부 static 으로 지정되어 있는 모습을 볼 수 있었다. 기본 패키지에 있는 내용들은 아무래도 어디서든 사용할 수 있어야 하고 추가적인 객체 생성으로 값을 구분할 필요가 없기 때문이라고 본다.
어떤 방식으로 불러오는지에 대해서 의문이 있었기에 다음과 같은 코드를 작성해보고 다른 클래스에서 불러와봤다.
package packageTest;
public class Test {
public static int random = 3;
public static int random(){
return 1;
}
}
// packageTest 라는 폴더에 다음과 같은 클래스 변수와 메서드를 선언해주었음.
import static packageTest.Test.random;
public class AAAB {
public static void main(String[] args){
System.out.println(random);
System.out.println(random());
}
}
// 실제로 두 값 모두 정상적으로 불러오는 것을 볼 수 있었음.
import static을 사용하였을 때, 메서드인지 변수인지에 대한 구분이 가능한가? 에 대한 질문에서 시작했는데, 알아서 구분하는 것을 볼 수 있었다. static 변수를 임포트 할 때에는 와일드카드를 사용하는 것이 더 편할 것 같긴 하다. 보통 static 변수로 선언한 것들은 상수인것들이 대부분이기 때문이다.
'Language > Java' 카테고리의 다른 글
7-5 캡슐화 (0) | 2023.03.23 |
---|---|
7-4 제어자 (0) | 2023.03.23 |
7-2 참조변수 super, 생성자 super() (0) | 2023.03.22 |
7-1 상속과 오버라이딩 (0) | 2023.03.22 |
6. 객체지향언어 1 (0) | 2023.03.22 |