티스토리 뷰


자바 데이터 타입

자바의 데이터 타입은 크게 프리미티브 타입레퍼런스 타입으로 나눌 수 있습니다.

 

프리미티브 타입 (Primitive Type)

자바의 기본 데이터 타입은 모두 8개 타입(자료형)이 있으며, 크게 논리형, 문자형, 정수형, 실수형으로 구분할 수 있습니다.

 

- 논리형: true, false 중 하나의 값을 가지며 조건식과 논리적 계산식에서 주로 사용됩니다. 

- 문자형: 하나의 변수에 하나의 문자를 저장하는데 사용됩니다.

- 정수형: 정수 값을 저장하는데 사용되며 주로 int 와 long 타입이 사용됩니다.

               (byte 는 이진 데이터를 다룰 때 사용되며, short 는 C 언어와의 호환을 위해 추가되었다고 합니다.)

- 실수형: 실수 값을 저장하는데 사용됩니다.

 

기본 자료형을 표로 살펴보면 다음과 같습니다. 정수형의 경우 int, 실수형의 경우 double 타입이 기본(default) 타입입니다. 문자형 char 의 기본값인 '\u0000'은 Unicode Character 'NULL'을 의미합니다.

종류 자료형 값의 범위 크기 기본값
논리형 boolean true, false 1byte(8bit) false
문자형 char 0 ~ 65,535 2byte(16bit) '\u0000' 
정수형
byte -128 ~ 127 1byte(8bit) 0
short -32,768 ~ 32,767 2byte(16bit) 0
int -2,147,483,648 ~ 2,147,483,647 4byte(32bit) 0
long -9,223,372,036,854,775,808 ~ 9,223,372,036,854,775,807 8byte(64bit) 0L
실수형
float (3.4 X 10-38) ~ (3.4 X 1038) 의 근사값 4byte(32bit) 0.0F
double (1.7 X 10-308) ~ (1.7 X 10308) 의 근사값 8byte(64bit) 0.0

 

레퍼런스 타입 (Reference Type)

레퍼런스 타입은 위에서 정의한 기본 타입들을 제외한 모든 타입입니다. 배열(Array), 열거형(Enumeration), 클래스(Class), 인터페이스(Interface) 타입이 있습니다. 

 

기본 타입과 차이점을 살펴보면 다음과 같습니다.

  • 기본값
    • 기본 타입의 경우 기본값이 존재하기 때문에 Null 값을 저장할 수 없습니다. Null 값을 기본 타입에 저장하고자 하는 경우 래퍼 클래스를 사용할 수 있습니다. 
    • 레퍼런스 타입의 경우 기본값으로 빈 객체를 의미하는 Null 값이 저장됩니다.
  • 저장 위치
    • 기본 타입의 경우 스택(Stack) 메모리에 실제 값이 저장됩니다.
    • 레퍼런스 타입의 경우 힙(Heap) 메모리에 실제 객체가 저장되고 이 위치를 가리키는 주소값이 스택 영역에 저장됩니다. 객체의 주소값은 4byte 입니다.
  • 에러
    • 기본 타입의 경우 컴파일 시점에 담을 수 있는 크기를 벗어나게 되면 컴파일 에러가 발생합니다.
    • 레퍼런스 타입의 경우 문법상으로는 문제가 없지만 실행시켰을 때 에러가 발생할 수 있습니다. (런타임 에러) 예를 들어, 배열이나 객체를 Null 값으로 지정하고 이를 가지고 작업을 진행하는 경우 NullPointException이 발생하기 때문에 변수값을 할당해주어야 합니다.

리터럴

리터럴은 그 자체로 데이터인 것으로 변수에 넣는 변하지 않는 데이터를 의미합니다. 예를 들어 'A', "ABC", 123 등이 리터럴입니다. int a = 3; 이 있다면 a 는 변수이고 3이 리터럴입니다.

long 타입 리터럴에는 접미사 'L' (또는 소문자 'l')이 붙습니다. 접미사를 붙이지 않으면 int 타입으로 인식합니다. float 타입 리터럴은 접미사 'f', double 타입 리터럴은 접미사 'd'를 붙입니다. 상수형의 경우 접미사를 붙이지 않으면 double 타입으로 인식합니다. 접미사는 대소문자를 구분하지 않습니다.

 

리터럴은 보통 기본 타입의 데이터를 의미하지만 특정 클래스에 한에서 리터럴이 될 수 있습니다. 보통의 인스턴스들은 내부 데이터가 언제 변할지 알 수 없어 리터럴의 대상이 아니지만, 데이터가 변하지 않게 설계한 불변 클래스의 경우 리터럴이라 할 수 있습니다. 이런 리터럴을 '객체 리터럴'이라고 하며 자바에서는 String이 대표적입니다. 이런 불변 클래스는 값의 변경이 필요한 경우 별도로 새로운 객체를 생성하게 됩니다.

 

