[Java] 연산자

JM·2022년 8월 1일
1

Java_Live_Study

목록 보기
3/15
post-thumbnail

학습할 것

  • 산술 연산자
  • 비트 연산자
  • 관계 연산자
  • 논리 연산자
  • instanceof
  • assignment(=) operator
  • 화살표(->) 연산자
  • 3항 연산자
  • 연산자 우선 순위
  • (optional) Java 13. switch 연산자



산술 연산자

산술 연산자는 +,-,*,/,%와 같은 사칙연산을 다루는 연산자이다.

/ : 정수형 타입일 경우 몫을 반환하고, 실수형 타입일 경우 실수로 표현되는 몫을 반환한다.

% : 정수형 타입일 경우 나머지를 반환하고, 실수형 타입일 경우 정수인 몫을 가질 때의 나머지를 반환한다.

산술 연산자는 두 개의 피연산자를 가지며, 연산은 오른쪽에서 왼쪽으로 이뤄진다.

int a = 10;
int b = 3;

int plus = a + b;
System.out.println("a + b = " + plus); // 13

int minus = a - b;
System.out.println("a - b = " + minus); // 7

int multiple = a * b;
System.out.println("a * b = " + multiple); // 30

int iDivide = a / b;
System.out.println("a / b = " + iDivide); // 3

double dDivide = a / (double)b;
System.out.println("a / b = " + dDivide); // 3.3333333333

숫자형 변수를 사용한 산술 연산은 위와 같다.
char type과 String type또한 산술 연산이 가능하다.

String s1 = "hello";
String s2 = "world";

String sPlus = s1 + s2;
System.out.println("s1 + s2 = " + sPlus); // helloworld

char c1 = 'a';
char c2 = 'b';

System.out.println(c1 + c2); // 195

System.out.println(c1 - c2); // -1

System.out.println(c1 * c2); // 9506

System.out.println(c1 / c2); // 0

String type은 덧셈 연산만 가능하지만, char type은 모든 산술 연산자가 적용된다.

char type은 정수형 타입으로 형변환되 산술 연산자가 적용된다.

추가적으로, 자바에서는 operator overloading을 지원하지 않는다. 이유는 다음과 같다.

  • 자바는 OOP 언어로서 단순함과 깔끔함을 추구한다. operator overloading을 지원한다면, 코드 디자인은 더 복잡해질 것이다.
  • 하나의 operator가 다양한 의미로 사용될 경우 프로그래밍 에러가 많아질 것이다.
  • operator의 진짜 의미를 찾기 위한 과정 때문에 JVM은 느려질 것이다.



비트 연산자

비트 연산자는 비트 단위로 논리 연산을 수행할 때 사용하는 연산자이다.

byte b1 = 1;
byte b2 = 3;

byte and = b1 & b2;

먼저 기본적으로 비트 연산자는 모든 정수형 타입에서 사용될 수 있지만,

비트 연산의 결과값은 int형이기 때문에 int형보다 큰 데이터 타입에서만 값을 받을 수 있다.

따라서 위의 b1 & b2는 연산되지만, 해당 값이 byte and에 담겨지지는 못한다.

&

byte b1 = 2; // 0010
byte b2 = 5; // 0101
System.out.println( b1 & b2 ); // 0

byte b3 = 3; // 0011
byte b4 = 2; // 0010
System.out.println( b3 & b4 ); // 2

&연산자는 비트 AND 연산을 수행한다. 이진수로 나타냈을 때 대응되는 두 비트가 모두 1이면 1을 반환한다.

|

byte b1 = 2; // 0010
byte b2 = 5; // 0101
System.out.println( b1 | b2 ); // 7

byte b3 = 3; // 0011
byte b4 = 2; // 0010
System.out.println( b3 | b4 ); // 3

|연산자는 비트 OR 연산을 수행한다. 대응되는 두 비트 중 하나라도 1이면 1을 반환한다.

^

byte b1 = 2; // 0010
byte b2 = 5; // 0101
System.out.println( b1 ^ b2 ); // 7

byte b3 = 3; // 0011
byte b4 = 2; // 0010
System.out.println( b3 ^ b4 ); // 1

^연산자는 비트 XOR 연산을 수행한다. 대응되는 두 비트가 다르면 1을, 같으면 0을 반환한다.

~

byte b1 = 2; // 00000010
System.out.println( ~b1 ); // 11111101, -3

byte b3 = 3; // 00000011
System.out.println( ~b3 ); // 11111100, -4

~연산자는 비트 NOT 연산을 수행한다. 모든 비트를 반전한다. 1이면 0, 0이면 1로 바뀐다.

부호를 바꿀 때 NOT연산을 수행 후 1을 더한다. NOT 연산만 수행할 경우는 부호를 바꾸고 1을 뺀 것과

동일한 결과를 갖는다. 따라서 ~22의 부호를 바꾸고 1을 뺀 것이므로 -3이 된다.

<<

byte b1 = 2; // 00000010
System.out.println( b1 << 1 ); // 00000100, -3

byte b3 = 3; // 00000011
System.out.println( b3 << 3 ); // 00011000, -4

