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

 

Optional의 등장 이유

자바로 프로그래밍을 하면서 NullPointerException을 안 겪어 본 사람은 없을 것이다. 그만큼 null은 오류를 자주 발생시킬 위험이 있으며 개발하는 입장에서도 상당 귀찮다.

 

null 때문에 발생할 수 있는 문제는 다음과 같다.

1. NullPointerException을 발생시킬 수 있다.

2. 반환된 객체를 처리할 때 if(obj == null){ } 등의 확인 코드가 증가한다. => 가독성이 낮아진다.

 

이러한 점을 해소하고자 등장한 java.util.Optional은 null이 반환되지 않게 하여 오류를 발생시키지 않고, 호출하는 쪽에서도 명시적으로 빈 값이 반환될 수 있음을 알게 하는 것이다.

Optional : 정상적인 결과물인 객체가 반환될 수도 있고, 아니면 빈 값이 반환될 수도 있다는 의미

 

Optional 객체 생성

1. 빈 Optional

Optional<Car> optCar = Optional.empty();

 

2. null이 아닌 객체로 Optional 만들기 : car가 null이라면 NullPointerException이 발생한다.

Optional<Car> otpCar = Optional.of(car);

 

3. null값으로 Optional 만들기 : car가 null이라면 빈 Optional 객체가 반환된다.

Optional<Car> otpCar = Optional.ofNullable(car);

 

Optional 실용 예제

메소드의 결과물로 Optional 반환

DB에서 데이터를 조회하거나 기타 로직으로 생성된 객체를 반환할 때 null이 반환되지 않게 하기 위해 Optional을 반환

// Object를 반환하지 않고 Optional로 한번 감싸서 반환한다.
//  >> Object객체는 빈값(null)이 될 수 있음을 알려준다.
public Optional<Object> getObject(String key){
    return Optional.ofNullable(map.get(key));
}

 

메소드를 호출하는 쪽에서도 if else로 분기하여 처리하지 않고 비즈니스 로직에 따라 처리

비어있는 Optional이 반환되면 Optional의 orEleseGet()으로 빈 객체를 생성하여 반환이 가능하다.

만일 빈 객체가 허용되지 않는다면 orElseThrow()로 오류를 쓰로우하고 종료할 수도 있다.

public Object run(){
    Optional<Object> optValue = getObject("key"); // 위의 getObject()를 호출

    // 1. 빈 객체를 생성하여 반환
    Object value = optValue.orElseGet(Object::new);
}
public Object run(){
    Optional<Object> optValue = getObject("key"); // 위의 getObject()를 호출

    // 2. 오류로 종료
    optValue.orElseThrow(() -> new NoSuchKeyException("해당 키의 값이 없습니다."));
}

 

 

컬렉션은 Optional로 감싸지 말고 빈 컬렉션을 반환

만일 컬렉션 데이터가 없다면 Optional으로 컬렉션을 감싸는 것보다는 빈 컬렉션을 반환하는 것이 효율적이다. (null은 반환하지 않는다.)

// 컬렉션에 데이터가 없으면 Optional로 감싸지 않는다.
List<Object> list = getList();
return Optional.ofNullable(list);

// 대신 빈 컬렉션을 반환한다.
List<Object> list = getList();
return list != null ? list : Collections.emptyList();

 

 

 

 

 


Camel case, Kebab case, Snake case 그리고 Pascal case

프로그래밍에서의 공백 제거

프로그래밍을 할 때 우리는 보통 단어 사이의 공백을 제거하고 위의 나열된 방식 중에 하나로 문자열을 표현합니다.

왜냐하면 공백(Space)은 프로그램에서 특별한 목적의 키워드로써 활용되기 때문이지요.

 

예를 들어 나의 방문자수를 나타내기 위해 변수명을 짓는다고 생각해보면,

My Visitor Count로 변수명을 짓고 싶지만 대부분은 int myVisitorCount 형식으로 변환하여 사용합니다.

 

여기서 My Visitor Count -> myVisitorCount 로 바꾸는 데 사용되는 규칙이 Camel Case이며 이 밖에도 여러 변환 규칙이 있습니다. 하나씩 살펴보겠습니다.

 

Camel Case(카멜 케이스)

