본문 바로가기

JAVA

자바 데이터 타입, 변수 그리고

  • 프리미티브 타입 종류와 값의 범위 그리고 기본 값
  • 프리미티브 타입과 레퍼런스 타입
  • 리터럴
  • 변수 선언 및 초기화하는 방법
  • 변수의 스코프와 라이프타임
  • 타임 변환, 캐스팅 그리고 타입 프로모션
  • 1차 및 2차 배열 선언하기
  • 타입 추론, var

 

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

프리미티브 타입. 영어로 primitive type 또는 원시 타입 또는 기본형 타입이라고 하기도 합니다.

우선 타입이란 데이터 타입을 줄인 말로 자료형이라고 하기도 합니다.

그럼 데이터 타입이란 무엇일까요?

컴퓨터 관점에서 타입은 데이터가 메모리에 어떻게 저장될 것이고 또 어떻게 다뤄져야 하는지에 대해 알려주는 것입니다.

즉, 데이터 타입을 보면 컴퓨터에서 어떤 형태를 가지며 어떻게 처리될 수 있는지 머리 속에 그릴 수 있습니다.

 

그 중에서 프리미티브 (기본형)타입에 대해 알아봅시다.

 

자바 언어에 기본적으로 내장 된 타입입니다.

구분 프리미티브 타입 메모리 크기 기본 값 표현 범위
논리형 boolean 1 byte false true, false
정수형 byte 1 byte 0 -128 ~ 127
short 2 byte 0 -32,768 ~ 32,767
int 4 byte 0 -2,147,483,648 ~ 2,147,483,647
long 8 byte 0L -9,223,372,036,854,775,808 ~ 9,223,372,036,854,775,807
실수형 float 4 byte 0.0F (3.4x10^-38) ~ (3.4x10^38) 의 근사 값
double 8 byte 0.0 (1.7x10^-308) ~ (1,7x10^308) 의 근사 값
문자형 char 2 byte (유니코드) '\u0000' 0 ~ 65,535

 

1 byte는 8 bit 입니다. 그리고 1bit는 2진수 한 자리를 뜻합니다.

 

우리가 일반적으로 사용하는 10진수는 한 자리에 10가지를 표현할 수 있습니다. (0 ~ 9)

2진수는 한 자리에 2가지 표현을 할 수 있습니다. (0 ~ 1)

 

1 비트가 2진수 한자리를 뜻하면, 2비트는 2진수 두 자리를 뜻하고 다음과 같은 값을 표현할 수 있습니다.

00, 01, 10, 11

비트가 1 증가하자 표현할 수 있는 가지수가 2배가 되었습니다.

 

3 비트로 표현 가능한 값은 다음과 같습니다.

000, 001, 010, 011, 100, 101, 110, 111

비트가 1 증가하자 표현할 수 이쓴 가지수가 역시 2배가 되었습니다.

 

(** 10진수에서 자릿수가 늘어나면 표현 가능한 수가 10배가 되는 것을 생각하면 됩니다. ex) 10 -> 100 )

 

정수형 프리미티브 타입 중 bye 자료형의 메모리 크기는 1 byte입니다.

즉 8비트 입니다. 8비트로 포현 가능한 값의 개수는 2의 8제곱 입니다.

2의 8제곱은 256인데 왜 표현범위가 0 ~ 255가 아니고 -128 ~ 127까지 일까요?

 

컴퓨터에서 음수를 표현하기 위해 MSB라는 것을 사용합니다.

MSB는 Most Significant Bit의 줄임말로 최상위 비트를 뜻합니다.

 

8비트를 다음과 같이 표현할 수 있다고 가정해봅시다.

0 0 0 0 0 0 0 0

빨간색으로 표현한 비트를 MSB라고 부르고 부호 비트라고 합니다.

이 값이 1이면 음수, 0이면 양수라고 판단합니다.

 

즉, 부호가 있는 자료형의 경우 1트를 부호를 표현하기 위해 사용하기 때문에

현재 예시를 기준으로 -128 ~ 127까지의 값 표현 범위를 가집니다.

양수는 0이 포함되기 때문에 128이 아닙니다.

만약 0 ~ 255 까지 표현하고 싶다면, 다시 말해 부호 비트의 자리도 데이터로 취급하려 한다면 unsigned (부호가 없는) 자료형을 사요하면 된다. 음수는 표현하지 못하는 대신 양수 표현 범위가 2배 늘어납니다.

 

