목표
자바가 제공하는 다양한 연산자를 학습하세요.
학습할 것
- 산술 연산자
- 비트 연산자
- 관계 연산자
- 논리 연산자
- instanceof
- assignment(=) operator
- 화살표(->) 연산자
- 3항 연산자
- 연산자 우선 순위
- (optional) Java 13. switch 연산자
연산자
연산자는 '연산을 수행하는 기호'를 말한다. 연산자가 연산을 수행하려면 반드시 연산의 대상이 있어야 하는데, 그것을 '피연산자'라고 한다.
- 연산자(operator) : 연산을 수행하는 기호(+, -, *, / 등)
- 피연산자(operand) : 연산자의 작업 대상(변수, 상수, 리터럴, 수식 등)
연산자의 종류는 아래와 같다.
종류 | 연산자 | 설명 |
산술 연산자 | + - * / % << >> | 사칙 연산과 나머지 연산(%) |
관계(비교) 연산자 | > < >= <= == != | 크고 작음과 같고 다름을 비교 |
논리 연산자 | && || ! & | ^ ~ | 그리고(AND)와 또는(OR)으로 조건을 연결 |
대입 연산자 | = | 우변의 값을 좌변에 저장 |
기타 | (type) ?: instance of | 형변환 연산자, 삼항 연산자, instance of 연산자 |
산술 연산자
산술 연산자에는 사칙 연산자와 나머지 연산자가 있다.
산술 연산자
- 덧셈(+), 뺄셈(-), 곱셈(*), 나눗셈(/)
각 산술 연산자를 사용해 아래 코드를 작성해 실행시켜보자.
public class Operator
{
public static void main(String[] agrs)
{
int a = 10;
int b = 3;
// 덧셈
System.out.printf("a + b = %d%n", a + b);
// 뺄셈
System.out.printf("a - b = %d%n", a - b);
// 곱셈
System.out.printf("a * b = %d%n", a * b);
// 나눗셈
System.out.printf("a / b = %d%n", a / b);
}
}
위 코드를 실행시키면 각각 아래 결과가 나온다.
a + b = 13
a - b = 7
a * b = 30
a / b = 3
Java 연산자를 이미 알고 있는 사람은 눈치챘겠지만, 나눗셈 연산자의 경우 우리가 수학적으로 생각하는 나눗셈 결과값이 아닌 다른 값이 반환된다.
변수 a와 b가 정수형 자료형(int)이기 때문에, 나눗셈 연산 이후 정수부 외 소수점 이하는 버려지기 때문이다. 이 때 중요한 것은 소수점 이하가 버려질때 반올림이 아니라 버림으로 처리된다는 것이다.
올바른 연산을 하기 위해서는 두 피연산자(a, b) 중 하나를 실수형으로 형변환 해주면된다.
public class Operator
{
public static void main(String[] args)
{
int a = 10;
int b = 3;
System.out.printf("a / b = %f", a / (float) b);
}
}
a / b = 3.333333
그리고 피연산자가 정수형인 경우 나누는 수로 0을 사용할 수 없다. 0으로 정수형을 나누게되면 컴파일시에는 문제가 없으나, 런타임 상황에서 ArithmeticException이 발생한다.
public class Operator
{
public static void main(String[] args)
{
int a = 10;
int b = 0;
System.out.printf("a / b = %f", a / b);
}
}
Exception in thread "main" java.lang.ArithmeticException: / by zero
at main.Operator.main(Operator.java:10)
단, 0을 float나 double 로 형변환해 나누면 ArithmeticException은 피할 수 있으나 결과값은 Infinity(무한)이 반환된다.
public class Operator
{
public static void main(String[] args)
{
int a = 10;
int b = 0;
System.out.printf("a / b = %f", a / (float) b);
}
}
a / b = Infinity
이렇듯, 산술 연산자의 경우 우리가 당연시 여기는대로 연산이 수행된다. 하지만 그렇지 않은 경우도 있는데, 이는 자료형에 의해 발생한다.
public class Operator
{
public static void main(String[] args)
{
byte a = 10;
byte b = 20;
byte c = a + b;
System.out.printf("a + b = %d", c);
}
}
위 코드를 작성하면 byte c = a + b; 라인에서 컴파일 오류가 발생하는데, 이유는 아래와 같다.
- a와 b는 모두 int 형보다 작은 byte 형 변수이다.
- + 연산자는 이 두 변수를 int형으로 변환해 연산을 수행한다.
- 때문에, 결과값은 byte가 아닌 int형 자료형이다.
- 그런데 이 결과값을 byte형 변수에 형변환 없이 넣으려 했기 때문에 컴파일 오류가 발생한다.
자료형에 의한 문제는 더있다.
public class Operator
{
public static void main(String[] args)
{
byte a = 10;
byte b = 30;
byte c = (byte)(a * b);
System.out.printf("a * b = %d", c);
}
}
위 코드를 수행하면 어떤 결과가 나올까? 300? 그렇지 않다.
a * b = 44
자료형을 제외하고 생각하면 300이 맞지만, 위 코드에서는 아래의 순서대로 동작한다.
- byte형 a 와 b가 곱셈 연산을 수행한다.
- 이 과전에서 자료형이 int형으로 바뀌고 결과값은 300이 된다.
- 결과값 300을 byte형 변수 c에 담기 위해 byte 형으로 형변환을 수행한다.
- 결과값이 byte 자료형의 최대 값 127을 초과하므로 오버플로우가 발생하고, 이로 인해 c에는 44가 담긴다.
이러한 값 손실 문제를 예방하기 위해서는 연산 시 충분히 큰 자료형을 사용하도록 해야한다.
나머지 연산자
나머지 연산자는 나눗셈 연산 이후 나머지 값을 결과로 반환하는 연산자이다. 나눗셈과 동일하게 나누는 수를 0으로 사용할 수 없고, 피연산자로 정수만 허용한다.
public class Operator
{
public static void main(String[] args)
{
int a = 10;
int b = 3;
System.out.printf("a / b = %f%n", a / (float) b);
System.out.printf("a %% b = %d%n", a % b);
}
}
a / b = 3.333333
a % b = 1
비트 연산자
비트 연산자는 데이터를 bit 단위로 논리 연산하는 연산자이다. 때문에 실수형은 허용하지 않고 정수형만 연산이 가능하다.
- | (OR 연산자)
피연산자 중 한 쪽의 값이 1이면, 1을 결과로 얻는다. 그 외에는 0을 결과로 얻는다. - & (AND 연산자)
피연산자 양 쪽이 모두 1이어야만 1을 결과로 얻는다. 그 외에는 0을 결과로 얻는다. - ^ (XOR 연산자)
피연산자의 값이 서로 다를 때만 1을 결과로 얻는다. 같을 때는 0을 얻는다.
코드로 간단히 살펴보자.
public class Operator
{
public static void main(String[] args)
{
int a = 1;
int b = 0;
// OR 연산
System.out.printf("a | b = %d%n", a | b);
// AND 연산
System.out.printf("a & b = %d%n", a & b);
// XOR 연산
System.out.printf("a ^ b = %d%n", a ^ b);
}
}
a | b = 1
a & b = 0
a ^ b = 1
이 비트 연산자를 활용하는 코딩 테스트도 있는데, 간단하게 하나 풀어보자.
1~10 범위의 정수형 값이 5개 담겨져있는 배열에는 한 개의 요소를 제외한 모든 요소가 중복된 값이다.
이 때 중복되지 않는 값을 찾아라.
이 문제는 위의 비트 연산자를 이용해 간단히 해결할 수 있다.
public class Operator
{
public static void main(String[] args)
{
int[] intArr = {1, 2, 3, 1, 2};
int result = 0;
for (int a : intArr)
{
result = result ^ a;
}
System.out.printf("중복되지 않은 값은 %d 입니다.", result);
}
}
중복되지 않은 값은 3 입니다.
어떻게 이런 결과를 도출했을까? 총 5 번의 연산을 하나씩 살펴보자
관계 연산자
관계 연산자는 비교 연산자라고도 하며, 두 피연산자를 비교하는데 사용한다. 연산 결과는 반드시 boolean 자료형의 true / false 둘 중 하나이다.
- > : 좌변 값이 크면 true, 작으면 false
- < : 좌변 값이 작으면 true, 크면 false
- >= : 좌변 값이 크거나 같으면 true, 그외에는 false
- <= : 좌변 값이 작거나 같으면 true, 그외에는 false
- == : 좌변과 우변의 값이 같으면 true, 다르면 false
- != : 좌변과 우변의 값이 다르면 true, 같으면 false
public class Operator
{
public static void main(String[] args)
{
int a = 10;
int b = 3;
System.out.printf("a > b : %b%n", a > b);
System.out.printf("a < b : %b%n", a < b);
System.out.printf("a >= b : %b%n", a >= b);
System.out.printf("a <= b : %b%n", a <= b);
System.out.printf("a == b : %b%n", a == b);
System.out.printf("a != b : %b%n", a != b);
}
}
a > b : true
a < b : false
a >= b : true
a <= b : false
a == b : false
a != b : true
논리 연산자
논리 연산자는 둘 이상의 조건을 AND 또는 OR 로 연결해 하나의 식을 표현할 수 있게 해준다.
- && : AND 결합. 피연산자 양쪽 모두 true이어야 true를 결과로 받는다.
- || : OR 결합. 피연산자 중 어느 한 쪽만 true이면 true를 결과로 받는다.
- ! : 단항 연산자로, 피연산자의 값의 반대 값을 결과로 받는다.
public class Operator
{
public static void main(String[] args)
{
int a = 10;
int b = 3;
// AND 결합
System.out.printf("a > 7 && b < 1 ==> %b%n"), a > 7 && b < 1);
// OR 결합
System.out.printf("a > 7 || b < 1 ==> %b%n", a > 7 || b < 1);
// ! 연산자
System.out.printf("!(a > 7) ==> %b%n", !(a > 7));
}
}
a > 7 && b < 1 ==> false
a > 7 || b < 1 ==> true
!(a > 7) ==> false
instanceof
변수가 참조하고있는 객체의 실제 타입을 확인하기 위해 사용하는 연산자이다. 연산자의 좌변에는 변수를, 우변에는 타입을 위치한다.
public class Operator
{
public static void main(String[] args)
{
CustomClass variable = new CustomClass();
System.out.printf("variable instance of CustomClass : %b", variable instanceof CustomClass);
}
public static class CustomClass
{
public CustomClass ()
{
}
}
}
variable instance of CustomClass : true
만약, 변수의 타입 클래스가 다른 클래스를 상속받는 클래스일 경우, 상위 클래스를 대상으로 instanceof 연산을 수행했을 때도 true가 반환된다.
public class Operator
{
public static void main(String[] args)
{
Bar bar = new Bar();
System.out.printf("bar instance of Bar : %b%n", bar instanceof Bar);
System.out.printf("bar instance of Foo : %b%n", bar instanceof Foo);
}
public static class Foo
{
public Foo () {}
}
public static class Bar extends Foo
{
public Bar () {}
}
}
bar instance of Bar : true
bar instance of Foo : true
마찬가지로, 인터페이스를 구현하는 클래스의 경우, 인터페이스를 대상으로 instanceof 연산을 수행했을 때도 true가 반환된다.
public class Operator
{
public static void main(String[] args)
{
Bar bar = new Bar();
System.out.printf("bar instance of Bar : %b%n", bar instanceof Bar);
System.out.printf("bar instance of Foo : %b%n", bar instanceof Foo);
}
public interface Foo
{
}
public static class Bar implements Foo
{
public Bar () {}
}
}
bar instance of Bar : true
bar instance of Foo : true
assignment(=) operator
대입 연산자는 변수와 같은 저장공간에 값 또는 연산 결과를 저장하는데 사용된다. 대입 연산자는 연산자들 중에서 우선순위가 가장 낮기 때문에 식에서 제일 나중에 수행되고 저장된 값을 연산 결과로 반환한다.
아래 코드를 수행하면 간단히 확인할 수 있다.
public class Operator
{
public static void main(String[] args)
{
int x, y;
System.out.println(x=y=3);
System.out.println(x);
System.out.println(y);
}
}
3
3
3
그리고, 앞서 설명한 것과 같이 값 또는 연산 결과를 저장하는데 사용되므로, 좌변에는 리터럴이나 상수 값이 위치할 수 없다.
화살표(->) 연산자
Java 8에서 람다가 도입되면서 사용하는 연산자이다. 자세한 내용은 나중에... 람다 부분에서 다루자...
public class Operator
{
public static void main(String[] args)
{
Bar bar = new Bar();
bar.doSomething("test");
}
@FunctionalInterface
public interface Foo
{
String doSomething(String s);
}
public static class Bar implements Foo
{
@Override
public String doSomething(String s) {
return "Bar ::: " + s;
}
public Bar(){}
}
}
위 코드는 익명 클래스를 활용해 아래와 같이 줄일 수 있다.
public class Operator
{
public static void main(String[] args)
{
Foo foo = new Foo() {
@Override
public String doSomething(String s) {
return "anonymous class " + s;
}
};
foo.doSomething("test");
}
@FunctionalInterface
public interface Foo
{
String doSomething(String s);
}
}
그리고 람다를 이용해 더 줄일 수 있다!
public class Operator
{
public static void main(String[] args)
{
Foo foo = (s) -> "lambda " + s;
foo.doSomething("test");
}
@FunctionalInterface
public interface Foo
{
String doSomething(String s);
}
}
3항 연산자
삼항 연산자는 3개의 피연산자를 필요로하는 연산자이다.
조건식 ? 식1 : 식2
구조로 되어있는데, 조건식이 참이면 식1이, 조건식이 거짓이면 식2이 연산결과가 된다.
public class Operator
{
public static void main(String[] args)
{
int a = 10;
int b = 3;
String result = (a > b)? "참" : "거짓";
System.out.printf("(a > b)? 참 : 거짓 ==> %s", result);
}
}
(a > b)? 참 : 거짓 ==> 참
연산자 우선순위
단항 연산자 > 이항 연산자(곱셉,나눗셈 > 덧셈,뺄셈) > 비교 연산자 > 논리 연산자 > 삼항 연산자 > 대입 연산자 > 람다 표현식
'☕️ JAVA > WhiteShip Java LIVE Study' 카테고리의 다른 글
[WhiteShip Java LIVE Study] 6주차 : 상속 (0) | 2021.02.17 |
---|---|
[WhiteShip Java LIVE Study] 5주차 : 클래스 (1) | 2020.12.25 |
[WhiteShip Java LIVE Study] 4주차 : 제어문 (0) | 2020.12.11 |
[WhiteShip Java LIVE Study] 2주차 : 자바 데이터 타입, 변수 그리고 배열 (0) | 2020.11.20 |
[WhiteShip Java LIVE Study] 1주차 : JVM은 무엇이며 자바 코드는 어떻게 실행하는 것인가 (0) | 2020.11.15 |