산술 연산자인 사칙 연산자, 나머지 연산자, 쉬프트 연산자는 이항 연산자이다. 이항 연산자는 피연산자의 크기가 4 byte 보다 작으면 4 byte(int 형) 로 변환한 다음 연산을 수행한다. (byte, char, short → int)

또, 연산을 수행하기 전에 피연산자들의 타입을 일치시킨다.

 

3.1 사칙 연산자 +, -, *, /

1. int 형(4 byte) 보다 크기가 작은 자료형은 int 형으로 형변환 후에 연산을 수행한다.

    - byte + short → int + int → int

2. 두 개의 피연산자 중 자료형의 표현범위가 큰 쪽에 맞춰서 형변환 한 후 연산을 수행한다.

    - int + float → float + float → float

3. 정수형 간의 나눗셈에서 0으로 나누는 것은 금지된다.

    - 0으로 나누면, 컴파일은 정상적으로 되지만 실행 시 오류가 발생한다.

[참고] 위의 규칙은 덧셈 연산자를 포함한 모든 이항 연산자에 공통적으로 해당한다.

 

public class ex2 {
	public static void main(String[]args) {
		boolean power = false;
		byte a = 10;
		byte b = 20;
		byte c = (byte)(a + b); //컴파일 에러가 발생하기 때문에 명시적 형변환 필요
		System.out.println(c);
	}
}
[실행 결과]
30

큰 자료형의 값을 작은 자료형의 변수에 저장하려면 반드시 명시적 형변환을 해야 한다.

[참고] byte c = (byte)a+b; 라고 해도 에러가 발생하는데, 그 이유는 캐스트 연산자는 단항 연산자이므로 이항 연산자인 덧셈 연산자보다 연산 순위가 높다. 그래서 (byte)a 가 먼서 수행된 다음 덧셈이 수행되므로, (byte)a 가 캐스트 연산자에 의해서 byte 형으로 형변환된 후에 다시 + b 의 덧셈 연산자에 의해서 int 형으로 변환된다.

 

public class ex2 {
	public static void main(String[]args) {
		byte a = 10;
		byte b = 30;
		byte c = (byte)(a*b);
		System.out.println(c);
	}
}
[실행 결과]
44

10*30 의 결과는 300이지만, 큰 자료형에서 작은 자료형으로 변환하면 데이터의 손실이 발생하여 값이 바뀔 수 있다. 300은 byte 형의 범위를 넘기 때문에 데이터 손실이 발생하여 44가 출력된다.

byte 형에서 int 형으로 변환하는 것은 2진수 8자리에서 32자리로 변환하는 것이기 때문에 자료 손실이 일어나지 않는다. 기존의 8자리는 유지하고 나머지는 모두 0으로 채운다. 음수인 경우에는 부호를 유지하기 위해 0 대신 1로 채운다.

반대로 int 형을 byte 형으로 변환하는 경우 앞의 24자리를 없애고 하위 8자리만 유지한다.

byte 형의 범위인 -128 ~ 127 의 범위를 넘는 int 형을 byte 형으로 변환하면 원래의 값이 유지되지 않고 byte 형의 범위 중 한 값을 가지게 된다. 이러한 값 손실을 예방하기 위해서는 큰 자료형을 사용해야 한다.

 

public class ex2 {
	public static void main(String[]args) {
		int a = 1000000;
		int b = 2000000;
		long c = a*b;
		System.out.println(c);
	}
}
[실행 결과]
-1454759936

int 형과 int 형의 연산결과는 int 형이기 때문에 long 형으로 자동 변환되어서 long 형 변수인 c 에 저장되어도 결과를 같다. 올바른 결과를 얻기 위해서는 a, b 모두 long 타입으로 바꿔야 한다.

[참고] a, b 중 하나만 long 으로 바꿔도 결과는 long 으로 나온다

 

public class ex2 {
	public static void main(String[]args) {
		long a = 1000000 * 1000000;
		long b = 1000000 * 1000000L;
		System.out.println(a);
		System.out.println(b);
	}
}
[실행 결과]
-727379968
1000000000000

b 는 int 와 long 의 연산이기 때문에 그 결과가 long 이다.

 

public class ex2 {
	public static void main(String[]args) {
		int a = 1000000 * 1000000 / 1000000;
		int b = 1000000 / 1000000 * 1000000;
		System.out.println(a);
		System.out.println(b);
	}
}
[실행 결과]
-727
1000000

먼저 곱하는 경우 int 의 범위를 넘어선다. 이처럼 연산의 순서에 따라서 결과가 달라질 수 있다.

 

public class ex2 {
	public static void main(String[]args) {
		char c1 = 'a'; //c1에는 'a'의 코드값인 97 저장
		char c2 = c1; //c1에 저장되어 있는 값이 c2에 저장
		char c3 = ' '; //c3을 공백으로 초기화
		
		int i = c1 + 1; //'a'+1 >>97+1>>98
		
		c3 = (char)(c1+1);
		c2++;
		c2++;
		System.out.println("i = "+i);
		System.out.println("c2 = "+c2);
		System.out.println("c3 = "+c3);
	}
}
[실행 결과]
i = 98
c2 = c
c3 = b

c1 + 1 을 계산할 때, c1 이 char 형이므로 int 형으로 변환한 후 덧셈연산을 수행한다. 

