[java] 3.연산자


연산자의 개념



연산자는 연산을 수행하는 기호를 말합니다.

예를 들어 ‘+’ 기호는 덧셈 연산을 수행하며, 덧셈 연산자라고 합니다.

자바에서는 사칙연산(+,-,*,/)을 비롯해서 다양한 연산자들을 제공하고 있습니다.


연산자(operator): 연산을 수행하는 기호(+,-,*,/ 등)


피연산자(operand): 연산자의 작업 대상(변수, 상수, 리터럴, 수식)



자주 쓰이는 연산자들을 중심으로 하나씩 배워나가보겠습니다.



연산자의 종류



연산자는 크게 산술, 비교, 논리, 대입 4가지로 나눌 수 있습니다.

다음은 자바에서 제공하는 연산자입니다.


image




<피연산자의 개수에 의한 분류>


피연산자의 개수로 연산자를 분류하기도 하는데,


피 연산자의 개수가 하나면 단항 연산자, 두 개면 이항 연산자,


세 개면 삼항 연산자라고 부른다.


단항 연산자 예) i++, i--


이항 연산자 예) a+b, a-b, a/b 


삼항 연산자 예) result=a>b?true:false;



연산자의 우선순위와 결합규칙



연산자의 우선순위는 말 그대로 어떤 연산자를 먼저 수행할 지 결정하는 것입니다.

예를 들어 곱셈을 덧셈보다 먼저 수행하는 것을 말합니다.

대부분 상식적으로 판단 가능한 수준이지만 판단하기 쉽지 않은 우선순위도 있습니다.


ex) 1. x<<2+1 : 쉬프트 연산자(<<)는 덧셈 연산자보다 우선순위가 낮다.

그래서 x<<(2+1) 과 같다.


2. data & 0xFF==0 : 비트 연산자(&)는 비교 연산자(==)보다 우선순위가 낮으므로

data & (0xFF==0)과 같다.

>
3. x<-1||x>3&&x<5 : 논리 연산자 중에서 AND를 의미하는 &와 &&가 OR을 의미하는 |와 ||보다

우선순위가 높다.

따라서 x<-1||(x>3&&x<5)와 같다.



또, 연산자의 결합규칙도 연산자 마다 다르다.

대부분 왼쪽에서 오른쪽의 순서로 연산을 수행하고, 단항 연산자와 대입 연산자만 그 반대로,

오른쪽에서 왼쪽의 순서로, 연산을 수행한다.


ex) 3+4-5 -> (3+4)-5 (왼쪽에서 오른쪽)


x=y=3 -> x=(y=3) (오른쪽에서 왼쪽)




image



이를 다시 정리하면 다음과 같습니다.


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


2. 단항(1)>이항(2)>삼항(3) 단항 연산자의 우선 순위가 이항 연산자보다 높다.


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



산술 변환



이항 연산자는 두 피연산자의 타입이 일치해야 연산이 가능하므로,

피연산자의 타입이 서로 다르다면 연산 전에 형변환 연산자로 타입을 일치시켜야한다.


ex) int i=10;

float f=20.0f;

float result=f+(float)i;



대부분의 경우, 두 피연산자의 타입 중에서 더 큰 타입으로 일치시키는데,

그 이유는 작은 타입으로 형변환하면 원래의 값이 손실될 가능성이 있기 때문이다.

저번 장에서 배운 것과 같이 작은 타입에서 큰 타입으로 형변환하는 경우,

자동적으로 형변환되므로 형변환 연산자를 생략할 수 있습니다.


float result=f+i; //큰 타입으로 형변환시, 형변환연산자 생략가능



이처럼 연산전에 피연산자 타입의 일치를 위해 자동 형변환되는 것을 산술 변환 또는 일반 산술 변환이라고 합니다.

산술 변환 규칙은 다음과 같습니다.


1. 두 피연산자의 타입을 같게 일치시킨다. (보다 큰 타입으로 일치)

ex)

long + int -> long + long -> long

float + int -> float + float -> float

double + float -> double + double -> double

 -> 피연산자의 값 손실을 최소화하기 위함



