프로그램에서 데이터를 처리하여 결과를 산출하는 것을 연산(operations)이라고 한다.
연산식에는 아래가 포함된다.
- 연산자(operator) (+,=,/,*)
- 피연산자(operand) (x,y,z 등 변수)
-
자바에서는 다양한 연산자를 제공하고 있다. ex) 산술, 부호, 문자열, 대입, 증감, 비교, 논리, 조건, 비트, 쉬프트
-
산출되는 값의 타입은 연산자별로 다르다.
-
단항 연산자, 이항 연산자, 삼항 연산자
(사망 연산자) -
연산식은 반드시 단 하나의 값을 산출한다.
다음과 같은 연산식에서 && 연산자가 먼저 처리될까 >,< 연산자가 먼저 처리될까?
x > 0 && y < 0
- 프로그램에서는 연산자의 연산 방향과 연산자 간의 우선순위가 정해져 있다.
- 대부분의 연산자는 왼쪽에서부터 오른쪽으로 연산을 시작한다.
*, /, % 연산은 우선순위가 같다. 즉 왼쪽에서부터 오른쪽으로 연산한다.
100 * 2 / 3 % 5
하지만 단항 연산자(++, --, ~, !), 부호 연산자(+, -), 대입 연산자(=, +=, -= etc)는 오른쪽에서 왼쪽으로 연산한다.
아래 연산식은 c = 5, b = c, a = b 순서로 연산된다.
a = b = c = 5;
위의 도표에서 알 수 있듯이, 괄호(Parentheses)
연산자가 연산 우선순위가 가장 높다.
그러므로 복잡한 연산이 있다면 괄호
를 사용하여 먼저 처리할 연산자를 묶는 것이 좋다.
연산자의 방향과 우선순위는 처음 접한다면 혼란스러울수도 있으니 4줄 정리
- 단항, 이항, 삼항 연산자 순으로 우선순위를 가진다
- 산술, 비교, 논리, 대입 연산자 순으로 우선순위를 가진다.
- 단항과 대입 연산자를 제외한 모든 연산자의 방향은 왼쪽에서 오른쪽
- 복잡한 연산식에는 괄호() 를 사용
피 연산자가 단 하나뿐인 연산자를 말한다. ex) 부호(+, -), 증감(++, --), 논리 부정(!), 비트 반전(~)
+, - 는 산술 연산자이기도 하고, 부호 연산자이기도 하다. 어떻게 구분할까? 피연산자의 개수(산술은 2개, 부호는 1개) 구분할 수 있다.
부호 연산자의 주의점은 산출 타입이 int형 이라는 것이다
short s = 100; short result = -s; //compile error
(쁠쁠x, x쁠쁠)
//code from geek for geeks #include <stdio.h> int increment(int a, int b) { a = 5; // POSTFIX b = a++; // b=5, a=6 printf("%d", b); // prints 5 // PREFIX int c = ++b; // c=6, b=6 printf("\n%d", c);// prints 6 }
증감 연산자는 피연산자의 기존 값에 1을 더하거나 빼준다. 증감 연산자가 앞에(prefix) 있으면 우선 피연산자를 1 증감해주고, 다른 연산자와 계산한다. 반대로, 증감 연산자가 뒤에(postfix) 있으면 다른 연산자의 계산이 끝나고 피연산자에 1을 증감한다.
알고 계셨나요? ++i와 i=i+1와 연산속도의 차이가 없답니다. 후자는 두 번의 연산을 하는 것으로 보이지만, 컴파일하면 동일한 바이트 코드가 생성된다고 하네요.
지식이 늘었다.
부정(deny)하는 거 아닙니다. logical negation이라고 하네요.
(참고 https://wikidiff.com/deny/negate)
논리 부정 연산자는 boolean
타입에만 사용 할 수 있습니다.
피연산자가 true
면 false
, false
면 true
를 반환합니다.
이러한 특성 때문에 조건문, 제어문에 사용되고 토글로도 사용됩니다.
비트 반전 연산자의 주의할 점은 산출 타입이 int
라는 것
피연산자는 연산 수행 전 int
로 변환되고, 비트반전이 일어난다.
(대체왜?라는 질문엔 아래 링크가 답변 가능)
https://docs.oracle.com/javase/specs/jls/se8/html/jls-15.html#jls-15.15.5
이항 연산자는 피 연산자가 두개인 연산자를 말한다.
대부분의 산술 연산은 익숙하지만 % 연산자는 익숙하지 않을 것이다. (라고 책에 써져있다.)
% 연산자는 나눗셈을 수행하고, 몫이 아닌 나머지를 돌려주는 연산자이다.
산술 연산자의 특징은 피연산자들의 타입이 동일하지 않을 경우 다음과 같은 규칙을 사용해서 타입을 일치시킨 후 연산한다.
- 피연산자들이 모두 정수타입이고,
int
타입(4byte)보다 작은 타입일 경우 모두 int 타입으로 변환 후 연산을 수행한다. 따라서 연산 결과는int
타입이다.- 피연산자들이 모두 정수타입이고,
long
타입이 있을경우 모두long
타입으로 변환 후, 연산을 수행한다. 따라서 연산 산출 타입은long
이다.- 피연산자중 실수 타입(
float
,double
)이 있을 경우 크기가 큰 실수 타입으로 변환후, 연산을 진행한다.
따라서 놀랍게도 아래 연산은 오류를 내뱉는다.
byte v1 = 1; byte v2 = 2; byte v3 = v1+v2; //error
모두 연산 중 byte
에서 int
로 변환되기 때문에, possible lossy conversion from int to byte
오류가 발생할 것이다.
정수 타입 연산 결과가 int
타입으로 나오는 이유는 자바 가상 기계(JVM)
가 기본적으로 32비트 단위로 계산하기 때문이다. (라고 써져있는데 64bit JVM은여)
- 오버플로우
- 정확한 계산은 정수 사용
float
,double
은 부동 소수점 타입이라 0.1을 정확히 표현할 수 없어 근사치로 표현한다.IEEE 754로 소수점 계산하던때가 새록새록
NaN
(Not a Number)과Infinity
(무한대) 연산
// both throws ArihthmaticExceptions 5 / 0.0 //infinity 5 % 0.0 //NaN
- 입력값의
NaN
검사 부동소수점(실수)을 입력받을 때는NaN
을 검사해야한다. 왜냐하면NaN
은 산술 연산이 가능하기 때문이다.
그러므로 악의적인 이용자가 데이터를 더럽히지 않도록 NaN
입력값 검사를 해주어야 한다.
문자열 연결 연산자인 +
는 문자열을 서로 결합한다.
String str1 = "JDK" + 6.0; //JDK6.0
우리 +
는 산술, 문자열 연결, 부호 다양한 연산자에 쓰인다. 😒
다음 연산의 결과는 어떨까?
"JDK" + 3 + 3.0;
결과는 "JDK33.0"이다. 그렇다면 다음 연산 결과는?
3 + 3.0 + "JDK"
결과는 "6.0JDK" 가 나온다. 그러므로 주의해야할 필요가 있다.
비교 연산자는 대소 또는 동등 을 비교해서 true
/false
를 반환한다.
boolean
을 제외한 기본 타입에도 적용이 가능 하다
ex)
'A'>'B' // 'A'=65, 'B'=66, returns false
비교 연산자에서도 연산 수행전 타입변환을 통해 피연산자의 타입을 일치시킨다.
ex)
3 == 3.0 // int type 3을 double 타입 3.0으로 바꾼뒤 연산
그러나 한가지 예외가 있다.
ex)
0.1 == 0.1f // 둘 다 double type으로 바뀌지 않을 까?
결론부터 말하자면 아니다! 이유는 부동소수점 타입(이진 포맷의 가수 사용)은 0.1을 정확히 표현할 수 없어 0.1f가 0.1의 근삿값으로 표현되기 때문이다. (소수의 비교는 항상 조심하자!)
해결책은 모든 피연산자를 float
타입으로 강제 형변환 하는 방법이 있다.
그렇다면 String
타입은 어떨까?
우리는 경험적으로 String
타입의 피연산자끼리 비교연산을 하면 원치 않는 결과가 나오는 것을 알고 있다.(라고하자)
아래 예시로 확인해보자.
String s1 = "우이천"; String s2 = "우이천"; String s3 = new String("우이천");
자바는 문자열 리터럴이 동일하다면, 동일한 String
객체를 참조하도록 되어 있다.
그러므로 s1==s2
는 같은 String 객체의 번지값을 갖고 있기에, true
를 반환 할 것이다.
그러나 변수 s3
은 새로운 객체가 heap 영역에 생기며 새로운 String
객체의 번지값을 가진다.
그렇기에, s1==s3
와 같은 연산을 한다면 원하는 결과와 다른 결과가 나올 수 있다.
그러므로 String
끼리의 값 비교를 원한다면 equals()
메소드를 사용해야 한다!
논리 연산자의 피연산자는 boolean 타입만 사용할 수 있다.
여기서 &&(||)와 &(|)의 차이가 궁금할 수도 있다.
&&(||)와 &(|)는 산출 결과는 같지만 연산 과정이 조금 다르다.
&&는 앞의 피연산자가 하나라도 false
라면 뒤의 피연산자는 평가하지 않는다. (논리곱이기에 결과가 true
일 수 없기에) 그러나 &는 두 피연산자 모두를 평가해서 산출 결과를 낸다.
그러므로 && 연산자가 더 효율적으로 동작한다.
||와 |의 경우도 마찬가지이다. (하나라도 true
라면 ~)
비트 연산자는 데이터를 비트(bit)단위로 연산한다.
비트 이동 연산자(쉬프트 연산)는 비트를 좌측 또는 우측으로 이동하는 연산자이다.
즉 모든 피연산자는 0
이거나 1
이다. 그렇기에 정수 타입만 비트 연산을 할 수 있다.
논리 연산자는 true
, false
를 연산하고, 비트 연산자는 0
, 1
을 연산하는 차이가 있다.
비트 연산자도 마찬가지로 피연산자를 int
타입으로 자동 타입 변환 후 연산을 수행한다.
- 비트 이동 연산자(<<, >>, >>>)
간단하게 생각하여, 왼쪽으로 방향을 보고 있다면 (<<) 비트를 왼쪽으로 보내는 것이고, 한 칸 옮길 때마다 2배가 되는 것이다. 맨 위의 비트는 옮긴 만큼 버린다.
>>
연산은 반대로 한 칸 옮길 때 마다 나누기 2가 된다. 맨 아래 비트는 옮긴 만큼 버린다.
>>>
연산의 경우에 >>
연산과 같지만, MSB를 신경쓰지 않고 무조건 0으로 채운다는 차이가 있다.
대입 연산자는 오른쪽 피연사자의 값을 피연사자인 변수에 저장한다.
사망연산자 앎 ? 끄덕 : 모름
삼항 연산자(?:
)는 세 개의 피연산자를 필요로 하는 연산자를 말한다.
삼항 연산자는 ?
앞 조건식에 따라 콜론(:
) 앞뒤의 피연산자가 선택된다.
조건식 ? (값 또는 연산식) : (값 또는 연산식) 피연산자 1 ? 피연산자 2 : 피연산자 3