낙타의 쌍봉과 같이 문자열의 첫 문자를 제외하고 단어의 첫 글자마다 대문자로 표현하는 방식입니다. 많은 프로그램 언어에서 컨벤션으로 사용됩니다.

 

변환 전 : My Visitor Count

변환 후 : myVisitorCount

 

Kebab Case(케밥 케이스)

생각하시는 그 먹는 케밥이 맞습니다. 카멜 케이스와 달리 모두 소문자로 표현하며 단어와 단어 사이를 대시(-)를 이용하여 구분합니다. 스프링의 yml파일이나 url주소에서 사용됩니다.

 

변환 전 : My Visitor Count

변환 후 : my-visitor-count

 

Snake Case(스네이크 케이스)

케밥의 대시(-)와 다르게 언더스코어(_)를 구분자로 합니다. 모든 문자를 대문자로 나타내는 방식도 사용되며 주로 상수 표현 시에 사용됩니다.

 

변환 전 : My Visitor Count

변환 후 : my_visitor_count

변환 후 : MY_VISITOR_COUNT

 

Pascal Case(파스킬 케이스)

카멜 케이스와 유사하지만 첫 문자도 대문자로 표현합니다.

 

변환 전 : My Visitor Count

변환 후 : MyVisitorCount

 

 

 


 


FileWriter에서 flush()와 close()의 차이점

FileWriter를 사용 후에는 flush()와 close()를 호출하는데요 두 메소드의 차이점에 대해 알아봅니다.

 

- flush() : FileWriter 내부 버퍼의 내용을 파일에 writer합니다. flush()를 호출하지 않는다면 내용이 버퍼에만 남고 파일에는 쓰이지 않는 상황이 나올 수 있습니다.

 

- close() : FileWriter는 스트림을 이용하여 파일의 내용을 읽어들입니다. 이때 close()를 호출하여 스트림을 닫으면 그 스트림을 다시 이용하여 파일에 쓰는 것이 불가능합니다.

파일은 파일시스템이나 기타 다른 곳에 있으므로 이 내용을 스트림으로 읽어 들이는데 메모리를 소모합니다. 작업이 끝나면 close()를 호출하여 스트림을 닫아 종료된 작업에 대해 메모리를 확보해야 합니다.


[Java] 파일 생성, 파일 읽기(File, FileReader. FileWriter)

이전에 자바에서 파일과 디렉터리를 추상화한 File 클래스에 대해 정리했었는데,

이번에는 생성한 파일에 내용을 작성하는 것에 대해 정리합니다.


1. 파일 생성 및 쓰기(FileWriter, BufferedWriter)

D:\MyWork 경로 하위에 Test.txt 파일을 생성하고 내용을 작성합니다.

FileWriter의 생성자는 두번째 인자로 boolean 타입을 받는데, 이는 파일에 내용을 이어붙혀서 작성할지 처음부터 덮어씌워서 작성할지 여부를 결정합니다. 디폴트는 덮어씌워 작성입니다. (기존 내용이 날아감)

BufferedWriter는 파일에 문자열을 쓰기위해 편리한 메소드를 제공합니다. 예를들면 newLine() 처럼 줄바꿈을 추가할 수 있죠.

public class FileWriterTest {
    public static void main(String[] args) throws IOException{
        String filePath = "D:/MyWork/Test.txt";

        File file = new File(filePath); // File객체 생성
        if(!file.exists()){ // 파일이 존재하지 않으면
            file.createNewFile(); // 신규생성
        }

        // BufferedWriter 생성
        BufferedWriter writer = new BufferedWriter(new FileWriter(file, true));

        // 파일에 쓰기
        writer.write("하이루!");
        writer.newLine();
        writer.write("반가워!");
        writer.newLine();

        // 버퍼 및 스트림 뒷정리
        writer.flush(); // 버퍼의 남은 데이터를 모두 쓰기
        writer.close(); // 스트림 종료
    }
}
하이루!
반가워!

2. 파일 읽기(FileReader, BufferedReader)

1번에서 작성한 내용을 읽어 콘솔에 출력하는 예제입니다. 파일을 읽기위해 FileReader, 한줄 단위로 읽어오는 편리한 readLine() 메소드를 사용하기 위해 BufferedReader를 사용했습니다.

