Primitive type은 기본형 타입이라 부르며 총 8가지를 미리 정의하여 제공한다. 여기서 중요한 점은 기본형 타입은 기본값이 있기 때문에 Null이 존재하지 않는다. 만약 Null을 넣고 싶다면 Wrapper Class를 활용하면 된다.
리터럴이란 번역하자면 "말 그대로"이고 소스 코드에서 "프로그래머가 직접 표현한 값"을 의미한다. 리터럴의 종류로는 정수,실수,문자,논리,문자열, null 이 있다.
10진주, 8진수, 16진수, 2진수 리터럴이 존재하며 int형으로 컴파일된다.
int a = 15; // 10진수
int b = 015; // 8진수
int c = 0x15; // 16진수
int d = 0b0101; // 2진수
정수 리터럴에 long타입 리터럴도 존재하고 숫자 뒤에 L을 붙여 표시한다.
long e = 10L;
소수점 형태나 지수 형태로 표현한 값이고 double타입으로 컴파일된다.
double f = 0.1234;
double g = 1234E-4; //1234x10^(-4)
float은 숫자 뒤에 f를 명시해줘야 한다.
float h = 0.1234f;
주로 ' ' 부호를 활용하여 하나의 문자만을 표현한다.
char a = 'A';
char b = '우';
char c = \uae00; //유니코드값, \u 이후 4자리 16진수로, 2바이트
그리고 문자 리터럴 중에 특수문자도 포함된다.
boolean 타입 변수에 치환하거나 조건문에 이용한다.
boolean a = true; // true
boolean b = 10 > 0; // true
boolean c = 1; // true이지만, java에서는 정수값 활용 불가
" "로 문자열을 표현한다.
String java = "JAVA";
java +2021 = "JAVA2021";
여기서 중요한 점은 문자열 객체는 문자열 리터럴을 기준으로 주소값이 할당되는 것이 기본이다. 예를 들어, 아래와 같이 각 문자열을 생성했다고 하면
String cpp = "JAVA";
String python = new String("JAVA");
java와 cpp는 JAVA라는 동일한 문자열 리터럴을 가지고 있기 때문에 같은 객체를 참조하여 주소값이 동일하다. 하지만 python은 new 연산자에 의해 문자열 리터럴은 동일해도 아예 다른 객체를 참조한다.
null 리터럴은 기본형 타입에서는 사용할 수 없지만 레퍼런스 타입에 대입해서 사용한다.
String java = null;
java = "JAVA";
- 선언: 변수의 타입을 지정 후 값을 저장할 메모리 공간에 이름을 부여
- 초기화: 선언된 변수에 대입(=) 연산자로 메모리 공간에 값을 저장
변수를 선언하면 메모리의 빈 공간에 변수 타입에 알맞은 크기의 저장 공간이 확보되고 이 저장공간은 '이름'을 통해 사용하게 된다. 그리고 변수 초기화가 꼭 필요한 이유는, 맨 처음 메모리를 할당 받았을 때 메모리는 공유자원이기 때문에 쓰레기값이 들어 있을 수 있어 꼭 초기화를 한 후 변수를 사용해야 한다.
변수의 스코프는 프로그램상에서 사용되는 변수들이 사용 가능한 범위를 의미한다.
기본적으로 변수가 선언된 블록이 그 변수의 사용범위이다.
public class Scope{
int instance = 10; // 인스턴스 변수
public void test(int value){
int local = 10; // 지역(local) 변수
System.out.println(value);
}
위의 내용과 관련해서 주의할 부분은 static 변수이다.
public class Static{
int instance = 10;
static int staticInstance = 10; //클래스 변수
public void test(int value){
int local = 10;
}
public static void staticTest(int value){
System.out.println(staticInstance); //사용가능
System.out.println(instance); //사용불가
}
static 변수들(메소드 포함)은 클래스가 정보가 메모리에 로드되면 인스턴스(클래스의 객체)가 생성되지 않더라도 힙(Heap)영역에 저장된다. 그렇기 때문에 static이 아닌 블럭에서 static 변수는 사용할 수 없으므로 이를 주의해야 한다. 그리고 static 변수는 여러개의 인스턴스가 생성되도 하나만 존재한다. 그래서 인스턴스와 달리 클래스에 종속된다 하여 클래스 변수라 부르기도 한다.
자바에서 연산은 "2(int타입) + 3(int타입)"과 같이 동일한 데이터 타입에서 일어난다. 하지만, 프로그램을 만들다 보면 "2(byte타입) + 3(int타입)"과 같이 서로 다른 데이터 타입끼리 연산이 필요할 때가 있다. 이럴 때 변수의 데이터 타입을 변환하여 하나의 데이터 타입으로 바꿔 주는 것을 타입 변환이라고 한다. 그리고 자바에는 자동 타입 변환과 강제 타입 변환 두 가지의 타입 변환이 있다.
캐스팅 리스크 요약
- 값 손실: 큰 타입에서 작은 타입으로 변환
- 타입 범위 초과시 값 손실
- 값 손실: 실수 타입에서 정수 타입으로 변환
- 소수점 이하 삭제에 의한 값 손실
- 정밀도 손실: 정수 타입에서 실수 타입으로 변환
- int타입을 float타입으로 변환 시 근사치 계산에 의한 정밀도 손실
캐스팅은 강제 타입 변환을 의미하고 일반적으로 큰 크기 타입을 작은 크기 타입으로 변환하는 것을 의미한다. 그리고 강제 변환에는 값 손실에 대한 리스크가 있다.
int intValue = 103029880;
byte byteValue = (byte) intValue;
위와 같이 강제 변환을 진행하게 되면,
00000110 00100100 0001110000001010
타입 변환 시 원래 값이 보존되지 않은 채 값의 손실이 일어나게 된다.하지만,
int intValue = 10;
byte byteValue = (byte) intValue;
위와 같은 경우는 intValue 의 타입이 int로 byte타입보다 3byte 크지만 값자체가 1byte 만으로 표현 가능하기 때문에 값 손실이 일어나지는 않는다.
여기에 더해, 실수 타입(float,double)에서의 정수 타입(byte,short,int,long)으로의 변환 또한 강제로 해야 한다. 실수 타입은 정수 타입으로 변환시 소수점이하 부분은 버려지고, 정수 부분만 저장된다.
double doubleVale = 3.14;
int intValue = (int) doubleValue; //intValue는 정수 부분인 3만 저장
그래서 강제 타입 시 주의할 점은 강제 타입 변환을 하기 전에 값이 안전하게 보존될 수 있는지 검사하는 것이 좋다.
int i = 128;
if( i < Byte.MIN_VALUE || i>Byte.MAX_VALUE) {
pass
} else {
byte b = (byte) i;
pass;
}
특히나, 어떤 정수값과 실수값을 다른 타입으로 변환하고자 할 때는 변환될 타입의 최소값과 최대값을 벗어나는지 반드시 검사하고, 만약 벗어난다면 타입 변환을 하지 말아야 한다.
마지막으로, 정수 타입을 실수 타입으로 변환할 때 정밀도 손실을 피해야 한다. 정밀도 손실은 int 타입의 float 타입 변환에서 볼 수 있다.
int num1 = 123456780;
int num2 = 123456780;
float num3 = num2; // 정밀도 손실 발생
num2 = (int) num3;
여기서 float 은 아래와 같이 구성되어 있는데
float: 부호(1비트) + 지수(8비트) + 가수(23비트)
int값을 float 타입의 값으로 변환할 수 있으려면 가수 23비트로 표현 가능한 값이어야 한다. 하지만, 123456780은 23비트로 표현할 수 없기 때문에 근사치로 변환된다. 이로 인해 num3은 num2와 다른 값이 되어버린다. 이를 방지하기 위해선 int 타입을 실수 타입으로 변환할 때는 double 타입을 활용해야 한다.
double: 부호(1비트) + 지수(11비트) + 가수(52비트)
int의 32비트는 double의 가수 52비트보다 작아 어떠한 int 값이라도 안전하게 정밀도 손실 없이 double 타입으로 변환될 수 있다.
타입 프로모션 주의점 요약
- char 타입이 int 타입으로 프로모션 시 유니코드 값이 저장
- 음수가 저장될 수 있는 정수 타입은 char타입으로 프로모션 불가
- 정수 값을 실수로 변환할 때 .0이 붙어 실수로 표현
타입 프로모션은 자동 변환이고 프로그램 실행 도중에 작은 크기를 가지는 타입이 큰 크기를 가지는 타입으로의 변환을 의미한다. 이를 통해 연산을 할 때 자동으로 상대적으로 큰 데이터 타입으로 형변환 진행한다.
타입별 크기 순서(byte)는 아래와 같다.
byte(1) < short(2) < int(4) < long(8) < float(4) < double(8)
예외적으로 float의 표현 범위가 정수형보다 크기 때문에 더 큰 타입으로 들어간다. 또한 타입 변환시 값은 그대로 보존되며 char 타입이 int 타입으로 변환될때는 유니코드 값이 저장된다.
단, 여기서 주의할 점은 char 타입은 2byte이지만 음수가 저장될 수 있는 byte 타입은 char 타입으로 자동 타입 변환될 수 없다.
그리고 정수 값을 실수로 변환할 때 .0이 붙어 실수로 표현된다.
int intValue = 200;
double doubleValue = intValue; // 200 -> 200.0
배열은 참조 타입 중 하나로써 힙(heap)영역에 저장되는 메모리 공간의 나열이며 참조 타입 변수가 스택(stack)영역에 배열의 주소값을 가진 채 저장된다. 자세히 말하자면, 배열은 같은 타입의 데이터를 연속된 공간에 나열시키고, 각 데이터에 인덱스(index)를 부여해 놓은 자료구조이다.
배열을 선언하는 방법은 두 가지다.
타입[ ] 변수;
타입 변수[ ];
그리고 배열 변수는 참조 변수이기 때문에 참조할 배열 객체가 없으면 null값으로 초기화 가능하다
데이터타입[] 변수 = {값0, 값1, 값2, ... };
위와 같이 배열 항목에 저장될 값의 목록이 있다면 간단하게 배열 객체를 만들 수 있다. 그리고 배열 변수를 미리 선언한 후, 값 목록들이 나중에 결정되는 상황에는 new 연산자를 통해 값 목록을 지정해주면 된다.
타입[] 변수 = null;
변수 = new 타입[]{값0, 값1, 값2, ... };
현재 값의 목록을 가지고 있지 않지만, 향후 값들을 저장할 배열을 미리 만들고 싶다면 new 연산자를 활용하면 된다.
타입[] 변수 = new 타입[길이];
new 연산자는 이미 배열이 선언된 후에도 사용할 수 있다.
타입[] 변수 = null;
변수 = new 타입[길이];
여기서 주목해야 하는 것은 "길이" 정보이다.
자바는 "길이" 정보를 활용해 해당되는 '타입의 크기x길이' 만큼의 메모리 공간을 확보한다. 그리고 new 연산자로 배열을 처음 생성할 경우 값 목록이 지정되어 있지 않지만 배열은 자동적으로 기본값으로 초기화된다.
앞서 말한 값 목록으로 구성된 배열은 1차원 배열이라 불린다. 이와는 달리 값들이 행과 열로 구성된 배열을 2차원 배열이라고 한다.
이 코드는 메모리에 3개의 배열 객체를 생성한다.
여기서 알 수 있는 점은, 2차원 배열을 선언한 순서대로 열의 정보를 가지고 있는 배열과 각 열에 해당하는 행의 정보를 가진 배열들이 생성된다.
데이터 타입을 소스코드에 명시하지 않아도, 컴파일 단계에서 컴파일러가 타입을 유추해 정해주는 것이 타입 추론이다. 자바 5 부터 추가된 Generic이나 자바 8 에서 추가된 Lambda에서 타입추론이 사용된다. 그리고 자바 10에서는 var이라는 local variable type-infernce가 추가되었다.
var a = "hello"; // String a = "hello";
var b = 10; // int b = 10;