JAVA 입문 _ 김영한의 자바 입문 강의 - Day 11[스코프, 형변환]
1. Scope = 변수의 접근 가능한 범위
package scope;
public class Scope1 {
public static void main(String[] args) {
int m = 10; //지역 변수 m 생존 시작
if (true) {
int x = 20;
System.out.println("if m = " + m); //블록 내부에서 블록 외부는 접근 가능
System.out.println("if x = " + x);
} // 변수 x는 if문 안에서만 생존
System.out.println("main x = " + x) // 오류, 변수 X에 접근 불가
System.out.println("main m = " + m);
} // 변수 m 생존 종료
}
1) 변수의 종류
- 변수는 선언한 위치에 따라 지역 변수, 멤버변수(클래스 변수, 인스턴스 변수)와 같이 분류된다.
- 지역 변수는 말 그대로 특정 지역(변수가 선언된 블록 {})에서만 생존해 사용할 수 있고, 선언된 코드블록을 벗어나면 제거된다.
- 블록 내부에서도 외부 블록에서 선언된 변수 m에 접근할 수 있다.(즉 if{} 블록 → 외부 블록에서 선언된 m에 접근 가능)
- int m 은 main{} 전체에서 접근 가능하기 때문에 스코프가 넓고, int x는 if{} 안에서만 접근할 수 있기 때문에 스코프가 짧다.
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("for m = " + m);
System.out.println("for i = " + i);
} //i 생존 종료
System.out.println("main i = " + i);
System.out.println("main m = " + m);
} // m 생존 종료
}
- 위 예제처럼 for문의 경우 초기식에서 변수를 선언하고, 이렇게 선언한 변수는 for문 블록 내에서만 사용할 수 있다.
2) Scope의 존재 이유
package scope;
public class Scope3_1 {
public static void main(String[] args) {
int m = 10;
int temp = 0; // if 조건이 true일 때 증가한 m의 값을 저장해두기 위한 임시 변수 temp
if ( m > 0 ) {
temp = m * 2; //true일 경우 m의 값을 2배 증가
System.out.println("temp = " + temp);
}
System.out.println("m = " + m);
}
}
- 위 예제처럼 if문을 만족시킬 때 값이 증가할 변수 m 을 담아두기 위해 임시 변수 temp를 사용했다.
하지만 int temp가 main{} 에서 선언되었기 때문에 스코프가 넓어졌다.
- 이러한 경우, if블록 내에서만 필요했던 int temp가 main{}이 종료될 때까지 메모리에 유지되어 비효율적이고 복잡해진다.
package loop;
public class While2_1 {
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++;
}
}
}
package loop;
public class For2 {
public static void main(String[] args) {
int sum = 0;
int endNum = 3;
for (int i = 1; i <=endNum; i++) {
sum = sum + i;
System.out.println("i=" + i + " sum=" + sum);
}
}
}
- 위 while문의 경우 변수 i의 스코프가 main {} 메서드 전체가 되지만 for문에서는 i의 스코프가 for {} 내부로 한정된다.
이와 같이 for문 안에서만 사용되는 카운터 변수가 있다면 while문 보다는 for문을 사용하는 것이 더 좋다.
2. 형변환
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); // 10
doubleValue = intValue; // int -> double
System.out.println("doubleValue = " + doubleValue); // 10.0
doubleValue = 20L; // long -> double
System.out.println("doubleValue2 = " + doubleValue); // 20.0
}
}
- 큰 범위에서 작은 범위로 값을 넣을 때엔 오버플로우나 소수점을 버려야 하는 등의 문제점이 생긴다. ( 자바 숫자 범위 int > long > double )
- 하지만 작은 범위에서 큰 범위로 값을 넣는 것은 문제 없이 실행이 가능하다.
//intValue = 10
doubleValue = intValue
doubleValue = (double) intValue // 형 맞추기
doubleValue = (double) 10 // 변수 값 읽기
doubleValue = 10.0 // 형 변환
- 이처럼 (double)과 같이 적어주면 int 형이 double형으로 형이 바뀌는데 이를 형변환이라고 한다.
- 작은 범위 숫자 타입에서 큰 숫자 타입으로의 대입은 자동으로 일어나기 때문에(java 내부적으로 수행) 자동 형변환 또는 묵시적 형변환이라고 한다.
2) 명시적 형변환 = 캐스팅, casting
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); // 실행 결과 1.5에서 소숫점을 버린 1이 된다.
}
}
- 다음 예제에서 double 에서 int로 형변환을 할 때에 (int)로 명시적 형변환 해주지 않으면 컴파일 오류가 발생한다.
//doubleValue = 1.5
intValue = (int) doubleValue
intValue = (int) 1.5 //doubleValue 값을 읽는다
intValue = 1 //(int)로 형변환 후 intValue의 int 형인 1을 대입한다.
- 형변환을 한다고 해서 doubleValue 자체의 타입이 변경되거나 들어있는 값이 변경되는 것은 아니고, 읽은 값을 형변환 하는 것이다.
- 변수의 값은 대입연산자 (=) 를 사용해 직접 대입할 때만 변경된다.
package casting;
public class Casting3 {
public static void main(String[] args) {
long maxIntValue = 2147483647; //int 최고값
long maxIntOver = 2147489648L; //int 최고값 + 1초과
int intValue = 0;
intValue = (int) maxIntValue;
System.out.println("maxIntValue casting = " + intValue);
intValue = (int) maxIntOver;
System.out.println("maxIntOver casting = " + intValue); //출력 : -2147483648
}
}
- long maxIntValue = 2147483647 는 int로 표현할 수 있는 범위이기 때문에 형변환을 해도 문제가 없지만
long maxIntOver = 2147489648L 는 리터럴이 int 범위를 넘어가기 때문에 L을 붙여야 하며 int로 표현할 수 있는 범위를 벗어나기 때문에
오버플로우되는 문제가 발생한다.
package casting;
public class Casting4 {
public static void main(String[] args) {
int div1 = 3 / 2; int 끼리의 계산, int 타입의 결과
System.out.println("div1 = " + div1); //1
double div2 = 3 / 2; // 자동 형변환
System.out.println("div2 = " + div2); //1.0
double div3 = 3.0 / 2; double/ double로 자동 형변환 발생
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
}
}
- 같은 타입끼리의 연산에서 결과도 같은 타입이고, 서로 다른 타입의 계산은 큰 범위로 자동 형변환이 일어난다.
Opinion
계속 프론트엔드 웹 개발 수업을 듣다가 오랜만에 백엔드 java 수업을 들었는데 프론트엔드나 백엔드나 처음에 배울 때는 항상 재미있는 것같다. 프론트엔드만 계속 배울 때는 점점 어려워져서 백엔드로 돌아오고 싶었는데 역시 돌아오고 나니 백엔드가 갑자기 재밌어졌다.
어느 하나에만 머무르면서 개발을 배울 수는 없나보다..
특히나 형변환은 여태 배웠던 연산문이나 반복문 부분보다 재밌었는데, 강의에서 그랬듯 개발에서는 무한한 자유가 있기보다는 적절한 제재가 있을 때 더 잘 굴러간다는 말이 크게 와닿았다. 다른 부분을 배울 때는 각각의 규칙이나 제재가 확실하게 다가오지 않았는데 형변환에서는 확실하게 되는 것 안되는 것이 명확하게 느껴져서 그런 듯 하다. 새로운 것을 배웠으니 내가 또 무엇을 더 해낼 수 있을 지 궁금하다!