[JAVA] 자바 데이터 타입, 변수 그리고 배열

SungBum Park·2021년 3월 7일
0

JAVA

목록 보기
2/9
post-thumbnail

자바의 변수는 크게 primitive type과 reference type으로 구분된다. 자바는 기본적으로 컴파일 시간에 타입이 결정되는 정적 언어(자바 버전이 올라가면서 동적 변수도 지원함)이므로, 변수를 사용하기 이전에는 반드시 해당 변수는 선언(declaration)이 되어 있어야 한다.

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


Primitive Type

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


Primitive Type

프리미티브 타입은 기본자료형 또는 원시자료형이라고도 불리며, 선언 시 메모리 주소 위치에 실제 값이 저장되는 타입이다. 이는 JVM 런타임 데이터 영역 중 stack 영역에 저장된다.

예를 들어, 아래와 같이 int형 변수를 선언해보자.

int num = 100;

위 변수의 메모리 상태는 다음과 같을 것이다.

위 그림은 stack 영역을 매우 단순화한 그림이다. num 변수는 1000번지 주소 위치에 100이라는 값을 저장하고 있다.

Reference Type

레퍼런스 타입은 프리미티브 타입을 제외한 모든 타입이 속한다. 대표적으로 클래스, 인터페이스, 배열, 열거 등이 레퍼런스 타입이다. 이는 프리미티브 타입과 달리 선언 시 메모리 주소 위치에 실제 값을 가리키는 또 다른 주소를 저장한다. 레퍼런스 타입의 실제 값이 존재하는 위치는 heap 영역이다.

예를 들어, Person이라는 객체를 선언해보자.

public class person {
	private int age;
	private String name;

	public Person(int age, String name) {
		this.age = age;
		this.name = name;
	}
}
public static void main(String[] args) {
	Persion person = new Person(25, "홍길동");
}

위 그림은 메모리 상태를 매우 단순화한 모습이다.(String 객체 정확히는 위처럼 동작하지 않는다.) 레퍼런스 타입은 선언한 변수의 위치에는 실제 값이 존재하는 heap 영역의 메모리 주소를 가리키고 있다.

만약 레퍼런스 타입이 선언 후 초기화를 하지 않는다면 기본값으로 NULL이 삽입된다.

자바의 레퍼런스 타입이 C/C++와 다른 점은 자바에서는 주소값을 저장하는 자료형을 제공하지 않는다는 것이다. C/C++은 * , & 와 같이 주소값 자체를 저장(*)하고, 변수의 주소값을 추출(&)하는 키워드를 제공한다. 반면에 자바는 이러한 주소값을 다루는 것을 추상화하여 개발자가 다루지 못하도록 하였다. 이로 인해 자바는 call by reference가 불가능하고 call by value만 가능하다.

리터럴(Literal)


리터럴은 고정된 값을 나타내는 코드상의 표현으로, 단순히 값 그 자체를 말한다. 예를 들어, int a = 10 이라는 자바 코드가 있다면 10이 리터럴이다. 따라서 리터럴은 자료형마다 다르게 표현된다.

정수 리터럴

정수형 리터럴은 int, byte, short, long과 같은 정수형 자료형을 초기화할 때 사용할 수 있다. 또한 10진법뿐만 아니라 2진법, 16진법 등으로 표현할 수도 있다.

int decimal =      83;	       // 10진법
int ocatal =       037;        // 숫자 앞에 0을 붙이면 8진법 
int heaxaDecimal = 0x1abc;     // 숫자 앞에 0x을 붙이면 16진법 
int binary =       0b1101011;  // 숫자 앞에 0b을 붙이면 2진법

정수 리터럴은 기본적으로 int형으로 타입이며, 뒤에 l 또는 L을 붙이거나 int형 타입 범위를 넘어서면 long 타입으로 취급된다.

Java 7 버전부터 큰 수를 표현하기 쉽도록 숫자 사이에 밑줄(_)을 사용할 수 있다. 이 밑줄은 숫자 사이에만 있을 수 있고, 연속으로 입력이 가능하다.

long phoneNumber = 010_1234_5678;
long billion = 1_000_000_000;

부동 소수점 리터럴

부동 소수점 리터럴은 float, double과 같은 소수형 자료형을 초기화할 때 사용한다.

float f = 1.234f
double d = 1.234
double d = 1.234d

문자 및 문자열 리터럴