아쉽게도 자바에는 unsigned 타입 자료형을 지원하지 않습니다.

그래서 보통 표현 범위를 넘을 때 더 큰 자료형을 사용합니다.

하지만 자바 8부터 Integer 와 Long의 Wrapper 클래스에 unsigned 관련한 static 메소드가 추가 되었습니다.

 

 

실수의 경우는 분명 정수형과 비교했을 때 메모리 크기는 별반 다르지 않은데, 값의 표현 범위가 훨씬 넓습니다.

심지어 소수점을 표현할 수 있습니다.

 

실수는 부호, 가수(mantissa), 지수(exponent)로 구성되며, 부동 소수점 방식을 사용합니다.

부동 소수점 방식을 사용하여 모든 가수를 0 보다 크거나 같고 1보다 작은 값 범위의 값으로 만들고 원래 수를 표현하기 기 위해 10을 몇 번 거듭 제곱해야 하는지 지수로 표현합니다.

 

즉, 1.234 라는 값을 0.1234 * 10^1로 표현 한다는 것을 의미합니다.

 

실수형 중 float 타입은 부호(1비트) + 지수(8비트) + 가수(23비트) = 32비트를 사용하고

double 타입은 부호 (1비트) + 지수(11비트) + 가수(52비트) = 64비트를 사용합니다.

 

 

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

프리미티브 타입은 위에서 살펴보았습니다.

레퍼런스 타입이란 무엇일까요?

 

reference. 참고, 참조의 뜻을 가지고 있습니다. 그래서 참조 타입이라고 하는 사람도 있습니다.

 

참조를 한다는 것은 여러가지 의미로 해석할 수 있지만, 자바에서는 실제 값이 저장되어 있는 곳의 위치를 저장한 값(주소 값)을 뜻합니다.

참조 타입의 종류는 배열, 열거(enum), 클래스 그리고 인터페이스가 있습니다.

 

기본 타입과 참조 타입을 구분하는 방법은 생각보다 단순합니다.

저장되는 값이 실제 값 그 자체이냐 아니면 메모리의 주소값이냐에 따라 구분할 수 있습니다.

 

그럼 이 값은 어디에 저장되는 걸까요?

지난 1주차에 공부한 JVM의 Runtime Data Area 입니다.

그 중에서도 런타임 스택 영역과 가비지 컬렌셥 힙 영역에 저장됩니다.

 

예를 들면 다음과 같은 코드가 있습니다.

public class Test {
    public static void main(String[] args) {
        String name = "TEST";
        int age = 20;
    }
}

레퍼런스 타입의 name 변수와 프리미티브 타입의 age 변수는 런타임 스택 영역에 생성 됩니다.

그리고 레퍼런스 타입의 값인 주소값과 프리미티브 타입의 값인 20 역시 런타임 스택 영역에 저장됩니다.

 

다만, 레퍼런스 타입의 값인 주소 값이 가리키는 실제 값은 가비지 컬렉션 힙 영역에 저장된 객체의 주소가 저장 됩니다.

그래서 값을 복사할 때 조심해야 합니다.

 

그 이유는 레퍼런스 타입의 경우 실제 값이 아닌 주소 값이 복사되기 때문입니다.

보통 기본서에서는 값에 의한 복사 (call by value)와 참조 또는 주소에 의한 복사 (call by reference) 라고 합니다.

주소에 의한 복사 경우 두 가지 경우가 있는데 얕은 복사와 깊은 복사로 또 나뉩니다.

 

얕은 복사는 주소 값을 복사하여 결국 동일한 가비지 컬렉션 힙 영역의 객체를 참조합니다.

그렇기에 하나의 값을 변경해버리면 다른 대상의 값 또한 바뀌어 버리는 문게 발생합니다.

 

깊은 복사는 프리미티브 타입에서의 값에 의한 복사처럼 완전히 똑같은 새로운 객체를 만들어 복사하는 것을 뜻합니다.

그렇기에 대상이 2개가 생기므로 앝은 복사의 문제점은 해결되지만 메모리 측면에서 본다면 한 객체로 할 수 있는 일은 하나로 끝내는 것이 좋습니다.

 

어느 방식이 좋다 나쁘다는 없습니다. 의도한 상황에 맞게 적절히 잘 판단하여 사용해야 합니다.

 

 

  • 리터럴