c2++ 은 형변환 없이 값을 1씩 두 번 증가되어 99가 된다. 99는 문자 'c' 이기 때문에 'c' 가 출력된다.

[참고] c2++ 대신 c2=c2+1 을 사용하게 되면 c2+1 의 연산결과가 int 형이기 때문에 에러가 난다. 결과를 다시 c2 에 담으려면 캐스트 연산자를 사용하여 char 형으로 형변환해야 한다.

 

public class ex2 {
	public static void main(String[]args) {
		char c1 = 'a';
		//char c2 = c1+1; 컴파일 에러 발생
		char c2 = 'a'+1;
		System.out.println(c2);
	}
}
[실행 결과]
b

int 보다 작은 타입의 피연산자를 int 로 자동형변환하지 않고, char c2 = (char)('a'+1); 로 형변환하지 않았는데도 에러가 없는 이유는 'a'+1 이 리터럴 간의 연산이기 때문이다. 상수 또는 리터럴 간의 연산은 실행과정동안 변하는 값이 아니기 때문에 컴파일 시, 컴파일러가 계산해서 그 결과로 대체함으로써 코드를 효율적으로 만든다.

컴파일러가 미리 덧셈연산을 수행하기 때문에 실행 시에는 덧셈 연산이 수행되지 않는다. 덧셈연산결과인 'b'를 변수 c2에 저장할 뿐이다.

그러나 c1+1 처럼 수식에 변수가 들어가 있는 경우에는 컴파일러가 미리 계산을 할 수 없기 때문에 char c2 = (char)(c1+1); 처럼 형변환을 해주어야 한다.

 

상수 또는 리터럴 간의 연산을 할 때 '86400' 이라는 값 대신 '60*60*24' 라고 풀어서 써도 컴파일러에 의해서 미리 계산되기 때문에 실행 시의 성능차이는 없다.

 

public class ex2 {
	public static void main(String[]args) {
		char c = 'a';
		for(int i=0; i<26; i++) { //블럭{} 안의 문장을 26번 반복
			System.out.print(c++); //'a' 부터 26개의 문자 출력
		}
		System.out.println(); //줄바꿈
		c = 'A';
		for(int i=0; i<26; i++) { //블럭{} 안의 문장을 26번 반복
			System.out.print(c++); //'A' 부터 26개의 문자 출력
		}
		System.out.println(); //줄바꿈
		c = '0';
		for(int i=0; i<10; i++) { //블럭{} 안의 문장을 10번 반복
			System.out.print(c++); //'0' 부터 26개의 문자 출력
		}
		System.out.println(); //줄바꿈
	}
}
[실행 결과]
abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ
0123456789

[참고] println 메서드는 값을 출력하고 줄을 바꾸지만, print 메서드는 줄을 바꾸지 않고 출력한다. 매개변수 없이 println 메서드를 호출하면, 아무것도 출력하지 않고 줄을 바꾸고 다음 줄의 처음으로 출력위치는 이동시킨다.

[참고] 대문자와 소문자 간의 코드값 차이는 10진수로 32이다.

 

public class ex2 {
	public static void main(String[]args) {
		char lowerCase = 'a';
		char upperCase = (char)(lowerCase - 32);
		System.out.println(upperCase);
	}
}
[실행 결과]
A

소문자를 대문자로 변경하려면 대문자와 소문자의 코드값 차이가 32이므로 소문자 a 의 코드값에서 32를 빼면 된다.

[참고] char 형과 int 형간의 뺄셈연산 결과는 int 형이므로 char 형으로 형변환해야 한다.

 

public class ex2 {
	public static void main(String[]args) {
		float pi = 3.141592f;
		float shortPi = (int)(pi*1000) / 1000f;
		System.out.println(shortPi);
	}
}
[실행 결과]
3.141

'int'/'int' 처럼 int 형 간의 나눗셈을 수행하면 결과가 float 나 double 이 아닌 int 가 나온다. 그리고 나눗셈의 결과를 버림하여 출력한다. 

위의 코드는 실수형 변수 pi 의 값을 소수점 셋째 자리까지만 빼내는 방법을 보여 준다.

shortPi 부분에서 가장 먼저 수행되는 것은 (pi * 1000) 이다. pi 가 float 이고, 1000이 정수형이니까 연산의 결과를 float 인 3141.5921 이 된다.

그 다음으로는 (int)(3141.592f) / 1000f; 가 되는데, 단항연산자인 캐스트연산자의 형변환이 수행된다. 3141.592f 를 int 로 변환하면 3141을 얻고, 소수점 이하는 반올림없이 버려진다.

다음엔 3141/1000f; 가 되는데, int와 float의 연산이므로, int가 float로 변환된 다음, float와 float의 연산이 수행된다.

마지막으로 3151.0f/1000f → 3.141f 는 float 와 float의 나눗셈이므로, 결과를 float로 나오게 된다.

[참고] 1000f는 1000.0f 와 같다.

 

public class ex2 {
	public static void main(String[]args) {
		float pi = 3.141592f;
		float shortPi = Math.round(pi*1000)/1000f;
		System.out.println(shortPi);
	}
}
[실행 결과]
3.142

round 메서드는 매개변수로 받은 값을 소수점 첫째자리에서 반올림하고 그 결과를 정수로 돌려준다. 위 코드는 소수점 넷째 자리에서 반올림하기 위해 1000을 곱했다가 다시 1000f로 나누었다.

 