리터럴은 상수(Contant)와 많이 비교가 됩니다. 상수는 변하지 않는 변수를 의미합니다. 상수에 넣을 수 있는 값은 기본 데이터 타입뿐만 아니라 클래스나 구조체도 넣을 수 있습니다. 상수는 변수 선언 시 final 키워드를 붙여 정의합니다. 예를 들어, final int a = 3; 이 있다면 final 키워드를 붙인 a 가 상수입니다. 변하지 않는 변수인 상수에 클래스나 구조체도 넣을 수 있다고 하였는데 상수로 선언된 클래스 자체(클래스의 주소값)를 변경하는 것은 불가능하지만 클래스 내부의 데이터를 변경하는 것은 가능합니다.

 


변수

변수란, 프로그램에 의해 이름을 할당받은 데이터를 저장할 수 있는 메모리 공간을 말합니다. 위 예제에서 살펴본 것과 같이 int a = 3; Person person = new Person(); 에서 a와 person이 변수입니다.

 

변수는 다음과 같이 변수에 맞는 자료형과 변수 이름으로 선언합니다.

// 변수타입 변수명
int a;          // 정수형 int 타입 데이터를 저장할 수 있는 a라는 공간
Person person;  // Person 객체 타입 데이터를 저장할 수 있는 person이라는 공간

 

변수를 선언한 이후 변수를 사용할 수 있는데 그 전에 적절한 값을 지정하는 것을 초기화라고 하며 방법은 다음과 같습니다. 아래 두 방식은 완전히 동일합니다.

// 1. 선언과 동시에 초기화하기
int a = 3;

// 2. 선언한 다음 변수에 초기값 저장하기
int a;
a = 3;

 

선언한 변수를 사용할 수 있는 범위가 정해져있는데 이를 변수의 스코프라고 합니다. 자바에서는 변수가 선언된 블록{} 이 스코프가 됩니다.

public class Scope {
    // 전역 변수
    static int globalScope1 = 10; 
    int globalScope2 = 10;
    
    void function(int parameter) {
        int localScope = 10;  // 지역 변수
    }
}

 

globalScope 변수는 클래스의 속성으로 쓰인 변수로 클래스 전체 범위에서 사용가능합니다. 반면, localScope 변수는 function 이라는 메소드 안에서만 사용이 가능합니다. function 메소드에 파라미터로 주어진 patameter 변수 또한 function 메소드 안에서만 사용 가능한 변수입니다.

 

변수의 라이프 타임으로 나눠보면 다음과 같습니다.

  • 전역 변수: 클래스 내 모든 영역에서 사용할 수 있는 변수
    • 클래스 변수: 정적 변수라고 불리며, 프로그램이 실행될 때 클래스와 함께 메모리에 로드되는 변수로 프로그램 종료 시 삭제됩니다. static 이라는 키워드를 붙여서 사용하며 인스턴스를 생성하지 않고 '클래스명.변수명(Scope.globalScope1)'으로 접근 가능합니다.
    • 인스턴스 변수: 객체가 생성(new)될 때 각각의 인스턴스마다 변수의 복사본을 가질 수 있는 변수이며, 객체가 소멸되기 전까지 존재합니다. 객체를 생성한 뒤 '객체를저장한변수명.변수명(Scope scope = new Scope(); scope.globalScope2;)'으로 접근 가능합니다.
  • 지역변수: 메소드, 반복문, 생성자 등에서 선언된 변수로 해당 범위({})내에서만 사용이 가능합니다.

 

타입 변환

변수 또는 리터럴의 타입을 다른 타입으로 변환할 수 있습니다. 타입 변환에는 두 종류가 있습니다.

 

- 자동 타입 변환 (Type Promotion)

프로그램 실행 도중 자동으로 타입 변환이 일어나는 경우로 작은 크기의 타입이 큰 크기 타입에 저장될 때 발생합니다.

✔️타입별 크기(byte) 비교를 하면 다음과 같습니다. float의 경우 표현 범위가 더 크기 때문에 더 큰 타입이 됩니다.
    byte(1) < short(2), char(2) <int (4) < long(8) < float(4) < double(8)
// 큰 크기 타입 = 작은 크기 타입

byte byteVal = 10;
int intVal = byteVal;  // 자동 타입 변환으로 byte 타입 10이 int 타입 10이 됩니다.

double doubleVal = intVal; // int 타입 10이 double 타입 10.0이 됩니다.

char charVal = 'A';
int intVal = charVal;  // char 타입 A가 int 타입 65(유니코드)가 됩니다.

// 음수가 될 수 있는 byte, int 타입은 char 타입으로 변환이 불가합니다!!!
byte byteVal = 10;
char charVal1 = byteVal; // 컴파일 에러
char charVal2 = (char) byteVal; // 강제 타입 변환은 가능합니다.

 