리터럴은 실제로 저장되는 값 그 자체로 메모리에 저장되어있는 변하지 않는 값 그 자체를 뜻합니다.

또는 컴파일 타임에 프로그램 안에 정의되어 그 자체로 해석 되어야 하는 값을 뜻합니다.

즉, 코드 내에서 직접 쓴 값이라고 생각하면 편할 거 같습니다.

 

리터럴 종류로는 정수, 실수, 문자, boolen, 문자열 등이 있습니다.

public class Main {
    public static void main(String[] args) {

        System.out.println("=== 정수 리터럴 ===");
        int intVal1 = 0b10;     // 접두문자 0b   -> 2진수
        int intVal2 = 010;      // 접두문자 0    -> 8진수
        int intVal3 = 10;       // 접두문자 없음 -> 10진수
        int intVal4 = 0x10;     // 접두문자 0x   -> 16진수
        long longVal1 = 10L;    // 접미문자 ㅣ 또는 L -> long 타입 리터럴

        System.out.println("2진수 정수 리터럴 : " +  intVal1);
        System.out.println("8진수 정수 리터럴 : " +  intVal2);
        System.out.println("10진수 정수 리터럴 : " + intVal3);
        System.out.println("16진수 정수 리터럴 : " + intVal4);
        System.out.println("long 타입 정수 리터럴 : " + longVal1);
        System.out.println();

        System.out.println("=== 실수 리터럴 ===");

        // 실수 타입 리터럴은 double 타입으로 컴파일 되므로
        // float 타입인 경우 명시적으로 f 또는 F를 명시해줘야 합니다.
        // double 타입도 d나 D를 명시해줘도 되지만, 안해줘도 상관 없습니다.
        float floatVal1 = 1.234F;
        double doubleVal1 = 1.234;
        double doubleVal2 = 1.234d;
        double doubleVal3 = 1234E-3d;

        System.out.println("float 타입 실수 리터럴 : " + floatVal1);
        System.out.println("double 타입 실수 리터럴 1 : " + doubleVal1);
        System.out.println("double 타입 실수 리터럴 2 : " + doubleVal2);
        System.out.println("double 타입 실수 리터럴 3 : " + doubleVal3);
        System.out.println();

        System.out.println("=== 문자 리터럴 ===");
        char charVal1 = 'C';
        char charVal2 = '민';
        char charVal3 = '\u1234';       // 백슬러시 u 다음 4자리 16진수 유니코드

        System.out.println("문자 리터럴 1 : " + charVal1);
        System.out.println("문자 리터럴 2 : " + charVal2);
        System.out.println("문자 리터럴 3 : " + charVal3);
        System.out.println();

        System.out.println("=== 부울(논리) 리터럴 ===");
        boolean booleanVal1 = true;
        boolean booleanVal2 = 12 > 34;

        System.out.println("부울(논리) 리터럴 1 : " + booleanVal1);
        System.out.println("부울(논리) 리터럴 2 : " + booleanVal2);
        System.out.println();

        System.out.println("=== 문자열 리터럴 ===");
        String stringVal1 = "Hello, ws study";
        System.out.println("문자열 리터럴 : " + stringVal1);
        System.out.println();

    }
}

 

=== 정수 리터럴 ===
2진수 정수 리터럴 : 2
8진수 정수 리터럴 : 8
10진수 정수 리터럴 : 10
16진수 정수 리터럴 : 16
long 타입 정수 리터럴 : 10

=== 실수 리터럴 ===
float 타입 실수 리터럴 : 1.234
double 타입 실수 리터럴 1 : 1.234
double 타입 실수 리터럴 2 : 1.234
double 타입 실수 리터럴 3 : 1.234

=== 문자 리터럴 ===
문자 리터럴 1 : C
문자 리터럴 2 : 민
문자 리터럴 3 : ሴ

=== 부울(논리) 리터럴 ===
부울(논리) 리터럴 1 : true
부울(논리) 리터럴 2 : false

=== 문자열 리터럴 ===
문자열 리터럴 : Hello, ws study

대입 연산자를 기준으로 모든 우항의 값들을 리터럴이라고 부릅니다.

 

 

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

자바에서 변수를 선언하는 방법은 기본적으로 그 변수의 타입(자료형) 다음에 변수의 이름을 작성하는 것입니다.

에를 들어 정수형 타입의 변수를 다음과 같이 선언할 수 있습니다.

 

