JAVA/이것이 자바다

Chapter .03 연산자

개념원리 2021. 7. 2. 18:43

3.1 연산자와 연산식

프로그램에서 데이터를 처리하여 결과를 산출하는 것을 연산(operations)이라고 한다.

연산에서 사용되는 표시나 기호를 연산자(operator)라고 하고, 연산되는 피연산자(operand)라고 한다.

연산자와 피연산자를 이용하여 연산의 과정을 기술한 것을 연산식(expressions)이라고 부른다.

예를들어 다음 연산 식에서 +, - *, ==은 연산자이고 x,y,z 변수는 피연산자 이다.

x + y
x - y
x * y + z
x == y

 

 

연산자의 종류

 

연산자는 필요로 하는 피연산자의 수에 따라 단항, 이항 삼항 연산자로 구분된다.

++X;                     //단항 연산자
X + Y;                   //이항 연산자
(sum>90) ? "A" : "B";    //삼항 연산자

 

연산식은 반드시 하나의 값을 산출한다.

연산자 수가 아무리 많아도 두 개 이상의 값을 산출하는 연산식은 없다.

그렇기 때문에 하나의 값이 올 수 있는 곳이면 어디든지 값 대신에 연산식을 사용할 수 있다.

보통 연산식의 값은 변수에 저장하는데, 다음과 같이 x와 y 변수의 값을 더하고 나서 result 변수에 저장한다.

int result = x + y;

 

연산식은 다른 연산식의 피연산자 위치에도 올 수 있다.

다음과 같이 비교 연산자인 < 의 좌측 피연산자로 ( x + y ) 라는 연산식이 사용되어, x와 y 변수의 값을 더하고 나서 5보다 작은지 검사한 후 결과값(true 또는 false)을 result 변수에 저장한다.

boolean result = (x+y) < 5;

 

 

3.2 연산의 방향과 우선순위

연산식에서는 다양한 연산자가 복합적으로 구성된 경우가 많다.

산술 연산식에서 덧셈(+), 뺄셈(-) 연산자보다 곱셈(*), 나눗셈(/) 연산자가 우선 처리된다는 것을 우리는 이미 알고 있다.

그러면 다른 연산자의 경우는 어떨까?

예를 들어 다음과 같은 연산식에서 && 연산자가 먼저 처리될까 아니면 > , < 연산자가 먼저 처리될까?

X > 0 && y < 0

프로그램에서는 연산자의 연산 방향과 연산자 간의 우선순위가 정해져 있다.

&& 보다는 > , < 가 우선순위가 높기 때문에 x>0과 Y<0이 먼저 처리되고, &&는 x>0과 Y<0의 산출값을 가지고 연산하게 된다.

 

그럼 우선순위가 같은 연산자들 끼리는 어떤 순서로 처리 될까?

이 경우에는 연산의 방향에 따라 달라진다.

대부분의 연산자는 왼쪽에서 오른쪽으로( ->) 연산을 시작한다.

a = b = c = 5;

위 연산식은 c = 5, b = c, a = b, 순서로 실행된다.

실행되고 난 후에는 a,b,c의 값이 모두 5가 된다.

이와 같이 어떤 연산잘르 사용하느냐에 따라 연산의 뱡향과 우선순위가 정해져 있기 떄문에 복잡한 연산식에서는 주의가 필요하다.

 

다음은 연산자의 연산 방향과 우선순위를 정리한 표이다.

 

 

연산의 방향과 우선순위

1. 단항, 이항, 삼항 연산자 순으로 우선순위를 가진다.

2. 산술, 비교, 논리, 대입 연산자 순으로 우선순위를 가진다.

3. 단항과 대입 연산자를 제외한 모든 연산의 방향은 왼쪽에서 오른쪽이다( -> ).

4. 복잡한 연산식에서는 괄호( )를 사용해서 우선순위를 정해준다.

 

 

3.3 단항 연산자

단항 연산자는 피연산자가 단 하나뿐인 연산자를 말하며, 여기에는 부호 연산자( + , - ), 증감 연산자( ++ , -- ), 논리 부정 연산자( ! ), 비트 반전 연산자( ~ )가 있다.

 

3.3.1 부호 연산자( +, - )

부호 연산자는 양수 및 음수를 표시하는 + , - 를 말한다. boolean 타입과 char 타입을 제외한 나머지 기본 타입에 사용할 수 있다.

 

+ , - 는 산술 연산자이기도 하고, 부호 연산자이기도 하다.

부호 연산자로 쓰일때에는 하나의 피연산자만 필요하다.

일반적으로 부호 연산자를 다음과 같이 정수 및 실수 리터럴 앞에 붙여 양수 및 음수를 표현한다.

