자바가 제공하는 다양한 연산자를 학습하세요.
학습할것
- 산술 연산자
- 비트 연산자
- 관계 연산자
- 논리 연산자
- instanceof
- assignment(=) operator
- 화살표(->) 연산자
- 3항 연산자
- 연산자 우선 순위
- (optional) Java 13. switch operator
산술(arithmetic) 연산자는 수학적인 계산에 사용되는 연산자이다.
산술 연산자는 산술(arithmetic)이라는 의미 그대로 수학적인 계산에 사용되는 연산자로 덧셈, 뺄셈, 곱셈, 나눗셈 등의 수학적 기호 연산자를 뜻한다. 다만 수학에서 사용하는 연산자와 프로그래밍에서 사용하는 연산자는 의미가 조금 다르니 그에 대해 살펴본다.
+
, -
, *
, /
)덧셈, 뺄셈, 곱셈, 나눗셈은 정말 자주 쓰이는 연산자로 모두가 잘 아는 사칙연산이다. 우선순위 역시 동일하게 적용되에 곱셈과 나눗셈은 덧셈이나 뺄셈보다 높기에 우선 적용된다.
예제
public static void main(String args[]) {
int a = 10;
int b = 4;
System.out.printf("%d + %d = %d%n", a, b, a + b); // 14
System.out.printf("%d - %d = %d%n", a, b, a - b); // 6
System.out.printf("%d * %d = %d%n", a, b, a * b); // 40
System.out.printf("%d / %d = %d%n", a, b, a / b); // 2---(1)
System.out.printf("%d / %f = %f%n", a, (float)b, a / (float)b); //---(2)
}
-> 실행결과
(1) : 10 / 4 이면 2.5가 맞지만 출력 결과를 보면 2가 나온다. 왜 그럴까? 이유는 자료형에 있다. 연산에 사용된 두 피연산자는 모두int
형이다. 그럼 결과역시int
형으로 반환을 하는데 결과값인 2.5는 실수형이기에 소숫점을 버리고 정수형인 2로 반환을 한다. 그리고 이 때, 소숫점은 버림을 하지 반올림을 하지 않는다.
그러면 어떻게 소숫점까지 표현한 정확한 결과을 얻을 수 있을까? 그럼 피연산자중 한 쪽을 실수형으로 변환해야한다. 위 예제중 (2) 항목을 보면 b를 실수형(float)으로 변경해주었다.
위의 연산과정을 보면 두 피연산자의 타입이 각각 int형과 float형으로 일치하지 않기에 타입을 맞춰줘야하는데, 보통 범위가 넓은 쪽으로 매치가 되기에 int보다 범위가 넓은 float타입으로 일치시킨 뒤 연산을 수행한다.
public static void main(String[] args) {
int a = 1_000_000;
int b = 2_000_000;
long value = a * b;
System.out.println(value);
}
-> value로 무엇이 출력될까? 2 x 10¹² 값을 long(8 byte)에 저장하니 정상적으로 출력이 될 것 같지만, 실제로 출력 되는 값은-1454759936 로 엉뚱한 값이 출력된다. 그 이유는 long형으로 자동형변환을 하는 시점에서는 이미 오버플로우가 발생하여 값이 변조되었기 때문에 형변환이 된다한들 값이 변하지 않는 것이다.
그렇기에 해당 연산이 진행되기 전 하나의 피연산자를 충분한 크기의 자료형으로 형변환을 해서 타입일치를 시켜 충분한 범위를 확보해야 한다.
public static void main(String[] args) {
int a = 1_000_000;
int b = 2_000_000;
long value = (long)a * b;
System.out.println(value);//2000000000000
나머지 연산자는 좌측의 피연산자를 우측 피연산자로 나누고 남은 나머지 값을 반환하는 연산자이다. 결국 나눗셈을 수행하기에 나눗셈과 같이 나누는 수(우측 피연산자)로 0을 사용할 수 없다.
int remainderValue = 10 % 3;
System.out.println(remainderValue);// 1
비트 단위로 논리 연산을 할 때 사용하는 연산자
비트 연산자는 피 연산자를 비트단위로 연산하는데, 피 연산자를 이진수로 표현했을 때의 각 자리를 규칙에 따라 연산을 수행하며, 피연산자로 실수는 허용하지 않으며 정수만 허용된다.
1.|
(OR연산자): 피연산자 중 한 쪽의 값이 1 이면, 1을 결과로 얻는다 그 외에는 0을 얻는다.
(주로 특정 비트의 값을 변경할 때 사용한다.)
2.&
(AND연산자): 피연산자 양 쪽이 모두 1일때만 1을 결과로 얻는다. 그외에는 0을 얻는다.
(주로 특정 비트의 값을 추출할 때 사용한다.)
3.^
(XOR연산자): 피연산자의 값이 서로 다를때만 1을 결과로 얻는다. 그 외에는 0을 얻는다.
(같은 값으로 두고 XOR 연산을 수행하면 원래값으로 돌아오기에 간단한 암호화에 사용한다)
논리부정 연산자와 유사한 연산자로 피연산자를 2진수로 표현했을 때 0은 1로 1은 2로 바꾼다.
물론 해당 연산자도 피연산자가 정수형에만 사용하며 실수는 사용할 수 없다.
이러한 비트 전환연산자는 음수를 표현하기위해 사용되는데 음수를 표현할 수 없는 컴퓨터의 제한적인 상황을 1의 보수를 통해 해결한 것이다.
두 피연산자를 비교하는데 사용되는 연산자
주로 조건문과 반복문의 조건식에 사용되며, 연산결과는 오직 true
, false
둘 중 하나이다.
관계 연산자 역시 이항 연산자이기에 비교하는 피연산자의 타입이 다르면 자료형의 범위가 큰 쪽으로 타입을 일치시킨 뒤 비교한다.
두 피 연산자간의 값의 크기를 비교하는 연산자로 참일경우 true, 아닐 경우 false를 반환한다.
기본형은 boolean을 제외하고 다 사용가능하지만 참조형에는 사용할 수 없다.
>
: 좌변 값이 크면 true 아니면 falseSystem.out.println(2 > 1); // true
System.out.println(2 > 2); // false
<
: 좌변 값이 작으면 true, 아니면 falseSystem.out.println(1 < 2); // true
System.out.println(2 < 2); // false
>=
: 좌변 값이 크거나 같으면 true 아니면 falseSystem.out.println(2 >= 2); // true
System.out.println(2 >= 1); // true
System.out.println(1 >= 2); // false
<=
: 좌변 값이 작거나 같으면 true 아니면 falseSystem.out.println(1 <= 2); // true
System.out.println(2 <= 2); // true
System.out.println(3 <= 2); // false
두 피연산자의 값이 같은지 또는 다른지를 비교하는 연산자이다. 대소비교 연산자와는 다르게 참조형을 포함하여 모든 자료형에서 사용이 가능하다. 참조형의 경우에는 객체의 주소값을 저장하고 있기에 해당 주소값을 비교하여 값을 비교할 수 있다.
기본형과 참조형은 서로 형변환이 가능하지 않기 때문에 등가비교 연산자로 기본형과 참조형을 비교할 수는 없다.
==
: Equal toclass Person {
...
}
Person person = new Person();
Person personSecond = person;
System.out.println(2 == 2); // true
System.out.println("abc" == "abc"); // true
System.out.println(2 == "2"); // false
System.out.println(person == personSecond); // true
!=
: Not equal toclass Person {
...
}
Person person = new Person();
Person personSecond = person;
System.out.println(2 != 1); // true
System.out.println("abc" != "abc"); // false
System.out.println(2 != '2'); // true
System.out.println(person != personSecond); // false
둘 이상의 조건을 결합하여 하나의 식으로 만들어주는 연산자
지금까지 여러 연산자들을 살펴보았다. 논리 연산자는 이런 연산자들을 사용한 각각의 조건들을 이어줄 수 있는 역할을 한다.
예시로 시험점수를 생각해보면 수학에서 2등급은 80점 이상 90점 미만(점수 >= 80 이면서 점수 < 90)이라고 하면 조건은 하나로 불가능하다. 이를 논리 연산자를 통해 하나의 식으로 결합해보자
||
(OR 결합): 피연산자중 어느 한 쪽만 true이면 true를 결과로 얻는다.&&
(AND 결합): 피연산자 양쪽 모두 true이어야 true를 결과로 얻는다!
)이 연산자는 피연산자의 결과를 반대로 반환하는데, true이면 false를 반환하고 false면 true를 반환한다. 마치 스위치를 껐다켰다 하듯이 논리 부정연산자를 중첩하면 true와 false가 계속 뒤바뀐다. 이 논리부정 연산자가 자주 쓰이는 곳은 조건문과 반복문의 조건식인데, 이 연산자를 이용하여 반복을 제어하거나 실패에대한 조건을 적용할 수 있다.
참조변수의 실제 타입을 알아보기 위해 사용한다.
-> 참조변수 instnaceof 타입(클래스)
A instanceof B : (공변) 타입 검증 연산자, A가 B의 타입 혹은 하위 구현체인지 판단한다.
반환 타입은 Boolean 이며 반환 값이 true일 경우 참조 변수가 검사한 타입으로 형변환이 가능하다는 의미가 된다. 이는 조상타입의 참조변수로 자손타입의 인스턴스를 참조할 수 있기 때문에, 참조변수의 타입과 인스턴스 타입이 항상 같지는 않다는 점을 기억한다면 instanceof 연산자를 이용해 참조변수 c가 가리키고 있는 인스턴스의 타입을 확인 후 적절히 형변환 할 수 있다. 예를 들어서 Parent라는 클래스와 이를 상속하는 Child가 있다면 Child는 Parent로 형변환이 가능하다.
class Parent {
...
}
class Child extends Parent {
...
}
Parent parent = new Child();
System.out.println(parent instanceof Child);//true
System.out.println(parent instanceof Parent);//true
System.out.println(parent instanceof Object);//true
변수와 같은 저장공간에 값 또는 수식의 연산결과를 저장하는데 사용된다.
-> 지정될 피연산자 = 지정할 피연산자
대입연산자는 연산자를 기준으로 우측 피연산자의 평가결과를 좌측 피연산자에 대입한다.
그리고, 연산자들 중 가장 낮은 우선순위를 가지고 있기에 항상 제일 늦게 수행된다. 그리고 진행방향은 우측에서 좌측으로 진행되기에 x = 3이면 3을 x에 대입하고, x = y = z = 3이면 z부터 차례대로 대입된다.
대입 연산자의 왼쪽 피연산자를 lvalue(left value) 오른쪽 피연산자를 rvalue(right value)라 한다.rvalue는 변수 뿐 아니라 식이나 상수 등 모두 가능하지만, lvalue는 반드시 변수처럼 값을 변경할 수 있는 것이어야 한다.
int i = 0; 3 = i + 3; //에러. lvalue가 저장할 수 있는 공간이 아닌 리터럴이다. i + 3 = i; //에러. lvalue의 평가결과는 i + 3 -> 0 + 3 -> 3이기에 리터럴으로 저장할수 없다.
대입 연산자는 다른 연산자(op)와 결합하여 op=
형태로 사용될 수 있다. 예를 들어 7살인 철수의 나이가 생일이지나 8살이 되는 것을 표현하면
//before case
int chulsuAge = 7;
chulsuAge = chulsuAge + 1;
//after case
int chulsuAge = 7;
chulsuAge += 1;
메소드를 하나의 식(expression)으로 표현한 것
기존의 메소드를 람다식(화살표 연산자)으로 표현하면 메소드의 이름과 반환값이 없어져서 익명함수라고도 부르는데, 메소드를 하나의 인수로 취급할 수도 있어서, 유연한 프로그래밍을 가능하게 해준다. 이 람다식은 Java 8 이후 나온 기술인데, 이 기술을 얘기하려면 함수형 인터페이스에 대해 알아야한다.
int min(int a, int b){
return a < b ? a : b ;
}
( a , b ) -> a < b ? a : b;
=> 클래스를 작성하고 객체를 생성하지 않아도 메소드를 사용할 수 있다.
유의 사항
1. 매개변수의 타입을 추론할 수 있는 경우에는 타입을 생략할 수 있습니다.
2. 매개변수가 하나인 경우에는 괄호(())를 생략할 수 있습니다.
3. 함수의 몸체가 하나의 명령문만으로 이루어진 경우에는 중괄호({})를 생략할 수 있습니다. (이때 세미콜론(;)은 붙이지 않음)
4. 함수의 몸체가 하나의 return 문으로만 이루어진 경우에는 중괄호({})를 생략할 수 없습니다.
5. return 문 대신 표현식을 사용할 수 있으며, 이때 반환값은 표현식의 결괏값이 됩니다. (이때 세미콜론(;)은 붙이지 않음)
조건의 결과에 따라 평가식을 반환하는 연산자
3항 연산자는 첫 번째 피연산자의 조건식의 평가결과에 따라 다른 결과를 반환한다.
조건식이 true이면 2번째 피연산자인 식1이, 조건식이 false이면 3번째 피연산자인 식2가 반환된다.
조건식의 가독성을 위해 괄호로 감싸도 되지만 필수는 아니다.
삼항연산자의 식1과 식2에는 주로 값이 오지만 경우에 따라 또다른 연산식(ex: 삼항연산자)이 올 수도 있다.
int a = 1;
int b = 2;
String result = "";
//before case
if(a < b) {
result = "a가 작습니다";
} else {
result = "b가 작습니다";
}
//after case
result = a < b ? "a가 작습니다" : "b가 작습니다"
연산자가 둘 이상인 경우 우선적으로 연산되어야 하는 순위
식에 사용된 연산자가 둘 이상인 경우, 연산자의 우선순위에 의해서 연산순서가 결정된다.
괄호의 우선순위가 제일 높으며, 그 다음 산술 > 비교 > 논리 > 대입의 순서입니다.
항은 단항 > 이항 > 삼항의 순서입니다. 연산자들의 진행방향은 좌측에서 우측으로 진행되며 단항 연산자와 대입 연산자의 경우에는 우측에서 좌측으로 진행됩니다.
java 13 스위치 표현식은 새키워드 yield를 추가하여 이전 표현식을 확장한다.
// 기존의 switch 문
int a = 0;
switch (a) {
case 0:
System.out.println("case 0");
break;
case 1:
case 2:
System.out.println("case 1 or case 2");
break;
default:
throw new Exception("non match");
}
// 변경된 switch 문
int a = 0;
switch (a) {
case 0 -> {
System.out.println("case 0");
break;
}
case 1, 2 -> {
System.out.println("case 1");
break;
}
default -> {
throw new Exception("non match");
}
}