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
    }
}

 


 

String 객체에는 다양한 메소드들이 있는데 한번씩 살펴보고자 한다.

 

1. int length() : 스트링 객체의 문자 갯수에 대해 반환한다.

"Hello".length();  // 5

 

2. Char charAt(int i) : i번째 인덱스에 있는 문자를 반환한다. 예시의 1번째 인덱스는 'e' 이다.(인덱스는 0부터 시작)

"Hello".charAt(1); // e

 

3. String substring(int i) : i번째 인덱스의 문자부터 끝까지를 반환한다.

"Hello".substring(3); // lo

 

4. String substring(int i, int j) : i번째 인덱스부터 j-1번째 인덱스를 반환한다. 1번째 인덱스의 문자는 'e'익고 (3-1)번째 문자는 'l'이다.

"Hello".substring(1,3); // el

 

5. String concat(String str) : 인자의 문자열을 끝에 병합하여 새로운 문자열을 반환한다.

"Hello".concat(" World"); // Hello World

 

6. int indexOf(String s) : 해당문자열이 첫번째로 나타는 인덱스를 반환한다. "Hello"가 처음 나타는 인덱스는 "Hi " 다음 3번째 인덱스이다. 뒤에 한번 더 나타나는 "Hello"는 무시된다.

"Hi Hello World Hello".indexOf("Hello"); // 3

 

7. int indexOf(String s, int i) : 6번과 유사하나 탐색하는 시작 인덱스를 지정한다. 4번째 인덱스부터 찾으므로 뒤에 "Hello"가 시작하는 인덱스인 15를 반환한다. 

"Hi Hello World Hello".indexOf("Hello", 4); // 15

 

8. int lastIndexOf(String s) : 인자의 문자열이 마지막으로 나타나는 인덱스를 반환한다. 마지막으로 나타나느 "Hello"의 시작 인덱스인 15가 반환된다.

"Hi Hello World Hello".lastIndexOf("Hello"); // 15

 

9. boolean equals(Object otherObj) : 인자의 스트링 객체와 동일한지 검증한다. 대소문자를 구분하여 검증한다.

"Hello".equals("hello"); // false

 

10. boolean equalsIgnoreCase(String anotherString) : 9번과 유사하나 대소문자를 구분하지 않고 검증한다.

"Hello".equalsIgnoreCase("hello"); // true

 

11. int compareTo(String anotherString) : 인자의 스트링과 사전상으로 비교한다. 사전상으로 비교한다는 의미는 abc.. 나 ㄱㄴㄷ.. 순으로 오름차순 비교하여 더 앞쪽이면 음수, 같으면 0, 뒷쪽이면 양수가 반환된다.

"Hello" <--> "Zello" 에서는 "H"가 "Z"보다 사전상으로 앞쪽에 위치하므로 음수가 반환됐다.

아래 예시에서는 'H'와 'Z' 또는 'A'와의 차이만큼 정수가 반환되지만 보통 (음수, 0, 양수) 이 정도로만 체크한다.

"Hello".compareTo("Zello"); // -18
"Hello".compareTo("Aello"); // 7
"Hello".compareTo("Hello"); // 0

 

12. int compareToIgnoreCase( String anotherString) : 11번과 유사하지만 비교시 대소문자를 구분하지 않고 비교한다.

"Hello".compareToIgnoreCase("hEllO"); // 0

 

13. String toLowerCase() : 스트링 객체를 소문자로 변경한 새로운 스트링 객체를 반환한다.

"HellO".toLowerCase(); // hello

 

14. String toUpperCase() : 스트링 객체를 대문자로 변경한 새로운 스트링 객체를 반환한다.

"HellO".toUpperCase(); // HELLO

 

16. String replace(char oldChar, char newChar) : 첫번째 인자의 문자를 찾아 두번째 인자로 치환한다.

"Hello".replace('l', 'z'); // hezzo

 


 

 

 

자바에서 String 객체는 불변(immutable)이다.

불변이라는 의미는 한번 생성되면 변경되지 않는다는 것을 말한다. 즉, String 문자열을 조작(서로 합치거나 나누거나)할 때마다 기존 객체가 변경되는 것처럼 보이지만 사실은 계속 새로 생성되는 것이다.

 

String 객체 생성 

1. 일반적인 스트링의 리터럴 생성

