JAVA STUDY(with whiteship) 4주차

hwk·2020년 12월 11일
0

JAVA STUDY

목록 보기
3/12

제어문

목표

자바가 제공하는 제어문 학습.

목차

  1. 조건문
    • if, else
  2. 선택문
    • switch와 break
  3. 반복문
    • for, while, do~while
    • continue & break
    • 반복문의 중첩
    • foreach

1. 조건문

- if, else

  • if~else문의 기본 골격.

[if문]

if (true or false)
{
	// true일 때 실행되는 영역
}

[if / else 문]

if (true or false)
{
	// true일 때 실행되는 영역
}
else
{
	//false일 때 실행되는 영역	
}

기본적으로 if의 괄호 안에는 true나 false를 반환하는 연산문이 온다.

  • if~else문의 중첩과 중괄호 생략

실행영역에서 하나의 문장으로 이뤄진 경우에는 중괄호를 생략할 수 있다.

if(num<0)
	System.out.println("0 미만);
else
{
	if(num<100)
    	System.out.println("0이상 100미만");
	else
		System.out.println("100 초과");
}

하지만 전부 제거한 것은 아니다. if~else문은 하나의 문장이기 때문에 다음과 같이 모두 중괄호를 제거할 수 있다.

if(num<0)
	System.out.println("0 미만);
else
	if(num<100)
		System.out.println("0이상 100미만");
	else
		System.out.println("100 초과");

여기서 라인을 정리하면

if(num<0)
	System.out.println("0 미만);
else if(num<100)
	System.out.println("0이상 100미만");
else
	System.out.println("100 초과");

조건부 이후의 문장이 하나의 문장으로 이뤄진 경우에는 중괄호를 생략할 수 있다는 규칙을 이용한 정리이다.

2. 선택문

- switch와 break

  • switch문의 기본 구성
switch(n)
{
case 1:
	~~~ // n이 1일 때의 실행문실행문
case 2:
	~~~ // n이 2일 때의 실행문실행문
    
    
    .
    .
    .
    
    
case 5:
	~~~ // n이 3일 때의 실행문
default:
	~~~ // n이 case 레이블이 아닐 때는 여기서 부터 실행
}

switch뒤에 소괄호의 n은 정수가 와야하고, 정수를 반환하는 연산식도 올 수 있다.
break문 이 없으므로 n의 레이블 위치부터 switch문의 마지막 까지 실행한다.
그래서 위 코드는 어느 경우에서든지 default의 실행문이 동작된다.

  • 일반적인 switch문
switch(n)
{
case 1:
	~~~ // n이 1일 때의 실행문실행문
	break;
case 2:
	~~~ // n이 2일 때의 실행문실행문
	break;
    
    .
    .
    .
    
    
case 5:
	~~~ // n이 3일 때의 실행문
	break;
default:
	~~~ // n이 case 레이블이 아닐 때는 여기서 부터 실행
}

switch문의 일반적인 사용 모델이다.
n의 레이블부터 마지막까지의 모든 레이블을 거치는 것 보다는
위 처럼 해당 영역만 실행되는 것이 더 자주 쓰일 코드이다.

  • 케이스 중첩
switch(n)
{
case 1: case 2: case 3:
	~~~ // n이 1일 때의 실행문
	break;  
case 4:    
case 5:
	~~~ // n이 3일 때의 실행문
	break;
default:
	~~~ // n이 case 레이블이 아닐 때는 여기서 부터 실행
}

3. 반복문

- for, while, do~while

  • while문
int num = 0;

while( num<7 )
{
	// 실행 영역
	system.out.println("Thank you, white ship" + num);
	num++
}

if문과 마찬가지로 조건절에 true 또는 false를 반환하는 연산문으로 구성할 수 있고
이 조건이 true인 동안에 실행 영역을 반복 실행한다.

while문은 반복조건을 먼저 검사한 후 반복영역의 실행을 결정하기 때문에 반복 영역이 한 차례도 실행되지 않을 수 있으며, 조건절이 항상 true가 되어 무한루프가 발생되지 않도록 주의해야 한다.

  • do~while문
do
{
	// 실행 영역
	System.out.println("Thank you, white ship" + num);
	num++;
} while(num<7);

조건부 보다 실행영역이 먼저이기 때문에 반복조건이 만족하지 않더라도 한차례는 실행된다.
그래서 최소한 한 차례의 실행을 필요로 하는 경우에 적합하다.

  • for문
for (int num=0; num<5; num++)
{
	System.out.println("Thank you, white ship")
}

while문과 for문에는 반복의 횟수를 시작부터 세기위한 변수(int num = 0;), 반복의 조건(num<5;), 반복의 조건을 break하기 위한 연산(num++)이 있다.

for문은 while문과 달리 반복을 구성하는 요소를 조건절 한 줄에 나열한다. 이것이 for의 장점이다.

실행의 흐름은 아래와 같이 볼 수 있다.
1. 반복의 횟수를 시작부터 세기위한 변수
2. 반복의 조건
3. 실행문
4. 반복의 조건을 break하기 위한 연산

for문의 조건절에 comma 연산자를 사용하여 다음의 형태로 굿헝하는 것도 가능하다.

for (int i=0,j=7; i<j; i++,j--)
	System.out.println("Thank you, white ship" + i + j);
        /* 출력
        Thank you, white ship 07
        Thank you, white ship 16
        Thank you, white ship 25
        Thank you, white ship 34
        */

[2. 반복의 조건] 부분에는 사용할 수 없지만 [1.반복의 시작을 나타내기 위한 변수]와 [4.반복의 조건을 break하기 위한 연산] 부분에 comma 연산자를 사용하여 둘 이상 삽입이 가능하다.

- continue & break

  • break문
    앞의 switch문에서 처럼 반복문을 빠져나가는 용도로 사용할 수 있다.
for (int i=0; i<100; i++)
{
	if (num%7==0) {
		System.out.println(num)
		break;
	}
}

조건문을 만족 할 때, 실행영역에 break이 있다면 그 조건문을 감싸고 있는 반복문 하나를 빠져나가게 된다.
이중 반복문 일 때는 반복문 두개를 빠져나가기 위해서 outerLoop이라는 키워드를 사용하는데, 뒤에 설명하겠다.

  • continue문
    break문 처럼 반복문을 빠져 나가는 것이아니라 실행영역을 실행하지 않고 다시 조건 검사로 이동한다.
for (int i=0; i<100; i++)
{
	if (i%2==0) {
		System.out.println(i+" 에서 조건 검사로 이동")
		continue;
	}
	System.out.println("i는 if문 밖)
    
	/*
	1
    2 에서 조건 검사로 이동
    3
    4 에서 조건 검사로 이동
    .
    .
    .
    99
    */
}
  • 무한루프와 break
    '무한루프' 라고 하면 프로그램을 로직을 잘못 작성한 듯 한 부정적인 생각이 먼저 떠오른다.
    하지만 break과 함께 사용하여 의미를 부여할 수 있고 유용하게 사용할 수 있다.
while (true)
{
	if(num&6==0 && num%14==0)
		break;
	num++
}
System.out.println(num);

6의 배수이면서 14의 배수가 되는 자연수의 크기를 예측할 필요 없이 바로 값을 도출할 수 있다.

챗봇 프로그램에서도 무한루프가 구현된다고 하는데,
채팅이 끝나는 시점 까지 사용자의 말에 대답하는 로직도 이러한 방식으로 구현될 것 같다는
생각이 든다.

- 반복문의 중첩

  • 자주 사용되는 중첩, for문 중첩
    while문, do while문, for문 세 가지를 모두 중첩하여 반복문을 만들 수는 있으나
    for문 중첩이 가장 활용도가 높다.
    아래는 그 유명한 구구단이다.
for (int i=2; i<10; i++)
{
	for (int j=1; j<10; j++)
	{
		System.out.println(i + " x "+ j + " = " + i*j);
	}
}
  • 중첩된 반복문 탈출은 outerLoop
for (int i=2; i<10; i++)
{
	outerLoop :
	for (int j=1; j<10; j++)
	{
		System.out.println(i + " x "+ j + " = " + i*j);
        if (i%2==0 && j&2==0)
			break outerLoop;
	}
}

outerLoop 키워드를 사용하여 원하는 위치로 빠져나갈 수 있다.
'break outerLoop;' 는 'outerLoop:' 라고 표시되어 있는 반복문을 빠져나가겠다는 의미이다.
만약 위 코드에 outerLoop가 아닌 break을 썼다면 중첩문 두개를 모두 빠져나가는 것이 아니라 if문을 감싸는 for문 하나만 빠져나간다.

- for each

  • 배열을 사용할 때 '배열 요소에 저장되어 있는 값을 전부 처리할 경우' 사용한다.
    ex)
int[ ] arr={1,2,3};
for(int i=0; i<arr.length; i++)
	System.out.print(arr[i]+" ");

위와 같은 코드를 아래와 같이 간단히 작성할 수 있다.

for(int e : arr)
	System.out.print(e+" ");

코드의 분량이 짧아졌다.
'for-each'문을 보는 순간 "배열 요소 값을 전부 처리하려는 의도" 임을 쉽게 파악할 수 있기 때문에 가독성도 좋다.

  • 의미
int[] arr={1, 2, 3, 4, 5};

int sum=0;
for(int e : arr)
	sum+=e;

System.out.prinln("배열 요소의 합 : "+sum); // 15 출력

위에 예시로 든 for-each문에서 arr, e가 있다.
arr은 배열의 이름을 의미하는 것.

e는 모든 요소에 적용할 참조 방법으로 쓰인다. 배열 요소를 지칭하기 위한 변수로써, 배열의 모든 요소 각각을 대상으로 연산이 진행된다.
그렇기 때문에 배열 요소를 지칭하는 변수인 e는 배열 요소의 자료형과 일치해야한다.

위의 예 처럼 java int[] arr={1,2,3}; 에서 arr이 int형 배열로 선언되었기 때문에
for-each문의 e 또한 참조할 배열에 맞추어 int형으로 작성하여 준다.
만약에 일치하지 않으면 형 변환 규칙에 의해서 자동으로 형 변환이 되거나 컴파일 에러가 발상하게 된다.

  • 배열 요소의 실제 값을 변경시키지 않음.
int[] arr={1, 2, 3, 4, 5};

for(int e : arr)
{
	e++;
    System.out.print(e+" "); // 2 3 4 5 6 출력
}

System.out.println("");
for(int e : arr)
    System.out.print(e+" "); // 1 2 3 4 5 출력

위와 같이 변경된 변수 e의 값은 for-each문 내에서만 의미를 지니므로,
실제 배열 요소의 값을 변경시키지는 않는다.

  • for-each 안에서 인스턴스 배열에 대한 실제 배열 값 변경

여기서는 약간 복잡하지만 꼭 기억해두어야 할 사항이다. for-each의 e라는 변수의 원리를 생각하면 쉽게 이해할 수 있다.

위에서 "for-each문은 값의 참조만 가능하고, 실제 배열 값의 변경은 불가능" 함에 대한 정의 때문에
인스턴스 배열에서는 이것이 어떻게 적용되는지 알아보자.

결론부터 말하면, for-each문에서의 인스턴스 배열은 배열의 실제 참조 값 변경 불가 로 적용된다.

우선 인스턴스 배열은 인스턴스로 이뤄진 배열이 아닌, '인스턴스의 참조 변수로 이뤄진 배열' 이라는 사실을 잊지 않는다면 쉽게 생각할 수 있다.

예를 들어, 아래와 같은 코드가 있을 때,

class Number
{
	public int num;
	public Number(int num) {this.num=num;}
	public int getNum() { return num}
}

class TestForInst
{
	public static void main(String args)
	{
		Number[] instArr = new Number[] {
			new Number(1),
			new Number(3),
			new Number(5)
		};
        
		for(Number e : instArr)
			e.num++;
            
		for(Number e : instArr)
			System.out.print(e.getNum() + " "); // 2 4 6 출력

		System.out.println("");
        
		for(Number e : instArr)
		{
			e=new Number(5);
			e.num+=2;
			System.out.print(e.getNum() + " "); // 7 7 7 출력
		}
        
		System.out.println("");
        
		for (Number e : instArr)
			System.out.print(e.getNum() + " "); // 2 4 6 출력
	}
}
           

for-each문 안에서 int[] arr에 대한 경우,
e가 나타내는 것은 각각의 "1,2,3,4,5" 라는 인스턴스이다.

하지만 Number[] instArr = new Number[] 의 배열의 경우,
e가 나타내는 것은 new [Number] 라는 인스턴스의 참조 변수이다.
즉 inst 배열에 저장되어 있는 참조 값의 변경이 불가능하다.

그렇기 때문에 인스턴스 배열에 대한 for-each 문 안에서 e의 값을 변경할 때
배열에 저장되어 있는 참조 값의 변경이 불가능 하다.
참조 값이 변경이 불가능 한 것이지, 인스턴스 배열의 멤버 변수(인스턴스 변수)에 저장된 값은 변경할 수 있다.

이 원리는 for-each문 안에서 e라는 변수에 대한 생명주기나 JVM에서의 흐름제어와 관련된 것으로 추측된다.

정리해보자면,
1. for-each 문에서 e라는 변수는 for-each문 안에서만 의미가 있다.
2. 그렇기 때문에 e가 가리키는 요소도 for-each문 안에서만 의미가 있다.
3. int[ ] arr={1,2,3}; 이 경우, e가 가리키는 것은 arr의 {1, 2, 3, 4, 5} 라는 각각의 숫자 값이다. 그래서 for-each문 안에서는 각각의 숫자 값이 변경 가능 하지만,
for-each문 바깥에 정의 되 어있는 실제 배열의 숫자 값은 변경 할 수 없다.
Number[] instArr = new Number[] 이 경우, e가 가리키는 것은 instArr에 저장되어 있는 참조 값이다. 그래서 실제 인스턴스 배열의 참조 값은 변경할수 없지만, e를 통해 배열에 저장되어 있는 참조 값을 통해서 인스턴스의 값을 변경시킬 수는 있다.

이것이 for-each문 안에서 일반 배열과 인스턴스 배열의 차이점이다.

# End

참고 서적 : 난 정말 JAVA를 공부한 적이 없어요 (ORANGE MEDIA 윤성우 저)

profile
Elegant Dev

0개의 댓글