관리 메뉴

나구리의 개발공부기록

코드로 시작하는 자바 첫걸음3, 스코프 및 형변환, 훈련 본문

인프런 - 실전 자바 로드맵/자바 입문 - 자바 첫걸음(무료)

코드로 시작하는 자바 첫걸음3, 스코프 및 형변환, 훈련

소소한나구리 2024. 11. 1. 16:42

출처 : 인프런 - 자바 입문 - 코드로시작하는 자바 첫걸음 (무료) / 김영한님 


1. 스코프 및 형변환

1) 지역 변수와 스코프

(1) 지역 변수

  • 변수는 선언한 위치에 따라 지역 변수, 멤버 변수(클래스 변수, 인스턴스 변수)와 같이 분류됨
  • 지금까지 학습한 변수들은 모두 영어로 로컬 변수(Local Variable), 한글로 지역 변수라 하며 나머지 변수들은 뒤에서 학습함
  • 지역 변수는 말그대로 특정 지역에서만 사용할 수 있는 변수를 뜻이며 특정 지역을 벗어나면 사용할 수 없음
  • 여기서 말하는 지역은 변수가 선언된 코드 블록{ } 이며 지역변수는 자신이 선언된 코드 블록 안에서만 생존하고 자신이 선언된 코드 블록을 벗어나면 제거되며 이후에는 접근할 수 없음

(2) 예제

  • 변수 m은 main의 코드 블록 안에서 선언된 지역 변수이며 main(String[] args) { <- 여기의 블록에서부터 마지막 블록 전의 블록 까지 생존함(맨 마지막의 블록은 클래스의 범위)
  • if 문의 블록안에서 선언된 변수 x의 생존범위는 if문의 블록까지만 생존하며 if문 밖에서 선언된 변수도 if문 내부 에서 사용할 수 있음
  • 즉, 외부에서 선언된 변수도 내부에서는 모두 접근 가능하며 각자의 변수는 자신이 속한 블록안에서만 생존하는데, 이렇게 변수의 접근 가능한 범위를 스코프(scope; 범위)라고 함
  • 여기에서는 변수 m은 main 전체에서 접근할 수 있기 때문에 스코프가 넓고 변수 x는 if문의 코드 블록안에서만 접근할 수 있기 때문에 스코프가 좁음
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 생존 종료
}

2) 스코프 존재의 이유

(1) 이유

  • 변수를 선언한 시점부터 변수를 계속 사용할 수 있을텐데 왜 복잡하게 접근 범위(스코프)라는 개념을 만든 이유는 메모리의 절약과 유지보수성을 높이기 위함임
  • 아래의 코드는 좋은 코드라고 보기 어려운데 변수 temp는 if문 안에서 조건이 만족할 때만 임시로 잠깐 사용하는 변수인데 if문의 밖에서 main() 코드 블록에 선언되어있음
  • temp는 main() 코드 블록이 종료될 때 까지 메모리에 유지되는데 실제 사용되는 공간은 if문의 코드블록이므로 불필요한 메모리가 낭비됨
  • 또한, 좋은 코드는 군더더기가 없는 단순한 코드인데, temp는 if코드 블록에서만 필요하고 여기서만 사용하면 되기 때문에 if코드 블록안에 temp를 선언했다면 해당 if문이 종료된 이후에는 temp 변수는 전혀 신경쓰지 않을 수 있음
  • 그러나 이렇게 밖에 선언하게되면 if문이 끝나도 main 범위안에서는 temp가 계속 살아있게되어 여전히 접근할 수 있게되고 누군가 해당 코드를 유지보수 할 때 m은 물론이고 temp까지 신경 써야 하게됨
  • 이렇게 특정 범위안에서만 사용할 변수가 해당 범위를 벗어나는 스코프를 가지게 되면 실무에서 코드가 복잡하게 되어있을 때 코드를 한눈에 알아보기가 어렵고 유지보수가 어려워질 수 있음
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);
}

 

(2) 개선

  • 이렇게 temp를 실제 사용하는 범위인 if문 안에서만 선언하면 temp를 신경써야하는 스코프가 줄어들게되어 메모리를 빨리 제거함으로써 메모리를 효율적으로 사용하고 범위도 적기때문에 유지보수하기도 좋은 코드가 되었음
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);
}

 

