간단하게 지난번에 배웠던
break
기능을 간단히 보고 넘어가자.
break;
의 특징 중 하나는 가장 가까운 반복문을 종료한다는 것이다.
그래서 다중 반복문일 경우,break;
를 곳곳에 입력해줘야 한다.
하지만 개발자로서 코드 한줄을 낭비하는 것 정말 참을 수 없다.
우리에겐 극한의 효율을 위해 사용되는 몇 가지 방법이 있다.
go to
: 편하지만, 유지보수가 어려워 거의 사용되지 않는다.flag
: 익히 쓰는 방법으로 다음과 같이 사용한다.boolean flag = false;
while(true){
for( 조건 만족 ){
flag = true;
}
}
if(flag) {
break;
}
코드의 흐름을 보면 중간에 flag
의 값이 변경되어 내부의 for
이 종료될 때, flag
값이 변경되고 조건문을 만나 break;
가 실행되어 while
문이 종료된다.
반복문마다 이름을 명명하여, break labelName;
으로 해당하는 반복문을 나가게 한다.
사실상 go to
문을 반복문 안에서만 제한적으로 사용하게 한 것이다.
label1 : while(true){
label2: for(){
break label1;
}
}
- 예외 처리
- 변수의 종류
- 배열 문법
예외처리는 쉽게 하려면 쉽지만, 객체지향의 요소로 들어가 어렵게 하려면 어렵게 할 수 있다.
다만 시작하는 입장에선 예외처리를 어떻게 진행하는지, 그 흐름을 읽는 것부터 시작해야한다.
Error?
Checked Exception
프로그램을 작성하면 코드에 붉은 줄이 생긴다. 이는 컴파일러가 에러가 생길 가능성을 알려주는 것이다.
Unchecked Exception
붉은 줄 없이도, 실행 시 에러가 발생해 종료되는 경우들이 있다.
이는 위와 반대로 컴파일러가 인지 못한 에러 가능성이 있기 때문이다.
그래서 프로그램은 코드 진행 중 에러가 발생하면 프로그램을 종료하는데, 정확히는 main()
밖으로 코드가 팅겨 나간다.
그런데 예상되지 않은 상황을 만날 때마다 꺼진다면, 좋은 프로그램이 될 수 있을까?
아마도 아닐 것이다. 따라서 프로그램을 작성할 때, 이런 상황을 해결하는 방식에는 두 가지가 있다.
throws Exception
: 예외 전가로 예외가 발생할 시, 자동으로 main
에서 처리하게 하는 것이다.
try{} - catch(예외종류 e) {}
: 예외처리로, 다음 예제를 보면서 생각해보자.
// 숫자 입력
System.out.print("첫 번째 숫자 입력 : ");
int num1 = Integer.parseInt(sc.nextLine());
위 코드는 숫자를 문자열로 입력받고 int
로 변환해준다.
그러나 이 경우 붉은 줄이 없어도 abc
를 입력받게 되면 에러가 발생하고 프로그램이 종료된다.
따라서 다음과 같이 에러가 발생할 시 어떻게 처리할지 알려줘야 한다.
try {
System.out.print("숫자를 입력하세요 : ");
int num = Integer.parseInt(scanner.nextLine());
} catch (Exception e) {
// 예외 처리
}
다음과 같은 상황에서 숫자가 아닌 것을 입력하면 Integer.parseInt()
에러가 발생할 여지가 있다. 그럼에도 try
로 실행을 하고 정상 작동이 된다면 코드 흐름은 그대로 밑으로 이어져 나간다.
반대로 문자를 입력하여 에러를 발생시킨다면 코드 흐름이 main
밖으로 나가 종료된다.
따라서 catch
를 통해 나가는 코드 흐름을 다시 붙잡아 그대로 이어나가게 한다.
try {
System.out.print("숫자를 입력하세요 : ");
int num = Integer.parseInt(scanner.nextLine());
System.out.println("정상 실행");
} catch (Exception e) {
System.out.println("예외 발생");
}
System.out.println("프로그램 완료");
앞서 말한 상황처럼 정상입력의 경우 정상 실행
프로그램 완료
가 출력된다. 반대엔 예외 발생
이 출력 후 프로그램 완료
가 출력된다.
결과적으로 try – catch
는 에러가 발생했을 때, main
밖으로 나가는 코드 흐름을 붙잡아(catch) 정상적으로 구동시키고, 우린 그 흐름을 알아야 한다.
추가로 catch (Exception e)
의 Exception e
은 거의 대부분의 에러를 인지하는 다형성을 가지고 있다. 이는 파이썬에서도 마찬가지인데 이로 인해 에러들이 걸러지게 된다.
하지만 만약 에러마다 세부적으로 예외처리를 하고 싶다면 작은 에러부터 순서대로 처리하게 설정하면 된다. 만약 Exception e
로 먼저 catch
를 하면 다른 에러에 대한 catch
가 스킵될 것이다. 그리고 예외종류도 초기 단계에는 차라리 if
로 예외처리 간단하게 하는 것이 낫다.
java에 존재하는 변수는 다음과 같다.
지역 변수
정적 변수(oop)
매개 변수(method)
멤버 변수(oop)
그리고 각 변수마다 메모리 상에 생성되는 위치가 다르다.
Stack
: 지역변수, 매개변수
Data
: 정적변수
Heap
: 멤버필드
원래 Text
메모리를 포함해서 총 4개 영역이지만, 변수랑 아직 관련 없으니 넘어가자.
여기서 우리가 볼 것은 지역 변수
로 그 성질을 이해하여 {}
범위와 변수의 위치에 따른 코드 흐름과 발생하는 문제를 이해할 수 있어야 한다.
먼저 지역 변수의 성질은 자신이 속한 {}
안에서만 사용이 가능하다는 것이다.
따라서 여러 scope
가 있을 때 외부에서 만들어진 변수는 내부에서 사용할 수 있다.
반대로 내부에서 만들어진 것은 외부에서 사용할 수 없다.
다음 코드를 보면 int selectMenu = 0;
은 가장 최상단의 영역에서 만들어진 변수이다.
따라서 내부의 if
에서 사용이 가능하다.
int selectMenu = 0;
while (true) {
try {
System.out.println("<<<<<< 경마게임에 오신 것을 환영합니다 >>>>>>");
System.out.println(
"1. 게임시작\n"
+"2. 잔액충전\n"
+"3. 잔액조회\n"
+"4. 게임종료\n"
+"메뉴를 선택해주세요");
System.out.print(">> ");
selectMenu = Integer.parseInt(scanner.nextLine());
} catch (Exception e) {
System.out.println("숫자만 입력하세요.");
}
// 예외 처리 (조건 8)
if (!(selectMenu == 1 || selectMenu == 2 || selectMenu == 3 || selectMenu == 4) ) {
System.out.println("메뉴를 다시 입력하세요.\n");
continue
여기서 입력을 받으면서 동시에 초기화가 되면 안될까?
생각과 달리 int selectMenu = 0;
을 int selectMenu;
로 변경하면 내부 코드들에 붉은 줄이 그어진다. 이는 Java라는 언어의 안정성과 '지역변수'문제 때문이다.
먼저 지역 변수를 위해서 while
문 안에 변수 선언을 했지만, 결과는 여전했다. 여기서 흔히 지역변수 문제는 다음과 같다.
원래 메모리는 Text
포함해서 총 4개의 영역으로 이뤄지는데, 변수랑 아직 관련 없으니 넘어가자.
집중할 것은 stack
으로 운영체제와 사용자가 함께 사용하는 공동 메모리이다.
그래서 이 메모리는 사용자 간의 구분이 없다.
따라서 지역변수를 생성하고 초기화를 하지 않으면, 그 변수에 운영체제가 사용하다가 버린 쓰레기값이 담길 가능성이 있다.
Java 컴파일러는 프로그램의 안정성을 위해 초기화되지 않은 변수는 제어문에서 확인된 예외로 처리한다.
그러나 결과는 try-catch
의 문제였다. 즉, 컴파일러의 안정성 상 에러가 발생하면 하단의 if
문의 값을 입력받지 못헤 조건식이 수행이 안되기 때문이다.
따라서 이런 문제들을 해결하려면 main()
의 지역에서 초기화하여 컴파일러가 에러를 일으키지 않도록 해야 한다. 물론 개발에 있어서 항상 초기화하는 습관을 가지는 것이 좋다.
배열 자체의 난이도는 높지만 당장의 시작과정에서 중요도는 높지 않다.
물론 C언어에서는 배열이 중요했지만 Java로 넘어가면 더 좋은 배열의 기능들이 무수히 많아진다.
하지만 배열을 이해하지 않으면 그 개념들을 사용하기 힘들어짐 따라서 중요한 부분이다.
그럼 배열은 무엇일까?
동일한 타입을 가지는 / 변수들의 집합
이 개념의 핵심은 "변수들의 집합"이다.
프로그램의 규모와 유형에 따라 변수의 개수는 늘어나기 때문에 대규모의 변수를 다루기 위해, 배열이라는 문법이 생겨났다. 하지만 배열 역시 사용하기 위해선 동일한 타입끼리만 가능하다.
배열을 사용하는 방법은 다음과 같다.
int[] arr; // 배열의 주소를 저장할 참조변수를 만든 것
여기서 배열의 길이는 기본형 변수처럼 고정적이지 않다. 즉, 그 길이와 규모는 사용자의 입력에 따라 달라질 수 있기에 가변적인 성질이라는 것을 알 수 있다.
따라서 변수의 집합일지라도 실제 배열의 데이터는 stack
의 arr
라는 공간에 들어가지 않는다.
우리가 선언한 arr
는 배열의 주소를 저장하는 참조변수
일 뿐이다. 그래서 실제 배열을 생성하기 위해서는 new
를 사용해 heap
영역에 배열의 공간을 만들어야 한다.
int[]
int형 배열이라는 또 하나의 자료형이다.
Heap
은 사용자만 사용하는 영역이기 때문에 공백값이 기본이다.
new
new int[4];
따라서 위 코드를 입력하면 Heap
메모리에 int
형 변수 4개가 있는 배열을 만들어지고, 실제로 우리가 사용할 때는 참조변수에 대입하며, 그때는 배열의 주소 값을 반환한다.
int[] arr = new int[4];
int[] arr = 주소값;
하지만 이 경우 배열 안의 값들이 초기화되지 않은 빈 배열일 뿐이다. 이제 참조변수 arr
로 배열에 접근해 index
마다 값을 입력해준다. 인덱스는 offset
의 개념으로 접근하여 arr
로부터 0부터 시작된다.
다음과 같이 각 배열에 위치에 초기값을 저장한다.
arr[0] = 10; .
arr[1] = 10;
arr[2] = 10;
arr[3] = 10;
그치만 위의 방법은 너무 비효율적이다. 따라서 다음과 같이 생략해서 사용한다.
int[] arr2 = new int[] {10, 20, 30, 40};
이 경우는 선언부에서만 가능하며 배열의 길이를 정해서 입력하지 않는다.
그리고 index
를 다룰 때 숫자를 바로 입력하지 않아도 된다. 이로 인해 결과가 숫자가 되는 연산 처리 또한 가능하다. 이를 응용해 배열을 반복문과 함께 다양한 방식으로 사용한다.
System.out.println(arr[2-2]);
System.out.println(arr[(int)(Math.random()*2)]);
// int 형 변수 100개를 집합시킨 arr4 배열을 만들고, 1~100까지 저장해보세요
int[] arr4 = new int[100];
for (int i=0; i<100; i++) {
arr4[i] = i+1;
System.out.print(arr4[i] + " ");
if (arr4[i] % 10 ==0 ) {
System.out.println();
}