이것이 자바다. 3~4

    char 타입의 데이터를 연산할 때 
    	 char c1 ='A' + 1;
    	 char c2 = 'A';
    	 //char c3 = c2 + 1;	//컴파일 에러
    	 System.out.println("c1: " + c1);
    	 System.out.println("c2: " + c2);
    	 //System.out.println("c3: " + c3);

    1행은 아무런 문제가 없지만, 3행에서는 컴파일 에러가 생긴다. 

    이런 결과가 나타나는 이유는 자바의 리터럴 연산 방벙 때문이다. 자바에서는 리터럴 간의 연산은 타입 변환이 일어나지 않고 해당 타입으로 계산하기 때문에 1행에서는 아무런 문제가 발생하지 않는 것이다. ( 1행은 "char c1 = 66;" 이라는 평범한 초기화 식이 된다.) 반면에 3행에서는 리터럴 간의 연산이 아닌, c2라는 변수와 정수 리터럴 1의 연산이 일어나면서, char 타입인 c2가 int형으로 변환되어 계산이 일어난다. 따라서 이 연산식의 결과값은 int형이 되기 때문에 컴파일에러가 발생한다.

     

    float, double형 연산할 때 발생하는 오차
    public class AccuracyExample1 {
    	public static void main(String[] args) {
    		int apple = 1;
    		double pieceUnit = 0.1;
    		int number = 7;
    		
    		double result = apple - number * pieceUnit;
    		
    		System.out.println("사과 한개에서 ");
    		System.out.println("0.7 조각을 빼면. ");
    		System.out.println(result + "조각인 남는다.");
    	}
    }
    사과 한개에서 
    0.7 조각을 빼면. 
    0.29999999999999993조각인 남는다.

     

    이전 포스팅에서 설명했듯이, 디지털 컴퓨터는(부동소수점 수를 사용하는 컴퓨터) 0.1을 제대로 표현하지 못 한다. 잠깐 기억을 떠올려 보자면. 컴퓨터가 0.1을 2진수로 표현하는 과정에서 순환소수가 되고, 컴퓨터가 표현할 수 있는 한계에 다다라 정확한 표현이 아닌 근사값을 사용하게 됐었다.  일반적으로 실수 변수는 절대 정확한 값을 가지고 있지 않는다!는 것을 항상 명심하자. 따라서 이와 같은 코드는 아래와 같이 적절히 정수형으로 계산 후 캐스팅해준다.

    public class AccuracyExample2 {
    	public static void main(String[] args) {
    		int apple = 1;
    		
    		int totalPieces = apple * 10;
    		int number = 7;
    		int temp = totalPieces - number;
    		
    		double result = temp / 10.0;
    		
    		System.out.println("사과 한개에서 ");
    		System.out.println("0.7 조각을 빼면. ");
    		System.out.println(result + "조각인 남는다.");
    	}
    }
    사과 한개에서 
    0.7 조각을 빼면. 
    0.3조각인 남는다.

     

    "/", "%" 연산 시, 우측피연산자가 0 인 경우

    자바에서 "/"와 "%" 연산을 수행할 때, 피제수가 정수 타입인 경우, 제수는 0이 될 수 없다.

    만일 0으로 나누는 경우, 컴파일은 정상적으로 되지만 실행 시 ArithmetiException(예외)이 발생한다.

    자바는 프로그램 실행 도중 예외가 발생하면 실행이 즉시 멈추고 프로그램이 종료된다. 따라서 불시에 프로그램이 종료되는 것을 막기 위해 개발자는 항상 예외처리를 생각해야 한다. 예외처리는 예외가 발생되었을 경우, 프로그램이 종료되지 않고, 해당 예외의 catch 블록을 실행하는 것이다. 아래 코드를 보자.

    try {
    	//int z = x / y;
        int z = x % y;
        System.out.println("z: " + z)
    } catch (ArithmeticException e) {
    	System.out.println("0으로 나누면 안됨");
    }

    2-3번 째 줄에서 y가 0인 경우 제수가 0인 나눗셈이 되기 때문에, ArithmeticException이 발생한다. try-catch 문을 사용하여 예외처리를 해주면, 프로그램을 종료시키는 대신 catch 블럭안에 명령어가 실행된다. 

     

    그러나 실수 타입인 0.0 또는 0.0f로 나누게 되면, ArithmeticException이 발생하지 않고 "/"연산의 결과는 Infinity(무한대) 값을 가지며, "%"연산의 결과는 NaN(Not a Number)을 가진다. 예외가 발생하지 않기 때문에 정상적으로 실행되는 것처럼 보이지만, 연산의 결과가 Infinty 또는 NaN인 경우 다음 연산에 어떤 수와 연산하더라도 Infinity/NaN이 되기 때문에 예상하지 못 한 데이터가 도출될 수 있다. 자바에는 이런한 값을 확인할 수 있는 Double.isInfinite() 메서드와 와 Double.isNaN() 메서드가 존재한다. 따라서 연산의 결과가 Infinity / NaN이 나올 수 있는 경우(특히나 사용자에게 입력받아 연산을 실행하는 경우) 절대적으로 if문에 위에 두가지의 메서드를 사용해서 이상한 데이터가 들어오는 것을 막아야 한다.

     

    String 동등 비교 연산자 사용 시 주의할 점.
    		String strVar1 = "신민철";
    		String strVar2 = "신민철";
    		String strVar3 = new String("신민철");

    자바에서는 문자열 리터럴이 동일하다면 동일한 String 객체를 참조하도록 되어 있다. 따라서 strVar1과 strVar2는 동일한 String 객체의 번지값을 가지고 있다. 그러나 strVar3은 객체 생성 연산자인 new로 생성한 새로운 String 객체의 번지를 참조하기 때문에 아래와 같은 결과가 나온다.

    strVar1 == strVar2 -> true
    strVar1 == strVar3 -> false

    만약 동일 객체인지가 아닌, 단순 문자열 '값'이 동일한지 확인하기 위해서는 ==연산자 대신 equals() 메서드를 사용해야 한다.

    package Java_3;
    
    public class StringEqualsExample {
    	public static void main(String[] args) {
    		String strVar1 = "신민철";
    		String strVar2 = "신민철";
    		String strVar3 = new String("신민철");
    		
    		System.out.println(strVar1 == strVar2);
    		System.out.println(strVar1 == strVar3);
    		System.out.println();
    		System.out.println(strVar1.equals(strVar2));
    		System.out.println(strVar1.equals(strVar3));
    		
    	}
    }
    true
    false
    
    true
    true

     

    논리연산자 "&&",  " &",  " ||",  " |"

    && 와 & , || 과 |는 같은 산출결과를 갖지만 연산과정이 조금다르다.

    &&는 앞의 피연산자가 false라면 뒤의 피연산자를 평가하지 않고 바로 false라는 산출결과를 낸다. AND 연산의 경우 하나라도 false라면 결과가 false가 되기 때문이다. 그러나, &는 두 피연산자를 모두 평가해서 산출 결과를 낸다. 

    || 과 | 의 경우에도 마찬가지로, 일반적인 상황에서 && / || 가 & / | 보다 효율적으로 동작한다.

     

     

    비트 연산자 "&"  "|"  "^"  "~"  "<<"  ">>"  ">>>"

    "&"  "|"  "^"  "~"는 비트 논리연산자로, "<<"  ">>"  ">>>"는 비트 이동 연산자로 사용된다.

     

    '&' 와 '|'은 일반 논리연산자이면서 비트 논리연산자로도 사용된다. 비트 논리연산자는 bit 단위의 0과 1을 대상으로 연산하기 때문에, 정수형 변수/리터럴에만 사용이 가능하다. '&' 와 '|' 의 피연산자로 boolean 타입이 오게되면 일반 논리연산자로 사용되는 것이고, 정수형 타입이 오게되면 비트 논리연산자로 사용된다. 비트 논리연산자의 경우에도 기본 int타입으로 변환되어 연산되므로 그 결과값도 int타입이 나온다.

     

    비트 이동연산자(shift 연산자)는 정수 데이터의 비트를 좌측 또는 우측으로 밀어서 이동시킨다.

    2진수 정수를 좌측으로 한 칸 이동시키면 곱하기 2를 한 것과 같고, 우측으로 한 칸 이동시키면 나누기 2배를 한 것과 같다.

    주의할 점은 "<<"과 ">>>"는 비트를 이동시키고 생긴 빈자리를 0으로 채우고, ">>"는 빈자리를 최상위 부호비트(MSB)와 같은 값으로 채운다. 음수인 정수에 ">>>"를 사용할 경우 나누기를 한 값이 아닌, 예상치 못 한 값이 나올 수 있으니 주의해야한다.

    package Java_3;
    
    public class BitShiftExample {
    	public static void main(String[] args) {
    		System.out.println("1 << 3 = " + ( 1<<3 ));
    		System.out.println("-2 << 3 = " + ( -2<<3 ));
    		System.out.println("-8 >> 3 = " + (-8>>3));
    		System.out.println("-8 >>> 3 = " + (-8>>>3));
    	}
    }
    1 << 3 = 8
    -2 << 3 = -16
    -8 >> 3 = -1
    -8 >>> 3 = 536870911

     

    Math.random(), 임의의 정수를 뽑는 방벙

    자바에서는 임의의 수, 난수를 생성하기 위한 방법으로 Math클래스의 random() 메소드를 지원한다. 

    이 메소드는 0.0과 1.0 사이에 속하는 double 타입의 난수 하나를 리턴한다. (0.0은 포함, 1.0은 미포함)

    0.0 <= Math.random() < 1.0

    그렇다면 이 random 메소드를 이용해서 1~10까지의 정수를 어떻게 얻을 수 있을까

    0.0 <= Math.random() < 1.0
    각 변에 10을 곱한다
    
    0.0 <= Math.random() * 10 < 10.0
    이제 int형으로 형변환을 해준다.
    
    0 <= (int) Math.random()*10 < 10
    "1~10"까지의 값을 얻기 위해 각 변에 1을 더해준다.
    
    1 <= ((int) Math.random()*10) + 1 < 11

    위와 같은 방법으로 start부터 시작하는 n개의 정수를 구하는 식을 만들면, 아래와 같다.

    int num = (int) (Math.random() * n) + start;

     

    System.in.read() 키보드에서 입력된 키를 확인하는 법

    java에서 지원하는 기본 라이브러리의 read() 메소드는 자바에서 컴퓨터를 다루는 System 클래스의 멤버변수 in 스트림이 가지고 있는 메소드로 키보드로부터 입력된 키 코드를 int형으로 리턴한다.

    당연하게도 System.in.read() 메소드를 사용하기 위해선 예외처리를 해줘야한다. (대표적으로 IOException..)

    조심해야될 부분은 System.in.read()로 문자를 입력받고 엔터를 치면, 버퍼에는 캐리지리턴(13)과 라인피드(10)가 남게된다. 따라서 계속해서 입력을 받아야 하는 상황이라면, 캐리지 리턴과 라인피드는 무시하도록 코드를 짜야한다.

    package Java_4;
    
    import java.io.IOException;
    
    public class WhileKeyControlExample {
    	public static void main(String[] args) throws IOException {
    		boolean run = true;
    		int speed = 0;
    		int keyCode = 0;
    		
    		while (run) {
    			
    			if (keyCode != 13 && keyCode != 10) {
    				System.out.println("---------------------");
    				System.out.println("1.증속 | 2.감속 | 3.중지");
    				System.out.println("---------------------");
    				System.out.print("선택 :");
    			}			
    			keyCode = System.in.read();
    			
    			if(keyCode == 49) {
    				speed++;
    				System.out.println("현재속도 : " + speed);
    			}
    			else if(keyCode == 50) {
    				speed--;
    				System.out.println("현재속도 : " + speed);
    			}
    			else if(keyCode == 51) {
    				run = false;
    			}
    		}
    		System.out.println("시스템 종료");
    	}
    }

     

    Scanner 문자열의 입력받는 방법

    위에서 본 System.in.read() 메소드는 하나의 키 코드만 읽기 때문에 문자열을 읽으려면 다른 방법을 써야한다.

    Scanner는 문자열을 읽기 위해 제공되는 클래스로, 사용자는 Scanner 클래스의 객체를 생성하고 멤버 메소드를 호출하여 문자열을 읽어들일 수 있다. 대표적으로 지원하는 메소드는 nextLine(), nextInt() 등이 있다.

     

    Scanner scanner = new Scanner(System.in);
    String input = scanner.nextLine();

    Scanner의 인스턴스를 생성하고, nextLine() 메소드를 호출한다. 당연히 Scanner 클래스를 사용하기 위해서는 사용 전에 Scanner 클래스를 import 해줘야 한다. Scanner 클래스는 util 아래 java.util.Scanner에 위치해 있다.

     

    package Java_4;
    
    import java.util.Scanner;
    
    public class DoWhileExample {
    	public static void main(String[] args) {
    		System.out.println("메시지를 입력하세여");
    		System.out.println("프로그램을 종료하려면 q를 입력하세요.");
    		
    		Scanner scanner = new Scanner(System.in);
    		String ans;
    		
    		do {
    			ans = scanner.nextLine();
    			System.out.println(ans);
    		} while(!ans.equals("q"));
    		
    		System.out.println("\n프로그램 종료");
    	}
    }

     

    break의 또 다른 사용

    반복문이 중첩되어 있을 경우, break는 일반적으로 가장 가까운 반복문만을 종료시키고 바깥쪽 반복문은 종료시키지 않는다. 이번에는 중첩된 반복문에서 바깥쪽 반복문을 종료시키는 방법을 소개하려 한다.

    뭐 사실 소개라고 하기엔, 굉장히 단순한 방법이다. 단지 종료시키고 반복문에 이름(Label)을 붙이고 break Label;을 호출하면 된다.

    package Java_4;
    
    public class BreakOutterExample {
    	public static void main(String[] args) {
    		Outter: for(char upper ='A'; upper<='Z';upper++) {
    			for(char lower='a';lower<='z';lower++) {
    				System.out.println(upper + "-" + lower);
    				if(lower=='g') {
    					break Outter;
    				}
    			}
    		}
    		System.out.println("프로그램 실행 종료");
    	}
    }

    바깥쪽 for문에 Outter라고 이름을 주고 중첩된 for 블럭안에서 break Outter를 호출하여 바깥쪽 for문을 종료시켰다.

    A-a
    A-b
    A-c
    A-d
    A-e
    A-f
    A-g
    프로그램 실행 종료

    댓글