(3) while문 vs for문의 스코프 관점

  • 변수의 스코프 관점에서 while문과 for문의 카운터 변수의 위치에대해 비교해보면 while문의 경우 변수 i의 스코프가 main()메서드 전체가 되는 반면 for문에서는 for문의 구조 안으로 한정되게 됨
  • 따라서 변수 i와 같이 for문 안에서만 사용되는 카운터 변수가 있다면 while문 보다는 for문을 사용해서 스코프의 범위를 제한하는 것이 메모리 사용과 유지보수관점에서 더 좋음
public static void main(String[] args) {
    
    // while 반복문에서 사용되는 초기 변수가 외부에 선언되어있음
    int i = 1;  
    while (i < 10) {
        // 기타 코드 생략
        i++;
    }

    // for 반복문에서 사용되는 변수가 for문 내부에 있음
    for (int j = 0; j < 10; j++) {	
        // 기타 코드 생략
    }
}

3) 스코프 정리

  • 변수는 꼭 필요한 범위로 한정해서 사용하는 것이 좋음
  • 변수의 스코프는 꼭 필요한 곳에서 한정으로 사용해야 메모리를 효율적으로 사용하고 더 유지보수하기 좋은 코드를 만들 수 있음
  • 좋은 프로그램은 무한한 자유가 있는 프로그램이 아니라 적절히 제약이 있는 프로그램이며 무한한 자유가있는 프로그램은 무한한 버그가 있을 수 있으나 적절한 제약이 있는 프로그램은 좋은 설계, 좋은 API 스펙을 가질 수 있음

4) 자동 형변환

(1) 형변환

  • 작은 범위에서 큰 범위로는 int -> long -> double 처럼 당연히 값을 넣을 수있음
  • 큰 범위에서 작은 범위로의 형변환은 소수점 버림 및 오버플로우와 같은 문제가 발생할 수 있음
  • double은 부동 소수점을 사용하기 때문에 int, long인 정수보다 더 큰 숫자 범위를 표현하기에 대입할 수 있음
  • 즉 자바는 기본적으로 작은 범위에서 큰 범위로의 대입을 허용하며 이런것을 형변환이라고함

(2) 자동 형변환

  • 하지만 결국 대입하는 타입(형)을 맞추어야하기 때문에 개념적으로는 아래의 코드처럼 동작하게 됨
  • 즉 intValue의 앞에 (double)처럼 적어주면 int타입이 double타입으로 변환하면서 형변환이 동작하게 되는데 위에서처럼 작은 범위에서 큰 범위로의 대입은 개발자가 직접 형변환을 하지않아도 자동으로 동작하게 됨
  • 이런 과정이 자동으로 일어난다고 해서 자동 형변환 혹은 묵시적 형변환이라고 함
int intValue = 10
double doubleValue = intValue
doubleValue = (double) intValue // 형 맞추기
doubleValue = (double) 10       // 변수 값 읽기
doubleValue = 10.0              // 형변환

5) 명시적 형변환

(1) 큰 범위에서 작은 범위로 형변환

  • int형은 double형보다 숫자의 표현 범위가 작고 실수를 표현할 수도 없기 때문에 double타입을 int로 형변환하는 경우 숫자 손실이 발생하게되어 이것을 자동 형변환이 허용되게 되면 개발자의 실수로 인해 큰 버그를 유발할 수 있음
  • 예를 들어 고객에게 은행이자를 계산해서 입금해야하는데 이런 코드가 아무런 오류 없이 수행된다면 비즈니스적으로 끔찍한 상황이 발생할 수 있기 때문에 자바는 이런 경우 컴파일 오류를 발생하여 오류를 빠르게 발견할 수 있음

(2) 강제로 형변환

  • 하지만 이런 위험을 개발자가 알고있는 상황에서 감수하고 대입을 하고싶다면 강제로 타입을 변경할 수 있음
  • 그럴때는 자동형변환이 진행되는 과정에서처럼 직접 형변환의 타입을 (int)처럼 명시적으로 사용하면 데이터 손실이 일어난 결과를 반환함
  • 이렇게 개발자가 직접 형변환 코드를 입력한다고해서 명시적 형변환이라고함
double doubleValue = 1.5
int intValue = (int) doubleValue;
intValue = (int) 1.5;     // doubleValue에 있는 값을 읽음
intValue = 1;             // (int)로 형변환, intValue에 int형인 숫자 1을 대입
                          // doubleValue의 값은 그대로 1.5가 유지되고있음

 

