변수는 선언한 위치에 따라 지역 변수, 멤버 변수(클래스 변수, 인스턴스 변수)와 같이 분류된다.
앞 게시글에서 사용했던 변수들은 모두 Local Variable(로컬 변수), 한글로 지역 변수라고 한다. 지역 변수는 이름 그대로 특정 지역에서만 사용할 수 있는 변수라는 뜻이다. 그 특정 지역을 벗어나면 사용할 수 없다. 여기서 말하는 지역이 바로 변수가 선언된 코드 블록( {} )이다. 지역 변수는 자신이 선언된 코드 블록( {} ) 안에서만 생존하고, 자신이 선언된 코드 블록을 벗어나면 제거된다. 따라서 이후에는 접근할 수 없다.
🔘 Scope란?
변수의 접근 가능한 범위를 스코프(Scope, 범위)라고 한다. 접근 범위에 따라서 스코프가 넓다 혹은 좁다라고 표현할 수 있다.
Scope1(while문)
package scope;
public class Scope1 {
public static void main(String[] args) {
int m = 10; //m 생존 시작
if (true) {
int x = 20; //x 생존 시작
System.out.println("if m = " + m);
System.out.println("if x = " + x);
} //x 생존 종료
//System.out.println("main x =" + x); //오류, 변수 x에 접근 불가
System.out.println("main m =" + m);
}//m 생존 종료
}
- int m
- int m은 main{ } 의 코드 블록 안에서 선언되었다. 따라서 변수를 선언한 시점부터 main{ }의 코드 블록이 종료될 때까지 생존한다.
- if{ } 블록 내부에서도 외부 블록에서 선언된 m에 접근할 수 있다.
- int x
- int x는 if{ } 블록 안에서 선언되었다. 따라서 변수를 선언한 시점부터 if{ } 의 코드 블록이 종료될 때 까지 생존한다.
- if{ } 코드 블록이 끝나면 x는 제거된다. 따라서 if{ } 코드 블록을 벗어난 곳에서는 x에 접근할 수 없다.
❗cannot find symbol
if{ } 코드 블록 외부에서 x에 접근하게 되면 cannot find symbol이라는 변수 이름을 찾을 수 없다는 컴파일 오류가 발생한다.
Scope2(for문)
package scope;
public class Scope2 {
public static void main(String[] args) {
int m = 10; //m 생존 시작
for (int i = 0; i < 2; i++) { //블록 내부, for문 내
System.out.println("if m = " + m); //블록 내부에서 외부는 접근 가능
System.out.println("if i = " + i);
} //i 생존 종료
//System.out.println("main i =" + i); //오류, i에 접근 불가
System.out.println("main m =" + m);
}//m 생존 종료
}
- for문의 경우 for(int i=0;..) 과 같이 for 문 안에서 초기식에 직접 변수를 선언할 수 있다.
- 이렇게 선언한 변수는 for 문 코드 블록 안에서만 사용할 수 있다.
🔘 Scope 존재 이유
메모리 사용과 코드의 효율성을 위해 스코프가 존재한다. 아래 코드를 보면서 확인해보자.
Scope3_1
package scope;
public class Scope3_1 {
public static void main(String[] args) {
int m = 10;
int temp = 0;
if (m > 0) {
temp = m * 2;
System.out.println("temp = " + temp);
}
System.out.println("m = " + m);
}
}
- 조건이 맞으면 변수 m 의 값을 2배 증가해서 출력하는 코드이다.
- 여기서 2배 증가한 값을 저장해두기 위해 임시 변수 temp 를 사용했다.
- 그런데 이 코드는 좋은 코드라고 보기는 어렵다.
- 왜냐하면 임시 변수 temp 는 if 조건이 만족할 때 임시로 잠깐 사용하는 변수이다.
- 그런데 임시 변수 temp main() 코드 블록에 선언되어 있다.
- 이렇게 되면 다음과 같은 문제가 발생한다.
❗문제
- 비효율적인 메모리 사용 : temp 는 if 코드 블록에서만 필요하지만, main() 코드 블록이 종료될 때 까지 메모리에 유지된다. 따라서 불필요한 메모리가 낭비된다. 만약 if 코드 블록 안에 temp 를 선언했다면 자바를 구현하는 곳에서 if 코드 블록의 종료 시점에 이 변수를 메모리에서 제거해서 더 효율적으로 메모리를 사용할 수 있다.
- 코드 복잡성 증가 : 좋은 코드는 군더더기 없는 단순한 코드이다. temp 는 if 코드 블록에서만 필요하고, 여기서만 사용하면 된다. 만약 if 코드 블록 안에 temp 를 선언했다면 if 가 끝나고 나면 temp 를 전혀 생각하지 않아도 된다. 머리속에서 생각할 변수를 하나 줄일 수 있다. 그런데 지금 작성한 코드는 if 코드 블록이 끝나도 main() 어디서나 temp 를 여전히 접근할 수 있다. 누군가 이 코드를 유지보수 할 때 m 은 물론이고 temp 까지 계속 신경써야 한다. 스코프가 불필요하게 넓은 것이다. 지금은 코드가 매우 단순해서 이해하는데 어려움이 없겠지만 실무에서는 코드가 매우 복잡한 경우가 많다.
Scope3_1을 다시 변경해보자.
Scope3_2
package scope;
public class Scope3_2 {
public static void main(String[] args) {
int m = 10;
if (m > 0) {
int temp = m * 2;
System.out.println("temp = " + temp);
}
System.out.println("m = " + m);
}
}
- temp를 if 코드 블록 안에서 선언했다.
- 이제 temp는 if 코드 블록 안으로 스코프가 줄어든다.
- 덕분에 temp 메모리를 빨리 제거해서 메모리를 효율적으로 사용하고, temp 변수를 생각해야 하는 범위를 줄여서 더 유지보수 하기 좋은 코드를 만들었다.
🌟 변수의 스코프는 꼭 필요한 곳으로 한정해서 사용하자!
메모리를 효율적으로 사용하고 더 유지보수하기 좋은 코드를 만들 수 있다. 좋은 프로그램은 무한한 자유가 있는 프로그램이 아니라 적절한 제약이 있는 프로그램이다.
🔘 자동 형변환
자바에는 자동 형변환과 명시적 형변환을 할 수 있다. 자동 형변환은 변환하고자 하는 타입을 적어주지 않아도 다른 타입인 변수에 대입하기만 해도 자동으로 형변환이 된다. 즉, 개발자가 직접 형변환을 하지 않아도 되는 것으로 묵시적 형변환이라고도 한다.
만약 int 타입의 값을 long 타입으로 선언된 변수에 대입하고 싶을 때 형변환을 통해 넣을 수 있다. 이처럼 작은 범위에서 큰 범위로의 대입은 가능하지만, 큰 범위에서 작은 범위로의 대입은 다음과 같은 문제가 발생한다.
- 소수점 버림
- 오버플로우
예제 - int 타입을 long타입과 double 타입으로, long 타입을 double 타입으로 변환해보자.
Casting1
package casting;
public class Casting1 {
public static void main(String[] args) {
int intValue = 10;
long longValue;
double doubleValue;
longValue = intValue; //int -> long
System.out.println("longValue = " + longValue);
doubleValue = intValue; //int -> double
System.out.println("doubleValue = " + doubleValue);
doubleValue = 20L; //long -> double
System.out.println("doubleValue2 = " + doubleValue);
}
}
실행 결과
longValue = 10
doubleValue1 = 10.0
doubleValue2 = 20.0
- int long 을 비교해보면 long 이 int 보다 더 큰 숫자 범위를 표현한다. 작은 범위 숫자 타입에서 큰 범위 숫자 타입에 대입을 하면 문제가 되지 않는다.
- long double 의 경우에도 double 은 부동 소수점을 사용하기 때문에 더 큰 숫자 범위를 표현한다. 따라서 대입할 수 있다.
- 정리하면 작은 범위에서 큰 범위로의 대입은 자바 언어에서 허용한다. 쉽게 이야기하면 큰 그릇은 작은 그릇에 담긴 내용물을 담을 수 있다.
🌟 자바에서는 작은 범위 숫자 타입에서 큰 범위 숫자 타입으로의 대입은 개발자가 직접 형변환을 하지 않아도 자동으로 일어난다. 그러나 큰 범위 숫자 타입에서 작은 범위 숫자 타입으로의 대입은 명시적 형변환을 통해서만 가능하다.
🔘 명시적 형변환
명시적 형변환은 자동 형변환과 반대로 변경하고 싶은 타입을 개발자가 직접 코드를 입력해서 변환을 시켜줘야 한다.
만약 앞서 했던 자동 형변환처럼 int 타입인 변수에 double 타입의 값을 대입하면 다음과 같은 컴파일 오류가 발생한다.
java: incompatible types: possible lossy conversion from double to int
int 형은 double 형보다 숫자의 표현 범위가 작다. 그리고 실수를 표현할 수도 없다. 따라서 이 경우 숫자가 손실되는 문제가 발생할 수 있다. 쉽게 이야기해서 큰 컵에 담긴 물을 작은 컵에 옮겨 담으려고 하니, 손실이 발생할 수 있다는 것이다.
그래서 개발자가 직접 형변환 코드를 입 해주어야 컴파일 오류가 발생하지 않는다.
예제 - int 타입의 변수에 double 타입의 값을 대입해보자.
Casting 2
package casting;
public class Casting2 {
public static void main(String[] args) {
double doubleValue = 1.5;
int intValue = 0;
//intValue = doubleValue; //컴파일 오류 발생
intValue = (int) doubleValue; //형변환
System.out.println(intValue);
}
}
- intValue = doubleValue 는 에러가 난다.
- 따라서 데이터 타입을 (int)와 같이 괄호를 사용해서 명시적으로 입력해주면 된다.
참고로 형변환을 한다고 해서 doubleValue 자체의 타입이 변경되거나 그 안에 있는 값이 변경되는 것은 아니다. doubleValue 에서 읽은 값을 형변환 하는 것이다. doubleValue 안에 들어있는 값은 1.5 로 그대로 유지된다. 참고로 변수의 값은 대입연산자( = )를 사용해서 직접 대입할 때만 변경된다.
🌟 같은 타입끼리의 계산은 같은 타입의 결과를 낸다.
- int + int 는 int 를, double + double 은 double 의 결과가 나온다.
🌟 서로 다른 타입의 계산은 큰 범위로 자동 형변환이 일어난다.
- int + long 은 long + long 으로 자동 형변환이 일어난다.int + double 은 double + double 로 자동 형변환이 일어난다.
'자바 > 자바 입문' 카테고리의 다른 글
[자바 기초] 04. 반복문(while/do-while/break/continue/for) (0) | 2024.03.24 |
---|---|
[자바 기초] 03. 조건문(if/else if/switch/삼항 연산) (0) | 2024.03.21 |
[자바 기초] 02. 연산자(Operator) (0) | 2024.03.19 |
[자바 기초] 01. 변수(Variable) (3) | 2024.03.18 |