- 강제 타입 변환 (Casting)

큰 크기 타입은 작은 크기 타입으로 자동 변환되지 않기 때문에 강제로 타입을 변경해줄 수 있습니다. int 타입 변수의 1byte를 잘라 byte 타입 변수에 저장할 수 있습니다. 이때 나머지 3byte는 버려지게 됩니다.

// 작은 크기 타입 = (작은 크기 타입) 큰 크기 타입

int intVal = 103029770;  // 00000110 00100100 00011100 00001010
byte byteVal = (byte) intVal;  // 앞에 3byte는 버려지고 00001010 즉, 10이 저장됩니다.

int intVal = 65;
char charVal = (char) intVal;  // int 타입 65가 char 타입 'A'(유니코드)로 저장됩니다.

double doubleVal = 3.14;
int intVal = (int) doubleVal;  // double 타입 3.14가 int 타입 3(소수점 아래 버림)으로 저장됩니다.

 

강제 타입 변환을 할 때는 값의 손실이 일어나지 않도록 확인할 필요가 있습니다. 이를 확인하기 위해 자바는 각 타입의 최대,최소값을 상수로 제공합니다.

기본 타입 최소값 상수 최대값 상수
byte Byte.MIN_VALUE Byte.MAX_VALUE
short Short.MIN_VALUE Short.MAX_VALUE
int Int.MIN_VALUE Int.MAX_VALUE
long Long.MIN_VALUE Long.MAX_VALUE
float Float.MIN_VALUE Float.MAX_VALUE
double Double.MIN_VALUE Double.MAX_VALUE

 

연산식에 서로 다른 타입의 피연산자들이 있는 경우 피연산자들 중 가장 크기가 큰 타입으로 자동 타입 변환된 후 연산을 수행하게 됩니다. 작은 타입으로 변환 후 연산을 수행하고자 한다면 큰 크기 타입의 피연산자를 강제 타입 변환한 뒤 연산을 수행하면 됩니다.

 

타입 추론

개발자가 명시적으로 타입을 지정하지 않아도 컴파일러가 알아서 이 변수의 타입을 대입된 리터럴을 통해 추론하는 것을 말합니다. Java 10 이상부터는 타입 추론을 위해 var를 제공합니다.

public void main(String[] args) {
    String str = "Hello";
    
    var varStr = "Hello";
    
    if (varStr instanceof String) {
        System.out.println("varStr 변수의 타입은 String 입니다.");  // 출력
    }
}

 

  • var는 초기화된 로컬 변수로만 선언이 가능합니다. 멤버 변수(전역 변수), 메소드의 파라미터, 리턴 타입 등으로는 사용이 불가능하며 초기화된 값을 통해 타입을 추론하기 때문에 반드시 초기화되어야 합니다.
  • var는 키워드가 아닙니다. 즉, 어떠한 타입도 아니고 클래스에서 사용할 수 있는 예약어도 아닙니다. 따라서 var라는 이름을 가진 변수를 만들 수 있습니다.
  • var는 런타임 오버헤드가 없습니다. 컴파일 시점에 var를 초기화된 값을 보고 추론해서 바이트코드에 명시적으로 타입을 지정하게 됩니다. 런타임 시에는 이미 타입이 결정된 상태이기 때문에 타입 변수를 읽을 때마다 타입을 알아내기 위한 연산을 수행하지 않는다는 것입니다. 그렇기 때문에 var로 선언된 변수는 중간에 타입이 변경되지 않습니다. 즉, var a = "hi"; 라고 선언한 경우 a = 3;으로 변경이 불가합니다. (자바스크립트는 가능)

 


배열

같은 타입의 여러 변수를 하나의 묶음으로 다루는 것을 배열이라고 합니다. 배열을 선언하고 초기화하는 방법은 다음과 같습니다.

// 타입[] 배열이름;
// 타입[] 배열이름 = new 타입[길이];
// 타입[] 배열이름 = {값, 값, ...};
// 타입[] 배열이름 = new 타입[]{값, 값, ...};

int[] numbers = new int[5]; // 크기가 5인 int 타입 데이터의 집합 (초기값은 int의 초기값인 0으로 저장되어 있습니다.)
int[] initNumbers = {1, 2, 3, 4, 5}; // 배열을 선언하면서 초기 값을 저장할 수 있습니다.

int[][] doubleNumbers = new int[3][2]; // 전체 배열의 크기가 3이고 내부 배열의 크기가 2인 이차원 배열입니다.
int[][] initDoubleNumbers = {{1, 2}, {4, 5}, {3, 6}}; // 배열을 선언하면서 초기 값을 저장할 수 있습니다.

 