** 참고

  • 캐스팅은 영어단어 cast에서 유래되었으며 cast는 금속이나 다른 물질을 녹여서 특정한 형태나 모양으로 만드는 과정을 뜻하는데, 이렇게 타입을 바꾸는 것을 캐스팅이라고 함

(3) 형변환과 오버플로우

  • 형변환을 할 때 표현할 수 있는 범위를 벗어나면 에러는 발생하지 않지만 전혀 다른 숫자가 표현하게되는데 이런 현상을 오버플로우라고함
  • 오버플로우가 발생하면 마치 시계가 한바퀴 다시 돈 것처럼 다시 처음부터 시작을 하며, 변수의 타입의 범위의 가장 작은 값부터 남은 값들을 표현함
  • 중요한 것은 오버플로우가 발생하는 것 자체가 문제이기때문에 오버플로우가 발생했을 때 결과가 어떻게 되는지 계산하는데 시간을 낭비하면 절대 안됨
  • 오버플로우 자체가 발생하지 않도록 막으며 개발하는 것이 중요함
long maxIntOver = 2147483648L;     // int 최고값 + 1
int intValue = (int) maxIntOver;   // 변수 값 읽기
intValue = (int) 2147483648L;      // 형변환 시도

// 오버플로우가 발생하여 남은 값인 1을 정수로 표현하여 정수의 가장 작은범위의 값이 출력됨
// 타입을 long으로 바꾸면 오버플로우가 발생하지 않음
intValue = -2147483648;

6) 계산과 형변환

  • 형변환은 대입뿐 아니라 계산을 할 때에도 발생함
  • 같은 타입끼리의 계산은 같은 타입의 결과를 내고 서로 다른타입의 계산은 큰 범위로의 자동 형변환이 일어난다는 원칙을 기억하면 됨
  • 즉 int + long 은 long + long 으로 자동 형변환이 일어나고 int + double은 double + double로 자동 형변환이 일어난다고 이해하면됨

2. 훈련

1) 훈련 시작

(1) 의의

  • 지금까지 학습한 변수, 연산자, 조건문, 반복문은 프로그래밍의 가장 기본이 되는 기능이기 때문에 프로그램언어는 해당 기능을 필수로 가지게 됨
  • 그렇기에 지금까지의 내용을 익숙하게 잘 다룰수 있도록 기본기를 훈련하는 시간이며 사용자입력을 받는 Scanner을 학습하고 프로그래밍의 기본인 변수, 연산자, 조건문, 반복문을 다룰수 있도록 훈련을 진행
  • 머리로만 생각하고 이해하는 것보다 몸으로 익히는 것이 더욱 중요하며 단순히 외우는 방식으로는 좋은 프로그래머가 될 수 없음
  • 전부 따라해보고 스스로 문제를 풀어보는 능력을 키우며 문제를 못풀겠으면 따라 쳐보고 문제를 지우고 다시 문제를 푸는 복습과정을 거치는 것이 좋은 학습법이며, 머리로만 이해하고 넘어가는 것은 좋지 않음
  • 백문이 불여일타!

2) Scanner 학습

(1) 사용자 입력

  • System.out을 통해서 출력했듯이 System.in을 통해서 사용자의 입력을 받을 수 있음
  • 그러나 자바가 제공하는 System.in을 통해서 사용자 입력을 받으러면 여러 과정을 거쳐야해서 복잡하고 어려움
  • 자바는 이런 문제를 해결하기 위해 Scanner라는 클래스를 제공하며 이 클래스를 사용하면 사용자 입력을 매우 편리하게 받을 수 있음

(2) 예제

  • Scanner scanner = new Scanner(System.in)은 객체와 클래스를 배워야 정확히 이해할 수 있어 지금은 Scanner의 기능을 사용하기 위해 필요한 필수 코드라고 이해하면되며 new 를 통해서 Scanner를 만든다 정도로 이해하고 넘어가면 됨
  • Scanner scanner로 scanner 변수를 선언하였기 때문에 scanner 변수를 통해서 기능을 사용할 수 있음
  • scanner.nextLine() : 엔터(\n) 즉, 개행문자가 들어올 때 까지 문자를 가져옴
  • scanner.nextInt() : 입력을 int형으로 가져오고 정수 입력에 사용하며, 타입이 다르면 오류가 발생함
  • scanner.nextDouble() : 입력을 double형으로 가져오고 실수 입력에 사용하며 마찬가지로 타입이 다르면 오류가 발생함