public class Main {
    public static void main(String[] args) {

        int intVal;    // 정수형 타입의 변수 intVal을 선언
    }
}

 

한 번에 여러 개의 변수를 선언한다면 다음과 같이도 할 수 있습니다.

public class Main {
    public static void main(String[] args) {
        // 한 번에 여러 개의 정수형 타입 변수를 선언
        int intVal1, intVal2, intVal3;
    }
}

 

 

초기화 하는 방법은 대입 연산자인 등호를 사용합니다.

프로그래밍에서 등호는 우항의 값을 좌항의 변수에 할당 한다는 의미로 쓰입니다.

이름도 대입 연산자라고 부릅니다.

 

동등하다는 것을 나타내고 싶을 때는 등호를 두 번 사용합니다. (==)

 

초기화 한다는 것은, 선언한 변수에 실제 값을 넣는다는 것을 의미합니다.

위에서 선언한 변수에 값을 초기화 해봅시다.

 

public class Main {
    public static void main(String[] args) {
        // 1. 선언과 동시에 초기화
        int intVal1 = 10;
        
        // 2. 선언한 다음 초기화
        int intVal2;
        intVal2 = 20;
    }
}

 

 

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

변수의 스코프는 그 변수에 접근할 수 있는 범위라고 생각하는게 무난할 거 같습니다.

자바 언어는 블록 스코프를 사용합니다. 

 

public class Devheyo {
    // 여기 선언된 변수는 Devheyo{} 블록 내에서 접근이 가능합니다.
    static int myBlock = 10;
    
    public static void main(String[] args) {
        
        System.out.println("result : " + myBlock );
    }
}

3라인에 선언한 myBlock 변수는 1라인의 Devheyo {} 블록 내에서 접근 가능하기 때문에

이 코드를 실행하면 다음과 같은 결과를 볼 수 있습니다.

- result : 10

 

다음과 같은 경우는 어떤 결과가 나올까요?

public class Devheyo {
    // 여기 선언된 변수는 Devheyo{} 블록 내에서 접근이 가능합니다.
    static int myBlock = 10;
    
    public static void main(String[] args) {
        int myBlock = 20;
        System.out.println("result : " + myBlock );
    }
}

예상했겠지만 20을 출력하는 것을 확인할 수 있습니다. 

- result : 20

 

7라인에서 myBlock을 사용할 때, 이 값을 자신과 가까운 블록 스코프에서 찾고

없을 경우 상위 블록 스코프에 존재하는지 찾아봅니다.

 

래퍼런스 타입의 변수의 라이프 타임음 쓰레기 수집기(GC : Garbage Collector) 와 관련이 있습니다.

이 GC는 가비지 컬렉션 힙 영역에 존재하는 참조 타입 변수의 객체에 대해 동작합니다.

힙 영역에 메모리가 부족할 경우 GC가 이 영역을 스캔하고, 아무곳에서도 사용하지 않는 즉, 참조 되지 않고 있는 객체를 제거해 버립니다.

 

예를 들면 다음과 같은 경우입니다.

public class Devheyo {
    
    public static void main(String[] args) {
        MyTest mt = new MyTest();
        mt = null;
    }
}

class MyTest {}

4라인에서 MyTest 클래스의 객체를 생성해서 mt 변수에 할당 했습니다.

여기까지 하면, 런타임 스택 영역에 mt 변수가 생성되고, 그 값은 가비지 컬레션 힙 영역에 생성 된 new MyTest()로 만들어진 객체가 저장된 주소 값을 가지고 있습니다.

 

이 때 런타임 스택 영역의 mt 변수의 값인 주소 값에 null을 할당하면, new MyTest()로 만든 이 객체는 더 이상 아무도 참조하지 않게 됩니다.

이런 객체가 GC의 대상이 됩니다.

 

마지막으로 런타임 스택 영역에 생성된 변수의 라이프 타임은 블록 스코프에 의존적입니다.

즉, 블록 내에서 선언된 변수는 블록이 종료될 때 런타임 스택 영역에서 함께 소멸됩니다.

 

 

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

타입이란 데이터 타입을 줄인 말이고, 다른 말로 자료형이라고 합니다.

특정 데이터 타입으로 표현된 리터럴은 다른 데이터 타입으로 변환할 수 있습니다.

