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를 이용한 로직
    }
}

 

 


 

 

프로그래밍 언어에는 크게 두가지 타입이 있습니다.

1. 정적인 언어 : 컴파일 때 이미 변수에 대한 데이터 타입이 확정되어 다른 데이터 타입을 할당할 수 없는 언어입니다.

예를들어 C, C++, JAVA와 같은 언어가 여기에 속합니다.

2. 동적인 언어 : 여기 언어들은 변수에 다른 데이터 타입을 할당할 수 있습니다. Ruby, Python 등의 언어가 있죠.

 

자바는 정적인 언어이기 때문에 변수를 사용하려면 데이터 타입을 꼭 지정해줘야 합니다. 자바에서 사용되는 타입은 다음과 같습니다.

https://www.geeksforgeeks.org/data-types-in-java/?ref=lbp

자바의 데이터 타입은 크게 두 가지 Primitive 와 Non-Primitive 타입으로 나눌 수 있습니다. 

- Primitive Data Type : boolean, char, int, short, byte, long, float, double

- Non-Primitive Type : String, Array 등


Primitive Data Type

Primitive 데이터는 오직 한 값만 가지며 다른 특수한 기능이 없습니다. 그 값 자체라고 생각하면 됩니다.

boolean, byte, short, int, long, float, double 이렇게 8가지 데이터 타입이 있으며 하나씩 살펴보겠습니다.

 

먼저 각 자료형마다 표현할 수 있는 범위가 정해져 있습니다. boolean은 1bit, byte는 8bit 처럼 말이죠.

여기서 1bit는 0 또는 1로 표현되는 한자리 값입니다. 즉, 2^1 = 2 가지 값을 표현할 수 있습니다.

마찬가지로 1byte(= 8bit)는 2진수로 00000000 ~ 11111111 사이의 값을 가지며 2^8 = 256 가지 값을 표현합니다.

데이터의 표현 범위는 여기까지 하고 한 자료형씩 보겠습니다.

 

1. boolean : 1bit의 값으로 참/거짓(true/false)만을 나타내는 자료형입니다. 

True는 1, False는 0의 값으로 표현합니다. 두 가지 값만 필요하니 1bit로 모든 값을 표현할 수있습니다.

class Test {
    public static void main(String args[])
    {
        boolean b = true;
        if (b == true)
            System.out.println("Hi boolean"); // Hi boolean 출력
    }
}

 

 

2. byte : 8 bit(1 byte)의 값으로 -128 ~ 127 범위의 값을 표현할 수 있습니다.

정수 자료형 중 가장 작은 범위를 지니고 있습니다. 8bit 이므로 2^8=256 개의 값인 -128~127을 표현합니다. 범위안에 0이 있어 128까지가 아님에 유의합니다.  

class Test {
    public static void main(String args[])
    {
        byte a = 126;
 
        System.out.println(a); // 126 출력
 
        a++;
        System.out.println(a); // 127 출력
 
        // 표현 가능한 범위(-128 ~ 127)를 넘었으므로 -128로 돌아갑니다.
        a++;
        System.out.println(a); // -128 출력
 
        // -128에서 1이 증가합니다.
        a++;
        System.out.println(a); // -127 출력
    }
}

 

3. short : 16 bit(2 byte)의 값으로 -32,768 ~ 32,767 범위의 값을 표현합니다.

2^16= 65,536개의 값을 표현하며, byte와 마찬가지로 중간에 0을 포함하여 양수의 갯수가 하나 더 적습니다. 

 

4. int : 32 bit(4 byte)의 값으로 -2,147,483,648 to 2,147,483,647 범위의 값을 표현합니다.

정수형 중 가장 많이 사용되는 자료형입니다. 범위를 봤을 때 대부분의 변수사용이 이 범위로 커버가 가능하므로 가장 많이 사용됩니다. 만일 사용하는 변수가 21억 이상의 값이 할당될 가능성이 있다면 더 큰 자료형인 long을 사용해야 합니다.

 

5. long : 64 bit(8 byte)의 값으로 -9,223,372,036,854,775,808 to 9,223,372,036,854,775,807 범위의 값을 표현합니다.