public static void main(String[] args) {
    Scanner scanner = new Scanner(System.in);

    System.out.print("문자열을 입력하세요:");
    String str = scanner.nextLine();    // 입력을 String으로 가져옴
    System.out.println("입력한 문자열 = " + str);

    System.out.print("정수를 입력하세요:");
    int intValue = scanner.nextInt();    // 입력을 int로 가져옴
    System.out.println("입력한 정수 = " + intValue);

    System.out.print("실수를 입력하세요:");
    double doubleValue = scanner.nextDouble();    // 입력을 double로 가져옴
    System.out.println("입력한 실수 = " + doubleValue);
}

 

** 참고

  • 출력문에서 print()와 println()의 차이는 개행문자가 포함되어있는지의 여부임
  • print()로 사용하면 자동으로 개행문자가 없기 때문에 다음 라인으로 넘어가지 않음
  • println()으로 사용하면 자동으로 개행문자가 포함되어있어 다름라인으로 넘어간 뒤에 다음의 코드가 실행되어 입력이나 출력시 다음 라인에서 실행 됨

2) Scanner 예제

(1) 예제 1

  • 첫 번째 숫자와 두 번째 숫자를 더해서 출력하는 프로그램
  • 첫 번째 숫자와 두 번째 숫자 모두 0을 입력하면 프로그램을 종료하고 프로그램은 반복해서 실행
public static void main(String[] args) {
    Scanner scanner = new Scanner(System.in);

    System.out.println("첫 번째 숫자와 두 번째 숫자 모두 0을 입력하면 프로그램을 종료합니다");

    while (true) {
        System.out.println("첫번째 숫자를 입력하세요");
        int num1 = scanner.nextInt();

        System.out.println("두번째 숫자를 입력하세요");
        int num2 = scanner.nextInt();

        if (num1 == 0 && num2 == 0) {
            System.out.println("프로그램 종료");
            break;
        }
        int sum = num1 + num2;
        System.out.println("두 숫자의 합 = " + sum);
    }
}

 

(2) 예제2

  • 사용자 입력을 받아 그 합계를 계산하는 프로그램
  • 사용자는 한 번에 한 개의 정수를 입력할 수 있으며, 사용자가 0을 입력하면 프로그램은 종료됨
  • 이 때 프로그램이 종료될 때 까지 사용자가 입력한 모든 정수의 합을 출력해야 함
public static void main(String[] args) {
    Scanner scanner = new Scanner(System.in);

    int sum = 0;

    while (true) {
        System.out.print("정수를 입력하세요 (0을 입력하면 종료) :");
        int num = scanner.nextInt();

        if (num == 0) {
            break;
        }
        sum += num;
    }
    System.out.println("입력한 모든 정수의 합 = " + sum);
}

2) 문제와 풀이

(1) 음식점 주문

  • 사용자로부터 음식의 이름(foodName), 가격(foodPrice), 수량(foodQuantity)를 입력받아 주문한 음식의 총 가격을 계산하고 출력하는 프로그램 작성
  • 음식의 총 가격을 계산, 총 가격은 각 음식의 가격과 수량을 곱한값이며 이를 totalPrice라는 이름의 변수에 저장
  • 주문 정보와 총 가격을 출력하고 출력 형태는 "[음식 이름][수량]개를 주문하셨습니다. 총 가격은 [총 가격]원 입니다." 여야 함
public static void main(String[] args) {
    Scanner scanner = new Scanner(System.in);

    System.out.print("음식 이름을 입력해 주세요: ");
    String foodName = scanner.nextLine();

    System.out.print("음식 가격을 입력해 주세요: ");
    int foodPrice = scanner.nextInt();

    System.out.print("음식 수량을 입력해 주세요: ");
    int foodQuantity = scanner.nextInt();

    int totalPrice = foodPrice * foodQuantity;

    System.out.println(foodName + " " + foodQuantity + "개를 주문하셨습니다. 총 가격은 " + totalPrice + "원 입니다.");
}

 

(2) 변수 값 교환

  • 변수 a = 10 , b = 20일 때 a의 값과 b의값을 서로 바꿈
  • temp 변수를 활용