예를 들어 int 타입 변수에 담긴 값을 long 타입 변수에 담을 수 있습니다.

 

public class Devheyo {
    
    public static void main(String[] args) {
        int v1 = 100;
        long v2 = v1;
        
        System.out.println("v1 : " + v1);
        System.out.println("v2 : " + v2);
    }
}

실행 결과 모두 100의 값을 출력 합니다.

 

이렇게 변환 될 떄 크게 2가지 경우를 생각해 볼 수 있습니다.

 

1. 자신의 표현 범위를 모두 포함한 데이터 타입으로의 변환 (타입 프로모션)

2. 자신의 표현 범위를 모두 포함하지 못한 데이터 타입으로 변환 (타입 캐스팅)

 

조금 복잡해 보이지만 이렇게 설명을 남기는 이유가 있습니다.

간혹 표현 범위의 크기를 가지고 분류하는 사람들이 있었기 떄문입니다.

 

예를 들어 실수형 데이터 타입인 flot의 경우 메모리 크기가 4byte 이고,

정수형 데이터 타입인 long의 경우 메모리 크기가 8byte입니다.

만약 표현 범위의 크기만 가지고 본다면 float 데이터 타입의 값을 long 타입으로 변환한다고 가정합시다.

4byte 메모리 크기를 갖는 값을 8 byte 메모리 크기의 데이터 타입으로 변환하기 때문에 타입 프로모션이라고 생각하는 사람이 있었기 때문입니다.

 

타입 프로모션과 타입 캐스팅을 굽누하기 위해서는 메모리 크기가 아닌 데이터 표현 범위를 비교해봐야 합니다.

 

지금 예시를 생각해보면, 실수를 표현하는 float 데이터 타입의 값을 정수로 표현하는 long 데이터 타입 값으로 변환을 시도한다면, long 데이터 타입은 실수를 표현할 수 없기 떄문에 원본 데이터에 손실이 발생 할 수 있습니다.

 

이렇게 원본 데이터가 담긴 데이터 타입의 표현 범위를 변환 할 데이터 타입의 표현 범위가 모두 수용하지 못할 경우 데이터 손실이 발생할 수 있는데, 이것을 타입 캐스팅이라고 합니다.

반대로 모두 수용할 수 있다면 타입 프로모션이라고 합니다.

 

예를 보겠습니다.

 

위와 같이 타입 캐스팅이 발생할 경우 컴파일 타임에 오류를 발생하빈다.

메시지는 다음과 같습니다.

 

- Type mismatch: cannot convert from float to long

그리고 어떻게 수정해야할지도 추천해줍니다.

public class Devheyo {
    public static void main(String[] args) {
      float floatV1 = 1.23f;
      //long longV1 = floatV1;
      long longV1 = (long)floatV1;

      System.out.println("floatV1 : " + floatV1);
      System.out.println("longV1 : " + longV1);
    }
}

그럼 이코드를 실행 해보면

 

floatV1 : 1.23

longV1 : 1

 

소수점을 표현할 수 있는 실수 데이터 타입을 정수 데이터 타입으로 강제로 변환했기 때문에 원본 데이터가 완전히 변환되지 않았습니다.

물론 실수 데이터 타입에 담은 리터럴의 소숫점 아래 값이 없었다면 온전히 정수로 표현되었을 것입니다.

그렇기 때문에 타입 캐스팅을 할 경우 원본 데이터에 손실이 발생할 가능성이 있다고 합니다.(무조건 손실이 일어나는 것은 아닙니다.) 

 

타입 프로모션의 경우 타입 캐스팅과 같이 어떤 데이터 타입으로 변환해야 하는지 명시하지 않아도 됩니다.

 

다음 코드는 float -> long으로 타입 캐스팅 했던 것을 long -> float로 타입 프로모션을 하도록 고친 것입니다.

public class Devheyo {
    public static void main(String[] args) {
      long longV1 = 123L;
      float floatV1 = longV1;

      System.out.println("longV1 : " + longV1);
      System.out.println("floatV1 : " + floatV1);
    }
}

longV1 : 123

floatV1 : 123.0

 

데이터 타입을 변환할 경우를 자주 볼 수 있는데, 타입 캐스팅을 할 경우 앞서 말했지만 원본 데이터가 손실 될 수 있기 때문에 조심히 다뤄야 합니다.

 

 

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

1차, 2차 배열을 선언하기에 앞서 배열이 무엇인지 먼저 알아야 합니다.