2^64개의 변수를 표현합니다. 값이 엄청나게 커진걸 볼 수 있습니다. 변수를 선언하게 되면 그 공간만큼 메모리가 할당되므로 사용하는 변수에 맞는 자료형을 선택해야 합니다.

물론 요즘 사용하는 하드웨어들이 메모리가 빵빵하기 때문에 자료형 몇개로 부족해지지는 않겠지만 습관을 들이는게 좋습니다.

 

6. float : 32 bit(4 byte)의 값으로 실수를 표현합니다.

위 5개 자료형과는 다르게 실수형 데이터 타입입니다. 실수형이란 소수점을 가진 값으로 이 소수점을 표현하기 위해 가진 bit중 일부를 사용합니다. 소수임을 알리기 위해 값에 f를 붙혀 할당합니다.

public class Main {
    public static void main(String[] args) {
        float number = 0.9f;
        System.out.println(number); // 0.9 출력
    }
}

 

7. double : 64 bit(8 byte)의 값으로 실수를 표현합니다.

float와 마찬가지로 소수점을 표현하기 위해 bit 일부를 사용하지만 f를 붙혀주지 않아도 됩니다.

public class Main {
    public static void main(String[] args) {
        double number = 0.09;
        System.out.println(number); // 0.09 출력
    }
}

 

8. char : 16 bit(2 byte)의 유니코드 문자를 표현합니다.

표현범위로는 '\u0000' (0) to '\uffff' (65535) 입니다. '\u' 는 유니코드라는 의미이고 16진수로 '0000'(0) ~ 'ffff'(65535) 까지 값이 할당된다는 의미입니다.  

자바에서는 문자를 표현할 때 2바이트를 사용하고 있지만 C, C++은 1바이트를 사용합니다. C, C++은 아스키(ASCII)로 문자를 표현하여 일부 문자가 표현이 불가능 합니다. 하지만 자바는 문자(Character)에 Unicode를 사용하므로 대부분 나라의 언어를 포함하여 표현이 가능합니다.

 

아래 테스트를 보면 char형으로 문자 한개를 표현할 수 있고, int 자료형이 4byte로 더 크므로 할당이 가능한 걸 볼 수 있습니다. 

public class Test {
    public static void main(String[] args) {
        char a = 'a';
        char b = 'b';
        System.out.println(a); // a 
        System.out.println(b); // b

        int intFromChar1 = a;
        int intFromChar2 = b;
        System.out.println(intFromChar1); // 97
        System.out.println(intFromChar2); // 98
        
        char x = 'ㄱ';
        char y = 'ㄲ';
        System.out.println(x); // ㄱ
        System.out.println(y); // ㄲ

        int intFromChar3 = x;
        int intFromChar4 = y;
        System.out.println(intFromChar3); // 12593
        System.out.println(intFromChar4); // 12594
    }
}

 

 


 

 

 

템플릿 엔진(Template Engine)

템플릿 엔진이란 템플릿 양식 입력 자료를 합성하여 결과 문서를 출력하는 소프트웨어를 말한다.
그 중 웹 템플릿 엔진은 브라우저에서 출력되는 문서를 위한 소프트웨어이다.

  1. 고정적으로 사용될 부분을 템플릿을 미리 작성해 놓고
  2. 동적으로 변경될 데이터 부분만 필요시 결합하여
  3. 화면(문서)을 완성한다.

어디서 결합하냐에 따라 서버 사이드 템플릿 엔진과 클라이언트 사이드 템플릿 엔진으로 분류할 수 있다.

  • 서버 사이드 템플릿 엔진 서버에서 데이터와 미리 정의된 템플릿으로 HTML을 완성하여 클라이언트에게 전달한다.
    Freemarker, Thymeleaf, Handlebars(Handlebars.java), JSP 등

  • 클라이언트 사이드 템플릿 엔진
    데이터와 템플릿을 합쳐 클라이언트에서 HTML을 완성한다.
    서버는 api 콜에대한 응답데이터만 제공하면서 화면 랜더링은 클라이언트가 담당하는 구조가 가능하다.
    Handlebars(Handlebars.js), EJS(Embedded Javascript Templates) 등