2. 나머지 연산자 %

왼쪽의 피연산자를 오른쪽 피연산자로 나누고 난 나머지 값을 돌려주는 연산자이다. boolean 형을 제외한 모든 기본형 변수에 사용할 수 있고, 짝수, 홀수, 배수 검사 등에 주로 사용된다.

정수형 연산에서는 오른쪽 피연산자를 0으로 사용할 수 없지만, 0.0이나 0.0f로 나누는 것은 허용된다.

public class ex2 {
	public static void main(String[]args) {
		int share = 10/8;
		int remain = 10%8;
		System.out.println("몫은 "+share+" 나머지는 "+remain);
	}
}
[실행 결과]
몫은 1 나머지는 2
public class ex2 {
	public static void main(String[]args) {
		for(int i=1; i<=10; i++){ //i가 1부터 10이 될 때까지, {} 안의 문장 반복 수행
			if(i%3==0) { //i가 3으로 나누어 떨어지면, 3의 배수이므로 출력
				System.out.println(i);
			}
		}
	}
}
[실행 결과]
3
6
9

 

 

public class ex2 {
	public static void main(String[]args) {
		System.out.println(-10 % 8);
		System.out.println(10 % -8);
		System.out.println(-10 % -8);
	}
}
[실행 결과]
-2
2
-2

% 연산자의 왼쪽에 있는 피연산자(나눠지는 수)의 부호를 따르게 된다. 즉 피연산자의 부호를 모두 무시하고 나머지 연산을 한 결과에 나눠지는 수의 부호를 붙이면 된다.

 

3. 쉬프트 연산자 <<, >>, >>>

쉬프트 연산자는 정수형 변수에만 사용할 수 있는데, 피연산자를 2진수로 표현했을 때, 각자리를 오른쪽 또는 왼쪽으로 이동시킨다. 오른쪽으로 n자리를 이동하면 피연산자를 $2^n$ 으로 나눈 결과와 왼쪽으로 n자리를 이동하면 $2^n$ 으로 곱한 것과 같은 결과를 얻는다.

 

x << n = x * $2^n$

x >> n = x / $2^n$

<< : 피연산자의 부호에 상관없이 자리를 왼쪽으로 이동시키며 빈칸을 0으로 채운다.

>> : 오른쪽으로 이동시키기 때문에 음수인 경우 부호를 유지시켜주기 위해서 음수인 경우, 빈자리를 1로 채운다.

>>> : 부호에 상관없이 항상 0으로 빈자리를 채운다. 결과가 10진수보다 2진수로 표현했을 때 기호로서 더 의미를 가지므로, 10진 연산보다는 비트연산(2진 연산)에 주로 사용.

[참고] 숫자 n의 값이 자료형의 bit수 보다 크면, 자료형의 bit수로 나눈 나머지만큼만 이동한다. 

 

곱셈이나 나눗셈 연산자를 사용하면 같은 결과를 얻을 수 있는데, 굳이 쉬프트 연산자를 사용하는 이유는 속도 때문이다.

public class ex2 {
	public static void main(String[]args) {
		int temp;
		System.out.println(-8);
		//-8을 2진수 문자열로 변경
		System.out.println(Integer.toBinaryString(-8));
		temp = -8 << 2;
		System.out.println("-8 << 2 = "+temp);
		System.out.println(Integer.toBinaryString(temp));
		temp = -8 >> 2;
		System.out.println("-8 >> 2 = "+temp);
		System.out.println(Integer.toBinaryString(temp));
		temp = -8 >>> 2;
		System.out.println("-8 >>> 2 = "+temp);
		System.out.println(Integer.toBinaryString(temp));
	}
}
[실행 결과]
-8
11111111111111111111111111111000
-8 << 2 = -32
11111111111111111111111111100000
-8 >> 2 = -2
11111111111111111111111111111110
-8 >>> 2 = 1073741822
111111111111111111111111111110

 

'JAVA > 연산자' 카테고리의 다른 글

2. 단항 연산자  (0) 2020.02.04
1. 연산자 (operator)  (0) 2020.01.28

 

2.1 증감 연산자 ++, --

일반적으로 단항 연산자는 피연산자의 왼쪽에 위치하지만, ++와 --는 양쪽 모두 위치하는 것이 가능하다. 위치에 따라 연산결과가 달라진다.

 

증가 연산자(++) : 피연산자의 값을 1 증가시킨다.

감소 연산자(--) : 피연산자의 값을 1 감소시킨다.

 

boolean 형을 제외한 모든 기본형 변수에 사용 가능하다.

 

public class ex2 {
	public static void main(String[]args) {
		int i = 5;
		i++;
		System.out.println(i);
		i=5;
		++i;
		System.out.println(i);
	}
}
[실행 결과]
6
6

두 결과 모두 i의 값을 증가시킨 후 출력한다. 연산자의 위치가 다름에도 결과가 같은 이유는 어떤 수식에 포함된 것이 아니라 단독적으로 사용됐기 때문이다. 그러나 단독으로 사용되지 않고, 다른 수식에 포함되거나 함수의 매개변수로 사용된 경우에는 결과가 다르다.

 