배열의 경우 초기에 크기를 정해주어야 합니다. 배열을 선언할 때 크기를 넣어주지 않으면 컴파일 에러가 발생합니다. 배열 선언 시 지정한 크기는 고정으로 변경할 수 없습니다. 배열은 인덱스(index)를 통해 접근 가능하며 인덱스는 0부터 시작합니다.

 

배열도 객체이기 때문에 힙(Heap) 영역에 연속된 데이터 저장 공간이 생기고 이 곳에 실제 데이터의 주소값이 저장됩니다. 그리고 이 영역을 가리키는 주소값을 스택(Stack) 영역에 저장하고 이 공간의 이름이 배열의 변수명이 됩니다. 배열 객체도 멤버변수를 가지고 있고 length라는 변수에 배열의 길이가 저장됩니다.

 

배열의 복사

필요에 따라 배열을 복사해야 하는 경우가 생깁니다. 복사에는 얕은 복사와 깊은 복사가 있습니다. 일반적으로 프로그래밍에서 배열 복사를 사용할 때 의도하는 것은 깊은 복사일 것입니다.

 

- 얕은 복사: 객체의 주소값만을 복사하는 방식으로 원본 배열이나 복사된 배열에서 값을 변경할 경우 서로 간의 값이 같이 변경됩니다.

 

- 깊은 복사: 객체의 실제 값들을 새로운 객체로 복사하는 방식으로 원본 배열이나 복사된 배열에서 값을 변경하여도 서로 간의 값에는 영향을 미치지 않습니다.

 

깊은 복사의 경우 배열의 모든 인덱스를 for 문을 이용하여 일일이 복사하는 방법도 있지만 자바에서 이미 다양한 메소드를 제공하고 있습니다.

int[] a = {1, 2, 3, 4};

// 1. Object.clone()
int[] cloneA = a.clone();

// 2. Arrays.copyOf(원본 배열, 복사할 길이)
int[] copyOfA = Arrays.copyOf(a, a.length);

// 3. Arrays.copyOfRange(원본 배열, 복사 시작 인덱스(포함), 복사 종료 인덱스(미포함))
int[] copyOfRangeA = Arrays.copyOfRange(a, 1, 3);  // {2, 3} 만 복사

// 4. System.arraycopy(원본 배열, 복사 시작 인덱스, 대상 배열, 복사된 값을 저장할 시작 인덱스, 복사할 길이)
int[] arraycopyA = new int[a.length];
System.arraycopy(a, 0, b, 0, a.length);

 

일차원 배열의 경우 위 메소드들을 사용하여 쉽게 깊은 복사가 가능하지만 이차원 배열의 경우 위 메소드들을 사용하여도 깊은 복사가 되지 않습니다. int[][] a 가 선언 및 초기화 되었다고 하면 a[x]에 들어있는 주소값은 깊은 복사가 되지만 a[x][y]가 가리키는 실제 값의 주소값은 깊은 복사가 일어나지 않기 때문입니다.

 

따라서 이차원 배열을 깊은 복사할 때는 이중 for 문을 사용하여 일일이 값을 복사해주거나 for 문을 돌면서 System.arraycopy(); 를 통해 이차원 배열을 복사할 수 있습니다.

int[][] a = {{1, 2}, {3, 4}, {5, 6}}

// 1. 이중 for문 사용하기
int[][] b = new int[a.length][a[0].length];
for (int i = 0; i < a.length; a++) {
    for (int j = 0; j < a[i].length; j++) {
        b[i][j] = a[i][j];
    }
}

// 2. System.arraycopy() 사용하기
int[][] c = new int[a.length][a[0].length];
for (int i = 0; i < a.length; i++) {
    System.arraycopy(a[i], 0, b[i], 0, a[i].length);
}

 


참조

자바의 데이터 타입: tychejin.tistory.com/230

상수와 리터럴: mommoo.tistory.com/14

변수: junghn.tistory.com/entry/JAVA%EC%9E%90%EB%B0%94-%EB%B3%80%EC%88%98%EB%9E%80-%EB%AC%B4%EC%97%87%EC%9D%B8%EA%B0%80

타입 추론: catch-me-java.tistory.com/19

배열: velog.io/@ednadev/%EC%9E%90%EB%B0%94-%EB%B0%B0%EC%97%B4-Array

배열 복사: coding-factory.tistory.com/548

'JAVA' 카테고리의 다른 글

[JAVA] 리스트 (List)  (0) 2021.04.15
[Java Study 04] 제어문  (0) 2021.04.14
[Java Study 03] 연산자  (0) 2021.04.13
[JAVA Study 01] JVM은 무엇이며 자바 코드는 어떻게 실행하는 것인가.  (0) 2021.04.06
[JAVA ] 맵 (Map)  (0) 2021.04.04
댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/05   »
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31
글 보관함