String s = "HI";

2. new 키워드를 이용한 생성

String s = new String("HI");

당연히 코드가 많아지는 2번보다 1번이 많이 사용된다.

 

String 생성자

1. String(byte[] byte_arr) : byte 배열을 이용(디코딩)하여 새로운 스트링 객체를 생생한다. 디코딩에는 디폴트 캐릭터셋이 사용된다.

System.out.println("사용되는 기본 캐릭터셋 : " + Charset.defaultCharset().name()); // UTF-8

byte[] byte_arr = {72, 101, 108, 108, 111}; // 스트링 생성
String str = new String(byte_arr);

System.out.println("생성된 스트링 : " + str); // Hello

 

2. String(byte[] byte_arr, Charset char_set) : 1번과 유사하나 디폴트 캐릭터셋이 아니라 인자로 캐릭터셋을 지정할 수 있다.

byte[] byte_arr = {72, 101, 108, 108, 111};
String str = new String(byte_arr, Charset.forName("UTF-8"));
System.out.println("생성된 스트링 : " + str); // Hello

 

3. String(byte[] byte_arr, String char_set_name) : 3번과 유사하게 캐릭터셋을 지정하지만 Charset 객체가 아닌 스트링으로 캐릭터셋을 지정한다.

byte[] byte_arr = {72, 101, 108, 108, 111};
String str = new String(byte_arr, "UTF-8");
System.out.println("생성된 스트링 : " + str); // Hello

 

4. String(byte[] byte_arr, int start_index, int length) :  인자의 바이트 배열중 일부분을 인덱스로 지정하여 스트링 객체를 생성한다.

byte[] byte_arr = {72, 101, 108, 108, 111};
String str = new String(byte_arr, 1, 3);
System.out.println("생성된 스트링 : " + str); // ell

 

5. String(byte[] byte_arr, int start_index, int length, Charset char_set) : 4번과 동일하나 Charset객체만 인자로 추가됐다.

byte[] byte_arr = {72, 101, 108, 108, 111};
String str = new String(byte_arr, 1, 3, Charset.forName("UTF-8"));
System.out.println("생성된 스트링 : " + str); // ell

 

6. String(byte[] byte_arr, int start_index, int length, String char_set_name) : 4번과 동일하나 캐릭터셋을 String 객체로 추가하여 지정한다.

byte[] byte_arr = {72, 101, 108, 108, 111};
String str = new String(byte_arr, 1, 3, "UTF-8");
System.out.println("생성된 스트링 : " + str); // ell

 

7. String(char[] char_arr) : 캐릭터 배열을 인자로 스트링 객체를 생성한다.

char char_arr[] = {'H', 'e', 'l', 'l', 'o'};
String str = new String(char_arr);
System.out.println("생성된 스트링 : " + str); // Hello

 

8. String(char[] char_array, int start_index, int count) : 7번과 유사하나 배열의 일부분을 지정한다.

char char_arr[] = {'H', 'e', 'l', 'l', 'o'};
String str = new String(char_arr, 1, 3);
System.out.println("생성된 스트링 : " + str); // ell

 

9. String(StringBuffer s_buffer) : StringBuffer 객체를 인자로 스트링 객체를 생성한다.

StringBuffer s_buffer = new StringBuffer("Hello");
String str = new String(s_buffer);
System.out.println("생성된 스트링 : " + str); // Hello

 

10. String(StringBuilder s_builder) : StringBuilder 객체를 인자로 스트링 객체를 생성한다.

StringBuilder s_builder = new StringBuilder("Hello");
String str = new String(s_builder);
System.out.println("생성된 스트링 : " + str); // Hello

 


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

모두 힘내세요! : )

 

 

 

 

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();

 


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

모두 힘내세요! : )

 

 

 

자바에서 콘솔로부터 값을 읽을 수 있는 3가지 방법을 소개하겠습니다.

 

각 방법마다 장단점이 있지만 가장 많이 사용되는 방법은 Scanner 클래스를 이용하는 방법입니다.

 

1. Scanner Class

가장 많이 사용되는 방법으로 Primitive 값들을 읽을 수 있는 편리한 메서드들을 제공합니다.(nextInt(), nextFloat() 등)