2. 피연산자의 타입이 int 보다 작은 타입이면 int로 변환된다.

ex)

byte + short -> int + int -> int

char + short -> int + int -> int

 -> 정수형의 기본 타입인 int가 가장 효율적으로 처리할 수 있는 타입이기 때문이다.



단항 연산자



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


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




전위형 : 값이 참조되기 전에 증가시킨다 ex) j=++i;


후위형 : 값이 참조된 후에 증가시킨다. ex) i++;



다음은 단항 연산자에 대한 예제입니다.


image



증감 연산자를 사용하면 코드가 간결해지지만, 지나치면 코드가 복잡해서 이해하기 어려워지기도 합니다.

예를 들어 x의 값이 5일 때 다음 값은 몇 일까요?


x=x++ - ++x;



대답하기 쉽지 않습니다. 따라서 하나의 식에서 증감연산자의 사용을 최소화하고,

식에 두 번 이상 포함된 변수에 증감연산자를 사용하는 것은 피해야합니다.



산술연산자



사칙 연산자, 덧셈(+), 뺄셈(-), 곱셈(*), 나눗셈(/),나머지 연산자(%)은 프로그래밍에

가장 많이 사용되는 연산자들입니다.

실생활에서 자주 쓰이는 것이기에 따로 설명은 안하겠습니다~

단, 곱셈(*),나눗셈(/),나머지(%) 연산자가 덧셈, 뺄셈 연산자보다 우선순위가 높다는 것을 기억해주시길 바랍니다.

그리고 피연산자가 정수형인 경우, 나누는 수로 0을 사용할 수 없습니다.

그러나 실수형 0.0f 와 0.0d로 나누는 것은 가능합니다.


System.out.println(3/0); // 컴파일 오류


System.out.println(3/0.0); //Infinity가 출력됨



다음은 사칙연산을 이용한 예제입니다!


image



그리고 나머지 연산자는 피연산자로 정수만 허용된다는 것을 기억해주세요~!

또, 전에 말했듯이 int형보다 작은 자료형의 연산은 int형으로 형변환이 이뤄진 다음 수행된다고 했는데,

따라서 다음과 같은 코드는 오류가 발생합니다.


byte a=10;

byte b=5;

byte c=a+b; //오류발생(a+b는 int형이 되므로 더작은 자료형에 값을 저장할 수 없다.)



따라서 두개의 덧셈 값을 byte 자료형에 저장하려면 다음과 같이 명시적 형변환을 해줘야 합니다.


byte c=(byte)(a+b);



하지만 a+b의 결과 값이 byte의 범위를 넘으면 데이터 손실이 발생할 수 있으니

사칙연산은 int형으로 수행하는 것이 바람직합니다.

또 결과값이 int형의 범위를 넘는 경우 오버플로우가 발생하기에

큰 수의 사칙연산 시에는 더 큰 자료형을 쓰는 것이 바람직합니다.


long a=100000*1000000; // 오버플로우 발생 결과가 int형의 범위를 넘기에


long b=100000*1000000L; // 올바른 결과 출력 (결과 값이 long 형이므로)



사칙연산 시 항상 자료형의 범위를 생각해야합니다. 정말 중요합니다!

또한 문자형인 char도 숫자가 유니코드에 의해 문자에 대응된 것이므로 사칙 연산이 가능합니다.

char형도 int 형보다 작은 자료형이므로 int 형으로 형변환이 이뤄진 후 이뤄집니다.


char a='a'; //a=97

char d='d'; //d=100

char result=d-a; //컴파일 오류 d-a가 int 형이기에

int result=d-a; //결과 3 저장




char c1='a';

c1++; //OK (형변환 없이 증가)

c1=c1+1; //컴파일 오류(int 형으로 형변환이 이뤄지기에)

c1=(char)(c1+1); //OK



int보다 작은 타입의 피연산자를 int로 자동 형변환한다고 배웠는데,

상수 또는 리터럴 간의 연산은 실행 과정동안 변하는 값이 아니기 때문에,

컴파일 시에 컴파일러가 게산해서 그 결과로 대체함으로써 코드를 보다 효율적으로 만듭니다.