클라이언트 사이드 템플릿 엔진의 필요성

  • 계속해서 페이지를 이동하면서(서버에 데이터를 요청하면서) 화면이 변경되는 것이 아니라, 단일 화면에 특정 이벤트에 따라 화면이 변경되어야 하는 경우 javascript로 html을 변경해야 한다.
  • 조작해야할 코드량이 많아지면 관리가 어려우므로 직접 javascript로 DOM을 제어하는 대신에 클라이언트 사이드 템플릿 엔진을 이용하면 좋다.

람다표현에서는 바깥쪽 스코프의 변수(static 변수, 지역 변수, 인스턴스 변수)를 참조 할 수 있으며 이를 capturing lambda 라고 한다. 

그 중 지역 변수를 참조하는 경우에만 컴파일러는 해당 변수가 final 또는 effectively final 인지 체크하여 아니라면 컴파일 오류를 표출하게 되는데 이에 대해서 알아보고자 한다.

 

아래 메서드는 람다를 리턴한다. 하지만 람다표현식에서 함수의 파라미터(지역변수)를 사용하고 있으며 이를 증가시키고 있으므로 컴파일 오류를 표출한다.

Supplier<Integer> incrementer(int start) {
    return () -> start++; // local variables referenced from a lambda expression must be final or effectively final
}

위 코드의 start 지역변수는 final로 선언되지 않았으며 값을 변경했으므로 effectively final이 아니다.

 > effective final : final은 붙지 않았지만 할당 후 값을 변경하지 않아 final과 같은 효과

 

컴파일 되지 않는 기본적인 이유는 람다는 참조하는 지역변수에 대한 복사본을 가지고 동작하기 때문이다.


람다는 왜 참조하는 외부스코프 지역변수에 대해 복사본을 가지고 동작할까?

위 코드에서 incrementer 함수는 실행되면서 start를 증가시키지 않고 람다를 리턴할 뿐이다.

incrementer 함수의 실행이 끝난 후의 다른 시점에서 리턴한 람다표현식이 실행되어 start 지역변수가 증가할 수 있으며, 그 시점이 오기전에 GC에 의해서 start 지역변수가 정리될 수도 있다.(함수의 실행이 끝나면 지역변수는 GC에 의해 정리대상이 된다.) 그러므로 실행 시점에 지역변수가 사라질 것을 방지하지 위해서 람다는 복사본을 생성하여 동작하게 된다.(또한 지역변수는 Stack영역에 저장되기 때문에 공유자원이 아니므로 다른 thread에서 람다가 실행된다면 변수에 접근할 수 없다.)

 

복사본을 가지고 미래에 동작할 예정인데 복사본에서 값이 바뀌어 버리면(= final or effective final이 아니면, = 최종상태값과 복사본값이 다르면) 오류를 발생할 여지가 생기므로 컴파일 단계에서 막는 것이다.


static 변수와 인스턴스 변수의 경우에는?

private int start = 0;
Supplier<Integer> incrementer() {
    return () -> start++;
}

지역변수가 아닌 인스턴스 변수는 컴파일 에러도 나지 않으며 정상 동작한다. 인스턴스 변수는 Heap영역에 저장되고 static 변수는 Method영역에 저장되며 두 영역모두 공유자원이므로 다른 thread에서 접근이 가능하다. 자유롭게 접근이 가능하므로 실행되는 시점에 최종상태값을 읽어 증가시킬 수 있다. 그러니 변수에 대한 제약조건이 없는 것이다.

 

이에대한 간단한 예제 코드이다.

변수 holder가 참조하는 배열객체는 heap 영역에 저장되어 자유롭게 참조가 가능하다. 그러므로 sums.sum()로 람다가 실행되는 시점에 holder[0]의 최종상태값을 읽어들여 계산하므로 6이 리턴된다.

public int workaroundSingleThread() {
    int[] holder = new int[] { 2 };
    IntStream sums = IntStream
      .of(1, 2, 3)
      .map(val -> val + holder[0]);

    holder[0] = 0;

    return sums.sum();
}

 

 


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

모두 힘내세요! : )

 

 

 

'개발 > JAVA' 카테고리의 다른 글

NullpointerException 과 해결법  (0) 2020.12.09
[Java 기본] Java Primitive 데이터 타입  (0) 2020.12.09
equals, hashcode  (0) 2020.12.07
JVM, JRE, JDK 간단 개념  (0) 2020.12.07
ExecutorService 사용법  (0) 2020.12.01

+ Recent posts