public class FileReaderTest {
    public static void main(String[] args) throws IOException{
        String filePath = "D:/MyWork/Test.txt";

        File file = new File(filePath); // File객체 생성
        if(file.exists()){ // 파일이 존재하면
            BufferedReader reader = new BufferedReader(new FileReader(file));

            System.out.println("파일내용 출력------------------");
            String line = null;
            while ((line = reader.readLine()) != null){
                System.out.println(line);
            }
            System.out.println("------------------------------");

            reader.close();
        }
    }
}

 

 


 

 

 

 

 

 

StringTokenizer 기본 및 사용법

StringTokenizer 클래스는 문자열을 구분자를 이용하여 쪼갤 때 사용할 수 있다. (쪼갠다. = 파싱한다.)

예를 들어 "Hi I'm Yangs!!" 라는 문자열을 " "(공백)을 구분자로 "HI", "I'm", "Yangs!!" 이렇게 3개로 쪼개는 것이 가능하다.

Token을 구분자에 의해 쪼개진 단어라고 생각하면 된다.

 

StringTokenizer 생성

- StringTokenizer(String str) : 파싱 할 문자열을 인자로 받는다. 구분자를 지정하지 않았으므로 스페이스, 탭, 줄바꿈, 캐리지 리턴 등 기본 구분자가 적용된다.

- StringTokenizer(String str, String delim) : 파싱할 문자열과 구분자를 인자로 받는다.

- StringTokenizer(String str, String delim, boolean flag) : flag는 구분자 자체도 토큰으로 인식하게 할지 여부를 정한다. 예를 들어 true라면 "Hi I'm Yangs!!"는 공백을 포함하여"HI", " ", "I'm", " ", "Yangs!!" 이렇게 5개의 토큰으로 파싱 된다.

String source = "Hi I'm Yangs!!";

StringTokenizer tokenizer1 = new StringTokenizer(source);
while(tokenizer1.hasMoreTokens()){
    System.out.println("tokenizer1's token : " + tokenizer1.nextToken());
}

StringTokenizer tokenizer2 = new StringTokenizer(source, " ");
while(tokenizer2.hasMoreTokens()){
    System.out.println("tokenizer2's token : " + tokenizer2.nextToken());
}

StringTokenizer tokenizer3 = new StringTokenizer(source, " ", true);
while(tokenizer3.hasMoreTokens()){
    System.out.println("tokenizer3's token : " + tokenizer3.nextToken());
}
// 출력결과
tokenizer1's token : Hi
tokenizer1's token : I'm
tokenizer1's token : Yangs!!

tokenizer2's token : Hi
tokenizer2's token : I'm
tokenizer2's token : Yangs!!

tokenizer3's token : Hi
tokenizer3's token :  
tokenizer3's token : I'm
tokenizer3's token :  
tokenizer3's token : Yangs!!

StringTokenizer 사용법

StringTokenizer를 이용하여 문자열 -> 배열로 파싱하기

가장 많이 사용되는 문자열을 파싱하여 배열에 담는 예제이다.

hasMoreTokens()로 총토큰의 개수를 구하고, nextToken()으로 한 토큰씩 꺼낼 수 있다.

StringTokenizer는 구분자에 의해서 파싱 후 빈 토큰은 버리는 동작을 확인할 수 있다.

String source = "|문자열||에서|배열로|갑니다|";

// StringTokenizer 생성
StringTokenizer tokenizer = new StringTokenizer(source, "|");
System.out.println("총 토큰 갯수 : " + tokenizer.countTokens()); // 총 토큰 갯수 : 4

String[] arr = new String[4]; // 결과 배열
int idx = 0;
while (tokenizer.hasMoreTokens()){
    arr[idx] = tokenizer.nextToken(); // 배열에 한 토큰씩 담기
    idx++;
}

System.out.println(Arrays.toString(arr)); // [문자열, 에서, 배열로, 갑니다]

 

 

여러 구분자로 파싱하기

StringTokenizer 생성자 구분자를 여러 개 지정하면 된다. > new StringTokenizer(source, ",|;!")

String source = "|문자열,,에서|배열로;갑니다!";