따라서 다음과 같은 코드는 정상적으로 컴파일됩니다.


char c1='a'+1; //컴파일 가능



다시 말해 상수 및 리터럴 간의 연산은 컴파일러가 미리 계산을 할 수 있지만,

변수 간의 연산은 컴파일러가 미리 계산을 할 수 없기에 형변환을 해줘야합니다.

다음은 알아두면 유용한 A와 a의 유니코드 값입니다.


a : 97 , b: 98 ...

A : 65, B: 66 ...



비교 연산자



다음은 프로그래밍에서 자주 사용되는 비교 연산자입니다.


image



대소 비교 연산자(>,<,>=,<=)는 boolean형을 제외한 나머지 자료형에 다 사용가능하나

참조형에는 사용할 수 없습니다.

반면에, 등가비교 연산자(==,!=)는 모든 자료형에 사용가능합니다.

그러나 참조형과 기본형은 서로 형변환이 안 되기에 서로 비교하는 건 불가합니다.

다음은 비교 연산자를 이용한 예제입니다.


image



결과가 왜 저렇게 나왔는지 알아보겠습니다.

비교 연산자도 이항 연산자이므로 연산을 수행하기 전에 형변환을 통해 두 피연산자의 타입을 같게 합니다.

따라서, 10==10.0f 의 결과가 true 입니다.

또, ‘0’의 유니코드 값은 0이 아니라 48이므로 ‘0’==0의 값이 false입니다.

다음 예제를 보시죠!


image



위에 결과를 보면 참 이상합니다. 10.0==10.0f 는 참이고,

0.1==0.1f 는 거짓이라니..

형변환이 이뤄져서 같을 것으로 예상되었지만 참 멘붕이네요

하지만 f와 d, d2 값을 출력해보니 그 이유를 발견할 수 있었습니다.

10.0f는 오차없이 저장할 수 있는 값이라서 double로 형변환해도 그대로 10.0이 되지만,

double타입의 상수인 0.1이 float타입인 0.1보다 적은 오차로 저장되고

float 타입의 리터럴인 0.1f를 double로 형변환해도 값은 변하지 않기에

0.1과 0.1f는 다른 것입니다.

즉, 형변환한다고 해서 오차가 줄어드는 것이 아닙니다.



문자열 비교



두 문자열을 비교할 때는, 비교 연산자 ==대신 equals()라는 메서드를 사용해야 합니다.

비교 연산자 ==은 같은 객체인지(참조값이 같은지) 비교하는 것이지 문자열 내용을 비교하는 것이 아닙니다.

따라서 다음과 같이 문자열을 비교해야 합니다.


String str="abc";


boolean result=str.equals("abc");



논리 연산자



다음은 논리연산자들 입니다.


image



논리 연산자들에 대한 예제입니다.


image




image



비트 연산자



다음은 비트 연산자입니다.


image



비트 연산자에 관한 예제입니다.

먼저, 정수를 2진수 16자리로 만들어주는 메서드를 정의하는데 아직 배우지 않은 내용이므로

아직까지 모르셔도 상관 없습니다.


image




image




image



그리고 시프트연산은 다음과 같은 성질이 있습니다.


 x<<n은 x를 2의 n승번 곱한 것과 같다.


 x>>n은 x를 2의 n승으로 나눈 것과 같다. (소수점은 잘림)



삼항 연산자



삼항 연산자는 다음과 같은 형식입니다.


 조건식 ? 식1: 식2



조건식이 참이면 식1이 실행되고, 조건식이 거짓이면 식2가 실행됩니다.


<예>

char result=(10>5)?'T':'F'; //조건식이 참이므로 result에는 'T'가 저장



복합 대입 연산자



복합 대입 연산자는 다음과 같습니다.


 i+=3; //i=i+3; 과 같음


 i-=3; //i=i-3; 과 같음



위의 코드처럼 코드를 더 간결하게 표시할 수 있습니다.

또한 복합 대입 연산자도 대입 연산자이므로 산술연산자보다 우선순위가 낮습니다.


 i*=10+j; // i=i*(10+j); 와 같음