public class ConsoleInput_Scanner {
    public static void main(String[] args) throws IOException {
        Scanner in = new Scanner(System.in); // Scanner 객체 선언

        String s = in.nextLine(); // 콘솔 문자열 입력 받기
        System.out.println("You entered string : "+s);

        int a = in.nextInt(); // 콘솔 int 입력 받기
        System.out.println("You entered integer : "+a);

        float b = in.nextFloat(); // 콘솔 float 입력 받기
        System.out.println("You entered float "+b);
    }
}

 

// 입력
하이
1
1.5
// 출력
You entered string : 하이
You entered integer : 1
You entered float 1.5

2. BufferedReader Class

JDK1.1 부터 있었던 BufferedReader를 이용할 수도 있습니다. Scanner와 같이 표준 입력 스트림인 System.in을 사용하며, InputStreamReader를 이용하여 선언해야 해서 읽기에 좀 불편할 수도 있습니다.

public class ConsoleInput_BufferedReader {
    public static void main(String[] args) throws IOException {
        BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
        String input = reader.readLine();
        System.out.println("input : " + input);
    }
}

 

// 입력
하이
// 출력
input : 하이

3. Console Class

IDE 환경에서는 동작하지 않습니다. 터미널 환경에서 컴파일 후 class 파일을 실행하면 결과를 확인할 수 있습니다. 

public class ConsoleInput_Console {
    public static void main(String[] args) throws IOException {
        String name = System.console().readLine();
        System.out.println("input : " + name);
    }
}

 

// 입력
하이
// 출력
input : 하이

 

 


 

 

 

 

자바는 항상 Call-By-Value(Pass-By-Value)로만 동작한다고 한다.

분명 자바를 처음 배울때는 Call-By-Value와 Reference-By-Value를 모두 사용한다고 배웠던거 같은데 어떻게 된건지 알아보겠다.

(Call-By-Value와 Pass-By-Value에서 Call과 Value는 거의 동일하게 사용한다고 하니 Call-By-Value로 통일하여 작성하기로 한다.)


일단 처음 Call-By-Value와 Reference-By-Value를 학습할 때 배우는 swap 예제를 살펴본다.

 

아래는 Call-By-Value의 예시로 작성된 코드이다.

a,b 변수를 swap 함수의 인자로 건네줘서 값을 바꿨지만 a, b 변수의 값은 그대로이다. 즉, a, b의 값만 복사(Call-By-Value)하여 x, y 변수가 지역변수로 새로 생성됐기 때문에 x와 y를 스왑하던, 값을 바꾸던 a, b 변수에는 영향이 없는 것이다. 

public class CallByTest {
    @Test
    public void callbyvalue(){
        int a = 10;
        int b = 20;

        System.out.println("swap() 호출 전 : a = " + a + ", b = " + b); // a = 10, b = 20
        swap(a, b);
        System.out.println("swap() 호출 후 : a = " + a + ", b = " + b); // a = 10, b = 20
    }

    public void swap(int x, int y){
        int temp = x;
        x = y;
        y = temp;
    }
}

 

그럼 Call-By-Reference 예제로 배웠던 swap 예제코드를 살펴본다.

swap 호출전후로 인자로 넘겼던 객체의 상태값이 바꼈으므로 Call-By-Reference의 예시로 알았던 코드이다.

public class CallByTest {
    @Test
    public void CallByReference(){
        Game a = new Game(10);
        Game b = new Game(20);

        System.out.println("swap() 호출 전 : a.star = " + a.star + ", b.star = " + b.star); // a.star = 10, b.star = 20
        swap(a, b);
        System.out.println("swap() 호출 후 : a.star = " + a.star + ", b.star = " + b.star); // a.star = 20, b.star = 10
    }

    public void swap(Game x, Game y){
        int temp = x.star;
        x.star = y.star;
        y.star = temp;
    }

    class Game {
        int star;
        public Game(int star) {
            this.star = star;
        }
    }
}

 

하지만, 진정한 Call-By-Reference는 참조하는 객체의 상태값을 변경하는게 아니라 실제 x,y 변수가 참조하는 객체를 변경하여 외부의 a 변수가 참조하는 객체를 바꿀수 있는 경우를 말한다.

자바는 Call-By-Value만 지원하므로 아래 코드에서는 a가 참조하는 객체가 realCallByReference() 함수에서 변경한 객체(star 값이 20인)로 변경되지 않고 여전히 10이 출력되는 걸 볼 수있다.

