NoClassDefFoundError 발생원인 및 해결(ClassNotFoundException와 차이점)

일반적으로 NoClassDefFoundError 에러는 컴파일 시점에 존재했던 클래스가 런타임에 존재하지 않으면 발생하는 에러라고 알고 있다.

 

포스팅 최하단에 링크한 Stackoverflow의 질문 답변부분의 그림을 인용하면 Class A는 Class B, Class C에 의존성을 가지고 있다. 정상적으로 컴파일 후 런타임에서 Classpath에서 Class C가 없어진다면 JVM이 Class C를 로드할 수 없기 때문에 NoClassDefFoundError가 발생한다.

 

위 예제대로라면 단순히 NoClassDefFoundError가 발생하면 Classpath만의 문제라고 생각할 수 있겠지만 NoClassDefFoundError은 더 다양한 발생 이유가 존재한다. 이 차이를 알기 위해 java.lang.ClassNotFoundException과 java.lang.NoClassDefFoundError를 같이 살펴본다.

 

1. java.lang.ClassNotFoundException

이 익셉션은 Classpath에 로드하고자 하는 Class가 발견되지 않았을 때 발생한다. 보통은 빌드에 문제가 있는 경우로 clean이나 Class파일 삭제 후 재빌드를 수행하여 해결한다.

2. java.lang.NoClassDefFoundError

이 익셉션은 JVM이 내부의 클래스 정의 데이터 구조(class definition data structure)에서 Class를 찾지 못했다는 것을 나타낸다. 단순히 Classpath에서 Class를 로드하지 못했다는 것과 약간은 다르다. 

일반적으로 과거에 Classpath에서 Class를 로드하려 시도했지만 실패하였고, 다시 해당 Class를 사용하려고 시도할 때 로드에 실패했던 전적이 있으니 로드를 시도하지도 않고 NoClassDefFoundError를 뱉는다. 과거의 로드실패는 ClassNotFoundException, ExceptionInInitializerError 또는 여러 다른 이유로부터 발생할 수 있다.

여기서 핵심은 NoClassDefFoundError의 발생은 단순히 Classpath의 문제는 아니라는 것이다.

 

아래 코드에서 SimpleCalculator의 static 초기화 구문은 에러를 발생시킨다. (static 초기화 구문에서 에러시 ExceptionInInitializerError가 발생한다.) 여기서 1차적으로 SimpleCalculator Class를 로드하려는데 실패하였고, 이후 다시 사용하려고 시도하면 이전의 실패를 기억하여 NoClassDefFoundError가 발생하게 된다.

public class NoClassDefFoundErrorDemo {
    public static void main(String[] args) {
        try {
            // 아래에서 ExceptionInInitializerError 발생
            SimpleCalculator calculator1 = new SimpleCalculator();
        } catch (Throwable t) {
            System.out.println(t);
        }
        // 아래에서 NoClassDefFoundError 발생
        SimpleCalculator calculator2 = new SimpleCalculator();
    }

}
public class SimpleCalculator {
    static int undefined = 1 / 0; // 에러발생
}

 

정리해보면,

ClassNotFoundException과 NoClassDefFoundError 모두 비슷한 의미로 Class를 로드할 수 없음을 가리키나 해결을 위해 봐야 할 포인트는 조금 다르다는 것이다. NoClassDefFoundError의 경우 위 예제처럼 Classpath의 문제가 아닌 다른 내제적인 문제에서 발생할 수 있다.

개인적으로 Maven을 즐겨 쓰는데 NoClassDefFoundError가 발생하면 의존하는 라이브러리의 버전이 서로 맞지 않아 발생하는 경우가 대부분이었다.

 

더 많은 내용에 대해서는 아래 Stackoverflow의 링크를 남긴다.

stackoverflow.com/questions/34413/why-am-i-getting-a-noclassdeffounderror-in-java

 

Why am I getting a NoClassDefFoundError in Java?

I am getting a NoClassDefFoundError when I run my Java application. What is typically the cause of this?

stackoverflow.com

 


Cannot find symbol 원인 및 해결방법(Cannot resolve symbol, Symbol not found 등)

 