public class ex2 {
	public static void main(String[]args) {
		int i = 5;
		int j = 0;
		j = i++;
		System.out.println("i++"+" i="+i+" j="+j);
		i = 5;
		j = 0;
		j = ++i;
		System.out.println("++i"+" i="+i+" j="+j);
	}
}
[실행 결과]
i++ i=6 j=5
++i i=6 j=6

전위형은 변수의 값을 먼저 증가시킨 후에 변수의 값을 읽어오지만, 후위형은 변수의 값을 먼저 읽어온 후에 값을 증가시킨다.

그렇기 때문에, ++i에서는 i의 값을 증가시킨 후에 읽어오기 때문에 i의 값이 5에서 6으로 증가된 후에 이 값이 j에 저장된다. i++에서는 i값인 5를 먼저 읽어온 후에 i를 증가시키기 때문에 j에 5가 저장된다.

 

함수의 매개변수에 증감 연산자가 사용된 예시

public class ex2 {
	public static void main(String[]args) {
		int i = 5, j = 5;
		System.out.println(i++);
		System.out.println(++j);
		System.out.println("i="+i+" j="+j);
	}
}
[실행 결과]
5
6
i=6 j=6

i 는 값이 증가되기 전에 참조되므로 println 메서드에 i 에 저장된 값인 5를 넘겨준 후에 i 의 값이 증가하기 때문에 5가 출력된다. j 의 경우에는 j 에 저장된 값을 증가 시킨 후에 println 메서드에 값을 넘겨주므로 6이 출력된다. 

 

'++i' 와 'i=i+1' 의 결과는 같지만 실제 연산이 수행되는 과정은 다르다. '++i' 가 더 적은 명령만으로 작업을 수행하기 때문에 더 빠르고, 수식을 더 간략히 할 수 있다.

'++i' 와 'i=i+1' 를 컴파일 했을 때 생성되는 클래스파일의 바이트 코드 명령어를 비교하면, 'i=i+1' 은 5개의 명령이지만, '++i' 는 2개의 명령으로 이루어진다. 

덧셈 연산자 '+' 는 필요에 따라 피연산자를 형변환하지만, 증감 연산자는 형변환없이 피연산자의 값을 변경한다.

 

2.2 부호 연산자 +, -

부호 연산자는 피연산자의 부호를 변경하는데 사용되며, boolean 형과 char 형을 제외한 나머지 기본형에 사용 가능하다.

부호 연산자 '+' 의 경우는 피연산자에 양수 1을 곱한 결과를 출력하고, 부호 연산자 '-' 의 경우에는 피연산자에 음수 1을 곱한 결과를 출력한다.

public class ex2 {
	public static void main(String[]args) {
		int i = -10;
		i = +i;
		System.out.println(i);
		i = -10;
		i = -i;
		System.out.println(i);
	}
}
[실행 결과]
-10
10

 

2.3 비트전환 연산자 ~

비트전환 연산자 '~' 는 정수형과 char 형에서만 사용되며, 피연산자를 2진수로 표현했을 때, 0은 1로 바꾸고 1은 0으로 바꾼다. 그래서 비트전환 연산자 '~' 에 의해 비트전환 되고 나면, 피연산자의 부호가 반대로 변경된다.

[주의] byte, short, char 형은 int 형으로 변환된 후에 전환된다.

2진수 10진수
0 0 0 0 1 0 1 0 10
1 1 1 1 0 1 0 1 -11

 

public class ex2 {
	public static void main(String[]args) {
		byte b = 10;
		System.out.println("b = "+b);
		System.out.println("~b = "+~b);
		System.out.println("-b+1 = "+ (~b+1));
	}
}
[실행 결과]
b = 10
~b = -11
-b+1 = -10

양의 정수 b 가 있을 때, b 에 대한 음의 정수를 얻으려면 '~b + 1' 을 계산하면 된다.

2진수 10진수
0 0 0 0 1 0 1 0 10
1 1 1 1 0 1 0 1 -11
1 1 1 1 0 1 0 1 -11 + 1
0 0 0 0 0 0 0 1
1 1 1 1 0 1 1 0 -10

10의 2진수에서 맨 왼쪽의 부호비트를 1로 바꾸면 -11이 되기 때문에 +1을 해줘서 -10으로 만든다.

 

public class ex2 {
	public static void main(String[]args) {
		byte b = 10;
		//byte result = ~b; ~연산자의 결과가 int 라서 byte 형 변수에 저장하지 못한다.
		byte result = (byte)~b;
		System.out.println("b = "+b);
		System.out.println("~b = "+result);
	}
}
[실행 결과]
b = 10
~b = -11

연산자 '~' 는 피연산자의 타입이 int 형보다 작으면 int 형으로 변환한 다음 연산을 하기 때문에 byte 형 변수 b 가 int 형으로 변환된 다음에 연산이 수행되어 연산결과가 int 형이 된다.

연산자 '~' 의 연산결과를 저장하기 위해서는 int 형 변수에 담거나 캐스트 연산자를 사용해야 한다.

 

2.4 논리부정 연산자 !

boolean 형에만 사용할 수 있으며, true 는 false 로, false 는 true 로 변경한다. 조건문과 반복문의 조건식에 사용되어 효율적인 조건문을 만들어 준다.

전원버튼과 같은 토글버튼을 논리적으로 구현할 수 있다.

public class ex2 {
	public static void main(String[]args) {
		boolean power = false;
		System.out.println(power);
		power = !power;
		System.out.println(power);
		power = !power;
		System.out.println(power);
	}
}
[실행 결과]
false
true
false

