타입 | 할당되는 메모리 크기 | 기본값 | 데이터의 표현 범위 | |
---|---|---|---|---|
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.4 X 10-38) ~ (3.4 X 1038) 의 근사값 |
double(기본) | 실수형 | 8 byte | 0.0 | (1.7 X 10-308) ~ (1.7 X 10308) 의 근사값 |
char | 문자형 | 2 byte(유니코드) | '\u0000' | 0 ~ 65,535 |
타입 | 기본값 | 할당되는 메모리 크기 |
---|---|---|
배열(Array) | Null | 4 byte(객체의 주소값) |
열거(Enumeration) | Null | 4 byte(객체의 주소값) |
클래스(Class) | Null | 4 byte(객체의 주소값) |
인터페이스(Interface) | Null | 4 byte(객체의 주소값) |
리터럴은 변수에 넣는 변하지 않는 데이터 자체를 의미한다.
보통은 기본형의 데이터를 의미하지만, 특정 객체(Immutable class, VO class)에 한해서는 리터럴이 될 수 있다.
변수(Variable)는 데이터를 저장하는 메모리 영역을 의미한다.
변수는 수시로 값이 변경될 수 있으며 하나의 값만 저장할 수 있다.
상수(Constant)
변하지 않는 변수를 의미한다.
final 키워드로 선언하며, 대문자로 주로 표기한다.
자바의 변수는 다음과 같은 종류로 구분할 수 있다.
static
키워드 없이 선언된 필드. 이 필드는 인스턴스 별로 다른 값을 가질 수 있기 때문에 인스턴스 변수라고 불린다.static
키워드와 함께 선언된 필드. 이 필드는 모든 인스턴스들이 공유하는 값이다. 클래스 명으로 접근이 가능하고, 클래스 하나에 한 값이기 때문에 클래스 변수라고 불린다.인스턴스 변수와 클래스 변수는 멤버 변수라고 통칭하기도 한다. 멤버 변수는 꼭 초기화를 해주지 않더라도 기본값으로 초기화되지만, 로컬 변수는 반드시 초기화를 해주어야 한다.
class Variables {
int instanceVar; // 0으로 초기화되는 인스턴스 변수
static int classVar; // 0으로 초기화되는 클래스 변수
int initInstanceVar = 10; // 명시적으로 초기화
static int initClassVar = 10; // 명시적으로 초기화
void method(int num) { // 매개변수는 초기화 할 수 없고, 전달받는 값을 사용만 할 수 있음
int a; // 선언은 가능
// int b = a; 자동으로 초기화 되지 않으므로 동작하지 않음
a = 10; // 선언을 미리 해줬다면 이렇게 초기화 가능
int b = a; // 선언과 동시에 초기화
}
}
스코프는 변수가 유효한 범위를 의미한다.
라이프타임은 변수가 메모리에서 살아있는 기간을 의미한다.
변수 타입 | 스코프 | 라이프타임 |
---|---|---|
인스턴스변수 | 클래스 전체(static 블록과 static 메소드 제외) | 객체가 생성되고 객체가 메모리에 살아있는 동안 |
클래스변수 | 클래스 전체 | 클래스가 초기화되고 프로그램이 끝날 때까지 |
로컬변수 | 변수가 선언된 블록 내부 | 변수 선언 이후부터 블록을 벗어날 때까지 |
타입 변환은 어떤 값이나 변수의 타입을 다른 타입으로 변경하는 것이다.
이 때 타입은 확장(자동 형변환)과 축소(명시적 형변환)두 가지 방향으로 변환될 수 있다.
확장은 다음 조건에서 두 데이터 타입이 자동으로 변환되는 경우이다.
자바에서의 숫자 타입은 byte -> short -> int -> long -> float -> double
순으로 자동 형변환이 된다.
축소는 더 큰 범위의 타입의 값을 더 작은 범위의 타입에 할당하기 위해서 형변환을 명시해주어야 한다.
또, 확장과 달리 호환되지 않는 데이터 타입에도 사용할 수 있다.
(타겟 타입) 값or변수
로 강제로 형변환을 할 수 있다.
void 명시적_형변환() {
// 동작하지 않는 코드
char ch = 'c';
int num = 88;
ch = num;
// 동작하기 위해선 아래와 같이 수행해야 합니다.
ch = (char) num;
}
타입 프로모션은 식을 평가할 때 중간에 피연산자의 범위를 초과할 수 있기 때문에 자동으로 값이 승격되는 것을 의미한다.
byte
, short
, char
를 식 평가시 자동으로 int
로 프로모션 된다.long
, float
, double
인 경우 전체 표현식이 각각 long
, float
, double
로 프로모션된다.void 타입_프로모션() {
byte b = 42;
char c = 'a';
short s = 1024;
int i = 50000;
float f = 5.67f;
double d = .1234;
// 표현식(명시적 형변환 없이 자동으로 형변환 되며, 값은 피연산자의 타입으로 프로모션 됩니다.)
double result = (f * b) + (i / c) - (d * s);
}
int[] intArr1;
int []intArr2; // 권장하지 않음
int intArr3[]; // 권장하지 않음
셋 다 가능하지만, 가장 위의 스타일이 선호된다.
int[] intArr1 = new int[10];
위와 같이 선언하면, 해당 크기만큼의 배열의 요소가 초기화되어 생성된다.
배열의 각 요소에는 인덱스로 접근할 수 있다.
또한 다음과 같이 초기화할 수도 있다.
int[] anArray = {
100, 200, 300,
400, 500, 600,
700, 800, 900, 1000
};
int[] anArray = new int[] {100, 200, 300};
이런 경우 배열의 길이는 중괄호 사이에 선언된 요소의 수로 결정된다.
Java는 배열도 객체이기 때문에 힙 영역에 배열이 생성되며, new
라는 키워드를 써줘야 한다.
int[][] ints;
위와 같이 선언할 수 있다. 또한 Java에서의 다차원 배열은 요소 자체가 배열인 배열이다.
각 요소 배열간 길이는 다를 수 있다.
String[][] names = {
{ "Mr.", "Mrs.", "Ms." },
{ "Smith", "Jones" }
};
// 각 배열 요소의 길이는 초기화시에 지정을 해주지 않아도 됩니다. 단, null로 초기화됩니다.
String[][] names2 = new String[10][];
// 혹은 지정해줄 수도 있습니다. 이 경우 같은 크기를 가지게 됩니다. 이 경우 배열이 초기화 됩니다.
String[][] names3 = new String[10][10];
이차원 배열도 마찬가지로 요소의 인덱스로 접근할 수 있다.
+ 배열의 크기는 length
속성으로 알 수 있다. 그리고 배열은 한번 초기화하고 나면 변경할 수 없다.
var
키워드는 로컬 변수의 타입 추론이라고 한다.
이 키워드의 사용은 반복되는 타입 선언을 줄여주고, 코드의 가독성을 높여준다.
이는 컴파일러가 데이터의 타입을 추론해낼 수 있기 때문이다.
var num = 3;
3
은 int
타입을 반환하기 때문에 로컬 변수 num
은 int
타입임을 추론할 수 있다.
이렇게 이미 타입이 정의되었기 때문에 var
는 다른 타입의 재할당을 허용하지 않는다.
var
를 사용할 수 없는 경우var
만 단독으로 선언할 수 없다.var
변수는 null
로 초기화할 수 없다.var
는 명시적으로 타겟이 되는 타입을 알아야 하기 때문에 같이 사용할 수 없다.var
선언var list = new ArrayList<>();
이 코드는 컴파일되지만, 실제 list
의 타입은 ArrayList<Object>
로 컴파일되며, 제네릭의 이점을 얻지 못하기 때문에 피하는 것이 좋다.타입 추론은 가독성을 높일 수 있는 경우와 그것을 사용한 것이 유용한 경우에만 선택적으로 사용해야한다.
이 말의 의미는 편하게 코드를 작성하기 위해 var
를 쓰는 것이 아니라, 가독성을 높이는 목적으로 사용해야 한다는 의미이다.
var
의 유형을 개발자가 추적할 수 없거나 어려운 경우에는 var
를 사용하지 않는 것이 좋다.
제네릭은 클래스 내부에서 사용하는 데이터의 타입을 클래스의 인스턴스를 생성할 때 결정하는 것을 의미한다.
객체의 타입을 컴파일 시점에 체크하기 때문에 타입 안정성을 높이고 형 변환의 번거로움을 줄일 수 있다.
public class TestGeneric<T> {
public T sample;
public void showYourType() {
if (sample instanceof Integer) {
System.out.println("Integer 타입이군요!!");
} else if (sample instanceof String) {
System.out.println("String 타입이군요!!");
}
}
}
public class Main {
public static void main(String[] args) {
TestGeneric<String> stringType = new TestGeneric<String>();
TestGeneric<Integer> integerType = new TestGeneric<Integer>();
stringType.sample = "Hello";
integerType.sample = 1;
stringType.showYourType(); // 결과: String 타입이군요!!
integerType.showYourType(); // 결과: Integer 타입이군요!!
}
}
TestGeneric 클래스의 변수 sample은 T라는 타입을 가지는데 T는 존재하는 타입이 아니고, 인스턴스가 생성될 때 결정된다.
var는 리터럴 값을 파악해 컴파일시 타입을 추론하는 반면 제네릭은 개발자가 직접 지정해주는 점이 차이점이다.