개발 중 종종 마주치는 에러인 Cannot find symbol에 대해 정리한다. (Cannot resolve symbol과 Symbol not found과 같은 의미이다.)


1. Cannot find symbol 의미

포괄적인 에러로 작성한 소스코드에 문제가 숨어있거나 컴파일 하는 방식에 문제가 있을 때 발생한다.

자바의 소스코드는 아래와 같은 구성요소 이루어져 있다.

 

1. 키워드 : true, false, class, while 등

2. 리터럴 : "Hello", 'H', 33 등

3. 오퍼레이터나 알파벳, 숫자가 아닌 문자 : +, ", { 등

4. 식별자 : 클래스명(Integer 등), 변수명(i 등), 함수명(toString 등)

5. 주석 및 공백

 

이 때 cannot find symbol 은 식별자와 관련된 오류이다.

코드가 컴파일될 때 컴파일러는 소스코드의 식별자들이 각각 어떤 의미가 있는지 해석한다. 이 작업을 할 수 없는 경우(= 컴파일러가 소스코드를 해석할 수 없음) cannot find symbol 에러가 출력된다.


2. Cannot find symbol 원인

굉장히 다양한 원인이 때로는 복합적으로 있을 수 있다. 

 

1. 스펠링 오작성 : 예를들어 StringBuffer를 SpringBuffer로 작성. SpringBuffer 클래스가 없어 코드를 컴파일할 수 없다. 

StringBuffer sb = new SpringBuffer();
Error:(5, 31) java: cannot find symbol
symbol: class SpringBuffer
location: class com.example.demo.cannotfindsymbol.Test

 

2. 대소문자 오작성 : StringBuffer -> Stringbuffer로 작성.

 

3. 변수 선언을 하지않고 사용

System.out.println(str); // 선언하지 않은 str 변수 사용

 

4. 다른 영역에 선언한 변수를 사용하는 오류 : for문 영역의 변수를 외부에서 접근.

for(int i=0; i<2; i++){
    System.out.println(i);
}

System.out.println(i); // i를 찾을 수 없어 오류

 

5. 객체가 가지고 있지 않은 변수나 함수에 접근하는 경우

String str = "123";
str.reverse(); // String 객체는 reverse() 메소드가 없다.

 

6. new 키워드를 작성하지 않은 경우

StringBuffer sb = StringBuffer();

 

7. 같은 클래스명의 다른 패키지 클래스가 import 된 경우 : IDE의 자동 임포트 기능을 이용하다 보면 종종 발생한다. 사용하고자 하는 패키지의 클래스가 임포트 되었는지 확인한다.

 

 

8. 세미콜론(;)이 잘못 작성된 경우 : 아래는 없어야 될 세미콜론이 중간에 작성되었다.

세미콜론에 의해 for(~~~) 와 코드블럭{ }이 분리되며 코드블럭{ }에서 i는 선언되지 않았으므로 오류가 발생한다.

for (int i = 0; i < 100; i++); {
    System.out.println("i : " + i);
}

 

9. 오퍼레이터가 오작성 된 경우 : 아래에서는 *를 생략함으로써 i라는 메소드를 찾으나 없으므로 오류가 발생한다.

int a = 1;
int b = 2;
int i = 1;

int result = i(a+b); // i*(a+b) 에서 *를 작성하지 않음

 

cannot find symbol 해결

발생할 수 있는 원인이 워낙 다양하고 복합적이다 보니, 오류가 발생한 부분을 중심으로 작성한 코드를 주의 깊게 디버깅하는 수밖에 없다.

개발자가 코드를 잘못 작성한 것 외에도 의도한 것과 다른 버전의 라이브러리를 사용했다던가(의존성 에러), IDE에서 문제가 있다던가, 특정 소스를 컴파일하지 않았던가 등의 다양한 원인이 있으므로 단계별로 살펴봐야 한다.

 


참고

stackoverflow.com/questions/25706216/what-does-a-cannot-find-symbol-or-cannot-resolve-symbol-error-mean

 

What does a "Cannot find symbol" or "Cannot resolve symbol" error mean?