public class CallByTest {
    @Test
    public void test(){
        Game a = new Game(10);

        realCallByReference(a);

        System.out.println("a가 참조하는 객체는 바뀌지 않음 : " + a.star); // 10
    }

    public void realCallByReference(Game x){
        x = new Game(20);
    }
}

1. 함수 내에서 지역변수 x가 새로 생성되며 함수인자인 a와 같은 객체를 참조함.

    > Call-By-Value에 의해 a가 참조하는 객체에 대한 위치값만 전달해줬음.

2. realCallByReference 함수내에서 x가 참조하는 객체를 신규로 생성한 객체로 변경함.

    > a와 같은 객체를 참조하다가 새로운 객체로 변경되었으며 a에는 전혀 영향없음.

3. realCallByReference 함수 종료후 출력결과를 보면 a는 그대로 기존 객체를 참조하고 있음.

 


정리해보면

자바는 Call-By-Value만을 지원한다. 

처음 학습시 Call-By-Reference의 예제로 알고있던 swap코드는 참조하는 객체의 상태값을 바꾸는 것이지 참조하는 객체 자체를 바꾸는 것이 아니므로 진정한 Call-By-Reference의 예제는 아니다.

 

참고 StackOverflow

stackoverflow.com/questions/40480/is-java-pass-by-reference-or-pass-by-value

 

Is Java "pass-by-reference" or "pass-by-value"?

I always thought Java uses pass-by-reference. However, I've seen a couple of blog posts (for example, this blog) that claim that it isn't (the blog post says that Java uses pass-by-value). I don't ...

stackoverflow.com

 


 

 

 

자바에서 코드를 작성하며 String을 비교하는 경우는 매우 많습니다.

보통 int나 char 타입의 경우 '==' 비교를 많이 사용합니다만, String의 경우 대부분 == 비교를 사용하면 안됩니다.

 

== 과 .equals() 의 차이

  • == 비교는 참조하는 객체가 동일한지를 비교합니다. 
  • .equals() 비교는 값 자체가 동일한지를 비교합니다.

String 비교시 대부분의 경우는 값 자체가 동일한지를 비교하므로 == 비교가 아닌 .equals() 비교를 해야합니다.

 

간단한 테스트 코드를 보겠습니다.

// 값에 대한 비교이므로 동일
new String("test").equals("test") // true 

// new String()으로 새로운 객체를 생성하여 같은 객체끼리 동일한지 비교
// 서로 참조하는 객체가 다르므로 false
new String("test") == "test" // false 
new String("test") == new String("test") // false 

// "test" 스트링 리터럴은 컴파일러에 의해 interned 되므로
// 같은 객체끼리 비교하는 것이 됨
"test" == "test" // true 

// 스트링 리터럴은 컴파일러에 의해 조립되어("te" + "st" = "test") interend 되므로 
// 같은 객체끼리 비교하는 것이 됨
"test" == "te" + "st" // true

// Objects.equals()로 null을 포함하여 값 비교가 가능함
Objects.equals("test", new String("test")) // true
Objects.equals(null, "test") // false
Objects.equals(null, null) // true

string의 값 비교를 위해서는 .equals()나 Object.equals()를 사용할 수도 있고,

대소문자를 신경쓰지 않고 비교하는 String.equalsIgnoreCase() 같이 추가기능이 있는 비교메서드를 사용할 수 있습니다.

 

String intern

위 내용 중 string intern에 대해 추가설명을 드리자면,

자바는 JVM에 문자열 풀(pool)을 생성 후, 새로운 문자열이 사용되면 그 풀에 등록하여 놓습니다.

만일 그 문자열이 다시 사용되는 경우 새로 생성하는 것이 아닌 문자열 풀에서 꺼내 사용하는 것이지요. 왜냐하면 String 객체는 불변이기 때문에 동일한 객체를 계속 생성하는 것이 아니라 생성 후 공유하는 특징을 가지기 때문입니다.

 

String 객체에 대해 intern된 값을 가져올 수도 있는데요 이를위해 String 클래스의 intern()을 사용할 수 있습니다.

아래 테스트 코드를 보시죠.

 

str1과 str2는 String intern에 의해 JVM 문자열 풀의 같은 String 객체를 참조합니다. 