배열은 동일한 자료형을 정해진 수 만큼 저장하는 순서를 가진 레퍼런스 타입 자료형입니다.

 

다음은 배열을 설명할 떄 자주 등장하는 상황입니다.

숫자 수집을 좋아하던 A는 길을 가다 3개의 숫자를 발견하여 이를 컴퓨터에 저장하였습니다.

 

public class Devheyo {
    public static void main(String[] args) {
      int num1 = 10;
      int num2 = 20;
      int num3 = 30;

      System.out.println("1번째 수집한 수 : " + num1);
      System.out.println("2번째 수집한 수 : " + num2);
      System.out.println("3번째 수집한 수 : " + num3);
    }
}

1번째 수집한 수 : 10

2번째 수집한 수 : 20

3번째 수집한 수 : 30

 

다음날 길에서 새로운 2개의 숫자를 발견해서 같은 방법으로 컴퓨터에 저장했습니다.

public class Devheyo {
    public static void main(String[] args) {
      int num1 = 10;
      int num2 = 20;
      int num3 = 30;
      int num4 = 40;
      int num5 = 50;

      System.out.println("1번째 수집한 수 : " + num1);
      System.out.println("2번째 수집한 수 : " + num2);
      System.out.println("3번째 수집한 수 : " + num3);
      System.out.println("4번째 수집한 수 : " + num4);
      System.out.println("5번째 수집한 수 : " + num5);
    }
}

1번째 수집한 수 : 10

2번째 수집한 수 : 20

3번째 수집한 수 : 30

4번째 수집한 수 : 40

5번째 수집한 수 : 50

 

이렇게 매일 평화롭게 숫자를 수집하고 있었는데, 어느 날 100개의 숫자를 한번에 발견하게 되었습니다.

수집한 숫자가 많아질 수록 int num{숫자}를 계속 붙여넣기 하고 출력을 할 때도 매번 똑같은 행동을 

반복하는 자신을 발견했습니다.

 

복사 붙여 넣기를 잘못 한 탓인지 중복된 변수명을 사용하거나 같은 숫자를 두 번 이상 출력하는 등 

엉망진창이 되었고 자바 공부를 하여 배열을 알게 되었습니다.

 

다음은 배열을 사용해서 숫자를 숮비하고 출력하는 예제입니다.

public class Devheyo {
    public static void main(String[] args) {
      int[] collectNum = new int[5];

      collectNum[0] = 10;
      collectNum[1] = 20;
      collectNum[2] = 30;
      collectNum[3] = 40;
      collectNum[4] = 50;

      for (int i = 0; i < 5; i++) {
        System.out.println((i + 1) + " 번째 수집한 수 : " + collectNum[i]);
      }
    }
}

 

위의 예제처럼 하면 더 이상 변수 이름을 계속 지을 필요도 없고, 내용을 출력하는 것도 보다 간결하게 구현할 수 있게 되었습니다.

 

여기서 눈 여겨 볼 것은

1. 동일한 데이터 타입을 하나의 배열로 관리할 수 있다는 것과

2. 배열은 순서를 가지고 있는데, 1부터 시작하지 않고 0부터 시작한다는 것입니다.(간혹 zero base 라고도 합니다.)

 

다음으로 배열을 선언하는 방법에 대해 알아봅시다.

배열 타입은 대괄호 [] 를 사용하고, 크게 2가지 방법으로 선언할 수 있습니다.

 

public class Devheyo {
    public static void main(String[] args) {
      int[] type1;
      int type2[];
    }
}

 

이렇게 선언한 배열 변수에 값을 할당하는 방법은 다음과 같습니다.

public class Devheyo {
    public static void main(String[] args) {
      int[] type1 = new int[5];
      int[] type2 = {10, 20, 30, 40, 50};
      int[] type3 = new int[]{10, 20, 30, 40, 50};
    }
}

직접 new 연산자를 사용해서 배열 객체를 생성하는 방법과 

어떤 값을 할당할지 정해진 경우 중괄호를 사용해서 간단하게 배열 객체를 만드는 방법이 있습니다.

 

4라인의 배열 객체 생성 및 할당 방법은 변수 선언과 동시에 할당할 경우에만 사용할 수 있는 방법입니다.

 

즉, 다음과 같은 방법은 컴파일 요류가 발생합니다.

 