public static void main(String[] args) {
    int a = 10;
    int b = 20;
    int temp;

    temp = b;
    b = a;
    a = temp;

    System.out.println("a = " + a);
    System.out.println("b = " + b);
}

 

(3) 사이 숫자

  • 사용자로부터 두 개의 정수를 입력받고 두 정수 사이의 모든 정수를 출력하는 프로그램
  • 사용자에게 num1의 변수에 첫 번재 숫자를 입력받고 num2의 변수에 두번째 숫자를 입력받음
  • 만약 첫 번째 숫자 num1이 두번째 숫자 num2보다 크면 두 숫자를 교환
  • num1부터 num2까지의 각 숫자를 출력하고 2, 3, 4, 5 처럼 ,로 구분해서 출력해야 함
  • 반복문은 for / while 두가지 방법으로 해결
public static void main(String[] args) {
    Scanner scanner = new Scanner(System.in);

    System.out.print("첫 번째 숫자를 입력하세요:");
    int num1 = scanner.nextInt();

    System.out.print("두 번째 숫자를 입력하세요:");
    int num2 = scanner.nextInt();

    if (num1 > num2) {
        int temp = num1;
        num1 = num2;
        num2 = temp;
    }

    // while문으로 해결
    System.out.print("두 숫자 사이의 모든 정수: ");
    while (num1 < num2) {
        System.out.print(num1 + ", ");
        num1 += 1;
    }
    System.out.println(num2);

    // for문으로 해결
    System.out.print("두 숫자 사이의 모든 정수: ");
    for (int i = num1; i <= num2; i++) {
        System.out.print(i);
        if (i != num2) {
            System.out.print(", ");
        }
    }
}

 

(4) 상품 구매

  • 사용자로부터 상품 정보(상품명, 가격, 수량)을 입력받고 이들의 총 가격을 출력하는 프로그램을 작성
  • 사용자는 여러 상품을 추가하고 결제할 수 있으며, 프로그램을 언제든지 종료할 수 있음
  • 사용자에게는 1: 상품 입력, 2: 결제, 3: 프로그램 종료 라는 옵션을 제공해야하며 옵션은 정수로 입력받고 옵션을 저장하는 변수의 이름은 option이여야 함
  • 상품 입력 옵션을 선택하면 사용자에게 상품명과 가격 수량을 입력받아야 함
  • 결제 옵션을 선택하면 , 총 비용을 출력하고 총 비용을 0으로 초기화(사용자가 총 비용을 확인하고 결제를 완료했다고 가정하여 총 비용을 다시 0으로 초기화)
  • 프로그램 종료 옵션을 선택하면 "프로그램을 종료합니다"라는 메시지를 출력하고 프로그램을 종료
  • 위의 세 가지 옵션 외에 다른 값을 입력하면 "올바른 옵션을 선택해주세요"라는 메시지를 출력
  • 중간에 scanner.nextLine();의 코드가 있는 이유는 nextInt()에서는 개행문자가 없기 때문에 바로 다음 print 코드가 출력되어버리기 때문에 강제로 입력값에 개행을 만들어 주는 것
public static void main(String[] args) {
    Scanner scanner = new Scanner(System.in);
    int totalCost = 0;

    while (true) {

        System.out.println("1: 상품 입력, 2: 결제, 3: 프로그램 종료");
        int option = scanner.nextInt();

        if (option == 1) {
            scanner.nextLine();     // 이전에 입력된 개행문자를 제거

            System.out.print("상품명을 입력하세요: ");
            String itemName = scanner.nextLine();

            System.out.print("상품 가격을 입력하세요: ");
            int itemPrice = scanner.nextInt();

            System.out.print("구매 수량을 입력하세요: ");
            int itemQuantity = scanner.nextInt();

            totalCost += itemPrice * itemQuantity;

            System.out.println("상품명: " + itemName +
                                ", 가격: " + itemPrice +
                                ", 수량: " + itemQuantity +
                                ", 합계: " + itemPrice * itemQuantity);

        } else if (option == 2) {
            System.out.println("총 비용 = " + totalCost);
            totalCost = 0;
        } else if (option == 3) {
            System.out.println("프로그램을 종료합니다.");
            break;
        } else {
            System.out.println("올바른 옵션을 선택해주세요.");
        }
    }
}