str1.intern() 은 JVM 문자열 풀에서 str1과 값이 동일한 String 객체를 가져옵니다. 만일 풀에 저장되어 있지 않다면 신규로 등록후 가져오게 되지요. 

str1.intern() 과 str2.intern()은 JVM 문자열 풀의 같은 객체를 가져오므로 동일합니다. 

 

str3과 str4는 new String()으로 생성된 각기 다른 String 객체를 참조합니다.

하지만 str1 ~ str4 의 intern() 호출은 결국 다 같은 JVM 문자열 풀의 같은 객체를 반환합니다.

public class StringComparison {
    @Test
    public void StringIntern(){
        String str1 = "하이";
        String str2 = "하이";

        System.out.println(str1 == str2); // true
        System.out.println(str1.equals(str2)); // true
        System.out.println(str1.intern() == str2.intern()); // true

        String str3 = new String("하이");
        String str4 = new String("하이");

        System.out.println(str3 == str4); // false
        System.out.println(str3.equals(str4)); // true
        System.out.println(str3.intern() == str4.intern()); // true
        System.out.println(str1.intern() == str4.intern()); // true
    }
}

 


 

 

자바 변수에는 생성한 오브젝트(객체)를 가리키는 참조형 타입의 변수가 있습니다.

아래 Integer 타입의 num변수를 선언 후 할당하는 경우입니다.

Integer num; // Integer 타입의 변수 num을 선언, 초기화 하지 않음
num = new Integer(10); // new 키워드를 사용해 Integer 객체를 생성후 num에게 참조시킴

첫째 줄은 Integer 타입의 num 변수를 선언했습니다. 참조형 변수를 선언만 했으므로 'null'로 초기화됩니다.

null 이란 참조형 변수가 아무것도 가리키지 않는 상태라는 의미입니다. 

두번째 줄에서 num에 세로운 Integer 객체를 생성하여 참조시켰습니다. 이로써 num은 null 에서 새로운 Integer 객체를 가리키도록(Integer 객체의 주소값이 할당) 변경되었습니다.

 

NullPointerException(아래부터는 줄여서 NPE라고 하겠습니다.)은 첫 번째 줄처럼 선언만 하고 아무것도 참조하지 않는 null 상태인 변수에서 내용을 참조하려 할 때 발생합니다.

 

내용을 참조한다는 것은 점(.)으로 객체 변수에 접근하는 것을 말합니다.

아래 코드는 null인 변수 k에 점(.)으로 indexOf() 함수를 호출하려 할 때 NPE가 발생합니다.

아무것도 가리키지 않는 상태인데 내부 내용에 접근하려 하니 오류가 나는 것이지요.

public void NPE_method1(){
    String k = null;
    k.indexOf(1);
}

 

컴파일러가 해당 참조변수는 아직 초기화 되지 않았다는 메시지를 표출하기도 하지만 아래와 같은 코드에서는 null이 넘어올지 초기화된 참조변수가 넘어올지 알 수 없습니다. 만일 doSomething(null); 로 호출하게 되면 NPE 오류가 나게됩니다. 

public void doSomething(SomeObject obj) {
   obj.myMethod();
}

 

이런 경우에는 어떻게 하여야 할까요?

제일 좋은 방법은 메서드 시작전에 obj가 초기화 되었는지 검증하는 것입니다.

doSomething 함수의 내부로직이 obj가 초기화 되었다는 것을 전제로 작성되었다면, 첫 줄에 null 값에 대한 검증 후 만일 null이 넘어온 경우 처리할 수 없다는 명시적인 메시지와 함께 종료하는 것이죠.

import java.util.Objects;

public class Test {
    public static void main(String[] args) {
        new Test().doSomething(null);
    }

    public void doSomething(Object obj){
        Objects.requireNonNull(obj, "obj는 null값이 아니여야 합니다.");
    }
}
Exception in thread "main" java.lang.NullPointerException: obj는 null값이 아니여야 합니다.

 

doSomething 내에서 obj에 null값을 허용하며 분기하여 처리할 수도 있습니다. 다음 코드를 참조해주세요.

public void doSomething(Object obj){
    if(obj == null){
        // obj가 null일 때의 로직
    }else {
        // obj를 이용한 로직
    }
}

 

 


 

 

+ Recent posts