'JAVA > 연산자' 카테고리의 다른 글

3. 산술 연산자  (0) 2020.02.18
1. 연산자 (operator)  (0) 2020.01.28

 

연산자의 우선순위

단항 연산자 산술 연산자 → 비교 연산자 → 논리연산자 → 삼항연산자 → 대입연산자

오른쪽으로 갈수록 우선순위가 낮아진다.

 

우선순위가 같은 연산자들 간에는 연산의 진행방향에 의해서 연산순서가 정해진다.

 

연산방향

산술, 비교, 논리, 삼항 :

단항, 대입 : ←

 

정리

1. 산술 > 비교 > 논리 > 대입, 대입은 제일 마지막에 수행된다.

2. 단항 > 이항 > 삼항. 단항 연산자의 우선순이가 이항 연산자보다 높다.

3. 단항 연산자와 대입 연산자를 제외한 모든 연산의 진행방향은 왼쪽에서 오른쪽이다.

'JAVA > 연산자' 카테고리의 다른 글

3. 산술 연산자  (0) 2020.02.18
2. 단항 연산자  (0) 2020.02.04

3.1 형변환 (casting) 이란?

서로 다른 타입의 값으로 연산을 수행해야할 때 사용한다.

형변환이란 변수 또는 리터럴의 타입을 다른 타입으로 변환하는 것이다.

 

3.2 형변환 방법

기본형과 참조형 모두 형변환이 가능하지만, 둘 사이에는 형변환이 성립되지 않는다.

형변환하는 방법은 형변환하고자 하는 변수나 리터럴 앞에 변환하려는 타입을 괄호와 함께 붙여주면 된다.

* 오토박싱 기능이 추가되면서 기본형을 Object, wrapper 클래스 같은 참조형으로 형변환 할 수 있게 되었다.

int score = (int)85.4; //double 형 값을 int 형으로 변환한다.
byte b = (byte)score; //score 에 저장된 값을 byte 형으로 변환

형변환 할 때 사용하는 괄호는 캐스트 연산자나 형변환 연산자라고 한다.

캐스트 연산자는 피연산자의 값을 읽고 지정된 타입으로 형변환 한 후에 그 결과를 반환하기 때문에 피연산자는 형변환 후에도 아무런 변화가 없다.

public class ex2 {
	public static void main(String[]args) {
		double d = 100.0;
		int i = 100;
		int result = i+(int)d;
		System.out.println(d);
		System.out.println(result);
	}
}
[실행 결과]
100.0 //형변환 후에도 변화가 없다.
200

형변환한 결과를 덧셈연산에 사용한다.

 

3.3 기본형의 형변환

boolean 형을 제외한 나머지 기본형 간에는 서로 형변환이 가능하다.

각 자료형마다 표현가능한 값의 범위가 다르기 때문에 큰 자료형에서 범위가 작은 자료형으로의 변환은 값 손실이 일어날 수 있다. 반대의 경우에는 값 손실이 절대 일어나지 않는다.

public class ex2 {
	public static void main(String[]args) {
		int a = 300;
		byte b = (byte)a;
		System.out.println(a);
		System.out.println(b);
		int c = 100;
		byte d = (byte)c;
		System.out.println(c);
		System.out.println(d);
	}
}
[실행 결과]
300
44
100
100

int 가 byte 보다 값의 표현 범위가 큰데, byte 값을 int 값으로 변환하게 되면 1 byte에서 4 byte 로 나머지 3 byte(24자리)를 0으로 채워주면 되므로 기존의 값이 그대로 보존된다. 하지만 int 값을 byte 값으로 변환하게 되면 3 byte 를 잘라내서 1 byte 로 만드는 것이기 때문에 기존의 값이 보존될 수도 있고, 안될 수도 있다.

 

값의 표현 범위가 작은 자료형에서 큰 자료형의 변환은 값의 손실이 없기 때문에 캐스트 연산자의 생략이 가능하다. 이 경우에 JVM 내부에서 형변환이 자동으로 수행된다.

반대의 경우에는 값이 손실될 수 있기 때문에 자동적으로 형변환은 진행되지 않고 캐스트 연산자를 이용하여 명시적 형변환을 하도록 강요한다. 그렇지 않을 경우 컴파일 시 에러가 발생한다.

 

기본형의 자동형변환이 가능한 방향은 byte → short (char) → int → long → float → double이다.

실수형은 정수형과 값을 표현하는 방식이 다르다. 그래서 같은 크기라도 실수형이 정수형보다 오른쪽에 위치하게 되는 것이다.

char 와 ,short 는 모두 2 byte 이지만 서로 범위가 달라 어느쪽으로 형변환을 하더라도 값 손실이 일어날 수 있기 때문에 자동적인 형변환이 이루어지지 않는다.

'JAVA > 변수' 카테고리의 다른 글

2. 변수의 타입  (0) 2019.12.13
1. 변수 (variable)  (0) 2019.12.05

모든 변수는 타입이 있으며, 타입에 따라 저장할 수 있는 값의 종류와 범위가 달라진다.

자바는 C언어와 달리 참조형 변수 간의 연산을 할 수 없으므로 실제 연산에 사용되는 것은 모두 기본형 변수이다.

 