선언한 배열 변수는 JVM의 런타임 스택 영역에 생성 됩니다.

그리고 배열은 레퍼런스 타입이기 때문에 값은 가비지 컬렉션 힙 영역에 객체가 생성됩니다.

힙 영역의 주소 값이 런타임 스택 영역에 생성된 변수의 값으로 할당됩니다.

 

즉, 다음과 같습니다

public class Devheyo {
    public static void main(String[] args) {
      int[] type1 = new int[5];
    }
}

 

100번지는 예시로 쓴 주소 값으로 실제와는 많이 다릅니다.

 

배열의 [0], [1], ... [n]을 각각 배열의 요소 또는 원소라고 부릅니다.

각 원소는 배열의 타입 크기를 갖습니다.

길이 5인 배열을 생성하면 다음과 같습니다.

가바지 컬렉션 힙영역에 생성되는 데이터는 각 타입의 기본 값으로 초기화 됩니다.

(** 각 원소의 크기가 4byte인 것은 예시를 int 데이터 타입으로 만들었기 때문이니다.)

 

2차원 배열 부터는 다차원 배열에 속합니다.

 

2차원 배열의 선언은 다음과 같이 할 수 있습니다.

public class Devheyo {
    public static void main(String[] args) {
      int[][] type1;
      int type2[][];
    }
}

 

여기서 대괄호를 한번 더 써주면 3차원 배열이 됩니다.

값을 할당하는 방법은 다음과 같이 할 수 있습니다.

 

public class Devheyo {
    public static void main(String[] args) {
      int[][] type1 = new int[2][3];
      int[][] type2 = {{1, 2}, {3, 4, 5}};
      int[][] type3 = new int[][]{{1, 2}, {3, 4, 5}};
    }
}

위의 예제 코드의 3라인 new int[2][3] 배열을 기준으로 2치원 배열은 메모리상에 다음과 같이 생성됩니다.

 

코드 4라인과 5라인의 경우는 위 그림과는 조금 다릅니다.

이 것을 행렬로 표현한다면 다음과 같습니다.

1 2  
3 4 5

테이블 표현상 첫 번째 행의 2 오른쪽에 공간이 있는 것처럼 보이지만

예제 코드 기준으로 존재하지 않는 공간입니다.

 

이렇게 각 행에 대해 열의 길이가 다른 배열을 지그재그하다 해서 재기드 배열(jagged array) 이라 하기도 합니다.

 

 

  • 타입 추론, var

타입 추론(Type inference) 이란 값을 보고 컴파일러가 데이터 타입이 무엇인지 추론 한다는 것을 의미합니다.

javascript를 예로 들면, 모든 변수를 var, let, const 등을 사용해서 선언합니다.

자바에서처럼 int, long, boolean 등의 데이터 타입을 명시하지 않고 사용합니다.

 

타입 추론에 대해서 대표적으로 제네릭에서 볼 수 있습니다.

예를 들면 다음과 같습니다.

public class Devheyo {
    public static void main(String[] args) {
      HashMap<String, Integer> myHashMap = new HashMap<>();
    }
}

3 라인에서 myHashMap에 HashMap 객체를 할당할 때, new HashMap<String, Integer>()를 사용하지 않고, new HashMap<>()을 사용했습니다.

이것은 myHashMap 변수에 담길 데이터 타입이 HashMap<String, Integer> 라는 것을 myHashMap 변수의 데이터 타입을 바탕으로 추론해낼 수 있기 때문입니다.

 

var을 사용할 경우 제약 사항이 몇가지 존재합니다.

1. 로컬 변수이면서

2. 선언과 동시에 값이 할당 되어야 한다는 것입니다.

 

로컬 변수로 선언하지 않아서 발생한 오류입니다.

 

선언과 동시에 값을 할당하지 않아서 발생한 오류입니다.

 

다른 언어에 비해 타입 추론 개념이 늦게 도입된 이유는 자바 개발진들이 매우 보수적이며, 하위 호환성을 매우 중요하게 생각하기 때문이라고 생각합니다.

'JAVA' 카테고리의 다른 글

HashMap 동작 방식에 대해 설명하세요.  (0) 2022.06.19
연산자  (0) 2021.06.20
JVM은 무엇이며 자바 코드는 어떻게 실행하는 것인가?  (0) 2021.04.18
람다식  (0) 2021.04.15
제네릭(Generic)  (0) 2021.02.27