int i1 = +100;
int i2 = -100;
double d1 = +3.14;
double d2 = -10.5;

 

변수 값의 부호를 유지하거나 바꾸기 위해서도 사용이 된다.

int x = -100;
int result1 = +x;           // -100이 저장됨
int result2 = -x;           // -(-100)인 +100이 저장됨.

 

부호 연산자를 사용할 때 주의할 점은 부호 연산자의 산출 타입은 int 타입이 된다는 것이다.

예를 들어 short 타입 값을 부호 연산하면 int 타입 값으로 바뀐다. 그래서 다음 코드는 컴파일 에러가 발생한다.

short s = 100;
shrot result = -s;       //컴파일 에러

그렇기 때문에 다음과 같이 변경되어야 한다.

shrots s = 100;
int result3 = -s;

 

3.3.2 증감연산자

증감 연산자는 변수의 값을 1증가(++)시키거나 감소(--)시키는 연산자를 말한다.

boolean타입을 제외한 모든 기본 타입의 피연산자에 사용할 수 있다.

 

연산식 설명
++ 피연산자 다른 연산을 수행하기 에 피연산자의 값을 1 증가시킴
-- 피연산자 다른 연산을 수행하기 에 피연산자의 값을 1 감소시킴
피연산자 ++ 다른 연산을 수행한 에 피연산자의 값을 1 증가시킴
피연산자 -- 다른 연산을 수행한 에 피연산자의 값을 1 감소시킴
int x = 1;
int y = 1;
int result1 = ++x + 10;        //연산 전에 x가 증가되어 12가 저장됨
int result2 = y++ + 10;        //y+10을 수행 후 y가 증가되어 11이 저장되고 y는 2가 됨.

 

3.3.3 논리 부정 연산자( ! )

논리 부정 연산자는 true를 false로, false를 true로 변경하기 때문에 boolean 타입에만 사용할 수 있다.

boolean result1 = true;             // true가 저장
boolean result2 = !result1;         // result1(true) 의 반대인 false가 저장

3.3.4 비트 반전 연산자( ~ )

비트 반전 연산자는 정수타입(byte, short, int, long)의 피연산자에만 사용되며, 피연산자를 2진수로 표현했을 때 비트값인 0을 1로, 1을 0으로 반전한다.

연산 후 최상위 비트를 포함 해서 모든 비트가 반전되기 떄문에, 부호가 반대인 새로운 값이 산출된다.

비트 반전 연산자를 사용할 때 주의할 점은 비트 반전 연산자 산출 타입은 int 타입이 된다는 것이다.

피연산자는 연산을 수행하기 전에 int 타입으로 변환되고, 비트 반전이 일어난다.

그래서 다음 코드는 컴파일 에러가 발생한다.

byte v1 = 10;
byte v2 = ~v1;         // 컴파일 에러

그렇기 때문에 다음과 같이 변경되어야 한다.

byte v1 = 10;
int v2 = ~v1;

 

 

3.4 이항 연산자

이상 연산자는 피연산자가 두 개인 연산자를 말하며 여기에는 산술 연산자( +, -, *, /, % ), 문자열 연결 연산자( + ), 대입 연산자( =, +=, -=, *=, /=, %=, &=, ^=, |=, <<=, >>=, >>>=), 비교 연산자( <, <=, >, >=, ==, != ), 논리 연산자 (&&, ||, &, |, ^, ! ), 비트 논리 연산자(&, |, ^ ), 비트 이동 연산자( <<, >>, >>> ) 등이 있다.

 

 

3.4.1 산술 연산자(  +, -, *, /, % )

우리가 일반적으로 말하는 사칙연산인 더하기( + ), 빼기 ( - ) , 곱하기 ( * ), 나누기( / )와 나머지를 구하는 연산자( % )를 포함해서 산술 연산자는 총 5개 이다.

산술 연산자는 boolean 타입을 제외한 모든 기본타입에 사용 할 수 있다.

%는 나머지를 구하는 연산자이다.

 

산술 연산자의 특징은 피연산자들의 타입이 동일하지 않을 경우 다음과 같은 규칙을 사용해서 피연산자들의 타입을 일치시킨 후 연산을 수행한다.

 

1) byte + byte -> int + int = int

2) int + long -> long + long = long

3) int + double -> double + double = double

 

간단하게 정리하면 long을 제외한 정수 탕비 연산은 int 타입으로 산출되고, 피연산자 중 하나라도 실수 타입이면 실수 타입으로 산출된다.

 

 

오버플로우 탐지

산술 연산을 할 때 주의할 점은 연산 후의 산출값이 산출 타입으로 충분히 표현 가능한지 살펴봐야 한다.

산출 타입으로 표현할 수 없는 값이 산출 되었을 경우, 오버플로우가 발생하고 쓰레기값(엉뚱한 값)을 얻을 수 있기 때문이다.