Please explain the following about "Cannot find symbol", "Cannot resolve symbol" or "Symbol not found" errors: What do they mean? What things can cause them? How does...

stackoverflow.com

 

 


 

 

Scanner 클래스는 콘솔의 입력값을 받기위해 주로 사용된다.

입력값을 받기위해 여러 편리한 메서드들을 제공하고 있는데 그 중 next(), nextInt()를 포함한 nextXXX() 시리즈를 사용할 때 바로 다음에 nextLine()을 사용할 경우 스킵되는 현상에 대해 정리한다.

 

아래 코드는 콘솔의 입력을 받기위해 Scanner 클래스를 사용했다.

먼저 숫자를 입력받기 위해 nextInt()를 사용하고, 문자열 2개를 입력받기 위해 nextLine()을 사용했다.

콘솔 결과를 보면 1 입력후에 첫번째 문자열 부분을 그대로 스킵하고 두번째 문자열 입력으로 넘어가는 것을 볼 수 있다.

마지막 최종 결과출력에도 숫자와 문자열2는 출력이 되지만 문자열1은 비어있다.

public class ScannerTest {
    public static void main(String[] args) {
        Scanner input = new Scanner(System.in);

        System.out.println("숫자를 입력하세요");
        int option = input.nextInt();

        System.out.println("첫번째 문자열을 입력하세요.");
        String str1 = input.nextLine();

        System.out.println("두번째 문자열을 입력하세요.");
        String str2 = input.nextLine();

        System.out.printf("숫자 : %d, 문자열1 : %s, 문자열2 : %s", option, str1, str2);
    }

}

 

// 콘솔 결과

숫자를 입력하세요
1
첫번째 문자열을 입력하세요.
두번째 문자열을 입력하세요.
하이
숫자 : 1, 문자열1 : , 문자열2 : 하이


발생원인은?

nextInt()를 포함해서 next(), nextFloat(), nextLong(), nextBoolean() 등은 "Enter"를 쳤을 때 입력되는 줄바꿈 문자를 읽지 못한다.

 

즉 콘솔에서 1을 입력하고 엔터를 치는 순간 nextInt()에 의해 1이 입력되고, 엔터는 다음 nextLine()의 입력값으로 전달된다. nextLine()은 줄바꿈 문자 전까지 파싱하여 값을 가져가는데 줄마꿈 문자 말고는 남은 입력값이 없으니 빈 문자열('')이 str1에 입력된다.

 

전체 과정을 정리하면 다음과 같다.

1. '숫자를 입력하세요' 가 콘솔에 표시됨

2. '1엔터' 입력 ('엔터'는 실제 키보드 엔터를 쳤을 때 입력되는 값을 임의로 표현한 것)

3. '1엔터' 에서 '1'이 nextInt()에 의해 추출되고 '엔터'만 남음

4. '첫번째 문자열을 입력하세요.'가 콘솔에 표시되고 '엔터'가 nextLine()에게 전달됨

4. '엔터' 에서 '엔터'를 제외한 ''(빈 문자열)만 nextLine()에 의해 추출됨

5. '두번째 문자열을 입력하세요.' 가 콘솔에 표시됨

6. '하이엔터' 에서 '엔터'를 제외한 '하이'만 nextLine()에 의해 추출됨


숫자값을 입력받으면서 위 같은 상황을 방지하기 위해서는 어떻게 할까?

엔터 입력값을 고려하여 숫자값을 받을 수 있는 코드는 아래와 같다.

nextLine()을 이용하여 입력값을 받고 Integer.parseInt()를 이용해 int 값으로 변환한다. 이 과정에서 숫자가 아닌 값이 입력되면 처리할 수 없으므로 오류(NumberFormatException)을 던지고 종료한다.

int option = 0;
try {
    option = Integer.parseInt(input.nextLine());
} catch (NumberFormatException e) {
    e.printStackTrace();
}
String str1 = input.nextLine();

 


읽어주셔서 감사합니다. 도움이 되셨다면 광고 클릭 부탁드립니다.

모두 힘내세요! : )

 

 

 

+ Recent posts