java, 변수의 선언과 생명주기
선언과 초기화
기본타입
- 기본 타입의 선언과 초기화는 다음과 같다.
int i;
i = 10;
int j = 10;
- int : 데이타 타입
- i, j : 변수명
- 10 : 변수의 값
-
;(세미콜론) : 코드의 종료
- 첫 번째 줄은 선언이다. 변수와 데이터 타입을 정의한다.
- 두 번째 줄은 초기화다. 변수의 값을 정한다.
- 세 번째 줄은 선언과 초기화를 동시에 한다.
참조타입
- 참조변수의 선언은 기본타입과 동일하나 초기화는 기본타입과 다르게 다양한 형태로 동작한다.
// import java.time.LocalDateTime;
// import java.util.Date;
Date date = new Date();
LocalDateTime localDateTime = LocalDateTime.now();
- 위의 코드를 보면 Date는 new를 사용하나 LocalDateTime은 new가 존재하지 않는다.
- 하지만 해당 코드를 따라가다 보면 결국 new를 사용한다.
- 참고 :
LocalDateTime#ofEpochSecond(long epochSecond, int nanoOfSecond, ZoneOffset offset)
- 참고 :
- String과 배열은 new를 사용할 수도 있고 사용하지 않을 수도 있다.
- 참고로 배열과 달리 String에 new를 사용하는 것과 사용하지 않는 것은 메모리 할당과 변수 주소값 설정에 있어서 큰 차이를 가진다.
String str1 = "java";
String[] strs1 = {"hello", "world"};
int[] ints1 = {1,2,3,4};
int[][] ints2 = {
{1,2,3},
{4,5},
{6,7,8,9}
};
변수의 범위(scope)와 생명주기(life cycle)
지역변수 local variable
- 지역변수란 특정 메서드나 코드 블럭 혹은 특정 스레드 내부에서 선언되고 초기화된 변수이다.
- 지역변수의 생명 주기는 변수가 소속한 블럭, 메서드, 스레드에 따른다. 해당 블럭, 메서드, 스레드가 종료되면 지역 변수는 함께 제거된다.
- 지역변수는 그것이 선언된 블럭 안에서만 사용 가능하다.
- 가비지컬렉터를 통하여 관리된다.
public static void main(String[] args) {
int l1 = 0;
int l2;
boolean b = true;
if(b){ // true false를 명시할 경우 블럭을 벗겨내거나 블럭 자체를 제거하는 것으로 보인다. 컴파일 최적화의 입장이서는 당연해보인다.
l1 = 1;
l2 = 2;
int localInt3 = 3;
}
if(b){
System.out.println(l1); // 1
System.out.println(l2); // 컴파일에러
System.out.println(l3); // 컴파일에러
}
}
- l1
- 메인 메서드에서 선언과 초기화가 동시에 이뤄졌다.
- 어느 절에서나 접근 가능하고 값을 변경할 수 있다.
- l2
- 메인 메서드에서 선언이 이뤄졌다.
- 어느 절에서나 값을 초기화 할 수 있다.
- 하지만 초기화한 값은 해당 절에 종속된다. 첫 번째 if 절에서 초기화를 하였지만 두 번째 if 절에서는 초기화한 것을 알 수 없다. 컴파일 에러가 발생한다 : Variable ‘l2’ might not have been initialized
- l3
- 첫 번째 if 절에서 선언되었다. 그 말은 생명주기와 접근이 첫 번째 if 절 내부에서만 가능하다는 의미이다.
- 두 번째 if 절에서 사용하려 할 때 컴파일 에러가 발생한다 : Cannot resolve symbol ‘l3’.
- resolve란 추상의 값으로 실질적인 값에 접근함을 의미한다. l3에 선언된 블럭을 두 번째 if절에서는 아예 접근 자체가 불가능하여 없는 것과 같다.
멤버 변수 (멤버, 멤버 변수, 필드)
- 지역 변수는 메서드나 메서드 내부의 절에 선언된다. 멤버는 클래스 하위에 선언된 변수이다.
- 멤버 변수는 정적 멤버와 인스턴스 멤버로 나뉜다.
- 정적 멤버는 클래스 로딩 시 선언되며 인스턴스 멤버가 참조할 수 있다.
- 인스턴스 멤버는 클래스가 인스턴스를 생성할 때 선언되는 변수이다. 정적 메서드나 정적 변수가 참조할 수 없다.
public class Test2 {
static String staticStr = "static field";
String str = "filed";
Test2(String str) {
this.str = str;
}
public static void main(String[] args) {
System.out.println(staticStr);
System.out.println(str); // (1) 컴파일 에러
Test2 test2 = new Test2("instance");
System.out.println(test2.str); // (2) 접근 가능
}
}
- 클래스 로더가 작동 할 때 런타임 데이타 영역(Runtime Data Area) 중 메소드 영역(Method Area)에 클래스와 정적 멤버가 저장된다.
- 메소드 영역의 데이타는 그것의 생명주기와 범위가 어플리케이션의 것과 동일하다. 이 말은 (접근 제어자는 고려하지 않으면) 해당 어플리케이션 내부 어디서든 정적 멤버를 참조할 수 있다는 의미이다.
- 인스턴스 멤버는 그것의 객체(변수)를 통해서만 접근 가능하다. 인스턴스 멤버의 사용 범위는 해당 객체의 변수가 선언된 블럭에 한정된다. 인스턴스 변수의 생명 주기는 인스턴스에 의존하고, 인스턴스의 생명주기는 선언된 블럭이나 해당 변수를 참조하고 있는 상황에 의존한다. 참고로 해당 변수에 대한 참조가 어떤 상황에도 이뤄지지 않으면 GC가 해당 인스턴스를 제거한다.
- 결과적으로 인스턴스가 선언되지 않는 한 인스턴스 변수를 참조할 수 없다.
- (1)이 컴파일 에러가 발생하는 이유는 그런 이유에서이다. (2)와 같이 인스턴스를 생성하고 해당 인스턴스를 통해 멤버에 접근 가능하다.
상수(constant) 변수와 final
- 값이 변경되지 않는 변수를 상수라고 한다. 선언 시 final 키워드를 추가한다.
- 상수를 정의할 때 1) 선언과 초기화를 하거나 2) 선언을 먼저 한 후 나중에 초기화를 할 수 있다.
- 초기화를 한 이후부터는 값을 바꿀 수 없다.
final int conInt1;
final int conInt2 = 0;
conInt1 = 1; ....(1)
conInt1 = 2; ....(2)
conInt2 = 3; ....(3)
- (1) 선언을 먼저한 후 초기화를 차후에 했다. 문제 없이 작동한다.
- (2) 컴파일 에러 : Variable ‘conInt1’ might already have been assigned to. 이미 값이 할당(assigned) 되어 에러가 발생한다.
-
(3) 컴파일 에러 : Cannot assign a value to final variable ‘conInt2’. 상수 변수는 값을 할당할 수 없다.
- 다만, 위의 예제는 기본 타입이며 그것의 값은 변경 불가능하다. 하지만 주소값이 변경되지 않지만 힙에서의 메모리 변경근 가능하다. 변수가 객체이거나 자료구조일 경우 내부 상태는 변경 가능하다.
var (타입 추론)
- 자바 10부터 도입된 타입 var는 변수 선언 때 데이타 타입을 정의하지 않는다.
- 초기화 할 때의 해당 값을 가지고 데이타 타입을 자동적으로 부여한다.
var msg;
msg = "hi";
System.out.println(msg.getClass().toString()); // class java.lang.String