기본형(Primitive type)

    - boolean, char, byte, short, int, long, float, double
    계산을 위한 실제 값을 저장한다.

참조형(Reference type)

    - 8개의 기본형을 제외한 나머지 타입, 객체의 주소를 저장한다.

* 참조형 변수는 null 또는 객체의 주소(4byte)를 값으로 갖는다. null은 어떤 값도 갖고 있지 않은 상태를 뜻하므로, 어떠한 객체도 참조하고 있지 않다는 것을 의미한다.

 

참조형은 직접 추가할 수 있으므로 수가 정해져 있지 않다.

참조변수를 선언할 때는 변수의 타입으로 클래스의 이름을 사용한다. 그래서 새로운 클래스를 작성한다는 것은 새로운 참조형을 추가하는 셈이다.

Date today; // 타입이 클래스이 이름인 것들은 모두 참조변수이다.

다음은 Date 클래스 타입의 참조변수 today를 선언한 것이다. 참조변수의 초기화는 다음과 같이 한다.

Date today = null;
Date today = new Date();

객체를 생성하는 연산자 new의 연산결과는 생성된 객체의 주소이다. 이 주소가 대입연산자 '=' 에 의해서 참조변수 today에 저장되는 것이다.

* 참조형 변수에는 값이 아닌 객체의 종류에 의해서 구분되므로, 자료형대신 타입이라는 용어를 사용한다.

 

2.1 기본형 (primitive type)

기본형은 논리형, 문자형, 정수형, 실수형으로 구분된다.

 

논리형 : true와 false 중 하나를 값으로 갖으며 조건식과 논리적 계산에 사용된다.

    - boolean (1 byte)

문자형 : 문자를 저장하는 데 사용되며, 변수 당 하나의 문자만을 저장할 수 있다.

    - char (2 byte)

정수형 : 정수 값을 저장하는 데 사용된다. 주로 int와 long을 사용하며, byte는 이진데이터를 다루기 위해, short는 C언어와의 호환을 위해 추가되었다.

    - byte (1 byte), short (2 byte), int (4 byte), long (8 byte)

실수형 : 실수 값을 저장하는데 사용된다. float은 부동소수점 방식으로 저장하고, double은 float보다 두 배의 정밀도를 갖는다.

    - float (4 byte), double (8 byte)

* 4개의 정수형 중에서 int형이 기본(default) 자료형이며, 실수형에서는 double이 기본 자료형이다.

 

boolean을 제외한 나머지 7개의 자료형은 서로 변환이 가능하기 때문에 연산이 가능하다. 특히 char는 내부적으로 문자를 정수값 코드로 저장하기 때문에 정수형과 밀접한 관련이 있다.

 

정수형이 가지는 값의 범위는 $-2^{n-1}$ ~ $2^{n-1}-1$ (n은 bit 수가 들어간다) 정도로만 기억한다.

예를 들어, int 형의 경우 32bit 이므로 $-2^{31}$ ~ $2^{31}-1$ 의 범위를 갖는다.

따라서 int형은 대략 9자리 수의 값을 저장할 수 있다. 만약 int와 float의 범위를 넘는 값을 다루게 된다면, long과 double을 사용하면 된다.

* 실수값을 저장할 때는 값의 범위 뿐만 아니라 정밀도도 고려해야 한다.

 

2.2 논리형 - boolean

boolean형 변수에는 true와 false 중 하나를 저장할 수 있으며, default는 false이다. 주로 yes/no, on/off 와 같은 논리구현에 사용되며, 두 가지 값만 표현하기 때문에 가장 작은 1 byte이다.

* 2가지 값만 표현하기 때문에 1 bit만으로 충분하지만 데이터를 다루는 최소 단위가 1 byte기 때문에 1 byte로 지정.

 

아래 문장은 power라는 boolean형 변수를 선언하고 ture로 변수를 초기화 했다.

boolean power = true;

* 자바에서는 대소문자를 구별하기 때문에 TRUE와 true는 다른 것으로 간주한다.

 

2.3 문자형 - char

C언어 같은 언어는 문자형의 경우 확장된 아스키 코드를 사용하여 1 byte의 크기를 갖지만, 자바에서는 유니코드를 사용하여 2 byte의 크기를 갖는다.

char 형의 크기는 2 byte이므로 16진수로 0000부터 ffff까지 65536($2^{16}$)개의 코드를 사용할 수 있으며, char 형의 변수는 이 중 하나를 저장할 수 있다.

 

예를 들어, 아래는 알파벳 'A' 의 유니코드값은 0041이므로 char 형 변수에 문자 'A' 를 저장하는 코드이다.

char firstLetter = 'A';
char firstLetter = '\u0041'; //16진수 41은 10진수로 65이다.

char 형 변수에 문자를 저장할 때는 작은따옴표('')로 문자를 둘러싼다.

 

char 형과 short 형은 크기가 모두 2 byte지만, 범위가 다르기 때문에 같은 2진 표현이더라도 실제 의미하는 값은 다를 수 있다.

char 형은 문자의 코드를 저장하므로 음수를 필요로 하지 않기 때문에 2진수로 표현했을 때 첫 번째 자리를 부호에 사용하지 않는다. 하지만 short 형은 첫 번째 자리를 부호를 표현하는데 사용하기 때문에 서로 다른 범위를 갖게 된다.