<<연산자는 지정한 수만큼 비트를 왼쪽으로 이동시킨다. 즉 2를 지정한 횟수만큼 곱해지는 결과를 얻는다.

>>

byte b1 = 2; // 00000010
System.out.println( b1 >> 1 ); // 00000001, 1

byte b3 = 3; // 00000011
System.out.println( b3 >> 3 ); // 00000000, 0

>>연산자는 지정한 수만큼 비트를 오른쪽으로 이동시킨다. 즉 2를 지정한 횟수만큼 나누는 결과를 얻는다.

이떄 비트의 이동으로 생기는 왼쪽 비트들은 양수일 때는 0, 음수일 떄는 1로 채워진다.

>>>

byte b1 = -1; // 11111111
System.out.println( b1 >>> 2 ); // 1073741823

byte b3 = 7; // 00000111
System.out.println( b3 >>> 1 ); // 3

>>>연산자는 부호 비트를 포함하여 모든 비트를 오른쪽으로 이동시킨다. >>연산자와는 다르게

왼쪽에 생긴 비트는 0으로 채워진다. 따라서 -1에 연산자를 적용시켰을 때 가능한 양수 중 가장 큰

값이 나오게 된다.



관계 연산자

관계 연산자는 두 변수를 비교하여 true or false를 반환한다.
==,!=,>,<,>=,<=

  • == : 두 피연산자가 동일하면 true
  • != : 두 피연산자가 일치하지 않으면 true
  • > : 왼쪽 피연산자가 오른쪽 피연산자보다 크면 true
  • < : 오른쪽 피연산자가 왼쪽 피연산자보다 크면 true
  • >= : 왼쪽 피연산자가 오른쪽 피연산자보다 크거나 같으면 true
  • <= : 오른쪽 피연산자가 왼쪽 피연산자보다 크거나 같으면 true
char c = 'a';
int i = 97;
System.out.println(c == i);

int i10 = 10;
double d10 = 10.0;
System.out.println(i10 == d10);

//        int i1 = 1;
//        boolean b = true;
//        System.out.println(i1 == b);

boolean형이 아닌 모든 primitive type 변수들은 모든 관계 연산자를 통해 비교가 가능하지만,

boolena형은 boolean형 끼리만 비교가능하다.



논리 연산자

논리 연산자에는 &&,||,!가 있다. 각 연산자의 의미보다는 short circuit effect에 관해 설명하고자 한다.

Short Circuit Effect

short circuit effect는 &&, ||연산자에서 명백하게 True일 경우 뒤에 있는 조건을 확인하지 않고,

결론을 내려버리는 것을 말한다. 예를 들어, &&연산에서 앞의 조건이 false일 경우 뒤 조건은 확인하지 않는다.

만약 조건에 특정 메소드를 실행시키고자 한다면 short circuit에 의해 실행되지 않을 수 있음에 주의해야 한다.

public static void main(String[] args) {
    if(returnFalse() && returnTrue()){

    }
    System.out.println();
    if(returnTrue() && returnFalse()){

    }
}
// returnFalse

// returnTrue
// returnFalse

public static boolean returnTrue(){
    System.out.println("returnTrue");
    return true;
}

public static boolean returnFalse(){
    System.out.println("returnFalse");
    return false;
}

위 경우, 첫 번째 if문에서는 앞의 조건이 false이기 때문에 뒤의 returnTrue()메소드가 실행되지 않는다.

그에 반해, 두 번째 if문에서는 앞의 조건이 true이기 때문에 뒤의 returnFalse()메소드가 실행된다.

public static void main(String[] args) {
    if(returnFalse() || returnTrue()){

    }
    System.out.println();
    if(returnTrue() || returnFalse()){

    }
}
// returnFalse
// returnTrue
//
// returnTrue

public static boolean returnTrue(){
    System.out.println("returnTrue");
    return true;
}

public static boolean returnFalse(){
    System.out.println("returnFalse");
    return false;
}

위 경우, 첫 번째 if문에서는 앞의 조건이 false이기 때문에 뒤의 returnTrue()메소드가 실행된다.

그에 반해, 두 번째 if문에서는 앞의 조건이 true이기 때문에 뒤의 returnFalse()메소드가 실행되지 않는다.

이처럼, &&연산은 앞의 조건이 false일 경우 뒤 조건을 확인하지 않고, ||연산에서는 앞의 조건이 true일 경우 뒤의 조건을 확인하지 않는다.

반드시 실행되어야 할 메소드를 조건으로 넣는 것에 유의해야 한다.



instanceof

instanceof는 말 그대로 주어진 객체가 주어진 클래스의 인스턴스 인지 확인하는 명령어이다.

객체 instanceof 클래스 형태로 입력하고, 인스턴스라면 true를 리턴하고, 아니라면 false를 리턴한다.

public class A {
}

public class B extends A {
}

public class InstanceOfTest {
    public static void main(String[] args) {
        A a = new A();
        B b = new B();

        System.out.println("a is instanceof A? " + (a instanceof A)); // true
        System.out.println("b is instanceof A? " + (b instanceof A)); // true
        System.out.println("a is instanceof B? " + (a instanceof B)); // false
        System.out.println("b is instanceof B? " + (b instanceof B)); // true
    }
}