StringTokenizer tokenizer = new StringTokenizer(source, ",|;!");
System.out.println("총 토큰 갯수 : " + tokenizer.countTokens()); // 총 토큰 갯수 : 4

String[] arr = new String[4];
int idx = 0;
while (tokenizer.hasMoreTokens()){
    arr[idx] = tokenizer.nextToken();
    idx++;
}

System.out.println(Arrays.toString(arr)); // [문자열, 에서, 배열로, 갑니다]

 

 


 

 

[Java] File 클래스 기본

Java.io.File 클래스는 자바에서 파일시스템의 파일이나 디렉터리(폴더)를 추상화 한 클래스이다.

즉, File 클래스를 통하여 파일시스템의 파일이나 디렉터리를 조작(삭제, 파일명변경 등)을 할 수 있다.

 

File 클래스를 통해 할 수 있는 작업이 많은 만큼 제공되는 메소드들도 다양하다. File 클래스의 생성과 케이스별 사용법에 대해 정리한다.

 


File 객체 생성

아래 경로처럼 Test.txt 파일을 생성하고 이 파일의 File 객체를 생성한다.

D:\MyWork\Test.txt

public class File_Practice {
    public static void main(String[] args) {
        String parentPath = "D:/MyWork";
        String filePath = "D:/MyWork/Test.txt";
        String fileNm = "Test.txt";

        // File(File parent, String child) 생성자
        File file1 = new File(parentPath, fileNm);

        // File(String pathname) 생성자
        File file2 = new File(filePath);

        // File(String parent, String child) 생성자
        File parent = new File(parentPath);
        File file3 = new File(parent, fileNm);

        if(file1.exists() && file2.exists() && file3.exists()){
            System.out.println("모두 생성 완료!");
        }
    }
}

file.exist() 는 파일의 존재여부를 검사하는 메소드를 이처럼 File 클래스에서는 파일에 관한 여러 편리한 메소드를 제공하도 있다.

아래부터는 이런 메소드들을 활용하여 어떤 작업을 할 수 있는지 케이스별로 살펴본다.


File 객체 활용

아래 소스코드를 실행하기 위한 디폴트 폴더/파일 구성은 아래와 같습니다.

D:/MyWork (Dir)
D:/MyWork/Test.txt (File)
D:/MyWork/MyDirectory (Dir)

1. 파일 또는 디렉터리의 존재유무 확인 및 기본정보 출력

public class Check_Default_Info {
    public static void main(String[] args) {
        String rootPath = "D:/MyWork";

        File rootDir = new File(rootPath);

        // 파일명
        System.out.println(rootDir.getName()); // MyWork

        // 절대경로
        System.out.println(rootDir.getAbsolutePath()); // D:\MyWork

        // 경로
        System.out.println(rootDir.getPath()); // D:\MyWork

        // 존재여부
        System.out.println(rootDir.exists()); // true

        // 읽기/쓰기/실행 가능여부
        if (rootDir.exists()) { // 존재하면
            System.out.println(rootDir.canRead()); // true
            System.out.println(rootDir.canWrite()); // true
            System.out.println(rootDir.canExecute()); // true
        }
    }
}

 


2. 디렉터리의 내부 내용물(파일, 디렉터리) 출력

디렉터리의 경우 listFiles() 메소드로 내부 파일(디렉터리 포함) 목록을 가져올 수 있으며 for문으로 순회하며 작업을 할 수 있다.

public class Check_DirectoryContents_Info {
    public static void main(String[] args) {
        String rootPath = "D:/MyWork";
        File rootDir = new File(rootPath);

        if(rootDir.isDirectory()){ // 디렉터리면
            File[] contents = rootDir.listFiles(); // 파일목록을 가져온다.
            for(File file : contents){
                System.out.println("파일명 : " + file.getName());
                System.out.println("절대경로 : " + file.getAbsolutePath());
                System.out.println("파일여부 : " + file.isFile());
                System.out.println("------------------------------");
            }
        }
    }
}
// 출력결과
파일명 : MyDirectory
절대경로 : D:\MyWork\MyDirectory
파일여부 : false
------------------------------
파일명 : Test.txt
절대경로 : D:\MyWork\Test.txt
파일여부 : true
------------------------------

3. 디렉터리 및 파일 생성

