youngfromnowhere

[Java] 수동 컴파일할 때, 소스코드 수정사항이 반영되지 않는 경우 본문

Java

[Java] 수동 컴파일할 때, 소스코드 수정사항이 반영되지 않는 경우

곽일땡 2022. 11. 25. 18:13

https://youngnowhere.tistory.com/36

 

[Git] 원격저장소 clone 후 Java compile 오작동

Git, Github으로 미니프로젝트를 진행하다가.. compile에 수정사항이 반영되지 않는것을 발견했다. 또, 한 branch에서 생성된 class file이 merge하지 않은 다른 branch에서 보인다거나 (로컬에서 확인한 결과

youngnowhere.tistory.com

이 글은 위 포스팅의 원인을 분석한 글이다.

 

Java Compiler는 '간접적으로' import된 class에 대해서는 기존에 생성된 class file을 불러오기만 할 뿐,

새롭게 컴파일 하지 않는것 같다.

 

실험해보자.

Test/ 디렉토리에 Main.java와 TestPackage01, TestPackage02 두 패키지를 만든다.

Main.java에서는 TestPackage01.Test01을 임포트하고,

"Hello World"를 출력한 후, Test01.testMethod()를 호출한다.

 

TestPackage01.Test01에서는

TestPackage02.Test02를 임포트하고 그 인스턴스를 생성한 뒤,

"This is a method of Test01"을 출력하고

Test02.testMethod()를 호출한다.

Test02.testMthod()는 "This is a method of Test02"를 출력한다.

 

이제 Main.java를 컴파일 한 뒤 Main을 실행해보자.

첫 컴파일로, 미리 생성된 class파일이 없으므로, 컴파일러는 간접적으로 임포트된 TestPackage02/Test02.java까지 컴파일했음을 알 수 있다.

이제 Test01과 Test02파일에 수정을 가한다. 각각의 testMethod()의 끝에 "Modified line ..." 문자열을 출력하도록 수정했다.

Main을 컴파일 한 후 실행하면...(두 번째 컴파일)

Test02의 수정사항까지 반영되어서 전부 잘 컴파일 되었다. (즉, 아직 위에서 말한 현상은 일어나지 않았다.)

 

여기서 다시 TestPackage02/Test02.java의 내용을 수정해보자.

"Modified line, Test02.testMethod()"라는 문자열을 출력하라는 부분을 없앴다.

여기서 다시 Main을 컴파일 한 후 실행하면...(세 번째 컴파일)

분명히 "Modified line, test02.testMethod()" 출력 코드를 없앴는데, 여전히 이 문구가 출력되고 있다!

즉, 컴파일러는 세 번째 컴파일(Test02를 마지막으로 수정한 후 실행한 컴파일)에서, 새롭게 클래스파일을 만들지 않고,

TestPackage01에서 import문을 만났을때 그냥 이전에 만들어졌던 TestPackage02/Test02.class를 불러온 것이다.

클래스 파일들의 생성시간을 보면, 마지막으로 Main.java를 컴파일한 시점(Main.class 생성 시점)은 17:31인데,

패키지 파일 내의 Test01.class, Test02.class의 생성 시점은 17:28이다. 즉 4분전에 컴파일하면서 생성한 클래스파일을

불러오기만 한 것이다. (따라서 Test02.java의 수정사항이 반영되지 않는다.)

 

생각해볼 점 : 세번째 컴파일 시점에서, 컴파일러가 이미 생성된 Test02.class를 불러오기만 함으로써 Test02.java의 수정사항이 반영되지 않았다는 것은 확인했다. 그런데 왜 두번째 컴파일 시점엔 간접적으로 임포트된 Test02.java의 수정사항까지 반영된 것일까?(왜 직접 컴파일 했을까?)

 

자바 컴파일러가 '다시 새로 컴파일할지, 아니면 그냥 클래스 파일을 불러올지'에 대해 판단하는 기준이 단순히 '간접 임포트' 여부가 아니라, '직접 임포트되는 클래스의 소스코드(Test01.java)가 변경되었는가' 또한 고려할 것이라고 추측해볼 수 있다.

 

 

마지막 상태 (Test01.java 에는 "Modified..." line이 있고, Test02.java 에는 "Modified..." line이 없음)에서 class 파일들만 싹 지웠다. 그리고 다시 컴파일하고 실행하면, Class파일이 없으므로 컴파일러는 패키지 내의 소스코드까지 컴파일 할 것이다.

예상대로 Test02에만 "Modified ..." 라인이 없는 것이 반영된 모습이다.

이제 Main.java가 직접 임포트하는 클래스인 Test01의 소스코드는 유지한 채, Test02의 소스코드에 다시

"Modified..." 라인을 추가한다.

컴파일 후 실행하면.. Test02에 다시 추가한 "Modified line. 2nd experiment"라는 문장은 출력되지 않는다.

여기서 Main.java가 직접 임포트 하는 Test01의 소스코드에 변화를 줘보자.

Test01에 코드를 추가하자, 컴파일 후 실행한 내용에는 Test01, Test02의 수정사항이 동시에 반영됐다.

 

이제까지의 실험 결과를 정리하면 다음과 같다.

 

(실험 준비)Main.java는 TestPackage01.Test01을, TestPackage01/Test01.java 에서는 TestPackage02.Teset02를 임포트하도록 코드 작성

1. 최초 컴파일 (class파일없음) : 컴파일러는 Test01, Test02를 전부 컴파일.
2. 두 번째 컴파일 (Test01, Test02 전부 수정) : 컴파일러는 Test01, Test02를 전부 컴파일.
3. 세 번째 컴파일 (Test02만 수정) : 컴파일러는 Main.java만 컴파일
4. 네 번째 컴파일 (Class 파일 없앰) : 컴파일러는 Test01, Test02 전부 컴파일.
5. 다섯 번째 컴파일 (Test02만 수정) : 컴파일러는 Main.java만 컴파일
6. 여섯 번째 컴파일 (Test01 수정) : 컴파일러는 Test01, Test02 전부 컴파일.

 

결론 :

간접적으로 임포트 되는 소스코드가 있을 때, Java의 컴파일러는 "직접 임포트한 소스코드의 변화 여부"에 따라서,

직접 임포트한 소스코드에 변화가 없으면 간접 임포트한 소스 코드는 컴파일 하지 않고 class 파일만 불러오며,

직접 임포트한 소스코드에 변화가 생기면 간접 임포트한 소스 코드까지 다시 컴파일 한다.

 

Git 카테고리에서 얘기한 "컴파일 오류"는 이러한 Java 컴파일러의 컴파일 방식 때문에 생긴 일이었던 것이다.