public class ex2 {
	public static void main(String[]args) {
		char ch = 'A';	     //'\u0041'로 바꿔써도 같다.
		int code = (int)ch;  // ch에 저장된 값을 int형으로 변환하여 저장한다.
		System.out.println(ch);
		System.out.println(code);
	}
}
[실행결과]
A
65

char 형 변수에 저장되는 값은 부호 없는 정수의 형태로 저장된다. 위 코드에서 'A' 에는 유니코드인 65가 저장된다. 모든 데이터는 숫자로 저장되기 때문이다. println()은 값을 출력할 때 변수의 타입에 따라 정수값을 출력하거나 정수값에 해당하는 문자를 찾아 출력한다.

 

영문자 이외에 특수분자를 저장하려면 아래의 표를 보면 된다.

특수문자 리터럴 특수문자 리터럴
tab \t backspace \b
from feed \f new line \n
carriage return  \r 역슬래쉬(\) \\
작은따옴표 \' 큰따옴표 \"
유니코드(16진수)문자 \u 유니코드 ( ex : char a = '\u0041') 
public class ex2 {
	public static void main(String[]args) {
		char single = '\'';
		String dblQuote = "\"Hello\"";
		String root = "c:\\";
		System.out.println(single);
		System.out.println(dblQuote);
		System.out.println(root);
	}
}
[실행결과]
'
"Hello"
c:\

 

여러 문자를 저장하기 위해서는 String 클래스를 사용해야 한다.

덧셈 연산자를 이용하여 문자열을 결합할 수 있다.

String name = "Ja" + "va"; //name에는 "Java"가 저장된다.

 

덧셈 연산자의 피연산자 중 어느 한 쪽이 String이면 나머지 한 쪽을 먼저 String으로 변환한 다음 두 String을 결합한다.

int 와 같은 기본형 타입의 값을 문자열로 변환할 때는 빈 문자열("")을 더해주면 된다.

public class ex2 {
	public static void main(String[]args) {
		String a = 7 + "";
		String b = " " + 7;
		String c = 7 + 7 + "";
		String d = "" + 7 + 7;
		System.out.println(a);
		System.out.println(b);
		System.out.println(c);
		System.out.println(d);
	}
}
[실행결과]
7
 7
14
77

 

기본형과 참조형의 구별없이 어떤 타입의 변수도 문자열과 덧셈연산을 수행하면 결과는 문자열이 된다.

 

 

2.4 정수형 - byte, short, int, long

byte (1 byte)  <  short (2 byte)  <  int (4 byte)  <  long (8 byte)

byte 와 short 는 int 보다 크기가 작아 메모리를 절약할 수 있지만 저장할 수 있는 값의 범위가 작은 편이라서 연산 시에 범위가 넘어갈 수 있다.

또한, JVM의 피연산자 스택이 피연산자를 4 byte 단위로 저장하기 때문에 byte 나 short 의 값을 4 byte로 변환하여 연산이 수행된다. 따라서 int 를 사용하는 것이 효율적이다. 

 

정수형 변수의 선언과 초기화

byte b = 1;
short s = 2;
int f = 10;
long l = 100000000000L; // long 타입의 리터럴에는 접미사 L을 붙여야 한다.

* long 타입의 리터럴 뒤에 'L' 또는 'l' 을 붙이지 않으면 int 타입으로 간주한다.

* 리터럴은 그 자체로 데이터인 것을 말한다. 상수와 의미가 같지만, 프로그래밍에서는 상수를 '값을 한 번 저장하면 변경할 수 없는 저장공간' 으로 정의하기 때문에 이와 구분하기 위해 리터럴이라는 용어를 사용한다.

 

 

16진수를 표시하기 위해서는 리터럴 앞에 접두사 '0x' 또는 '0X' 를 붙이고, 8진수에서는 '0'을 붙인다.

int octnum = 010; //8진수 10, 10진수로는 8
int hexnum = 0x10; //16진수 10, 10진수로는 16

 

정수형은 0과 1로 이루어진 2진수로 저장된다.

1 byte 는 8 bit 이므로 8자리로 구성되며 $2^8$ 즉, 256가지의 값을 표현할 수 있다.

모든 정수형은 0을 포함한 양수와 음수를 저장하는 값의 범위로 하기 때문에, 왼쪽에서 첫번째 자리를 부호자리로 사용한다. 그래서 1 byte 가 실질적으로 값을 표현할 수 있는 자리수는 7개가 된다.

[실행결과]
123	123
124	124
125	125
126	126
127	127
-128	128
-127	129
-126	130
-125	131
-124	132

위 실행결과에서 왼쪽은 byte, 오른쪽은 int 형 타입의 값이 270까지 증가한다. 하지만 byte 의 표현 가능한 정수의 범위는 127까지이기 때문에 오버플로우(overflow) 가 발생하여 최소값부터 다시 반복된다. 에러없이 실행되긴 하지만 원하는 결과를 얻을 수 없다.

그러나 변수의 범위를 넘는 값으로 초기화하게 된다면 컴파일했을 때 에러가 발생한다.

 

 

2.5 실수형 - float, double

float는 4 byte, double 은 8 byte로 int 와 long의 크기와 같지만, 정수형과는 저장방식이 달라, 훤씬 더 넓은 범위의 값을 표현할 수 있다.

실수형은 부동소수점 방식으로 저장된다. 