mkdir() 메소드를 사용하여 디렉터리를 생성한다. mkdirs() 메소드도 제공되며 차이점은 mkdirs()는 필요한 상위의 디렉터리가 없으면 같이 만든다는 것에 있다.

createNewFile() 메소드는 신규 파일을 생성할 수 있다. 

length() 메소드는 파일의 용량을 체크할 수 있으며 신규 파일의 경우 빈 파일이므로 0이 출력된다.

public class Mkdir {
    public static void main(String[] args) {
        String rootPath = "D:/MyWork";
        File rootDir = new File(rootPath);

        // 디렉터리 생성시작
        File newDir = new File(rootDir, "newDir"); // 생성할 디렉터리
        System.out.println("신규 디렉터리 생성여부 : " + newDir.exists()); // false

        boolean isMake = newDir.mkdir(); // 디렉터리 생성
        System.out.println("신규 디렉터리 생성결과 : " + isMake); // true
        System.out.println("신규 디렉터리 생성여부1 : " + newDir.exists()); // true
        System.out.println("신규 디렉터리 생성여부2 : " + newDir.isDirectory()); // true

        // 파일 생성시작
        File newFile = new File(newDir, "newFile");
        System.out.println("신규 파일 생성여부 : " + newFile.exists()); // false

        try {
            newFile.createNewFile(); // 파일생성
        } catch (IOException e) {
            e.printStackTrace();
        }

        System.out.println("신규 파일 생성여부 : " + newFile.exists()); // true
        System.out.println("신규 파일 용량 : " + newFile.length()); // 0
        System.out.println("신규 파일 경로 : " + newFile.getAbsolutePath()); // D:\MyWork\newDir\newFile
    }
}

 


4. 파일명 변경 및 삭제

createNewFile()로 새로운 파일 생성 후 delete() 메소드로 삭제한다.

public class Rename_Delete {
    public static void main(String[] args) throws IOException {
        String rootPath = "D:/MyWork";
        File rootDir = new File(rootPath);

        File file = new File(rootDir, "tmpFile");
        file.createNewFile();
        System.out.println("삭제전 파일 존재여부 : " + file.exists()); // true

        // 파일삭제 시작
        boolean isDel = file.delete();
        System.out.println("파일 삭제 여부 : " + isDel); // true
        System.out.println("삭제후 파일 존재여부 : " + file.exists()); // false
    }
}

 


 


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

 

 


 

 

Java String reverse (문자열 뒤집기)

자바에서 문자열을 다루며 빈번하게 하는 작업 중 하나가 뒤집기(reverse)이다.

자바에서 문자열을 뒤집을 수 있는 방법 중 몇 가지를 소개한다.

(String 클래스에 reverse() 메서드가 있으면 좋을 텐데 없다.)

 

문자열 뒤집기 예시 :  "abcde" -> "edcba"


1. StringBuilder, StringBuffer 를 이용

reverse() 메소드를 제공하는 StringBuilder/StringBuffer 클래스를 이용한다. 

바꾸고 싶은 문자열을 이용하여 StringBuilder 또는 StringBuffer 객체를 생성 후에 제공되는 reverse() 메서드를 사용한다. 

public class String_Reverse_Practice {
    public static void main(String[] args) {
        String str = "abcde";

        // reverse
        StringBuffer sb = new StringBuffer(str);
        String reversedStr = sb.reverse().toString();

        System.out.println(reversedStr); // edcba
    }
}

StringBuffer에 대한 추가 설명은 기존에 정리한 링크를 남겨둔다.

2020/12/13 - [Dev/JAVA] - StringBuffer(StringBuilder) 클래스 기본 및 사용

 

StringBuffer(StringBuilder) 클래스 기본 및 사용