int x = 1000000;
int y = 1000000;
int z = x * y;          //-727379968 이 z에 저장된다.

변수 x와 y는 int 타입이고 x * y 역시 int 타입이므로 연산의 산출 타입은 int 타입이다.

위 코드는 컴파일 에러는 발생하지 않치만 변수 z에는 올바른 값이 저장되지 않는다.

그 이유는 1000000 * 1000000은 int 타입에 저장 될 수 있는 값의 범위를 초과하기 때문이다.

올바른 값을 얻기 위해서는 변수 z가 long 타입이 되어야 한다.

 

정확한 계산은 정수 사용

정확하게 계산해야 할 떄는 부동소스점(실수) 타입을 사용하지 않는 것이 좋다.

int apple = 1;
double piceUnit = 0.1;
int number = 7;

double result = apple - number * priceUnit;   //result에 0.2999999999999993이 저장된다.
                  1   -   7    *    0.1

이진 포멧의 가수를 사용하는 부동소수점 타입(float, double)은 0.1을 정확히 표현할 수 없어 근사치로 처리하기 때문이다.

따라서 다음과 같이 계산해야 한다.

int apple = 1;

int totalPieces = apple * 10;
int number = 7;
int temp = totalPices - number;

double result = temp / 10.0

 

 

Nan과 Infinity연산

/ 또는 % 연산자를 사용 할 때도 주의할 점이 있다.

좌측 피 연산자가 정수 타입인 경우 나누는 수인 우측 피연산자는 0을 사용할 수 없다.

만일 0으로 나눈다면 컴파일은 정상적으로 되지만, 실생 시 ArithmeticException(예외)가 발생한다.

5 / 0 ;              //ArithmeticException 예외 발생
5 % 0 ;              //ArithmeticException 예외 발생

 

실수 타입인 0.0 또는 0.0f로 나누면 ArithmeticException이 발생하지 않고, / 연산의 결과는 Infinity(무한대) 값을 가지며, %연산의 결과는 NaN(Not A Number)을 가진다.

5 / 0.0            //Infinity
5 % 0.0            //NaN

연산의 결과가 Infinity 또는 NaN이 나오면 데이터가 엉망이 될수 있기 때문에 다음 연산을 수행해서는 안된다.

프로그램에서 / 와 % 연산의 결과가 Infinity , NaN인지 확인하기 위해서는 Double.isInfinite()와 Double.isNan()을 사용하면 된다.

int x = 5;
double y = 0.0;

double z = x / y;

System.out.println(Double.isInfinite(z));
System.out.println(Double.isNaN(z));

 

 

3.4.2 문자열 연결 연산자 ( + )

문자열 연결 연산자인 +는 문자열을 서로 결합하는 연산자이다.

피연산자중 한쪽이 문자열이면 + 연산자는 문자열 연결 연산자로 사용되어 다른 피연산자를 문자열로 변환하고 서로 결합한다.

 

3.4.3 비교 연산자( <, <=, >, >=, ==, != )

비교 연산자는 대소( <, <=, >, >= ) 또는 동등 (==, != )을 비교해서 boolean 타입은 true/false를 산출한다.

대소 연산자는 boolean 타입을 제외한 기본 타입에 사용할 수 있고, 동등 연산자는 모든타입에 사용 될 수 있다.

비교 연산자는 흐름 제어문인 조건문(if), 반복문(for, while)에서 주로 이용되어 실행 흐름을 제어할 때 사용된다.

만약 피연산자가 char타입이면 유니코드 값으로 비교 연산을 수행한다.

예를들어 'A'의 유니코드는 65이고 'B'의 유니코드는 66이므로 비교 연산자는 65와 66을 비교하게 된다.

('A' > 'B')    ->     ( 65 > 66 )

 

String 타입의 문자열을 비교 할때는 대소( <, <=, >, >= ) 연산자를 사용할 수 없고, 동등(==, != ) 연산자는 사용 할 수 있으나 문자열이 같은지, 다른지를 비교하는 용도로는 사용되지 않는다.

 

기본 타입(byte, char, short, int, long, float, double, boolean)인 변수의 값을 비교할 때에는 == 연산자를 사용하지만 참조 타입인 String 변수를 비교할 때 == 연산자를 사용하면 원하지 않은 결과가 나올 수도 있다.

String strVar1 = "신용권";
String strVar2 = "신용권";
String strVar3 = new String("신용권");

자바는 문자열 리터럴이 동일하다만 동일한 String 객체를 참조하도록 되어 있다.

그래서 변수 strVar1과 strVar2는 동일한 String 객체의 번지값을 가지고 있다. 

