변수란, 자바 애플리케이션이 실행되는 동안 값을 저장할 수 있는 메모리상의 공간이다.
variable 은 "vary" 와 "able" 의 합성어로 값은 변할 수 있다는 것을 의미한다.
- 변수에 저장된 값은 애플리케이션 런타임 중에 변경할 수 있다.
- 변수는 메모리상의 위치에 주어진 이름일 뿐이며, 해당 변수에 대한 모든 연산은 해당 메모리 위치에 영향을 미친다.
- 모든 변수들은 사용하기 전에 선언되어 있어야 한다.
변수에 저장할 수 있는 데이터 유형이다.
자바는 두 가지 유형의 데이터 타입이 존재한다.
타입 | 설명 | 기본값 | 크기 | 값의 범위 |
---|---|---|---|---|
boolean | true or false 값을 나타낸다. | false | VM 에 따라 다르다. | true, false |
char | 유니코드 문자 | \u0000 | 16 bits | \u0000(0) to \uffff(65535) |
byte | 2의 보수인 정수 | 0 | 8 bits | -128 to 127 |
short | 2의 보수인 정수 | 0 | 16 bits | -32,768 to 32,767 |
int | 2의 보수인 정수 | 0 | 32 bits | -2,147,483,648 to 2,147,483,647 |
long | 2의 보수인 정수 | 0 | 64 bits | -9,223,372,036,854,775,808 to 9,223,372,036,854,775,807 |
float | IEEE 754 부동소수점 | 0.0 | 32 bits | |
double | IEEE 754 부동소수점 | 0.0 | 64 bits |
JVM 메모리 안의 스택에 값이 저장된다.
C 나 C++ 같은 언어에서는 오직 ASCII 코드 문자만을 사용하여 1바이트로 충분히 표현이 가능하다.
반면에 자바에서는 ASCII 코드 기반이 아닌 유니코드 기반이어서 1바이트로는 안되기 때문에 2바이트를 차지한다.
유니코드는 세계 각 국의 언어를 통일된 방법으로 표현할 수 있게 제안된 국제적인 코드 규약이다.
Primitive Type 을 제외한 나머지가 여기에 속한다.
예를 들면, Class, Interface, Enumeration, Array 등등이 있다.
참조형의 실제 값(인스턴스)은 JVM 메모리 힙 영역에 저장되며 해당 주소를 스택에 저장한다.
참조형은 주소값을 저장하기 때문에 두 종류의 복사 개념이 존재한다.
shallow copy(얕은 복사)는 주소값을 복사하는 것으로 복사한 변수도 동일한 주소를 가리키기 때문에 어느 한쪽에서 값을 수정하면 나머지 한 쪽에서도 수정된 값을 가리키게 된다.
deep copy(깊은 복사)는 값이 동일한 인스턴스를 힙 영역에 복사하고 그 주소를 스택 영역에 저장하는 것으로 완전히 다른 변수가 되기 때문에 서로에게 영향을 주지 않는다.
리터럴은 실제로 저장되는 데이터 그 자체를 의미한다.
타입마다 표현할 수 있는 방법도 다양하다.
int binaryInt = 0b10;
int octalInt = 010;
int decimalInt = 10;
int hexadecimalInt = 0x10;
System.out.println("int 2진수 리터럴: " + binaryInt);
System.out.println("int 8진수 리터럴: " + octalInt);
System.out.println("int 10진수 리터럴: " + decimalInt);
System.out.println("int 16진수 리터럴: " + hexadecimalInt);
long longLiteral = 10L;
System.out.println("long 리터럴: " + longLiteral);
float floatLiteral = 1.23f;
System.out.println("float 리터럴: " + floatLiteral);
double doubleLiteral = 1.23;
double doubleLiteral2 = 1.23d;
double doubleLiteral3 = 123e-2d;
System.out.println("double 리터럴 1: " + doubleLiteral);
System.out.println("double 리터럴 2: " + doubleLiteral2);
System.out.println("double 리터럴 3: " + doubleLiteral3);
char charLiteral = 'A';
char charLiteral2 = '가';
char charLiteral3 = '\u1234';
System.out.println("char 리터럴 1: " + charLiteral);
System.out.println("char 리터럴 2: " + charLiteral2);
System.out.println("char 리터럴 3: " + charLiteral3);
boolean booleanLiteral = true;
boolean booleanLiteral2 = 0 < 1;
System.out.println("boolean 리터럴 1: " + booleanLiteral);
System.out.println("boolean 리터럴 2: " + booleanLiteral2);
변수를 초기화하는 방법에는 몇 가지가 존재한다.
- 명시적 초기화 (explicit initialization)
- 변수 선언과 동시에 초기화 하는 방법이다.
- 인스턴스 변수, 클래스 변수, 지역 변수 모두 사용 가능하며 가장 최우선적으로 고려되는 방법이다.
- 초기화 블럭 (initialization block)
- 인스턴스 변수, 클래스 변수에 대하여 사용 가능하다.
- 인스턴스 초기화 블럭과 클래스 초기화 블럭으로 나뉜다.
- 인스턴스 초기화 블럭 같은 경우는 모든 생성자가 공통으로 수행해야 하는 로직이 있을 경우 사용한다.
- 생성자 (constructor)
- 인스턴스 생성 시에 생성자 메서드 안에서 초기화 하는 방법이다.
아래 코드는 초기화 방법과 호출 순서를 확인하기 위해 작성되었다.
public class InitializationStudy {
/**
* 명시적 초기화 (explicit initialization)
* */
private int explicitVariable = 0;
private int instanceVariable;
private static int staticVariable;
/**
* Static Initialization Block
* */
static {
staticVariable = 10;
System.out.println("Static Initialization Block...");
}
/**
* Instance Initialization Block
* */
{
instanceVariable = 20;
System.out.println("Instance Initialization Block...");
}
public InitializationStudy() {
System.out.println("Constructor...");
}
public static void main(String[] args) {
new InitializationStudy();
}
}
아래와 같이 클래스 초기화 블럭 -> 인스턴스 초기화 블럭 -> 생성자 순으로 호출되는 것을 확인할 수 있다.
변수들은 사용 가능한 범위를 가진다.
그 범위를 변수의 스코프라고 한다.
스코프는 globalScope 와 localScope 로 나뉜다.
public class VariableScopeStudy {
public static void main(String[] args) {
VariableScopeStudy vss = new VariableScopeStudy();
vss.localBlock(30);
System.out.println("static variable: " + VariableScopeStudy.staticVariable);
}
int globalVariable = 10;
static int staticVariable = 40;
private void localBlock(int localParameter) {
int localVariable = 20;
System.out.println("global variable: " + globalVariable);
System.out.println("local variable: " + localVariable);
System.out.println("local parameter: " + localParameter);
}
}
위 코드를 실행하면 아래와 같은 결과를 얻을 수 있다.
클래스의 속성으로 선언된 변수 glovalVariable 의 스코프는 클래스 전역이다.
globalVariable 같은 변수는 인스턴스가 생성될 때 생성되기 때문에 인스턴스 변수라고도 한다.
매개변수로 선언된 localParameter 는 메서드 블럭 바깥에 존재하지만 선언부에 존재하기 때문에 스코프는 해당 메서드 블럭 안이다.
메서드 블럭 안에서 선언된 localVariable 역시 스코프는 메서드 블럭 안이다.
main() 메서드 안에서 인스턴스 변수인 globalVariable 은 사용할 수 없다.
main() 메서드는 static 메서드이 때문에 static 변수만 사용할 수 있다.
static 으로 선언된 변수(staticVariable)는 클래스가 처음 로딩될 때 JVM 메모리에 한 번만 저장되고 생성된 모든 인스턴스에서 공유한다.
static 변수는 클래스 변수라고도 한다.
클래스 변수는 클래스명.변수명 형태로 사용하는것을 권장한다.
스택 영역에 생성된 변수의 라이프타임은 블럭에 의해 결정된다.
블럭 내에서 선언된 변수는 블럭이 종료될 때 스택 영역에서 제거된다.
인스턴스 변수: 해당 인스턴스가 생성되고 GC 에 의해 제거되기 전까지 남아있는다.
클래스 변수: 프로그램이 시작될 때 생성되고 종료될 때까지 남아있는다.
지역 변수: 해당 변수가 선언된 블럭이 시작되고 종료될 때까지 남아있는다.
타입 캐스팅이란, 자신의 표현 범위를 모두 포함하지 못한 타입으로의 변환을 의미한다.
예를 들면, 실수형에서 정수형으로의 형 변환 또는 같은 정수형이더라도 큰 범위의 정수형에서 작은 범위의 정수형으로의 형 변환 같은 경우라고 할 수 있다.
원본 데이터의 손실이 발생할 수 있는 형 변환이다.
타입 프로모션이란, 자신의 표현 범위를 모두 포함하는 타입으로의 변환을 의미한다.
예를 들면, 정수형에서 실수형으로의 형 변환 또는 같은 정수형이더라도 작은 범위의 정수형에서 큰 범위의 정수형으로의 형 변환 같은 경우라고 할 수 있다.
원본 데이터의 손실이 발생하지 않는 형 변환이다.
아래는 타입 캐스팅과 타입 프로모션에 대하여 확인해보기 위해 작성하였다.
public class TypeConversionStudy {
public static void main(String[] args) {
int intVariable = 1_000_000;
long longVariable = intVariable;
float floatVariable = intVariable;
double doubleVariable = intVariable;
System.out.println("타입 프로모션 longVariable: " + longVariable);
System.out.println("타입 프로모션 floatVariable: " + floatVariable);
System.out.println("타입 프로모션 doubleVariable: " + doubleVariable);
short shorVariable = (short) intVariable;
byte byteVariable = (byte) intVariable;
System.out.println("타입 캐스팅 shortVariable: " + shorVariable);
System.out.println("타입 캐스팅 byteVariable: " + byteVariable);
}
}
코드에서 알 수 있듯이 타입 캐스팅같은 경우 변환하고자 하는 타입에 대하여 명시해줘야 한다.
배열은 동일한 데이터 타입을 정해진 개수 만큼 저장하는 참조형 변수이다.
배열의 크기는 long 이 아닌 int 또는 short 값으로 지정해야한다.
public class ArrayStudy {
public static void main(String[] args) {
int[] intArray = new int[Integer.MAX_VALUE + 1];
}
}
만약 int 형 범위 밖의 값으로 초기 크기를 지정한다면 런타임 시 아래와 같은 오류가 발생한다.
배열의 부모클래스는 Object 클래스이다.
또한 모든 배열은 인터페이스 Cloneable 및 java.io.Serializable 을 구현한다.
1차원 배열 선언의 일반적인 방식은 다음과 같다.
public class ArrayStudy {
int intArray[]; // type variable_name[]
int[] intArray2; // type[] variable_name
}
배열은 new 연산자와 함께 크기를 명시해서 생성했을 때 힙 메모리 영역에 저장된다.
public class ArrayStudy {
public static void main(String[] args) {
int intArray[]; // declaring array
intArray = new int[6]; // allocating memory to array
}
}
배열의 인덱스는 0부터 시작해서 전체 배열의 크기 - 1 로 끝난다.
만약, 배열의 크기를 벗어난 인덱스로 접근하게 될 경우 어떻게 될까?
아래와 같이 런타임 시, ArrayIndexOutOfBoundsException 이 발생하는 것을 확인할 수 있다.
배열에는 1차원 외에 다차원으로도 생성할 수 있다.
배열의 배열로 각각의 엘리먼트는 또 다른 배열의 주소를 참조하고 있다.
다차원 배열의 선언은 다음과 같다.
public class ArrayStudy {
int intArray[][] = new int[10][20]; // 2D array or matrix
int intArray2[][] = { {1, 2, 3}, {4, 5, 6} }; // declaring and initializing 2D array
long longArray[][][] = new long[10][20][30]; // 3D array
}
1차원 배열인 경우 clone() 메서드를 사용하면 deep copy 가 수행된다.
int[] oneArray = new int[]{1, 2, 3};
int[] cloneOneArray = oneArray.clone();
System.out.println("oneArray reference: " + oneArray);
System.out.println("cloneOneArray reference: " + cloneOneArray);
System.out.println("oneArray == cloneOneArray: " + (oneArray == cloneOneArray));
다차원 배열인 경우에는 clone() 메서드를 사용하면 shallow copy 가 수행된다.
즉, 복사된 각각의 상위 배열은 동일한 하위 배열을 참조한다.
int[][] multiArray = new int[][]{ {1, 2}, {3, 4} };
int [][] cloneMultiArray = multiArray.clone();
System.out.println("multiArray reference: " + multiArray);
System.out.println("cloneMultiArray reference: " + cloneMultiArray);
System.out.println("multiArray == cloneMultiArray: " + (multiArray == cloneMultiArray));
System.out.println("multiArray[0] reference: " + multiArray[0]);
System.out.println("cloneMultiArray[0] reference: " + cloneMultiArray[0]);
System.out.println("multiArray[0] == cloneMultiArray[0]: " + (multiArray[0] == cloneMultiArray[0]));
var 키워드는 자바 10 에서 처음 소개되었다.
타입추론은 주변 컨텍스트를 기반으로 변수의 데이터 타입을 자동으로 감지하는 var 키워드에서 사용된다.
public class VarStudy {
public static void main(String[] args) {
var intVariable = 10;
var doubleVariable = 1.1;
var charVariable = 'A';
var stringVariable = "variable";
var booleanVariable = true;
System.out.println("intVariable: " + intVariable);
System.out.println("doubleVariable: " + doubleVariable);
System.out.println("charVariable: " + charVariable);
System.out.println("stringVariable: " + stringVariable);
System.out.println("booleanVariable: " + booleanVariable);
}
}
지역 변수 선언에서 사용할 있으며 명시적 초기화와 함께 사용해야 한다.
인스턴스 변수나 전역 변수에서는 사용할 수 없으며 제네릭 타입에서도 사용할 수 없다.
또한, 메서드의 매개변수와 리턴으로도 사용할 수 없다.
웹 문서