이항 연산자는 피연산자가 두 개인 연산자를 말하면 여기에는 산술 연산자 (+,-,*,/,%),문자열 연결 연산자 (+) , 대입연산자 (=,+=,-=,*=,/=,%=,&=,^=,|=,<<=,>>=,>>>=) , 비교 연산자 , 논리연산자, 비트논리연산자, 비트이동연산자 등이 있다.
종류
산술 연산자: +, -, *, /, %
문자열 연결 연산자: +
대입 연산자: =, +=, -=, *=, /=, %=, &=, ^=, |=, <<=, >>=, >>>=
비교 연산자: <, <=, >, >=, ==, !=
논리 연산자: &&, ||, &, |, ^, !
비트 논리 연산자: &, |, ^
비트 이동 연산자: <<, >>, >>>
1. 산술 연산자 (+,-,*,/,%)
우리가 일반적으로 말하는 사칙연산인 더하기뺴기곱하기나누기와 나머지를 구하는 (%)연산자를 포함해서 산술연산자는 총 5 개이다. 이 산술 연산자는 boolean 타입을 제외한 모든 기본 타입에 사용할 수 있다.
특징은 피연산자들의 타입이 동일하지 않을 경우 long타입을 제외한 정수타입연산은 int 타입으로 산출되고, 피연산자중 하나라도 실수타입이면 실수 타입으로 산출된다.
---------------------------------------------------------
byte byte1 = 1;
byte byte2 = 2;
byte result1 = byte1 + byte2; //컴파일 에러
int result2 = byte1 + byte2;
----------------------------------------------------------
int타입으로 나오는 이유는 자바가상기계 (JVM) 이 기본적으로 32비트 단위로 계산하기 때문이다. 다른예를 한번 보자
-----------------------------------------------------------------------------------------------------------
int int1 = 10;
int int2 = 4;
int result2 = int1 / int2 ;
double result3 = int1 / int2;
-----------------------------------------------------------------------------------------------------------
상식적으로 생각하면 int1 / int2 면 2.5 가 맞는데 연산결과는 소수점 이하 부분을 버리고 2만 산출된다, 그렇다고 result3 은 double 타입이니까 2.5가 저장될것이라고 생각하기 쉬운데 연산후의 결과가 2이므로 2를 실수화해서 double타입으로 저장을하기때문에 2.0이 저장된다. 만약 2.5를 산출결과로 얻고싶다면 피연산자중 최소 하나는 실수 타입이어야 한다.
------------------------------------------------------------------------------------------------------------------------------
int int1 = 10;
int int2 = 4;
int result2 = int1 / int2 ; // result2 = 2
double result3 = int1 / int2; // result3 = 2.0
double result4 = (int1*1.0) / int2; //result4 = 2.5 int1에 1.0 을 곱하여 실수타입으로 변환
double result5 = (double) int1 / int2; //result5 = 2.5 int1을 double타입으로 강제변환
double result6 = int1 / (double) int2; //result6 = 2.5 int2을 double타입으로 강제변환
-----------------------------------------------------------------------------------------------------------------------------
------------------------[ ArithmeticOperatorExample.java ] 산술 연산자 -----------------------------------------------------
public class ArithmeticOperatorExample {
public static void main(String[] args) {
int v1 = 5;
int v2 = 2;
int result1 = v1 + v2;
System.out.println("result1=" + result1);
int result2 = v1 - v2;
System.out.println("result2=" + result2);
int result3 = v1 * v2;
System.out.println("result3=" + result3);
int result4 = v1 / v2;
System.out.println("result4=" + result4);
int result5 = v1 % v2;
System.out.println("result5=" + result5);
double result6 = (double) v1 / v2;
System.out.println("result6=" + result6);
}
}
Console
result1=7
result2=3
result3=10
result4=2
result5=1
result6=2.5
---------------------------------------------------------------------------------------------------------------------------------------------------
char 타입도 정수 타입이므로 산술 연산이 가능하다. 주의할 점은 char 타입이 산술연산이 될 경우 int 타입으로 변환되므로 산출 차입은 int 타입이다. 다음예제는 컴파일 에러가 발생한다.
-----------------------------------[ CharOperationExample.java ] char 타입 연산 ------------------------------------------------------------
public class CharOperationExample {
public static void main(String[] args) {
char c1 = 'A' + 1; //'B' 유니코드 66
char c2 = 'A'; //'A' 유니코드 65
//char c3 = c2 + 1; //컴파일 에러
char c4 = (char) (c2 + 1);
System.out.println("c1: " + c1);
System.out.println("c2: " + c2);
//System.out.println("c3: " + c3);
}
}
Console
c1: B
c2: A
c4: B
--------------------------------------------------------------------------------------------------------------------------------------------------------
오버플로우 탐지
산술연산을 할 떄 주의할 점은 연산 후의 산출값이 산출 타입으로 충분히 표현 가능한지 살펴봐야 한다. 산출 타입으로 표현할 수 없는 값이 산출되었을 경우, 오버플로우가 발생하고 쓰레기값(엉뚱한 값) 을 얻을 수 있기 떄문이다. 다음예제를 실행시켜 보자.
--------------------[ OverflowExample.java ] 오버플로우 -------------------------------------------------------
public class OverflowExample {
public static void main(String[] args) {
int x1 = 1000000;
int y1 = 1000000;
int z1 = x * y;
System.out.println(z1);
long x2 = 1000000;
long y2 = 1000000;
long z2 = x * y;
System.out.println(z2);
}
}
--------------------------------------------------------------------------------------------------------------------------
Console
-727379968
1000000000000
변수 x와y는 int 타입이고 x * y 역시 int 타입이므로 연산의 산출 타입은 int 타입이다. 위 코드는 컴파일 에러는 발생하지 않지만, 변수 z에는 올바른 값이 저장되지 않는다. 그 이유는 1000000*1000000 은 10^6*10^6 = 10^12 이 되어 int 타입에 저장될수 있는 값의 범위를 초가하게되어 -727379968 이라는 쓰레기값을 얻게 된다. 상기 예제가 올바른 값을 얻기 위해서는 변수 x 와 y 중 최소 하나라도 long 타입이 되어야 하고 변수z 가 long 타입이여야 한다.
바로 산술 연산자를 사용하지말고 메소드를 이용하는게 오버플로우를 탐지할 수 있기 때문에 메소드를 이용하는것이 좋다.
아직 배우지 않은 내용이지만 이런 코드가 필요하다는 것만 이해하고 넘어가도록 하자. 예외처리는 10장에서 배운다.
-----------------[ CheakOverflowExample.java ] 산술 연산 전에 오버플로우를 탐지 -------------------------------------------
public class CheckOverflowExample {
public static void main(String[] args) {
try {
int result = safeAdd(2000000000, 2000000000);
System.out.println(result);
} catch(ArithmeticException e) {
System.out.println("오버플로우가 발생하여 정확하게 계산할 수 없음");
}
}
public static int safeAdd(int left, int right) {
if((right>0)) {
if(left>(Integer.MAX_VALUE - right)) {
throw new ArithmeticException("오버플로우 발생");
}
} else {
if(left<(Integer.MIN_VALUE - right)) {
throw new ArithmeticException("오버플로우 발생");
}
}
return left + right;
}
}
3. 비교 연산자 (<,<=,>,>=,==,!=)
비교연산자는 대소 (<,<=,>,>=) 또는 동등 (==,!=) 을 비교해서 boolean 타입인 ture/false 를 산출한다. 대소 연산자는 boolean 타입을 제외한 기본 타입에 사용할 수 있고, 동등 연산자는 모든 타입에 사용될 수 있다. 비교연산자는 흐름제어문인 조건문(if) 반복문(for,while) 에서 주로 이용되어 실행 흐름을 제어할 때 사용된다.
만약 피연산자가 char 타입이면 유니코드 값으로 비교 연산을 수행한다. 예를 들어 'A' 의 유니코드는 65이고 'B' 의 유니코드는 66 이므로 비교 연산자는 65와 66 을 비교하게 된다.
('A' < 'B') --> ( 65 < 66 )
--------------------------------[ CompareOperatorExample1.java ] 비교 연산자------------------------------------------------------------------------------
public class CompareOperatorExample1 {
public static void main(String[] args) {
int num1 = 10;
int num2 = 10;
boolean result1 = (num1 == num2);
boolean result2 = (num1 != num2);
boolean result3 = (num1 <= num2);
System.out.println("result1=" + result1);
System.out.println("result2=" + result2);
System.out.println("result3=" + result3);
char char1 = 'A';
char char2 = 'B';
boolean result4 = (char1 < char2);
System.out.println("result4=" + result4);
}
}
비교 연산자에서도 연산을 수행하기 전에 타입 변환을 통해 피연산자의 타입을 일치시킨다. 예를 들어, 'A'==65는 'A'가 int 타입으로 변환되어 65 가 된 다음 65==65로 비교한다. 마찬가지로 3==3.0에서 3은 int 타입이고 3.0은 double타입이므로 int 타입인 3 을 보다 큰 타입인 double타입으로 변환한 다음 3.0==3.0 으로 비교한다.
-----------------------------------------------------------------------------------------------------------------------------------------
'A' == 65 -> ture
3 == 3.0 -> ture
-----------------------------------------------------------------------------------------------------------------------------------------
그러나 한가지 예외가 있는데, 0.1 == 0.1f 와 같은 경우를 보자. 정상적이라면 0.1f가 좌측 피연산자의 타입인 double로 변환되어 0.1==0.1이 되고 ture가 산출되어야 하지만, 이 결과값은 false가 산출된다.
-----------------------------------------------------------------------------------------------------------------------------------------
그 이유는 이진 포맷의 가수를 사용하는 모든 부동소수점 타입은 0.1을 정확히 표현할 수가 없어서 0.1f는 0.1의 근사값으로 표현되어 0.10000000000149011612와 같은 값이 되기 때문에 0.1 보다 큰 값이 되어 버린다. 해결책은 다음 예제와 같이 피연산자를 모두 float타입으로 강제 타입 변환한 후에 비교연산을 하든지, 정수로 변환해서 비교하면 된다.
----------------------------------------[ CompareOperatorExample2.java ] 비교 연산자---------------------------------------------------------------------
public class CompareOperatorExample2 {
public static void main(String[] args) {
int v2 = 1;
double v3 = 1.0;
System.out.println(v2 == v3); //true
double v4 = 0.1;
float v5 = 0.1f;
System.out.println(v4 == v5); //false
System.out.println((float)v4 == v5); //true
System.out.println((int)(v4*10) == (int)(v5*10)); //true
}
}
------------------------------------------------------------------------------------------------------------------------------------------------------------------
Console
true
false
true
true
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의 ==연산은 ture를 산출하고 strVar1이나2 와 strVar3의 ==연산은 false를 산출한다. == 연산자는 변수에 저장된 값만 비교하기 때문에 이러한 결과가 나온다.
---------------------------------------------------------------------------------
strVar1 == strVar2 -> ture
strVar2 == strVar3 -> false
---------------------------------------------------------------------------------
동일한 String 객체이건 다른 String 객체이건 상관없이 String 객체의 문자열만을 비교하고 싶다면 == 연산자 대신에 equals() 메소드를 사용해야 한다. equals() 메소드는 원본 문자열과 매개 값으로 주어진 비교 문자열이 동일한지 비교한 후 ture 또는 false를 리턴한다.
---------------------------------------
boolean result = str1.equals(str2)
---------------------------------------
strVar1.equals(strVar2) ->ture
strVar2.equals(strVar3) ->ture
-----------------------------------------
-------------------------------[ StringEqualsExample.java ] 문자열 비교-----------------------------------------------------------
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));
}
}
4. 논리 연산자 (&&, ||, &, |, ^, !)
논리 연산자는 논리곱(&&), 논리합(||), 배타적 논리합(^), 그리고 논리 부정(!) 연산을 수행한다. 논리 연산자의 피연산자는 boolean 타입만 사용할 수 있다. 다음은 논리 연산자의 종류와 기능을 설명한 표이다.
&&와 &는 산출 결과는 같지만 연산 과정이 조금 다르다. &&는 앞의 피연산자가 false라면 뒤의 피연산자를 평가하지 않고 바로 false라는 산출 결과를 낸다. 왜냐하면 하나라도 false라면 전체 연산식은 false이기 떄문이다. 그러나 & 는 두 피연산자 모두를 평가해서 산출 결과를 낸다. 따라서 & 보다는 &&가 더 효율적으로 동작한다. || 와 | 도 마찬가지이다. || 는 앞의 피연산자가 ture라면 뒤의 피연산자를 평가하지 않고 바로 ture라는 산출 결과를 낸다. 왜냐하면 하나라도 ture이면 전체 연산식은 ture이기 때문이다. 그러나 | 는 두 피연산자 모두를 평가해서 산출 결과를 낸다. 왜냐하면 하나라도 ture 이면 전체 연산식은 ture 이기 때문이다. 그러나 | 는 두 피연산자 모두를 평가해서 산출결과를 낸다. 따라서 | 보다는 || 가 더 효율적으로 동작한다. 논리 연산은 흐름 제어문인 조건문, 반복문 등에서 주로 이용된다. 다음 예제처럼 if문의 괄호 () 안에는 조건식이 오는데, 조건식은 비교 연산자와 논리 연산자들이 주로 사용된다. if 문의 조건식이 ture라면 블록을 실행하고 false라면 블록을 실행하지 않는다.
----------------------- [ LogicalOperatorExample.java ] 논리 연산자 ------------------------------------------------------------------------
public class LogicalOperatorExample {
public static void main(String[] args) {
int charCode = 'A';
if( (charCode>=65) & (charCode<=90) ) {
System.out.println("대문자 이군요");
}
if( (charCode>=97) && (charCode<=122) ) {
System.out.println("소문자 이군요");
}
if( !(charCode<48) && !(charCode>57) ) {
System.out.println("0~9 숫자 이군요");
}
int value = 6;
if( (value%2==0) | (value%3==0) ) {
System.out.println("2 또는 3의 배수 이군요");
}
if( (value%2==0) || (value%3==0) ) {
System.out.println("2 또는 3의 배수 이군요");
}
}
}
--------------------------------------------------------------------------------------------------------------------------------------------
5. 비트 연산자 (&, | ,^ , ~, <<, >>, >>>>)
비트 연산자는 데이터를 비트 단위로 연산한다. 즉 0과 1이 피연산자가 된다. 그렇기 떄문에 0과1로 표현이 가능한 정수 타입만 비트연산을 할 수 있다. 비트연산자는 기능에 따라 비트 논리 연산자 (&, | ,^, ~) 와 비트 이동 연산자 (<<, >>, >>>>) 로 구분한다. 일반 논리 연산자가 ture와 false를 연산한다면 비트 논리 연산자는 0과 1을 연산한다. 비트이동 연산자는 비트를 좌측 또는 우측으로 이동하는 연산자이다.
1) 비트 논리 연산자 (&, |, ^)
비트 논리 연산자에는 &, |, ^, ~ 가 있다. &, |, ^ 연산자는 피연산자가 boolean 타입일 경우에는 일반 논리 연산자이고, 피연산자가 정수 타입일 경우에는 비트 논리 연산자로 사용된다. ~ 는 단항 연산자에서 이미 소개가 되었다. 다음은 비트 논리 연산자의 종류와 기능을 설명한 표이다.
비트연산자는 피연산자를 int 타입으로 자동 타입 변환한 후 연산을 수행한다. 그렇기 때문에 bytem short, char타입을 비트 논리 연산하면 그 결과는 int 타입이 된다. 그래서 다음은 컴파일 에러가 난다.
----------------------------------------
byte num1 = 45;
byte num2 = 25;
byte result = num1 & num2 ; //컴파일 에러 -> int result = num1 & num2 ;
------------------------------------------------------------------------------------------
------------------------------------[ BitLogicExample.java ] 비트 논리 연산자 ----------------------------------------------------------------
public class BitLogicExample {
public static void main(String[] args) {
System.out.println("45 & 25 = " + (45 & 25));
System.out.println("45 | 25 = " + (45 | 25));
System.out.println("45 ^ 25 = " + (45 ^ 25));
System.out.println("~45 = " + (~45));
System.out.println(toBinaryString(45));
System.out.println("&");
System.out.println(toBinaryString(25));
System.out.println("||");
System.out.println(toBinaryString(45&25));
}
public static String toBinaryString(int value) {
String str = Integer.toBinaryString(value);
while(str.length() < 32) {
str = "0" + str;
}
return str;
}
}
Console
45 & 25 = 9
45 | 25 = 61
45 ^ 25 = 52
~45 = -46
00000000000000000000000000101101
&
00000000000000000000000000011001
||
00000000000000000000000000001001
2) 비트 이동 연산자
비트 이동(shift) 연산자는 정수 데이터의 비트를 좌측 또는 우측으로 밀어서 이동시키는 연산을 수행한다. 다음은 비트 이동 연산을 수행하는 연산자의 종류와 기능을 설명한 표이다.
좌측 이동 연산자 (<<) 를 사용하여 정수1 을 3비트만큼 왼쪽으로 이동시켜 보자.
---------------------
int result = 1 << 3;
----------------------
===== 2^3
32비트 전체를 왼쪽으로 3비트 이동할 때 맨 왼쪽 3비트는 밀려서 버려지고, 맨 오른쪽에 새로 생기는 3비트는 0 으로 채우면 2^3 값을 얻게 된다. 따라서 result 변수에는 8이 저장된다. 이번에는 우측 이동 연산자 (>>) 를 사용하여 정수 -8을 3비트만큼 오른쪽으로 이동시켜 보자.
-------------------------
int result = -8 >> 3;
-------------------------
===== -1
32비트 전체를 오른쪽으로 3비트 이동할 때 맨 오른쪽 3비트는 밀려서 버려지고, 맨 왼쪽에 새로 생기는 3비트는 최상위 부호 비트(MSB) 와 동일한 값으로 채워진다. 따라서 -8은 최상위 부호 비트가 1 이므로 맨 왼쪽 빈 공간은 1로 채워진다. 32비트가 모두 1이면 정수-1이므로 변수 result에는 -1이 저장된다. 이번에는 우측 이동 연산자(>>>) 를 사용하여 정수 -8을 3비트만큼 오른쪽으로 이동시켜보자
---------------------
int result =-8 >>> 3;
-----------------------
===== 5336870911 ( 00011111 11111111 11111111 11111111)
32비트 전체를 오른쪽으로 3비트 이동할 때 맨 오른쪽 3비트는 밀려서 버려지고, 맨 왼쪽에 새로 생기는 3비트는 무조건 0 으로 채워진다. 이렇게 변환된 이진수를 십진수로 변환하면 536870911 값을 얻을 수 있다.
----------------------------------[ BitShiftExample.java ] 비트 이동 연산자 -------------------------------------------------------
package sec04.exam05_bit;
public class BitShiftExample {
public static void main(String[] args) {
System.out.println("1 << 3 = " + (1<<3));
System.out.println("-8 >> 3 = " + (-8>>3));
System.out.println("-8 >>> 3 = " + (-8>>>3));
System.out.println(toBinaryString(1));
System.out.println("<< 3");
System.out.println(toBinaryString(1<<3));
}
public static String toBinaryString(int value) {
String str = Integer.toBinaryString(value);
while(str.length() < 32) {
str = "0" + str;
}
return str;
}
}
---------------------------------------------------------------------------------------------------------------------------------
Console
1 << 3 = 8
-8 >> 3 = -1
-8 >>> 3 = 536870911
00000000000000000000000000000001
<< 3
00000000000000000000000000001000
---------------------------------------------------------------------------------------------------------------------------------
6. 대입 연산자 (=, +=, -=, *=, /=, %=, &=, ^=, |=, <<=, >>=, >>>=)
대입 연산자는 오른쪽 피연산자의 값을 좌측 피연산자인 변수에 저장한다. 오른쪽 피연산자는 리터럴 및 변수, 그리고 다른 연산식이 올 수 있다. 단순히 오른쪽 피연산자의 값을 변수에 저장하는 단순 대입 연산자가 있고, 정해진 연산을 수행한 후 결과를 변수에 저장하는 복합 대입 연산자도 있다. 다음은 대입 연산자의 종류를 설명한 표이다.
대입 연산자는 모든 연산자들 중에서 가장 낮은 연산순위를 가지고 있기 떄문에 제일 마지막에 수행된다. 그리고 연산의 진행방행이 오른쪽에서 왼쪽이기 때문에 a=b=c=5; 는 다음 순서로 연산된다.
5=c , c=b , b=a
------------------------------------[ AssignmentOperatorExample.java ] 대입 연산자----------------------------------------------------------------------
package sec04.exam06_assignment;
public class AssignmentOperatorExample {
public static void main(String[] args) {
int result = 0;
result += 10;
System.out.println("result=" + result);
result -= 5;
System.out.println("result=" + result);
result *= 3;
System.out.println("result=" + result);
result /= 5;
System.out.println("result=" + result);
result %= 3;
System.out.println("result=" + result);
}
}
-------------------------------------------------------------------------------------------------------------------------------------------------------------
'legacy' 카테고리의 다른 글
이것이java다 3.2 연산의 방향과 우선순위 (0) | 2023.04.16 |
---|---|
이것이java다 3.3 단항 연산자 (0) | 2023.04.16 |
이것이java다 3.5 삼항 연산자 (0) | 2023.04.16 |
이것이java다 3장 확인문제 풀이 (0) | 2023.04.16 |
이것이java다 4.1 조건문 (0) | 2023.04.16 |