부동소수점 : $\pm a\times10^n$ 의 형태로 표현되는데, a는 가수이고, n은 지수이다. 가수 a는 $0\le a<1$ 범위의 실수이다.

예를 들어, 3.1415를 부동소수점 방식으로 표현하면 $0.31415\times10^1$ 이며, 가수는 0.31415이고, 지수는 1이다.

float은 32 bit(4 byte) 중 S(부호)는 1, E(지수)는 8, M(가수)는 23으로 자리수가 할당되어 있다. 그에 비해, double은 64 bit(8 byte)는 S(부호)는 1, E(지수)는 11, M(가수)는 52로 자리수가 할당되어있다. 즉, double이 float 보다 가수가 두 배 이상으로 자리수가 배정되어 있기 때문에 정밀도(소수점 이하의 자리수)가 더 높은 값을 표현할 수 있다.

float은 가수를 10진수로 8자리 정도만 표현할 수 있기 때무네 높은 정밀도 계산에서는 double을 사용해야 한다.

 

public class ex2 {
	public static void main(String[]args) {
		float f = 1.2345678901234567890f;
		float f2 = 0.100000001f;
		double d2 = 0.100000001;
		System.out.println(f);
		System.out.println(f2);
		System.out.println(d2);
	}
}
[실행결과]
1.2345679  //끝자리에서 반올림되었다.
0.1
0.100000001

f2가 0.1로 출력된 이유는 저장된 값이 표현할 수 있는 가수의 자리수(정밀도)를 넘었기 대문이다.

float 형 리터럴에는 접미사 f가 사용되고, double은 d가 사용된다.

* 접미사는 대소문자를 구분하지 않는다.

 

float tpi = 3.14f; //f 대신 F 사용 사능
double velocity = 3.0e5d; //d 대신 D, e 대신 E 사용 가능
double rate = 1.618; //접미사 d 생략 가능

실수형에서는 double 이 기본 자료형이기 때문에 접미사를 생략하면 double 형 리터럴로 간주된다.

 

float pi = 3.14; //에러. float 변수에 double 형 리터럴을 저장할 수 없다.

* 리터럴에 접미사가 붙는 자료형은 long, float, double 인데, double 은 생략가능하므로, long 과 float에만 잘 붙이면 된다.

 

리터럴에 소수점이나 10의 제곱을 나타내는 E, e, 접미사 f, F, d, D, l, L 을 포함하면 실수형 리터럴로 간주된다.

 

'JAVA > 변수' 카테고리의 다른 글

3. 형변환  (0) 2019.12.19
1. 변수 (variable)  (0) 2019.12.05

1.1 변수란?

변수란, 값을 저장할 수 있는 메모리 상의 공간을 의미한다.

값을 직접 사용하는 것보다 의미있는 이름의 변수에 저장하여 사용하는 것이 더욱 바람직하다.

변수의 값은 바뀔 수 있으며, 하나의 변수에는 단 하나의 값만을 저장할 수 있다. 따라서 값을 여러 번 저장하면 마지막에 저장한 값을 갖게 된다.

 

1.2 변수의 선언

변수를 사용하기 위해서는 먼저 변수를 선언해야 한다. 변수가 선언되면 메모리에 변수의 타입에 맞는 크기의 저장공간이 확보된다.

int number; //정수형 변수 number를 선언한다.

변수의 초기화는 변수를 사용하기에 앞서 적절한 값을 저장해주는 것을 말한다. 

int number = 10; //정수형 변수 number를 선언하고 변수의 값을 10으로 초기화한다.
int number; //정수형 변수 number를 선언한다.
number = 10; //number라는 변수에 10으로 초기화(저장)한다.

int a;
int b;
int x = 0;
int y = 0;

int a, b;
int x = 0, y = 0; //콤마를 이용해서 코드를 간결하게 할 수 있다.

변수의 종류에 따라 초기화를 생력하는 경우도 있지만, 변수가 사용되기 전에 적절한 값으로 초기화 하는 것이 좋다.

* 지역변수는 사용되기 전에 초기화를 반드시 해야 하지만, 클래스 변수와 인스턴스 변수는 초기화를 생력할 수 있다.

 

1.3 명명규칙 (naming convention)

 변수, 메소드, 클래스의 이름을 지을 때는 반드시 지켜야 할 규칙이 있다.

 

1. 대소문자가 구분되며 길이에 제한이 없다.

    - True와 true는 서로 다른 것으로 간주된다.

2. 예약어를 사용해서는 안 된다.

    - true는 예약어라서 사용할 수 없지만, True는 가능하다.

3. 숫자로 시작해서는 안 된다.

    - top10은 허용되지만, 7up은 허용되지 않는다.

4. 특수문자는 '_'와 '$'만 허용한다.

    - $harp은 허용되지만, S#arp은 허용되지 않는다.

* 예약어는 keyword, reserved word라고 하는데, 프로그래밍 언어의 구문에 사용된다. 그래서 클래서, 변수, 메소드의 이름으로 사용할 수 없다.

* 유니코드를 인식하지 못하는 OS도 있기 때문에 클래스 이름만큼은 아스키 코드로 하는 것이 좋다.

 

'JAVA > 변수' 카테고리의 다른 글

3. 형변환  (0) 2019.12.19
2. 변수의 타입  (0) 2019.12.13

+ Recent posts