StringBuffer(StringBuilder) 클래스 기본 및 사용 StringBuffer(StringBuilder) 클래스는 문자열에 대한 많은 편의기능을 제공한다. 그래서 필요에 따라 문자열을 작업할 때 String 객체가 아닌 StringBuffer(Str..

yangbox.tistory.com


2. char[] 로 변환하여 뒤집기

String 문자열을 char배열로 변환 후 역순으로 새로운 char배열에 저장하여 문자열로 변환한다.

좀 돌아가는 느낌이 들지만 참고로 봐둔다.

public class String_Reverse_Practice {
    public static void main(String[] args) {
        String str = "abcde";

        char[] arr = str.toCharArray(); // String -> char[]
        char[] reversedArr = new char[arr.length];
        for(int i=0; i<arr.length; i++){
            reversedArr[arr.length-1-i] = arr[i];
        }

        String reversedStr = new String(reversedArr);
        System.out.println(reversedStr); // edcba
    }
}

 


3. List로 변환 후 Collections.reverse(list) 이용하여 뒤집기

reverse 메소드를 제공하는 Collections 클래스를 이용하기 위해 List<Character> 타입으로 변환 후 뒤집기를 수행한다.

public class String_Reverse_Practice {
    public static void main(String[] args) {
        String str = "abcde";

        char[] arr = str.toCharArray(); // String -> char[]
        List<Character> list = new ArrayList<>();
        for(char each : arr){ // char[] -> List
            list.add(each);
        }

        // reverse
        Collections.reverse(list);

        ListIterator li = list.listIterator();
        while(li.hasNext()){
            System.out.print(li.next()); // edcba
        }
    }
}

 


 

 

StringBuffer(StringBuilder) 클래스 기본 및 사용

StringBuffer(StringBuilder) 클래스는 문자열에 대한 많은 편의기능을 제공한다. 그래서 필요에 따라 문자열을 작업할 때 String 객체가 아닌 StringBuffer(StringBuilder) 객체를 사용경우가 종종 있다.


String과 StringBuffer(StringBuilder)의 차이

String과 StringBuffer(StringBuilder)의 차이점은 일단 String은 고정된 길이의 불변(immutable) 문자열이다.

불변이므로 String 객체를 변경시 실제로 변경되는 것이 아닌 새로운 String 객체를 생성한다. 반면에 StringBuffer와 StringBuilder는 변경이 가능하다.(String 처럼 새로운 객체를 생성하는 것이 아닌 그 객체 자체가 변경된다.)

그러므로 문자열의 변경이 잦은 작업이라면 String 대신 StringBuffer 또는 StringBuilder의 사용을 고려한다.

 

StringBuffer와 StringBuilder의 차이는 멀티스레드 환경에서 thread-safe 여부가 다르다. StringBuffer는 thread-safe 하므로 여러 쓰레드에서 동시에 해당 문자열에 접근한다면 사용을 고려하고, 그렇지 않다면 StringBuilder를 사용하는 것이 성능에 더 유리하다. (성능과 thread-safe는 반비례라고 생각하면 된다.)

 

정리하면

1. 문자열 변경이 빈번하지 않는다면 String 사용을 고려

2. 문자열이 빈번하게 변경되면서 멀티쓰레드 환경이라면 StringBuffer 사용을 고려

3. 문자열이 빈번하게 변경되면서 멀티쓰레드 환경이 아니라면 StringBuilder 사용을 고려


StringBuffer 생성

StringBuffer는 3가지 생성자로 생성할 수 있다. StringBuffer와 StringBuilder는 사용법이 대동소이 하므로 아래부터는 StringBuffer를 대표로 정리한다.

 

1. 기본 생성자

StringBuffer sb = new StringBuffer();

 

2. int size 값을 인자로 하는 생성자 : int 타입의 값으로 buffer의 사이즈(capacity)를 지정한다. 

StringBuffer sb = new StringBuffer(20);

 

3. String 문자열을 인자로 하는 생성자 : 제일 많이 사용하는 생성자

StringBuffer sb = new StringBuffer("Yangs");

StringBuffer 메소드

많이 사용되는 메소드를 정리해본다.

 

1. append() : 문자열을 추가하기 위해 사용한다.

최초 빈 문자열로 생성 후 "Yangs" 문자열이 추가되었다. 이 후 "World"는 "Yangs" 문자열 뒤에 추가되어 "YangsWorld"가 된다.

public class StringBuffer_Practice {
    public static void main(String[] args) {
        StringBuffer sb = new StringBuffer();
        System.out.println("최초 : " + sb.toString()); // 최초 :

        sb.append("Yangs");
        System.out.println("append 후 : " + sb.toString()); // append 후 : Yangs
        
        sb.append("World");
        System.out.println("append 후 : " + sb.toString()); // append 후 : YangsWorld
    }
}

 

2. length(), capacity() : StringBuffer의 문자열 길이나 전체 할당된 capacity를 구할 때 사용된다. capacity는 StringBuffer 내부의 문자열을 저장하기 위한 버퍼의 총 갯수로 생각하면 된다. 신경쓰지 않아도 문자열의 길이가 늘어나면 같이 증가한다.

최초 설정된 10의 capacity를 넘어서는 문자열이 append되자 자동으로 capacity가 22로 증가했다.

public class StringBuffer_Practice {
    public static void main(String[] args) {
        StringBuffer sb = new StringBuffer(10); // 최초 capacity는 10으로 설정
        System.out.println("최초 문자열길이 : " + sb.length() + ", 총 capacity : " + sb.capacity()); // 최초 문자열길이 : 0, 총 capacity : 10

        sb.append("12345678"); // append() 메소드로 문자열을 이어붙힌다.
        System.out.println(sb.toString()); // 12345678
        System.out.println("1. 문자열길이 : " + sb.length() + ", 총 capacity : " + sb.capacity()); // 1. 문자열길이 : 8, 총 capacity : 10

        sb.append("901"); // append() 메소드로 문자열을 이어붙힌다.
        System.out.println(sb.toString()); // 12345678901
        System.out.println("2. 문자열길이 : " + sb.length() + ", 총 capacity : " + sb.capacity()); // 2. 문자열길이 : 11, 총 capacity : 22
    }
}

 

3. insert() : 문자열을 맨 뒤에 삽입하는 append()와 달리 시작 인덱스를 지정하여 원하는 위치에 삽입할 수 있다.

public class StringBuffer_Practice {
    public static void main(String[] args) {
        StringBuffer sb = new StringBuffer("Yangs"); // 최초 "Yangs"로 생성
        System.out.println(sb.toString()); // Yangs

        sb.append("World"); // 맨 뒤에 추가
        System.out.println(sb.toString()); // YangsWorld

        sb.insert(5, "Happy"); // 5번째 인덱스에 Happy를 추가(기존 'W' 자리였지만 "Happy"가 추가되면서 밀려났다.)
        System.out.println(sb.toString()); // YangsHappyWorld
    }
}

 

4. reverse() : 문자열을 뒤집을 때 사용한다.

public class StringBuffer_Practice {
    public static void main(String[] args) {
        StringBuffer sb = new StringBuffer("Yangs"); // 최초 "Yangs"로 생성
        System.out.println(sb.toString()); // Yangs

        System.out.println("뒤집어 보자 : " + sb.reverse()); // sgnaY
    }
}

 

5. delete(), deleteCharAt() : 인덱스를 지정하여 문자열의 일부분을 삭제하거나 한 문자만 삭제할 때 사용한다.

문자열의 인덱스를 0부터 시작함에 유의한다.

deleteCharAt(int start, int end) 에서 start 인덱스부터 (end-1) 인덱스까지가 삭제됨에 유의한다.

public class StringBuffer_Practice {
    public static void main(String[] args) {
        StringBuffer sb = new StringBuffer("YangsWorld"); // 최초 "YangsWorld"로 생성
        System.out.println(sb.toString()); // YangsWorld

        sb.delete(1,3); // "an" 삭제
        System.out.println(sb.toString()); // YgsWorld

        sb.deleteCharAt(3); // 3번째 인덱스인 'W' 삭제
        System.out.println(sb.toString()); // Ygsorld
    }
}

 

6. replace() : 문자열의 일부분을 다른 문자열로 치환할 수 있다.

문자열의 인덱스는 0부터 시작하며, start 인덱스부터 (end-1) 인덱스까지가 치환됨에 유의한다.

public class StringBuffer_Practice {
    public static void main(String[] args) {
        StringBuffer sb = new StringBuffer("YangsWorld"); // 최초 "YangsWorld"로 생성
        System.out.println(sb.toString()); // YangsWorld

        sb.replace(1,5, "ZZYX"); // "angs"를 "ZZYX"로 치환
        System.out.println(sb.toString()); // YZZYXWorld
    }
}

 


 

+ Recent posts