위 코드에서는 클래스 A,B를 InstanceOfTest 클래스와 동일한 파일에 둔 것처럼 하였지만, 실제로

자바에서 클래스는 클래스명과 동일한 파일에 속해야 한다. 상속관계와 implement관계에 있는 인스턴스라도,

자식 클래스의 인스턴스는 부모 클래스의 인스턴스라고 취급된다. 따라서 true를 반환한다.

instanceof 연산자는 언제 사용될까?

어떤 타입인지 알 수 없는 객체를 casting할 때 해당 객체의 type을 확인하기 위해 instanceof를 사용한다.

이는 ClassCastException을 필할 수 있게 도와준다.



instanceof는 객체에 대한 null check를 수행하는데 사용될 수 있다.

Circle circle = null
if(circle instanceof Object){

}

java의 모든 클래스는 Object 클래스를 상속받고 있다. null이 아닌 모든 객체는 Object의 instance이다.

이를 활용하여 Object의 인스턴스가 아닌 객체는 null이라고 판단할 수 있다.



assignment(=) operator

variable = value형태로 동작하여 값을 변수에 할당하는 operator를 assignment(=) operator라고 말한다.

primitive type의 경우 value에 담긴 값을 variable에 할당하지만, object type의 경우 value에 담김

object의 reference를 variable에 할당한다. 만약 object type을 primitivee type처럼 값 전체를 할당하고

싶으면, 다음과 같은 방법을 사용해야 한다.

// Shallow Copy
User u = new User("kjm", "26");
User s = new User(u.name,u.age);

// Deep Copy
User u = new User("kjm", "26");
User d = new User(u)
// Copy Constructor를 활용해야 한다.

assignment operator는 +,-,*,/,%와 함께 사용될 수 있다.

  • a += 10 : a = a + 10
  • a -= 10 : a = a - 10
  • a = 10 : a = a 10
  • a /= 10 : a = a / 10
  • a %= 10 : a = a % 10



화살표(->) 연산자

화살표 연산자는 Lambda 표현식이라고 불린다. 이는 메소드를 하나의 표현식으로 나타낼 때 사용된다.

예를 들어, 일반적인 메소드를 화살표 연산자를 통해 간략하게 표현할 수 있다.

int max(int x, int y){
    return x > y ? x : y;
}
(x,y) -> x > y ? x : y;

(parameters) -> {statements} or (parameters) -> expression 형태로 사용된다.

화살표 연산자는 15주차에 학습할 람다식과 관련되있으므로 그때 자세하게 공부할 예정이다.



3항 연산자

3항 연산자는 영어로 Ternary Operator라고 부른다. 연산자의 형태는 ? :이며, if-else 구문과 리턴하는

구문을 합쳐 간단하게 표현한 것이다. 문법은 다음과 같다.

booleanExpression ? expression1 : expression2

booleanExpression이 true이면 expression1이 반환되고, false이면 expression2가 반환된다.

int a = 10;
int b;
if(a > 7){
    b = 20;
}else{
    b = 5;
}
int a = 10;
int b = a > 7 ? 20 : 5

위와 같이 매우 간략하게 코드를 작성할 수 있다. 3항 연산자는 중첩하여 사용이 가능한데 되도록이면

가독성을 위해서 중첩하여 사용하는 것은 지양해야 한다.||



연산자 우선 순위

위에서 부터 우선순위가 높은 순서이다.
| 연산자 | 우선순위 |
| --- | --- |
| post increment and decrement | ++, -- |
| prefix increment and decrement, and unary | ++, --, +, -, ~, ! |
| multiplicative | , /, % |
| additive | +, - |
| shift | <<, >>, >>> |
| relational | <, >, <=, >=, instanceof |
| equality | ==, != |
| bitwise AND | & |
| bitwise exclusive OR | ^ |
| bitwise inclusive OR | | |
| logical AND | && |
| logical OR | || |
| ternary | ? : |
| assignment | =, +=, -=,
=, /=, %=, &=, ^=, |=, <<=, >>=, >>>= |



(optional) Java 13. switch 연산자

Java 13에서의 switch 연산자는 switch문과 유사하면서 다르다.

Java 12이전의 switch문

int result;
switch(String mode){
    case "a":
    case "b":
        result = 1;
        break;
    case "c":
        result = 2;
        break;
    default:
        result = -1;
}

Java 12에서의 switch문에서는 ,를 이용해 case들을 묶을 수 있다. 또한 break 1;과 같은 형태로

값을 리턴할 수 있으며 -> 연산자를 통해 break표현 없이 리턴값을 지정할 수 있다.

int result = switch(String mode){
    case "a","b" -> 1
    case "c" -> 2
    default -> -1;
}

Java 13부터는 yield라는 키워드를 통해 리턴값을 지정할 수 있다.

int result = switch(String mode){
    case "a","b" :
        yield 1;
    case "c" :
        yield 2;
    default :
        yield -1;
}




참고자료

profile
블로그 이전 : https://blog.naver.com/tjsqls2067

0개의 댓글