문자 리터럴은 char이고, 문자열 리터럴은 String 자료형을 기본적으로 사용한다. char은 작은 따옴표(')를 사용하고, String은 큰 따옴표(")를 사용한다. 그리고 따옴표 내부는 유니코드값을 삽입할 수 있다.

char c = 'a';
String s = "abc";

또한, 문자 또는 문자열 리터럴은 유니코드 이외에 특수문자 리터럴을 사용할 수 있다.

  • '\b': 백스페이스
  • '\t': 탭
  • '\n': 개행문자
  • '\f': 폼 피드
  • '\r': 캐리지 리턴 문자
  • '\"': 큰 따옴표 문자
  • '\'': 작은 따옴표 문자
  • '\': 백 슬래시

불린 리터럴

boolean 자료형을 초기화할 때 사용한다.

boolean t = true;
boolean f = false;

그 외 리터럴

  • null

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


변수 선언(Declaration)

변수를 선언하는 것은 저장 공간을 확보한다는 의미이다. 자바는 변수를 사용하려면 반드시 사용 이전에 선언되어 있어야 한다.

int n;
char c;
boolean b;
String s;

위처럼 프리미티브 타입 또는 레퍼런스 타입을 선언하면 해당하는 메모리 영역에 저장 공간을 확보한다.

변수 초기화(Initialization)

변수를 선언하는 것은 저장 공간을 확보하는 것이고, 변수를 초기화하는 것은 확보된 저장 공간에 초기화된 값을 삽입한다.

만약 초기화하지 않고 선언만 한 변수를 사용하려고 하면, 아래와 같은 컴파일 에러가 발생한다.

Error:(...) java: variable a might not have been initialized

변수는 선언과 동시에 초기화도 가능하다.

int n = 100;

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


변수는 선언하는 위치에 따라 스코프와 라이프타임이 달라진다. 선언 위치를 구분하는 기본적인 방법은 어느 중괄호({}) 블록 내부에 선언되었는지에 따라 다르다.

  • 스코프: 변수가 유효한 범위(변수를 사용할 수 있는 범위)
    • 자바는 접근 제어자(access modifier)에 따라 스코프가 달라진다.
      • private: 클래스 내부
      • public: 클래스 내부 + 외부, 다른 패키지 등 어디에서나 접근 가능
      • default: 패키지 내부(접근 제어자를 선언하지 않으면 default)
      • protected: 클래스 내부, 자식 클래스(다른 패키지의 자식 클래스도 가능)
  • 라이프타임: 변수가 메모리 영역에 저장되어있는 시간

변수 종류

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


타입 변환은 현재 타입을 다른 타입으로 변경하는 것을 말한다.

Primitive Type의 형변환

자동 형변환(확장)

자동 형변환은 두 데이터 타입이 자동으로 변환이 이루어지는 것을 말한다. 이를 위해서는 다음과 같은 조건이 필요하다.

  • 두 데이터 타입은 호환된다.
  • 범위가 작은 데이터 타입의 값을 더 큰 범위의 데이터 타입으로 할당하는 경우 동작한다.(바이트 크기가 아닌 범위에 주의할 것)

자바에서 대부분의 자동 형변환은 숫자 타입에서 일어나며, 자동 형변환이 가능한 순서는 byte → short → int → long → float → double 이다.

int intNum = 100;
long longNum = intNum;      // 자동 형변환
float floatNum = longNum;   // 자동 형변환

명시적 형변환(Casting, 축소)

명시적 형변환은 범위가 큰 타입의 값을 그보다 더 작은 타입으로 할당하기 위해서는 반드시 형변환을 명시해주어야 한다. 또한, 명시적 형변환은 호환되지 않는 타입에도 사용할 수 있다.

char c = 'a';
int num = 100;
ch = num          // X
ch = (char) num   // O

타입 프로모션(Promotion)

타입 프로모션은 캐스팅과 반대로 범위가 작은 타입의 값을 그보다 더 큰 타입에 할당하는 것을 의미한다. 따라서 데이터 손실이 일어나지 않으므로, 형을 따로 명시할 필요없이 자동으로 형변환이 일어난다.

short shortNum = 100;
int intNum = shortNum    // O

오토박싱(Autoboxing)/언박싱(Unboxing)

자바는 primitive type에 대한 Wrapper 클래스를 제공한다. 그 종류는 다음과 같다.

  • int ↔Integer
  • shot ↔ Short
  • ...

Java 5 버전 이후로부터는 오토박싱과 언박싱 기능이 생겨 primitive type과 Wrapper 클래스 사이는 명시적으로 형변환할 필요가 없어졌다.(컴파일러가 대신 함.)

  • 오토박싱: primitive type → Wrapper 클래스
  • 언박싱: Wrapper 클래스 → primitive type

Reference Type의 형변환

업캐스팅(Up-Casting)

부모-자식 관계의 객체 사이에서 자식 객체를 부모 객체로 형변환하는 것을 말한다. 이는 컴파일에서 자동으로 수행하므로 명시적으로 선언해줄 필요는 없다. 업캐스팅덕분에 다형성이 가능하다.

다운캐스팅(Down-Casting)

다운캐스팅은 업캐스팅과는 반대로 부모-자식 관계의 객체 사이에서 부모 객체를 자식 객체로 형변환하는 것을 말한다.

1차 및 2차 배열 선언하기


배열(Array)은 선형 자료구조로서, 고정된 연속하는 메모리 크기를 가지며 index를 사용하여 빠르게 데이터에 접근할 수 있다.

1차원 배열

int[] array;   // 가장 선호되는 형식
int []array;
int array[];

자바에서 1차원 배열 선언은 위 3가지 모두가 가능하지만, 대부분 첫 번째 방법을 사용한다.

1차원 배열은 선언만 해서는 사용할 수 없다. 왜냐하면 크기를 정하지 않았기 때문에 유효한 메모리에 접근을 할 수 없다.

int[] array = new int[10];

위는 총 10개의 int형 변수를 저장하는 1차원 배열 array 변수를 선언한 것이다. 하나의 int형 변수는 4byte이므로, 40byte의 크기를 가지고 있다. 위 코드에서 new 키워드에서도 알 수 있듯이 배열은 heap 영역에 저장된다.

1차원 배열은 데이터를 직접 명시하여 초기화할 수도 있다.

int[] array = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
int[] array = new int[] {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};

위는 명시된 요소의 수만큼 배열의 크기가 결정된다.

2차원 배열

2차원 배열 선언

int[][] array;   // 가장 선호되는 형식
int array[][];
int[] array[];

2차원 배열 초기화

int[][] array = new int[3][3];
int[][] array = new int[3][3] { {1, 2, 3}, {4, 5, 6}, {7, 8, 9} };
int[][] array = { {1, 2, 3}, {4, 5, 6}, {7, 8, 9} };

타입 추론, var


var 키워드는 Java 10 버전부터 추가된 기능으로, 지역 변수의 타입 추론이라고 한다. 이는 변수 선언을 할 때 사용되는 키워드로, 데이터 타입을 명시하지 않고 var 키워드를 명시하면 값을 보고 컴파일러가 자동으로 추론을 한다.

타입 추론은 Java 10 버전 이전부터 사용되어 왔다.

Java 5 버전부터 제네릭 메서드가 추가되었고, 초기화 타입을 다음과 같이 생략할 수 있었다.

List<String> s = Collections.emptyList();  // Collections.<String>emptyList()

Java 7 버전은 다이아몬드 오퍼레이터(<>)가 추가되었고, 이 역시 초기화 타입을 다음과 같이 생략할 수 있다.

Map<String, List<String>> m = new HashMap<>();  // new HashMap<String, List<String>>()

Java 8 버전은 람다 표현식이 추가되었고, 역시나 타입을 다음과 같이 생략했다.

Predicate<String> p = s -> s.length() > 10;  // (String s) -> s.length() > 10

이는 모두 왼쪽 타입이 오른쪽 타입이 같아야 한다는 것이 매우 당연하므로 추론을 할 수 있었다. var 키워드는 반대로 왼쪽 데이터 값을 보고 왼쪽의 자료형 타입을 추론할 수 있으므로 이를 사용할 수 있다.

var s = "String";
var n = 100;

var 키워드를 사용할 때 주의할 점은 다음과 같다.

  • 지역 변수에서만 사용할 수 있다.
  • 선언과 함께 명시적인 초기화를 반드시 해야한다.
  • null로 초기화할 수 없다.
  • 람다 표현식과 같이 사용할 수 없다.

참고자료


profile
https://parker1609.github.io/ 블로그 이전

0개의 댓글