지역 변수는 이름 그대로 특정 지역에서만 사용할 수 있는 변수라는 뜻이다. 그 특정 지역을 벗어나면 사용할 수 없다.
지역 변수는 자신이 선언한 코드 블록{}안에서만 생존하고, 자신이 선언된 코드블록을 벗어나면 제거된다.
Scope1
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 x = " + m);
} //m 생존 종료
}
지역 변수는 본인의 코드 블록 안에서만 생존한다.
자신의 코드 블록을 벗어나면 제거되기 때문에 접근할 수 없다.
변수의 접근 가능한 범위를 스코프라고 한다.
int m은 main{}전체에서 접근할 수 있기 때문에 스코프가 넓고, int x는if{} 코드 블록 안에서만 접근할 수 있기 때문에 스코프가 짧다.
Scope2
package scope;
public class Scope2 {
public static void main(String[] args) {
int m = 10;
for (int i = 0; i < 2; i++) { //블록 내부, for문 내
System.out.println("for m = " + m); //블록 내부에서 외부는 접근 가능
System.out.println("for i = " + i);
} //i 생존 종료
//System.out.println("main i = " + i); //오류, i에 접근 불가
System.out.println("main m = " + m);
}
}
왜 스코프라는 개념을 만들었을까?
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);
}
}
위 코드는 좋은 코드라고 보기 어렵다.
비효율적인 메모리 사용: temp는if코드 블록에서만 필요하지만, main()코드 블록이 종료될 때 까지 메모리에 유지되므로, 불필요한 메모리가 낭비된다.
코드 복잡성 증가: 좋은 코드는 군더더기 없는 단순한 코드이다. 만약 if코드 블록 안에 temp를 선언했다면 if가 끝나고 나면 temp를 전혀 생각하지 않아도 된다.
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 메모리를 빨리 제거해서 메모리를 효율적으로 사용하고, temp변수를 생각해야 하는 범위를 줄여서 더 유지보수 하기 좋은 코드를 만들었다.
while VS for문
package loop;
public class While2_3 {
public static void main(String[] args) {
int sum = 0;
int i = 1;
int endNum = 3;
//i=2, endNum=3
while (i <= endNum) {
sum = sum + i;
System.out.println("i=" + i + " sum=" + sum);
i++;
}
}
}
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를 비교해보자.
while문의 경우 변수i의 스코프가 main() 메서드 전체가 된다. 반면에 for문의 경우 변수 i의 스코프가 for문 안으로 한정된다.메모
for문은 while문과 달리 범위안에서 변수를 선언할 수 있다.
정리
좋은 프로그램은 무한한 자유가 있는 프로그램이 아니라 적절한 제약이 있는 프로그램이다.
작은 범위에서 큰 범위로 대입은 허용한다
int < long < double
int보다는 ling이,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
doubleValue = 10.0
doubleValue2 = 20.0
쉽게 말하면 큰 그릇에서 작은 그릇에 담긴 내용물을 담을 수 있다고 보면 된다.
결국 대입하는 형(타입)을 맞추어야 하기 때문에 개념적으로는 다음과 같이 작동한다.
//intValue = 10
doubleValue = intValue
doubleValue = (double) intValue //형 맞추기
doubleValue = (double) 10 //변수 값 읽기
doubleValue = 10.0 //형변환
이렇게 (double)과 같이 적어주면 int형이 double형으로 형이 변한다. 이렇게 형이 변경되는 것을 형변환이라한다.
큰 범위에서 작은 범위 대입은 명시적 형변환이 필요하다
double -> int로 대입
Casting2
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형은 double형보다 숫자의 표현 범위가 작아서 숫자가 손실되는 문제가 발생할 수 있다. 그래서 컴파일 오류가 발생한다.
형변환
형변환은 변경하고 싶은 데이터 타입을 (int)와 같이 괄호를 사용해서 명시적으로 입력하면 된다.
intValue = (int) doubleValue; //형변환
영어로는 캐스팅이라 하고 개발자가 직접 형변환 코드를 입력한다고 해서 명시적 형변환이라 한다.
명시적 형변환 과정
//doubleValue = 1.5
intValue = (int) doubleValue;
intValue = (int) 1.5; //doubleValue에 있는 값을 읽는다.
intValue = 1; //(int)로 형변환 한다. intValue에 int형인 숫자 1을 대입한다.
doubleValue안에 들어있는 값은 1.5로 그대로 유지된다.
만약 범위를 넘어선다면?
Casting3
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) 2147483648L; //형변환
System.out.println("maxIntValue casting= " + intValue);
}
}
실행 결과
maxIntValue casting= 2147483647
maxIntValue casting= -2147483648
정상 범위
maxIntValue = 2147483647; //int 최고값
intValue = (int) maxIntValue; //변수 값 읽기
intValue = (int) 2147483647L; //형변환
intValue = 2147483647;
초과 범위
int로 표현할 수 있는 가장 큰 숫자보다 더 크기때문에 마지막에 L을 붙여서 long형을 사용해야 한다.
int로 표현할 수 있는 범위를 넘기 때문에 다음과 같이 long -> int로 형변환 하면 문제가 발생한다.
maxIntOver = 2147483648L; //int 최고값 + 1
intValue = (int) maxIntOver; //변수 값 읽기
intValue = (int) 2147483648L; //형변환 시도
intValue = -2147483648;
결과를 보면 -2147483648라는 전혀 다른 숫자가 나온다. int형은 이숫자를 표현할 수 있는 방법이 없기때문에, 전혀 다른 숫자가 표현된다. 이것을 오버플로우라 한다.
보통 오버플로우가 발생하면 마치 시계가 한바퀴 돈 것 처럼 다시 처음부터 시작한다.
중요한 것은 오버플로우가 발생하는 것 자체가 문제이다.
오버플로우 자체가 발생하지 않도록 막아야한다.
메모
int -> long으로 변경해서 사이즈를 늘리면 오버플로우 문제가 해결된다.
Casting4
public class Casting4 {
public static void main(String[] args) {
int div1 = 3 / 2;
System.out.println("div1 = " + div1); //1
double div2 = 3/ 2;
System.out.println("div2 = " + div2); //1.0
double div3 = 3.0 / 2;
System.out.println("div3 = " + div3); //1.5
double div4 = (double) 3 / 2;
System.out.println("div4 = " + div4); //1.5
int a = 3;
int b = 2;
double result = (double) a / b;
System.out.println("result = " + result); //1.5
}
}
출력 결과
div1 = 1
div2 = 1.0
div3 = 1.5
div4 = 1.5
result = 1.5
자바에서 계산은 다음 2가지를 기억하자.
같은 타입끼리의 계산은 같은 타입의 결과를 낸다.
int + int는 int를, double +double은 double의 결과가 나온다.서로 다른 타입의 계산은 큰 범위로 자동 형변환이 일어난다.
int + long은 long + long으로 자동 형변환이 일어난다.int + double은 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;
3 / 2 와 같이 int 형끼리 나눗샘을 해서 소수까지 구하고 싶다면 div4 의 예제처럼 명시적 형변환을 사용하면 된다.
물론 변수를 사용하는 경우에도 다음과 같이 형변환을 할 수 있다.
int a = 3;
int b = 2;
double result = (double) a / b;
처리 과정
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;
오늘은 범위(스코프)에 관해서 정리해보았는데 쉽게 말해서 변수의 생존 범위이다. {]코드 블록으로 선언하는데 앞에서 공부하면서 자연스럽게 개념을 느끼고 있었던 것 같다. 그래서 이해하는데 그렇게 어렵지 않았다. 중요하다고 생각한 부분은 꼭 필요한 범위에 변수를 넣어주어서 내가 코드를 작성하면서 신경써야할 변수를 최대한 줄이는 것이 중요하다고 느꼈다. 그리고 계산을 할 때 먼저 명시적 형변환을 시킨후 계산해야 숫자가 손실되는 문제를 막을 수 있으므로, 신경써야 겠다.