자바가 제공하는 다양한 연산자를 학습한다
연산을 수행하는 기호
그 기능에 따라 여러 가지의 연산자가 있다
피연산자(Operand) : 연산의 대상으로, 연산자가 연산을 수행하기 위해 반드시 필요한 것(e.g. 변수, 상수, 리터럴, 수식)
x + 5
위와 같은 식이 있을 때
- 연산자 : +
- 피연산자 : x, 5
대부분의 연산자는 2개의 피연산자를 필요로 하지만, 1 또는 3개를 필요로 하는 것도 있다
연산자는 피연산자로 연산을 수행하고 나면 항상 결과값을 반환한다
- '+' 연산에서 두 피연산자가 byte형일 때 연산자 '+'는 두 피연산자의 자료형을 int형으로 변환한 다음 연산을 진행한다
다음과 같은 코드에서는 에러가 나타난다
따라서 명시적인 형변환이 필요하다public class Test { public static void main(String[] args) { byte a = 5; byte b = 10; byte c = (byte)(a * b); System.out.println(c); } }
- '/' 연산자의 피연산자가 int타입인 경우, 연산결과 역시 int타입이므로
소수점 이하는 버리고 정수만 남겨지게 된다
(e.g.) 10 / 3 = 3- '/' 연산자의 피연산자 중 하나가 실수형인 경우, 다른 한쪽도 형변환되어 실수의 값을 얻는다
(e.g.) 10 / 3.0f → 10.0f / 3.0f → 3.3333333- 즉, 두 피연산자의 타입이 일치하지 않을 때는 더 넓은 타입으로 일치시킨 후에 연산을 수행한다
- 피연산자가 정수형인 경우, 나누는 수로 0을 사용할 수 없다
- 부동소수점 값인 0.0f, 0.0d로 나누는 것은 가능하지만, 결과값은 Infinity다
- 이렇게 산술 연산자에서는 형변환(casting)을 유념해야 된다
public static void main(String[] args) { int a = 1000000; int b = 3000000; long c = a * b; System.out.println(c); //출력값 : 2112827392 c = (long)a * b; System.out.println(c); //출력값 : 3000000000000 }
↳ (int타입 * int타입)의 연산결과는 int타입이기 때문에 long형으로 자동 형변환되어도 값은 변하지 않는다. 따라서 a 또는 b를 long형으로 형변환 시켜주어야 한다.
public static void main(String[] args) { char a = 'A'; char b = 'b'; System.out.println(a-b); //출력값 : -33 char nine = '9'; char one = '1'; System.out.println(nine-one); //출력값 : 8 }
↳ 사칙연산의 피연산자로 문자도 가능하다. 해당 문자의 유니코드로 바뀌어 계산된다.
- 나머지 연산자 (%)
왼쪽의 피연산자를 오른쪽 피연산자로 나누고 난 나머지 값을 결과로 반환한다
나누는 수로 0을 사용할 수 없고, 피연산자로 정수만 허용된다
짝수, 홀수, 배수 검사 등에 주로 사용된다
- | (OR 연산자) : 피연산자 중 한 쪽의 값이 1이면 결과는 1, 그 외에는 0
- & (AND 연산자) : 피연산자 양 쪽이 모두 1이면 결과는 1, 그 외에는 0
- ^ (XOR 연산자) : 피연산자의 값이 서로 다르면 결과는 1. 그 외에는 0
- ~ (비트 전환 연산자) : 피연산자를 2진수로 표현했을 때 0은 1로, 1은 0으로 전환한다
- << (왼쪽 쉬프트 연산자) : 좌측 피연산자를 2진술로 표현했을 때 각 자리를 왼쪽으로 우측 피연산자만큼 이동시킨다
>>
(오른쪽 쉬프트 연산자) : 좌측 피연산자를 2진술로 표현했을 때 각 자리를 오른쪽으로 우측 피연산자만큼 이동시킨다
public static void main(String[] args) { int x = 0xA5; int y = 0xC; System.out.println("x = " + x + "\t\t\tint타입"); System.out.println("y = " + y + "\t\t\tint타입"); System.out.println("x = " + Integer.toBinaryString(x) + "\t32bit 2진수"); System.out.println("y = " + Integer.toBinaryString(y) + "\t\t32bit 2진수"); System.out.println(Integer.toHexString(x) + " | " + Integer.toHexString(y) + " = " + Integer.toBinaryString(x|y) + " = " + (x|y)); System.out.println(Integer.toHexString(x) + " & " + Integer.toHexString(y) + " = " + Integer.toBinaryString(x&y) + " = " + (x&y)); System.out.println(Integer.toHexString(x) + " ^ " + Integer.toHexString(y) + " = " + Integer.toBinaryString(x^y) + " = " + (x^y)); System.out.println(Integer.toHexString(x) + " ^ " + Integer.toHexString(y) + " ^ " + Integer.toHexString(y) + " = " + Integer.toBinaryString(x^y^y) + " = " + (x^y^y)); System.out.println("~" + Integer.toHexString(x) + " = " + Integer.toBinaryString(~x) + " = " + (~x)); System.out.println(Integer.toHexString(x) + " >> 2 = " + Integer.toBinaryString(x>>2) + " = " + (x>>2)); System.out.println(Integer.toHexString(x) + " << 2 = " + Integer.toBinaryString(x<<2) + " = " + (x<<2)); }```
위 코드에 대한 출력값
- 대소 비교
관계 연산자 연산결과 > 좌변 값이 크면 true, 아니면 false < 우변 값이 크면 true, 아니면 false >= 좌변 값이 크거나 같으면 true, 아니면 false <= 우변 값이 크거나 같으면 true, 아니면 false
- 등가 비교
관계 연산자 연산결과 == 두 값이 같으면 true, 아니면 false != 두 값이 다르면 true, 아니면 false - 두 값의 타입이 달라도 된다 (e.g. 10 == 10.0f 는 true이다) - String타입의 문자열을 비교할 때는 관계연산자'==' 대신 equals()라는 메서드를 사용한다
둘 이상의 조건을 'AND'나 'OR'으로 연결하여 하나의 식으로 표현한다
|| (OR결합): 피연산자 중 어느 한 쪽만 true이면 true
&& (AND결합) : 피연산자 양쪽 모두 true이면 true
! (논리 부정 연산자) : 피연산자가 true이면 false, false이면 true
Short Circuit Evaluation
class Parent{} // 부모 객체
class Child extends Parent {} // 부모 객체를 상속받는 자식 객체
public class Test {
public static void main(String[] args) {
Parent p = new Parent();
Child c = new Child();
System.out.println(p instanceof Parent); // true
System.out.println(p instanceof Child); // false
System.out.println(c instanceof Child); // true
System.out.println(c instanceof Parent); // true
}
}
int x;
System.out.println(x = 3); // 출력값 : 3 (x에 3이 저장된다)
System.out.println(x + 3); // 출력값 : 6
화살표 연산자를 알아보기 전에 람다식에 대한 개념부터 알아야 한다
Java에 두 번의 큰 변화가 있다면 JDK1.5에서의 generics, JDK1.8에서의 lambda expression일 것이다
람다식(lambda experssion)은 메서드를 하나의 식으로 표현하여 함수를 간략하면서 명확한 식으로 나타낼 수 있다
메서드를 람다식으로 표현하면 메서드의 이름과 반환값이 없어지므로, 람다식을 익명함수(anonymous function)이라고도 한다
모든 메서드는 클래스에 포함되어야 하므로 클래스도 새로 만들어야 하고 객체도 생성해야만 메서드를 호출할 수 있는데, 람다식은 이 모든 과정없이 오직 람다식 자체만으로도 메서드의 역할을 대신할 수 있다
함수(function)와 메서드(method)의 차이점
객체지향개념에서는 함수를 객체의 행위나 동작을 의미하는 메서드라는 용어로 사용한다
메서드는 함수와 같은 의미이지만, 특정 클래스에 반드시 속해야 한다는 제약이 있기 때문에 기존의 함수와 같은 의미의 다른 용어를 선택해서 사용한 것이다
그러나 이제 람다식을 통해 메서드가 하나의 독립적인 기능을 하기 때문에 함수라는 용어를 사용하게 된 것이다
람다식에 대해서는 다음에 인터페이스와 람다식에 대해 공부할 때 더 자세히 알아보고,
화살표 연산자가 어떻게 사용되는지만 훓고 넘어가자
보통 우리는 메서드를 정의할 때 다음과 같이 작성한다
반환타입 메서드이름(매개변수 선언){
본문
}
람다식은 메서드에서 이름과 반환타입을 제거하고 매개변수 선언부와 몸통{} 사이에 화살표(->)를 추가하여 작성한다
(매개변수 선언) -> { 본문 }
또한, 반환값이 있는 메서드의 경우에는 return문 대신 식(expression)으로 표현할 수 있다
식의 연산결과가 자동으로 반환값이 된다(식의 끝에는 ';'을 붙이지 않는다)
람다식에 선언된 매개변수의 타입은 추론이 가능한 경우 생략될 수 있으며, 대부분 생략가능하다
필요로 하는 매개변수가 하나라면 매개변수 선언부의 괄호를 생략할 수 있지만, 매개변수의 타입이 있으면 생략할 수 없다
메서드의 본문이 하나의 문장일 때는 괄호{ }를 생략할 수 있고, 이 때는 문장의 끝에 ';'을 붙이면 안된다
람다식에서 괄호{ } 안의 문장이 return문일 경우에는 괄호{ }를 생략할 수 없다
다음은 메서드를 람다식으로 표현한 몇 가지 예이다
/*method*/
int min(int x, int y){
return a < b ? a : b;
}
/*lambda*/
(int a, int b) -> { return a < b ? a : b; };
(int a, int b) -> a < b ? a : b
(a, b) -> a < b ? a : b
/*method*/
void printNameID(String name, int ID){
System.out.println("name : " + name + "\tID : " + ID);
}
/*lambda*/
(String name, int ID) -> {System.out.println("name : " + name + "\tID : " + ID);}
(naem, ID) -> {System.out.println("name : " + name + "\tID : " + ID);}
(naem, ID) -> System.out.println("name : " + name + "\tID : " + ID)
조건식, 식1, 식2 모두 세 개의 피연산자를 필요로 해서 3항 연산자(조건 연산자)이다
(조건식) ? 식1 : 식2
위와 같은 구조이며, 조건식이 true일 때 식1을 수행하고 조건식이 false일 때 식2를 수행한다
3항 연산자를 중첩해서 사용하면 셋 이상 중의 하나를 결과로 얻을 수 있다
/* a가 양수면 1, 0이면 0, 음수면 -1을 result에 저장하는 문장*/
int result = (a > 0) ? 1 : (a == 0 ? 0 : -1);
3항 연산자의 결합규칙은 오른쪽에서 왼쪽이다
그 이유로 위의 코드에서 괄호( )는 필요가 없지만 가독성을 위해 사용했다
만약 식1과 식2, 두 피연산자의 타입이 다른 경우에는 이항 연산자에서처럼 자동 형변환이 일어난다
식에 사용된 연산자가 둘 이상인 경우에 연산자의 우선순위에 의해서 연산순서가 결정된다
또한, 하나의 식에 같은 우선순위의 연산자들이 여러 개 있는 경우, 우선순위가 같다고 해서 아무거나 먼저 처리하는 것이 아니고 나름대로의 규칙을 가지고 있는데 그 규칙을 연산자의 결합규칙이라고 한다
말로서 정리하자면 다음과 같다
- 산술 > 관계 > 논리 > 대입 순이다. 대입은 제일 마지막에 수행됨
- 단항 > 이항 > 삼항 순이다
- 단항 연산자, 삼항 연산자, 대입 연산자를 제외한 모든 연산의 진행방향(결합규칙)은 왼쪽에서 오른쪽이다
(참고)
단항 연산자에 있는 '+'와 '-'는 부호 연산자이다
괄호는 연산자가 아니라 연산자의 우선순위를 임의로 지정할 때 사용하는 기호이다
switch는 if와 비슷한 조건식 문법으로 조건에 따라 분기해야 할 내용이 많아질 경우 사용하는 문법으로 알고 있었다
하지만 Java13에서 switch는 문법이 아니라 operator(expression)으로도 사용된다
switch도 다른 연산자처럼 데이터를 처리하고 그 결과가 존재한다는 것이다
Java 12와 Java 13에서 switch연산자가 어떻게 작동하는지 설명되어 있는 참고자료이다