그러나 변수 strVar3은 객체 생성 연산자인 new로 생성한 새로운 String 객체의 번지 값을 가지고 있다.

 

 

이 경우 변수 strVar1과 strVar2의 == 연산은 true를 산출하고 strVar2와 strVar3의 연산은 false를 산출한다.

==연산자는 변수에 저장된 값만을 비교 하기 때문에 이러한 결과가 나온다.

 

동일한 String 객체이건 다른 String 객체이건 상관없이 String 객체의 문자열만을 비교하고 싶다면 == 연산자 대신에 equals() 메소드를 사용해야 한다.

equals() 메소드는 원본 문자열(str1)과 매개값으로 주어진 비교 문자열(str2)이 동일한지 비교한 후 true 또는 false를 리턴한다.

boolean result = str1.equals(str2);
strVar1.equals(strVar2)      //true;
strVar2.equals(strVar3)      //true;

 

 

3.4.4 논리 연산자( &&, ||, &, |, ^, ! )

논리 연산자는 논리곱(&&), 논리합(||), 배타적 논리합(^) ,논리 부정(!) 연산을 수행한다.

논리 연산자의 피연산자는 boolean 타입만 사용할 수 있다.

논리 연산자의 종류와 기능

&&는 &와 산출 결과는 같지만 연산 과정이 조금 다르다.

&&는 앞의 피연산자가 false이면 뒤의 피연산자를 평가하지 않고 false라는 산출 결과를 낸다.

왜냐하면 하나라도 false이면 전체 연산식은 false이기 때문이다.

그러나 &는 두 피연산잘르 모두 평가해서 산출 결과를 낸다.

따라서 & 보다 &&가 더 효율적으로 동작한다.

||와 |도 마찬가지이다.

||는 앞의 피연산자가 true라면 뒤의 피연산자를 평가하지 않고 true를 리턴한다.

 

3.4.5 비트 연산자(&, |, ^, ~, <<, >>, >>>)

비트 연산자는 데이터를 bit탄위로 연산한다. 즉 0과 1이 피연산자가 된다. 그렇기 때문에 0과 1로 표현이 가능한 정수 타입만 비트 연산을 수행할 수 있다.

실수 타입인 float와 double은 비트 연산을 수행 할 수 없다.

 

종류

 • 비트 논리 연산자(&, |, ^, ~)

     - 일반 논리 연산자가 true와 false를 연산한다면 비트 논리 연산자는 0과 1을 연산한다.

 

 • 비트 이동 연산자(<<, >>, >>>)

    - 비트를 좌측 또는 우측으로 이동하는 연산자이다.

 

 

비트 논리 연산자(&, |, ^, ~)

피연산자가 boolean 타입일 경우에는 일반 논리 연산자이고, 피연산자가 정수 타입일 경우에는 비트 논리 연산자로 사용된다.

비트 논리 연산자의 종류와 기능

비트 연산자는 피연산자를 int 타입으로 자동 타입 변환한 후 연산을 수행한다.

그렇기 때문에 byte, short, char 타입을 비트 논리 연산하면 그 결과는 int가 된다.

 

 

비트 이동 연산자(<<, >>, >>>)

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

비트 이동 연산자의 종류와 기능

 

 

3.4.6 대입 연산자(=,  +=,  -=,  *=,  /=,  %=, &=, ^=, |=, <<=, >>=, >>>=)

대입 연산자는 오른쪽의 피연산자의 값을 좌측 피연산자인 변수에 저장한다.

오른쪽 피연산자는 리터럴 및 변수, 그리고 다른 연산식이 올 수 있다.

 

종류

단순 대입 연산자

   - 단순히 오른쪽 피연산자의 값을 변수에 저장

복합 대입 연산자

   - 정해진 연산을 수행한 후 결과를 변수에 저장

 

 

 

3.4.7 삼항 연산자

삼항 연산자(?:)는 세 개의 피연산자가 필요로 하는 연산자를 말한다.

삼항 연산자는 ? 앞의 조건식에 따라 콜론(:) 앞 뒤의 피연산자가 선택된다고 해서 조건 연산식이라고 부르기도 한다.

삼항연산자를 사용하는 방법은 다음과 같다.

조건식을 연산하여 true가 나오면 삼항 연산자의 결과는 피연산자 2가 된다.

반면에 조건식을 연산하여 false가 나오면 삼항 연산자의 결과는 피연산자3이 된다.

 

int score = 95;
char grade = (score>90) ? 'A' : 'B';

(score>90)을 연산하면 true가 나오므로 grade 변수에는 'A'가 저장된다.

삼항 연산자는 if문으로 변경해서 작성할 수 도 있지만, 한 줄에 간단하게 삽입해서 사용할 경우에는 삼항 연산자를 사용하는 것이 더 효율적이다.