ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [whiteship] 온라인스터디 - 2주차. 자바 데이터 타입, 변수 그리고 배열
    IT 발자취.../JAVA 2020. 11. 22. 17:03

    저번 스터디에서 블로그 언급해주셔서 감사합니다. 스터디를 듣고 기선님께서 하는걸 따라해본건데, 좋게 봐주셔서 감사합니다. ╰(*°▽°*)╯

    목표

    자바의 프리미티브 타입, 변수 그리고 배열을 사용하는 방법을 익힙니다.

    학습할 것

    • 프리미티브 타입 종류와 값의 범위 그리고 기본 값

    • 프리미티브 타입과 레퍼런스 타입

    • 리터럴

    • 변수 선언 및 초기화하는 방법

    • 변수의 스코프와 라이프타임

    • 타입 변환, 캐스팅 그리고 타입 프로모션

    • 1차 및 2차 배열 선언하기

    • 타입 추론, var

    프리미티브 타입 ( Primitive type ) 종류와 값의 범위 그리고 기본 값

    데이터중에서 주로 사용하는 종류는 크게 '문자와 숫자'로 나눌 수 있으며, 숫자는 정수와 실수로 나눌 수 있다.

    이러한 값의 종류에 따라 값이 저장될 공간의 크기와 저장형식을 정의한 것이 자료형이다.

    자료형은 크게 '기본형(Primitive type)' 과 '참조형 (Reference Type)'으로 나눌 수 있다.

    기본형 변수는 실제 값을 저장하는 반면, 참조형 변수는 어떤 값이 저장되어 있는 주소(memory address)를 값으로 갖는다. 이에 대한 자세한 내용은 조금있다가 알아보도록 한다.

     

    정수형

    데이터 타입 메모리의 크기 표현 가능 범위
    byte 1 byte ( 8bit ) -128 ~ 127
    short 2 byte ( 16bit ) -32,768 ~ 32,767
    int 4 byte ( 32 bit ) -2,147,483,648~2,147,483,647
    long 8 byte ( 64 bit ) -9,223,372,036,854,775,808 ~ 9,223,372,036,854,775,807

    실수형

    데이터 타입 메모리의 크기 표현 가능 범위
    float 4byte ( 32-bit IEEE 754 floating point ) ±(1.40129846432481707e-45 ~ 3.40282346638528860e+38)
    double 8byte ( 64-bit IEEE 754 floating point )

    ±(4.94065645841246544e-324d ~ 1.79769313486231570e+308d)

    정수형의 int와 실수형의 float 그리고 long / double 이 각각 같은 메모리의 크기를 가지지만, 왜 표현 가능 범위가 다른지 진지하게 궁금한 사람만 펼쳐 보세요.!!
    처음 자바를 공부할 때, 같은 메모리를 사용하는데 예전에 메모리가 부족할 땐... 뭐 이해하지만 요즘 메모리도 남아도는데 크게 크게 쓰는게 좋지않나??? 라는 의문을 가지고 있었는데, 컴퓨터 구조를 공부하면서 이해를 한 부분입니다. 
    더보기

    해당 내용은 방대한 내용이므로, 해당 스터디에 해당 ( 도움 )이 되는 부분만 최대한 추려서 적어 보려고 합니다.

    먼저, 데이터에 대한 실제적으로 산술 및 논리 연산을 수행하는 하드웨어가 뭔지 알아야 하겠죠?

    1. ALU

    데이터에 대한 실제적인 산술 및 논리 연산은 CPU에서 담당합니다. CPU 중에서 ALU라는 부품이 이 것을 담당합니다.

    컴퓨터의 CPU를 뗴어서 뒤를 보면!! 볼 수 있지 않지만. 구글에서 CPU 구조를 치면 CPU내부에 다양한 요소들이 있는 것을 확인 할 수 있습니다. CPU의 다른 모든 요소들, 제어 유니트, 레지스터, 기억장치 및 I/O 장치는 주로 처리할 데이터를 ALU로 가져오거나 그 결과를 다시 내보내는 일을 합니다. 따라서 ALU에 대하여 설명한다는 것은 컴퓨터의 핵심부 (core) 혹은 근본(essence)에 도달했다는 것을 의미합니다.

    * (중요) ALU를 비롯해 사실상 컴퓨터의 모든 전자 부품들은 2진수를 저장하고 간단한 부울 논리 연산들을 수행할 수 있는 간단한 디지털 논리회로들을 이용해서 만들어 집니다. 

    2. 정수 표현

    2진수 체계에서, 임의의 수는 0과 1, (음수에 대한) 마이너스 부호, 그리고 (소수 부분을 가진 수들을 위하여) 소수점 혹은 기수점을 이용하여 표현될 수 있습니다. 그러나 컴퓨터의 저장 및 처리 목적을 위해서는, 마이너스 부호와 소수점을 위한 특수 기호들의 이점을 누리지 못합니다. 2진 숫자들만 수를 표현하는 데 사용될 수 있습니다.

    만약 음수가 아닌 정수들만으로 제한된다면 표현 방법은 간단해 집니다.

     

    8비트는 다음과 같이 0부터 255까지의 수들을 표현할 수 있습니다.

    00000000 = 0
    00000001 = 1
    00101001 = 41
    10000000 = 128
    11111111 = 255

    하지만, 고과 교육 과정에서 배운대로 정수는 양수만 있는 것이 아니라 음수도 있습니다. 음수를 표현하는 여러 방식이 있으며, 모든 방법에서 가장 중요한 맨 좌측 비트 (leftmost bit)는 부호 비트(sign bit)로 사용됩니다. 만약 그 비트가 0이면 양수 1이면 음수

    이러한 이유로 표현 가능 범위가 부호 비트를 뺀 -2

    2.1 부호 크기 표현

    그렇다면 가장 쉽게 생각할 수 있는 것이 맨 좌측 비트만을 부호 표시로 생각하고 나머지 비트로 숫자를 표현 하는 것입니다. EASY!

    +18 = 00010010
    -18 = 10010010
    
    +0 = 00000000
    -0 = 10000000

    띠용? -0? +0? 이 방법의 치명적인 결정이 0에 대한 표현이 두가지라는 것이다.. 0은 고유한대 ㅜ 그래서 0에 대한 검사가 더 어려워지는 결점으로 부호-크기 표현은 ALU의 정부 부분을 표현하는데 거의 사용되지 않고 있습니다.

    2.2 2의 보수 표현

    부호-크기와 마찬가지로 2의 보수 표현도 최상위 비트를 부호 비트로 사용하여 정수가 양수인지 음수인지 검사합니다. 

    그러나 다른 비트들을 해석하는 방법은 부호-크기 표현과 다릅니다.

    2의 보수 표현은 음수를 생성하는 규칙에 초점을 두고 있습니다. 

    음수 생성은 해당 양수의 각 비트에 대한 boolean 보수를 취한 다음 부호없는 정수로 표시되는 결과 비트 패턴에 1을 더합니다.

    말이 너무 어렵습니다 ㅜㅜ 실제 상황을 값들을 보면 이해가 쉽습니다.

     

    4비트 정수들에 대한 표현을 보겠습니다.

    +1 표현

    부호-크기 표현법과 같이 숫자 1은 0001 입니다. 부호비트가 0이고 숫자를 나타내는 값이 001 입니다.

    그러면 말로 표현한 음수 표현법을 따라가보겠습니다.

    -1 표현 

    해당 양수( 001 )의 각 비트에 대한 boolean 보수(1은 0으로 0은 1로)를 취한 다음 부호없는 정수로 표시되는 결과 비트 패턴(110)에 1을 더합니다. => 결과 : 111

    음수 부호비트를 젤 왼쪽에 붙여주면 1111이 나옵니다.

     

    4비트 정수들에 대한 표현들

    10진수 수 부호 크기 표현 2의 보수 표현
    +8 - -
    +7 0111 0111
    ... ... ...
    +1 0001 0001
    +0 0000 0000
    -0 1000 -
    -1 1001 1111
    ... ... ...
    -7 1111 1001
    -8 - 1000
    -128 64 32 16 8 4 2 1
    1 0 0 0 0 0 1 1

    -128                                                                                                                     +2       +1=-125

     

    0000, 즉 0이 양수로 취급되면서, 양의 정수를 표현할 수 있는 범위는 0부터 2^(n-1) - 1 까지 입니다.

    음수의 경우 부호비트가 1이고, 나머지 n-1개의 비트들이 수를 표현하므로 -2^(n-1) 까지 표현 가능합니다.

     

    위의 내용을 이해해야지 일단 왜 양의 정수와 음의 정수의 표현 가능 범위가 다른지 알 수 있습니다.

     

    추가적으로 범위 확장이라는 것이 있는데, 비트 수가 달라졌을 때 (8bit -> 16bit), 어떻게 처리하는 지에 대해서도 알아야 하지만, 현재 내용과는 연관성이 떨어져서 생략하기로 한다

     

    이제 정말 알고자 하는, 정수형의 int와 실수형의 float는 같은 32비트를 사용하는데, 표현값의 범위는 비교할 수 없을 만큼 차이가 나는지 알아봅니다.

     

    위에서 알아봤던 모든 표현들은 모두 고정 소수점 (fixed point)이라고 불립니다.

    범위가 차이나는 이유이는 여기에서 있습니다. 

     

    실수형부동소수점(floating point)이란 것을 사용합니다.

     

    int 와 float가 같은 비트를 사용하지만, 다른 숫자 표현 범위를 보이는것은 int는 고정 소수점을 사용하고, float는 부동소수점을 사용하기 때문입니다.

     

    고정 소수점 예시

     

    정수부 실수부
    12 345
    = 12.345

    부동 소수점 예시

    지수부 가수부
    10 ^ -3 12345
    = 12.345  

    부동소수점은 고정 소수점의 아주 큰수를 표현하거나, 아주 작은 소수를 나타낼 수 없는 단점을 해결합니다.

    부동 소수점에는 세 개의 필드가 있습니다.

    - 부호 : 플러스 (+) 혹은 마이너스 (-)

    - 가수 (Significand) S

    - 지수 (exponent) E

    전형적인 32 비트 부동소수점 형식

    부호 비트 (sign bit) : 가장 왼쪽의 비트 0: 양수 1:음수

    바이어스된 표현 : 실제 지수값을 얻어 낼 영역이다. 2^(k−bias)에서의 k값을 나타냅니다. bias는 거듭제곱의 범위가 음수와 양수에 걸쳐 고르게 나타날 수 있도록 정해놓은 오프셋으로, 32비트 자료형에서는 27의 값을 가집니다.

    가수 : 1.xyz...의 형태로 표기한 가수입니다.

     

    자세한 내용은 아래 사이트를 참조하면 좋을 것같습니다.
    www.secmem.org/blog/2020/05/15/float/
    ko.wikipedia.org/wiki/%EB%B6%80%EB%8F%99%EC%86%8C%EC%88%98%EC%A0%90

     

    부동소수점은 변환 과정의 한계로 값이 부정확합니다. 그러므로 정확한 계산이 요구되거나, 돈과 관련된 시스템에서는 사용을 하면 안됩니다.

     

    자세한 내용은 아래 블로그에 잘 설명되어있습니다. ( 위 문제의 해결법도 포함되어있음 )
    ssoco.tistory.com/25

     

    정리

    - 같은 비트를 사용하지만, 표현 범위의 차이를 보이는 것인 고정 소수점 / 부동 소수점의 차이 때문이다.

    - 표현 범위는 고정 소수점 / 부동 소수점을 생성하는 방법을 이해하면, 숫자 표현 범위를 이해할 수 있다.

     

    ====================================================================

    펼쳐 보신분은 괜히 펼쳐보셔서 ... TMI를 얻고 가셨을 것 같습니다 ㅎㅎ..

    ====================================================================

    문자

    데이터 타입 메모리의 크기 표현 가능 범위
    char 2byte ( 16bit ) 모든 유니코드 문자

    논리

    데이터 타입 메모리의 크기  표현 가능 범위
    boolean 1bit를 나타내지만 "크기는 정확하게 정의 X true / false

    ---

    참고 : docs.oracle.com/javase/tutorial/java/nutsandbolts/datatypes.html

    ---

     

    프리미티브 타입과 레퍼런스 타입

     

    Privitive type 메모리에 실제 값을 저장한다.
    - boolean, char, byte, shourt, int, long, float, double
    Reference type 메모리에 객체 주소를 저장한다.
    - 기본형 8개를 제외한 나머지 타입
    - 클래스 타입, 인터페이스 타입, 배열 타입, 열거 타입

    글은 어려우니 그림으로 알아보도록 한다.

     

    궁금해서 실제로 할당되는 주소값을 확인해 보았다...

    그런데 이해가 잘 안되는 부분이 있었다... 혹시 아시는분은 댓글에 적어 주시면 감사합니다 ㅜㅜ

    더보기

    www.baeldung.com/java-object-memory-address

    JOL을 사용하기 때문에, 메이븐 디펜던시를 추가해준다.

    <dependency> 
        <groupId>org.openjdk.jol</groupId> 
        <artifactId>jol-core</artifactId>    
        <version>0.10</version> 
    </dependency>
    import org.junit.Test;
    import org.openjdk.jol.vm.VM;
    
    public class VariableTest {
        int primitive_int_1 = 5;
        int primitive_int_2 = 5;
    
        int primitive_int_3 = 6;
        int primitive_int_4 = 7;
        int primitive_int_5 = 8;
    
        String reference_String_1 = "hello";
        String reference_String_2 = "hello";
    
        @Test
        public void 메모리_주소_확인() {
            System.out.println("The memory address of the primitive_int_1 (숫자 5) is " + VM.current().addressOf(primitive_int_1));
            System.out.println("The memory address of the primitive_int_2 (숫자 5) is " + VM.current().addressOf(primitive_int_2));
            System.out.println("The memory address of the primitive_int_3 (숫자 6) is " + VM.current().addressOf(primitive_int_3));
            System.out.println("The memory address of the primitive_int_4 (숫자 7) is " + VM.current().addressOf(primitive_int_4));
            System.out.println("The memory address of the primitive_int_5 (숫자 8) is " + VM.current().addressOf(primitive_int_5));
            System.out.println("The memory address of the reference_String_1 is " + VM.current().addressOf(reference_String_1));
            System.out.println("The memory address of the reference_String_1 is " + VM.current().addressOf(reference_String_2));
        }
    }
    

     

    The memory address of the primitive_int_1 (숫자 5) is 26319646936
    The memory address of the primitive_int_2 (숫자 5) is 26319646936
    The memory address of the primitive_int_3 (숫자 6) is 26319646952
    The memory address of the primitive_int_4 (숫자 7) is 26319646968
    The memory address of the primitive_int_5 (숫자 8) is 26319646984
    The memory address of the reference_String_1 is 26310470784
    The memory address of the reference_String_1 is 26310470784

    결과를 보면, 같은 숫자는 같은 메모리 주소를 바라보고, 각 숫자별로 16의 주소값의 차이를 확인했다.

    예상했던 결과는 자유롭게 메모리가 할당되거나 int 타입은 32bit의 차이가 차이나게 메모리를 할당 받는 줄 알았는데, 아니었다. 

    테스트는 corretto 11로 하였다.

    처음에는 String constant pool 같은건가?? 했는데, 그건 아닌것 같다.

     

    https://stackoverflow.com/questions/16737078/do-we-have-pools-for-all-primitive-types-in-permgen-area-of-heap

    혹시 테스트를 잘못한 것인지, 해당 테스트 결과가 어떻게 나온것인지 아는 분은 댓글 부탁드립니다.!

    아무튼 본론으로 돌아와서, primitive type롸 reference type이 메모리에 어떻게 저장되는지 알면 좋다.

     

    primitive type은 local로 선언된 기본 형식은 스택에 저장되는 반면 object 인스턴스로 정의 되면 heap에 저장된다.

    지역 변수는 스택에 저장되고 인스턴스 및 정적 변수는 힙에 저장된다.

     

    추가내용

    더보기

    www.baeldung.com/java-stack-heap

    class Person {
        int id;
        String name;
     
        public Person(int id, String name) {
            this.id = id;
            this.name = name;
        }
    }
     
    public class PersonBuilder {
        private static Person buildPerson(int id, String name) {
            return new Person(id, name);
        }
     
        public static void main(String[] args) {
            int id = 23;
            String name = "John";
            Person person = null;
            person = buildPerson(id, name);
        }
    }

     

    간단한 예시로 변수들이 어떻게 메모리에 저장되는지 알아본다.

     

    순서대로 알아 보기로 한다.

    1. main() 메서드이 호출 될 때, 스택 메모리에 메소드의 primitive 와 이 메소드의 reference를 저장하기 위한 공간이 생성된다.

    - 정수 id 의 primitive 값은 스택 메모리에 바로 저장된다.

    - Person 타입 ( reference type )의 reference 변수 person은 스택 메모리에 생성되어 힙의 실제 개체를 가리킵니다.

     

    2. main() 함수에서 Person(int, String) 파라미터가 있는 생성자가 호출되면, 이전 스택 위 (top)에 추가 메모리가 할당 됩니다. 여기에는 다음을 저장하게 됩니다.

    - 스택 메모리에서 호출 객체의 this 객체 참조

    - 스택 메모리에 primitive value id

    - 힙 메모리의 string pool에서 실제 문자열을 가리키는 문자열 인수 이름의 참조 변수

     

    3. main() 메서드는 buildPerson() 이라는 static method를 호출하고, 이전의 스택 메모리 위(top)에 새로운 공간을 할당합니다. 이는 위에서 설명한 방식으로 변수를 다시 저장합니다.

     

    4. 하지만, 새로 생성 된 Person type의 객체 person의 경우 모든 인스턴스 변수가 힙 메모리에 저장됩니다.

     

     

    상수(Constant)와 리터럴(Literal)

    상수 변하지 않는 변수 (메모리 위치)
    메모리 값을 변경할 수 없다.
    리터럴 변수의 값이 변하지 않는 데이터 ( 메모리 위치 안의 값)
    보통 primitive data를 의미하지만 특정 객체
    ( Immutable class - 변경 불가능 클래스, VO class)에 한해서 리터럴이 될 수 있다.

    글을 읽어 봤을 때, 상수와 리터럴은 둘다 값이 변경되지 않는 것을 의미하는 용어라고 알 수 있다.

    먼저 상수란, 변하지 않는 변수를 뜻합니다.

     

    상수는 데이터가 변하지 않아야 한다고 했다. 그래서 참조 변수 (reference type)를 상수로 지정 할 때, 참조 변수에 넣은 인스턴스 안의 데이터까지도 변하지 않는 줄 착각 할 수 있지만, 참조 변수 메모리의 주소값이 변하지 않는다는 의미지 주소가 가리키는 데이터 까지 상수라는 의미는 아니다.

     

    Java에서 상수를 쓸 때 final을 사용한다.

     

    예를들어보자

    final Test test = new Test();
    
    // 불가능
    test = new Test();
    
    //가능
    test.id = 10;
    

    final로 선언되어 test = new Test(); 처럼 새로운 메모리 주소를 줄수는 없지만,

    test.id = 10; 이렇게 가리키는 메모리 값의 데이터는 변경이 가능하다.

    TMI

    더보기

    위 같은 문제를 사전에 방지하고자, 서적이펙티브 자바에서는 변경 가능성을 최소화해라는 장이있다

    1. 객체 상태를 변경하는 메서드 ( 수정자 mutator)를 제공하지 않는다.

    2. 계승할 수 없도록 한다

    3. 모든 필드를 final로 선언한다

    4. 모든 필드를 private로 선언한다.

    5. 변경 가능 컴포넌트에 대한 독점적 접근성을 보장한다.

     

    리터럴에 대해서 알아본다.

    리터럴은 데이터 그 자체를 의미한다.

    변수에 넣는 변하지 않는 데이터를 의미하는 것이다.

     

    예시 >

    int num1 = 1;

    int 앞에 final을 붙이면 num1은 상수가 된다. 여기서의 리터럴은 1이다.

    즉, 1과 같이 변하지 않는데이터를 리터럴이라고 부른다.

     

    그렇다면 인스턴스 ( 클래스 데이터)가 리터럴이 될 수 있을 까?

    답은 아니오이다.

    보통의 인스턴스는 동적으로 사용하기 위해 작성되므로 리터럴이 될 수 없다.

     

    하지만, 데이터가 변하지 않도록 설계를 한 클래스를 불변 클래스라 칭한다.

    해당 클래스는 한번 생성하면 객체 안의 데이터가 변하지 않는다. 변할 상황이면 새로운 객체를 만들어준다.

    자바의 String, Color 같은 클래스가 이와 같은 예이다.

     

    따라서 "abc"와 같은 문자열을 자바에서는 '객체 리터럴' 혹은 '리터럴'이라고 표현하는 것이다.

     

     

    변수 선언 및 초기화하는 방법

    자바에서 변수를 선언할 때는 다음과 같은 규칙을 따른다.

    지금까지 공부했던, 자료형과 변수명을 이용해서 변수를 선언할 수 있다.

     

    자료형 변수명 = 데이터;

    이렇게 선언한다면 자료형의 크기만큼 적절한 위치에 메모리가 할당되게 됩니다.

     

    예를 들면, int a; 라고 선언한다는 것은 int형 변수를 a라는 이름으로 선언한다는 것입니다. 메모리에 4바이트 할당

    int a = 0;
    double b = 3.14;
    String str = "Hello";
    Test test = new Test();

    예시의 마지막은 쫌 특이한 형태를 가진것을 볼 수 있습니다.

    참조형 타입의 변수를 선언할 때는 new라는 연산자를 사용합니다.

     

    자료형 변수명 = new 클래스명 ();

     

    참조 변수를 선언할 때는 new 라는 연산자를 사용하는데, 이 연산자의 역할은 다음과 같습니다.

    - 클래스의 새로운 인스턴스에 대한 참조 (reference)를 리턴합니다.

     

    말이 조금 어려울 수 있습니다. 하지만 하나만 알아두면 될 것은 new를 사용하면 해시코드로 만들어진 참조값(reference)를 참조 변수 (reference variable)에 할당 할 수 있습니다.

     

    예를 들면

    Test test = new Test(); 에서 연산자 new가 반환한 참조 ( 해시값 )은 new Test(); 의 반환 값이고, 참조 변수는 test 입니다.

     

    추가설명

    더보기

    참조와 참조변수

     

    연산자 new와 생성자를 이용해서 특정 메모리에 인스턴스를 생성했다면 당연히 생성된 인스턴스를 사용할 수 있습니다.

    그런데 연산자 new가 인스턴스가 있는 메모리를 생성한 경우, 그 메모리의 주소와 어떻게 연결할 수 있나를 알아야 합니다. 

    C++ 언어에서는 new를 이용해서 메모리를 생성하면 주소 그 자체를 직접 넘겨줍니다. 

    주소를 저장하기 위한 포인터 변수를 이용해서 주소를 관리하기 때문에 C++에서는 사용자가 주소를 직접 처리합니다.

     

    하지만 자바에서는 메모리 주소를 바로 주지 않는데, 즉 인스턴스의 메모리 주소 대신 참조값을 할당받게 됩니다.



    출처: https://dohe2014.tistory.com/entry/참조reference와-참조변수reference-variable [돕자클럽]

     

     

    변수의 스코프와 라이프타임

    변수의 스코프

    프로그램상에서 사용되는 변수들은 사용 가능한 범위를 가지는데 그 범위를 변수의 스코프라고 합니다.

     

    변수는 클래스 변수, 인스턴스 변수, 지역 변수 모두 세 종류가 있습니다. 변수의 종류를 결정하는 중요한 요소는 '변수의 선언된 위치' 입니다. 변수의 종류를 파악하기 위해서는 변수가 어느 영역에 선언되었는지를 확인하는 것이 중요합니다. 

    멤버 변수를 제외한 나머지 변수들은 모두 지역변수이며, 멤버변수static이 붙은 것은 클래스변수, 붙지않은 것은 인스턴스 변수입니다.

     

    /* 클래스 영역 */
    class Variables
    {
      int instance_variable;	// 인스턴스 변수
      static int class_variable; 	// 클래스 변수 (static변수, 공유 변수)
      
      /* 매서드 영역 */
      void method() {
        int local_variable;			// 지역 변수
      }
    }

     

    변수의 종류 선언위치 생성시기
    클래스 변수
    (class variable)
    클래스 영역 클래스가 메모리에 올라갈 때
    인스턴스 변수
    (instance variable)
    인스턴스가 생성되었을 때
    지역 변수
    (local variable)
    클래스 영역 이외의 영역
    (메서드, 생성자, 초기화 블럭 내부)
    변수 선언문이 수행되었을 때

     

    1. 지역 변수

    메서드 내에 선언되어 메서드 내에서만 사용 가능하며, 메서드가 종료되면 소멸되어 사용할 수 없게 된다.

    for문 또는 while 문의 블럭 내에 선언된 지역변수는 지역변수가 선언된 블럭 {} 내에서만 사용 가능하며, 블럭 {}을 벗어나면 소멸되어 사용할 수 없게 된다.

     

    2. 인스턴스 변수

    클래스 영역에 선언되며, 클래스의 인스턴스를 생성할 때 만들어진다. 그렇기 때문에 인스턴스 변수의 값을 읽어 오거나 저장하기 위해서는 먼저 인스턴스를 생성해야 한다.

    인스턴스는 독립적인 저장공간을 가지므로 서로 다른 값을 가질 수 있다. 인스턴스 마다 고유한 상태를 유지해야하는 속성의 경우, 인스턴스 변수로 선언한다.

     

    2. 클래스 변수

    클래스 변수를 선언하는 방법은 인스턴스 변수 앞에 static을 붙이기만 하면 된다. 인스턴스마다 독립적인 저장공간을 갖는 인스턴스변수와는 달리, 클래스 변수는 모든 인스턴스가 공통된 저장공간(변수)를 공유하게 된다. 한 클래스의 모든 인스턴스들이 공통적인 값을 유지해야하는 속성의 경우, 클래스 변수로 선언해야한다.

     

    좀더 알아보기

    더보기

    위에 설명한 것과 같이 변수별로 스코프를 가지는건 자바의 메모리 관리와 관련이 있다.

    https://www.hanumoka.net/2017/09/13/java-20170913-java-static-initialization/

    클래스 변수

    먼저, 스터디 1차시에서 했던 JVM의 구조를 떠올려 본다. 

    컴파일된 .class 파일을 가지고 자바의 클래스 로더가 JVM으로 클래스 파일이 로딩됩니다. 이 때 클래스 변수 (정적필드)가 메소드 영역으로 로드되게 됩니다. 클래스 변수는 클래스가 메모리가 올라가는 시점부터 사용 가능합니다. 이는 모든 클래스 전체에서 접근이 가능하기에 모든 인스턴스가 공통된 저장 공간 (변수를 ) 공유할 수 있게 됩니다.

     

    인스턴스 변수

    https://dnhome.wordpress.com/2012/06/28/how-does-java-allocate-stack-and-heap-memory/

    위 블로그에서 설명하는 것과 같이 인스턴스 변수는 heap 영역에 생성됩니다. 

    import java.util.*;
    
    class Hello{
      public static void main(String[] args) {
         // 힙에 저장
          Person[] persons = new Person[4];
          persons[0] = new Person("james",29);
          persons[1] = new Person("john",1);
          persons[2] = new Person("andre",80);
          persons[3] = new Person("mike",35);
    
          Arrays.stream(persons).forEach(System.out::println);
      }
    }
    
    
    
    class Person {
        // 사람들마다 다르게 가지는 이름과 나이 그리고 생년월일
        // instance variable
        private final String name;
        private final int age;
    
        public Person(String name, int age) {
            this.name = name;
            this.age = age;
        }
    
        public String getName() {
            return name;
        }
    
        public int getAge() {
            return age;
        }
    
        @Override
        public String toString() {
            return "Person{" +
                    "name='" + name + '\'' +
                    ", age=" + age +
                    ", bitrthDate=" + bitrthDate +
                    '}';
        }
    }
    

     result

    각 사람마다 다른 값을 표출한다.

    Person{name='james', age=29}
    Person{name='john', age=1}
    Person{name='andre', age=80}
    Person{name='mike', age=35}

    지역변수

    메서드 내에 선언되어 메서드 내에서만 사용 가능하며, 메서드가 종료되면 소멸되어 사용할 수 없게 된다.

    메서드가 종료되면 소멸되는 이유는 호출 스택과 관련된다.

    호출 스택은 메서드의 작업에 필요한 메모리 공간을 제공하는데, 메서드가 호출되면, 호출스택에 호출 된 메서드를 위한 메모리가 할당되며, 이 메모리는 메서드가 작업을 수행하는 동안 지역변수(매개변수 포함)들과 연산의 중간 결과 등을 저장하는데 사용된다.

    그리고 메서드가 작업을 마치면 할당되었던 메모리공간은 반환되어 지워지기 때문이다.

     

    TMI

    무엇이든 정리를 할 때, 메모리와 연관해서 정리가 하게되는데, 자바 서비스를 운영하다보면, 여러가지의 오류의 형태를 볼 수 있는데, 상태에 대한 분석을 하기 위해서는 어떤 것이 어떻게 되어있는지 정확하게 아는 것이 중요하다. 

    ( 저도 아직 정확히 이해를 하지 못해서 ... 머리속에 정리할 것은 많은데 글로 정확하게 정리를 하지 못해 .. 게시물이 길어지기만하고 체계적이지 못해 아쉽습니다 ㅜㅜ )

     

    예를 들어, 시스템 운영 중에 stackoverflow 오류가 발생할 수 있습니다.

    더보기

    Test를 위한 코드를 작성합니다.

    빨리 stackoverflow를 발생시키기 위하여, -Xss10m jvm을 동작시킬 때, stack의 크기를 작게 잡아줍니다.

    class Test{
      public static void main(String[] args) {
          stackoverflow_func(0);
      }
      private static void stackoverflow_func(int i) {
          stackoverflow_func(i + 1);
      }
    }
    Exception in thread "main" java.lang.StackOverflowError
    	at Hello.stackoverflow_func(Test.java:xx)
    	at Hello.stackoverflow_func(Test.java:xx)

    간단하게 stackoverflow가 발생하는 것을 확인할 수있다. 그 이유는 메서드는 stack에 쌓이게 되는데, 재귀로 같은 함수를 계속 호출해주었기 때문이다. 정해진 메모리 영역을 넘게되고, stackoverflow오류가 발생하는 것을 알 수 있습니다. 

    (local 변수를 무한대로 생성해서 stackoverflow 장애를 발생시키고 싶었는데... 잘 안되었습니다 ㅜ.ㅜ)

     

    알고리즘 문제 ( 예를 들면 dfs )같은 문제를 풀다보면 재귀 호출에서 처리를 제대로 안해줬을 때, 이러한 오류가 발생할 수 있습니다.

    이와 같이 서버 운영을 하는데 stackoverflow로 시스템이 죽었을 경우, 스택 영역에서 쌓이는 어느 데이터에서 leak이 생겼다고 의심할 수 있는데, 이는 거의 발생하지 않습니다.

     

    시스템 운영중에서 자주 발생하는 장애는 Java Heap space OutOfMemory Error (OOM) 입니다.

    힙에는 참조형의 변수들의 실제 데이터와 인스턴스 변수 등이 저장된다고 하였다. 

    그럼 Heap OOM을 발생시켜보겠다.

    더보기

    인스턴스를 힙메모리 이상으로 생성해주면 heap OOM이 발생한다.

    발생을 쫌 쉽게 하기위해서 JVM 메모리를 적게 할당하겠다.

    -Xms10m -Xmx10m 옵션을 JVM 시작할 때 줍니다.

    import java.util.*;
    
    class Test{
      public static void main(String[] args) {
          Person[] persons = new Person[500000];
    
          for(int i=0; i< 499999; i++) {
              persons[i] = new Person("test", new Random().nextInt());
          }
      }
    }
    
    
    
    class Person {
        // 사람들마다 다르게 가지는 이름과 나이 그리고 생년월일
        // instance variable
        private final String name;
        private final int age;
    
        public Person(String name, int age) {
            this.name = name;
            this.age = age;
        }
    }
    

    result

    Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
    	at java.base/java.util.Random.<init>(Random.java:xxx)
    	at java.base/java.util.Random.<init>(Random.java:xxx)
    	at Hello.main(Test.java:xx)

     

    장애 해결 방법은 문제가 되는 소스 코드를 찾거나, 메모리 할당을 늘려주면됩니다. 위 예시는 간단한 예시이기 때문에 간단하게 힙 메모리 할당을 늘려주면 에러가 발생하지 않고 동작하게 됩니다.

    -Xms1g -Xmx1g 

     

    heap memory usage

    위는 실제 운영 중인 JVM의 힙 메모리 상태입니다. GC (가비지컬렉션)이 돌면서 적절히 힙메모리를 조절해주는데, 인스턴스 생성을 너무 많이 하거나 정리가 잘 되지 않았을 때, Heap OOM 장애가 발생하게 됩니다. ( 발생전에 GC가 너무 많이 발생해서 시스템이 극격히 느려질 수 도 있음)

     

    개발자나 시스템 운영자 모두 간단하지만 중요한 이러한 내용을 잘 알고 있어야 합니다.

    ( effective java 같은 서적을 보면 java를 개발할 때 주의할 점에 대해 잘 정리되어있다. [중고급자용])

     

     

    타입 변환, 캐스팅 그리고 타입 프로모션

    Java 변수에는 데이터 타입을 변환하는 방법이 있다.

    - 자동 형변환 ( 프로모션 )

    - 강제 형변환 ( 캐스팅 )

     

    java에서 연산은 같은 데이터 타입에서 가능합니다.

    하지만 개발을 하다보면, 2(int) + 3.5(double)과 같이 서로 다른 타입끼리의 연산이 필요할 떄가 있습니다.

    이럴 경우 변수의 데이터 타입을 바꿔주는 작업이 필요한데, 이것이 데이터 타입의 형변환입니다.

     

    자동 형변환 ( 프로모션 )

    프로그램 실행 도중에 자동적으로 형변환이 일어나는 것을 말함

    작은 메모리 크기의 데이터 타입을 큰 메모리 크기의 데이터 타입으로 변환하는 행위

     

    타입별 크기 순서 (byte)

    byte(1) short(2) int(4) long(8) float(4) double(8)
    int a = 0;
    double b = a;

    위에 작성한 예시처럼 작은 메모리 크기의 데이터 타입에서 큰 메모리 크기의 데이터 타입에 값을 저장하면 별다른 문제 없이 형변환이 일어난다.

     

    강제 형변환 (캐스팅)

    위의 조건을 충족하지 못할 때는 강제 형변환을 하게 됩니다.

    double b  = 0;
    int a = b;

    IDE에서 다음과 같이 적으면 빨간줄(컴파일에러)이 그이며, 다음과 같은 것을 볼 수 있습니다 (Intellij의 경우 )

    그 이유는 저장될 값에 상관없이 double 데이터 타입이 int 데이터 타입보다 메모리 크기가 크기 때문입니다.

    아래 블로그에 설명이 잘되어있습니다. 

    stage-loving-developers.tistory.com/8

     

    6. Java 데이터 타입 자동 형변환(Promotion)과 강제 형변환(Casting)

    안녕하세요. 무대를 사랑하는 개발자입니다. JAVA를 학습하면서, 학습한 내용을 복습차 포스팅함을 알려드립니다. 그럼 JAVA 여섯번째 시간 시작하겠습니다. 6. Java 데이터 타입 자동 형변환(Promotio

    stage-loving-developers.tistory.com

    강제 형변환 (캐스팅)을 하는 방법은 intellj 에서도 친절히 도와주고 있는데, 괄호를 사용하면 됩니다.

    double b = 0;
    int a = (int) b;
    
    // int 데이터 타입의 피연산자와 double 타입의 피연산자를 더하면 int 데이터 타입이 피연산자 double로 자동 현변환 (프로모션) 된다.
    double b  = 0;
          int a = 1;
    
          double c = a + b;

     

     

    1차 및 2차 배열 선언하기

    배열이란 자료구조중 하나로 동일한 타입의 연관된 데이터를 메모리에 연속적으로 저장하여 하나의 변수로 묶어서 관리하기 위한 자료 구조이다.

    배열의 길이는 최초 선언한 값으로 고정되며 위와 같이 인덱스 (index)를 통해 데이터에 접근 할 수 있다.

    1차 배열 선언 / 초기화

    class Test{
      public static void main(String[] args) {
          // 크기 할당 & 초기화 없이 배열 참조변수만 선언
          int[] arr1;
          int arr2[];
          
          // 선언과 동시에 배열 크기 할당
          int[] arr3 = new int[6];
          
          
          // 기존 배열의 참조 변수에 초기화 할당하기
          arr1 = new int[6];
          
          // 선언과 동시에 배열의 크기 지정 및 값 초기화
          int[] arr4 = new int[]{1, 2, 3, 4, 5, 6};
          
      }
    }

     

    2차배열 선언 / 초기화

    class Test{
      public static void main(String[] args) {
          // 크기 할당 & 초기화 없이 배열 참조변수만 선언
          int[][] arr1;
          int arr2[][];
    
          // 선언과 동시에 배열 크기 할당
          int[][] arr3 = new int[6][6];
    
    
          // 기존 배열의 참조 변수에 초기화 할당하기
          arr1 = new int[6][6];
    
          // 선언과 동시에 배열의 크기 지정 및 값 초기화
          int[][] arr4 = new int[][]{
                  {1,2,3,4,5,6},
                  {1,2,3,4,5,6},
                  {1,2,3,4,5,6},
                  {1,2,3,4,5,6},
                  {1,2,3,4,5,6},
                  {1,2,3,4,5,6}};
    
      }
    }

     

     

    타입 추론, var

    java 10이전에는 Generic과 Lambda를 사용하여 타입 추론이라는 단어를 사용하였다.

    JAVA10 이상부터 var라는 Local Variable Type-Infernce가 추가되었다.

     

    Java 10 이상

    var

    자바 10부터 타입 추론을 지원하는 var키워드가 추가되었다. 이 키워드는 local variable 이면서 선언과 동시에 initializer가 필수적으로 요구된다.

     

    자바 9까지 지역 변수의 타입을 명시적으로 선언해줘야 했다

    String message = "Good bye, Java 9";

    자바 10부터 지역 변수를 다음과같이 선업할 수 있다.

    @Test
    public void 타입추론_var() {
      //given
      var message = "Hello, Java 10";
      //when
      //then
      assertThat(message).isInstanceOf(String.class);
    }
    @Test
    public void 타입추론_var_map() {
      //given
      var idToNameMap = new HashMap<Integer, String>();
      //when
      //then
      assertThat(idToNameMap).isInstanceOf(Map.class);
    }

    잘못 사용하는 예

    더보기

    As mentioned earlier, var won't work without the initializer:

    var n; // error: cannot use 'var' on variable without initializer

    Nor would it work if initialized with null:

    var emptyList = null; // error: variable initializer is 'null'

    It won't work for non-local variables:

    public var = "hello"; // error: 'var' is not allowed here

    Lambda expression needs explicit target type, and hence var cannot be used:

    var p = (String s) -> s.length() > 10; // error: lambda expression needs an explicit target-type

    Same is the case with the array initializer:

    var arr = { 1, 2, 3 }; // error: array initializer needs an explicit target-type

     

     

     

     

    참고

     

    리터럴 : mommoo.tistory.com/14

    자바 메모리 관리 : www.youtube.com/watch?v=aAjkJW08BGQ

    변수와 메모리 : blog.naver.com/smilennv/220246062980

    타입 변환 : stage-loving-developers.tistory.com/8

    타입 추론 : velog.io/@bk_log/Java-%ED%83%80%EC%9E%85-%EC%B6%94%EB%A1%A0

    타입 추론 : www.baeldung.com/java-10-local-variable-type-inference

     

    댓글

Designed by Gintire