1. 지역 변수와 스코프
지역 변수(Local Variable)
특정 지역( { } )에서만 사용할 수 있는 변수
그 특정 지역을 벗어나면 사용할 수 없다.
지역 변수는 자신이 선언된 코드 블록 ( { } ) 안에서만 생존하고, 자신이 선언된 코드 블록을 벗어나면 제거됨
스코프(Scope)
변수의 접근 가능한 범위
int m 은 main{ } 전체에서 접근할 수 있기 때문에 스코프가 넓고, int x 는 if{ } 코드 블록 안에서만 접근할 수 있기 때문에 스코프가 좁다.
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);
System.out.println("main m = " + m);
} //m 생존 종료
}
2. 스코프 존재 이유
[좋지 않은 코드]
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("temp = " + temp);
System.out.println("m = " + m);
}
}
임시 변수 temp는 if문에서 임시로 잠깐 사용하는 변수인데, main( ) 코드 블록에 선언되어 있다.
이렇게 되면 다음과 같은 문제가 발생한다.
- 비효율적인 메모리 사용: temp는 if 코드 블록에서만 필요하지만, main( ) 코드 블록이 종료될 때까지 메모리에 유지된다. 만약 if 코드 블록 안에 temp를 선언했다면 if 코드 블록의 종료 시점에 이 변수를 메모리에서 제거해서 더 효율적으로 메모리를 사용할 수 있다.
- 코드 복잡성 증가: 코드를 유지보수할 때 m은 물론이고 temp까지 계속 신경써야 한다. 스코프가 불필요하게 넓은 것이다.
[좋은 코드]
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("temp = " + temp);
System.out.println("m = " + m);
}
}
temp를 if 코드 블록 안에서 선언했기 때문에 if 코드 블록 안으로 스코프가 줄어든다.
덕분에 temp 메모 리를 빨리 제거해서 메모리를 효율적으로 사용하고, temp 변수를 생각해야 하는 범위를 줄여서 더 유지보수 하기 좋은 코드를 만들었다.
[while문 vs for문 - 스코프 관점]
while문
package loop;
public class While2_3 {
public static void main(String[] args) {
int sum = 0;
int i = 1;
int endNum = 3;
while (i <= endNum) {
sum = sum + i;
System.out.println("i=" + i + " sum=" + sum);
i++;
}
}
}
변수 i의 스코프가 main( ) 메서드 전체가 된다.
for문
package loop;
public class For2 {
public static void main(String[] args) {
int sum = 0;
int endNum = 3;
//1+2+3 = 6
for(int i = 1; i<=endNum; i++) {
sum = sum + i;
System.out.println("i=" + i + " sum=" + sum);
}
}
}
변수 i의 스코프가 for문 안으로 한정된다.
따라서 변수 i 와 같이 for 문 안에서만 사용되는 카운터 변수가 있다면 while 문 보다는 for 문을 사용해서 스코프의 범위를 제한하는 것이 메모리 사용과 유지보수 관점에서 더 좋다.
- 변수의 스코프는 꼭 필요한 곳으로 한정해서 사용하자.
- 좋은 프로그램은 무한한 자유가 있는 프로그램이 아니라 적절한 제약이 있는 프로그램이다.
3. 형변환
int < long < double
int → long → double // 작은 범위에서 큰 범위 → 자동 형변환(=묵시적 형변환)
double → long → int // 큰 범위에서 작은 범위 → 명시적 형변환
1) 자동 형변환
작은 범위에서 큰 범위로 대입은 허용한다
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);
}
}
자동 형변환 과정
//intValue = 10
doubleValue = intValue
doubleValue = (double) intValue //형 맞추기
doubleValue = (double) 10 //변수 값 읽기
doubleValue = 10.0 //형변환
(double): int형이 double형으로 변한다. => 형변환
작은 범위 숫자 타입에서 큰 범위 숫자 타입으로의 대입은 개발자가 이렇게 직접 형변환을 하지 않아도 된다.
이런 과정이 자동으로 일어나기 때문에 자동 형변환, 또는 묵시적 형변환이라 한다.
2) 명시적 형변환
큰 범위에서 작은 범위 대입은 명시적 형변환이 필요하다
package casting;
public class Casting2 {
public static void main(String[] args) {
double doubleValue = 1.5;
int intValue = 0;
//intValue = doubleValue; //double -> int; 컴파일 오류 발생
intValue = (int) doubleValue; //형변환
System.out.println("intValue = " + intValue);
}
}
int는 double보다 숫자의 표현 범위가 작기 때문에 double → int를 하면 숫자가 손실될 수 있다.
하지만 이런 위험을 감수하고도 개발자가 값을 대입하고 싶다면 데이터 타입을 강제로 변경할 수 있다.
형변환(=캐스팅): (int) 와 같이 괄호를 사용해서 명시적으로 형을 변환 → 명시적 형변환
※ 캐스팅 용어 유래
"cast": 금속이나 다른 물질을 녹여서 특정한 형태나 모양으로 만드는 과정
명시적 형변환 과정
//doubleValue = 1.5
intValue = (int) doubleValue;
intValue = (int) 1.5; //doubleValue에 있는 값을 읽는다.
intValue = 1; //(int)로 형변환 한다. intValue에 int형인 숫자 1을 대입한다.
참고로 형변환을 해도 doubleValue 자체의 타입이 변경되거나 그 안에 있는 값이 변경되는 것은 아니다.
즉, doubleValue 안에 들어있는 값은 1.5로 그대로 유지된다.
형변환과 오버플로우
형변환을 할 때 만약 작은 숫자가 표현할 수 있는 범위를 넘어서면 어떻게 될까?
package casting;
public class Casting3 {
public static void main(String[] args) {
long maxIntValue = 2147483647; //int 최고값
long maxIntOver = 2147483648L; //int 최고값 + 1(초과)
int intValue = 0;
intValue = (int) maxIntValue; //형변환
System.out.println("maxIntValue casting = " + intValue);
intValue = (int) maxIntOver; //형변환
System.out.println("maxIntOver casting = " + intValue);
}
}
- int의 최고값인 2147483647을 입력한 경우 int로 포현할 수 있는 범위 안에 있기 때문에 long → int로 형변환을 해도 아무 문제가 없다.
- 그러나 int의 최고값에서 +1 초과한 2147483648을 입력한 경우에는 in로 표현할 수 있는 범위를 넘어서기 때문에 long → int로 형변환을 하면 문제가 발생한다. ※ int 범위인 2147483647을 초과하면 L을 붙여 long형을 사용해야 함 이 경우의 결과는 -2147483648이라는 전혀 다른 숫자가 보인다. 이런 현상을 오버플로우라고 한다.
- 오버플로우가 발생하면 마치 시계가 한 바퀴 돈 것처럼 다시 처음부터 시작한다.
- 중요한 것은 오버플로우가 발생하는 것 자체가 문제라는 점이다! 오버플로우 자체가 발생하지 않도록 막아야 한다. 이 경우에는 intValue의 타입을 int → long으로 변경해서 사이즈를 늘리면 오버플로우 문제가 해결된다.
4. 계산과 형변환
형변환은 대입뿐만 아니라, 계산을 할 때에도 발생한다.
package casting;
public class Casting4 {
public static void main(String[] args) {
int div1 = 3 / 2; //1.5 → 1
System.out.println("div1 = " + div1);
double div2 = 3 / 2; //1.5 → 1.0
System.out.println("div2 = " + div2);
double div3 = 3.0 / 2; //1.5 → 1.5
System.out.println("div3 = " + div3);
double div4 = (double) 3 / 2; //1.5 → 1.5
System.out.println("div4 = " + div4);
int a = 3;
int b = 2;
double result = (double) a / b;
System.out.println("result = " + result); //1.5 → 1.5
}
}
자바 계산에서 두 가지 법칙
- 같은 타입끼리의 계산은 같은 타입의 결과를 낸다.
int + int = int
double + double - 서로 다른 타입의 계산은 큰 범위로 자동 형변환이 일어난다.
int + long = long
int + double = double
int div1 = 3 / 2; //int / int
int div1 = 1; //int / int이므로 int타입으로 결과가 나온다.
double div2 = 3 / 2; //int / int
double div2 = 1; //int / int이므로 int타입으로 결과가 나온다.
double div2 = (double) 1; //int -> double에 대입해야 한다. 자동 형변환 발생
double div2 = 1.0; // 1(int) -> 1.0(double)로 형변환 되었다.
double div3 = 3.0 / 2; //double / int
double div3 = 3.0 / (double) 2; //double / int이므로, double / double로 형변환 발생
double div3 = 3.0 / 2.0; //double / double -> double이 된다.
double div3 = 1.5;
double div4 = (double) 3 / 2; //명시적 형변환을 사용했다. (double) int / int
double div4 = (double) 3 / (double) 2; //double / int이므로, double / double로 형변
환 발생
double div4 = 3.0 / 2.0; //double / double -> double이 된다.
double div4 = 1.5;
double result = (double) a / b; //(double) int / int
double result = (double) 3 / 2; //변수 값 읽기
double result = (double) 3 / (double) 2; //double + int 이므로 더 큰 범위로 형변환
double result = 3.0 / 2.0; //(double / double) -> double이 된다.
double result = 1.5;
정리
형변환
int → long → double
- 작은 범위에서 큰 범위로는 대입할 수 있다 = 자동 형변환(=묵시적 형변환)
- 큰 범위에서 작은 범위의 대입은 '소수점 버림' or '오버플로우'와 같은 문제가 발생할 수 있다. 이런 위험을 감수하고도 값을 대입하고 싶다면 데이터 타입을 강제로 변경할 수 있다 = 명시적 형변환
- ① 같은 타입은 같은 결과를 낸다. ② 서로 다른 타입의 계산은 큰 범위로 자동 형변환이 일어난다.