[Java] - Operator

uHan2·2020년 11월 27일
1

todayilearned.Java

목록 보기
4/6
post-thumbnail

안녕하세요.
지금까지 계속 저만의 기술 블로그를 만들어야지, 만들거야
마음으로만 다짐하다가 인제야 시작하게 되었습니다.
비록 시작은 코딩일기지만, 그 끝은 창대하게
어엿한 개발자 블로그로 성장할 수 있도록 노력하겠습니다.


3주차 과제 : 연산자

해당 게시글은 백기선님의 Java live-study 의 과제 내용을 정리한 글입니다.

산술 연산자

  • 수학에서 산술 연산자 라고 하면 가감승제사칙연산이 있다.
    [더하기, 빼기, 곱하기, 나누기] 를 의미한다. 그렇다면 컴퓨터는 어떨까?

  • Java에서는 다른 일반적인 PL과 마찬가지로 다섯 가지 연산이 있다.(오칙연산?)
    [더하기, 빼기, 곱하기, 몫, 나머지] 이다. 더하기, 빼기, 곱하기 까지는 동일하지만 나누기 부분에 차이가 있음을 볼 수 있다.

  • 왜 수학처럼 7 나누기 3을 했을때 7/3 이라든지, 2.333... 이라든지 이렇게 안하냐 생각할 수 있다. 나 또한 그랬지만 학교에서 [컴퓨터시스템구조] 수업을 듣고 어느정도 납득이 갔다. 해당 내용을 상세히 기술 하기엔 내용이 많아서 간단히 말하자면 나누기의 연산 방식이 비트를 Shift하며 계산을 하기 때문에(곱하기도 마찬가지) 몫과 나머지를 구하는데 최적화 되어있기 때문이다. 물론 소수 출력의 경우는 변수의 Data Type을 실수형으로 사용하면 부동소수점의 활용으로 가능하다.

  • [더하기, 빼기, 곱하기, 몫, 나머지] 의 5가지 연산은 다음과 같이 사용가능하다.

  • 우선 더하기는 "+", 빼기는 "-", 곱하기는 "*" 로 사용가능하다. 앞서 말했듯이 나누기가 수학에서의 사칙연산과 다른데
    나누기에는 두 가지 연산 몫 연산나머지 연산 이 있으며
    몫 연산은 "/" 나머지 연산은 "%" 으로 보기와 같이 사용가능하다.

  • 지금의 예시는 정수형 타입의 결과이고 변수의 Data Type을 실수형으로 바꿔서 해보면 다음과 같은 결과가 나온다.

  • 몫 연산의 부분이 보이는 것과 같이 정수일때와 다르게 나온다.
    소숫점 자리가 16자리까지 나오는데 이는 double의 가수부 정밀도가 15~16 자리 이기 때문에 16자리 까지 나오는 것이다. 그 밑의 코드처럼 format을 사용하면 원하는 소숫점자리까지 잘라서 출력할 수 있다.

  • 수학에서 0으로 나눌 수 없다 는 진리이다. 따라서 우리가 코드에서 0으로 몫 연산을 하려하거나 나머지 연산을 하려하면 다음과 같은 결과가 나온다.

  • 따라서 이 점에 유의하여 코드를 작성해야한다.
    마지막으로 지난 2주차에서 살펴보았던 타입 캐스팅과 프로모션도 연산 과정에서 자주 일어날 수 있다. 표현 범위가 좁은 자료형에서 넓은 자료형으로 형변환이 일어날 때에는 크게 상관없지만, 반대로 표현 범위가 넓은 자료형에서 좁은 자료형으로 형변화이 일어날 경우에는 변환 과정에서 값손실이 발생할 수 있으므로 각별히 주의해야한다.

  • 추가로 산술 연산에는 단항 연산자 로써 증감 연산자 가 있는데 이는 ++, -- 기호의 연산이다. 예시를 보자.

int num1 = 3;
int num2 = 3;

System.out.print(num1++); // 3
System.out.print(num1); // 4
System.out.print(++num2); // 4
System.out.print(num2); // 4
  • 이처럼 ++가 뒤에 붙는 후위 연산일 경우 해당 라인을 지나고 나서 증가 연산이 실행된다. 그래서 num1++을 출력해봐야 아직 증가하기 전이므로 3이 출력된다.
    반대로 ++가 앞에 붙는 전위 연산의 경우 해당 라인을 수행할때 바로 증가 하기 때문에 ++num2를 출력했을 때 1이 증가한 4가 출력되는 것을 알 수 있다.
    -- 연산도 동일하다.

비트 연산자

  • 비트 연산자를 살펴보기 전에 우선 값이 컴퓨터에 어떻게 저장이 되는지 간단히 살펴보자. 만약 176 이라는 숫자가 컴퓨터에 저장된다면 이 수는 2진법으로 변환되어 10110000 으로 저장된다. 여기서 0과 1로 이루어진 한 자리, 한 단위를 "비트(bit)" 라고 하고 이 비트가 8개 모인것을 "바이트(Byte)" 라고 한다.

  • 각 비트에 대해서 연산을 하여 비트 연산자 라고한다. 참고로 비트 연산자는 데이터를 비트 단위로 연산하기 때문에 0과 1로 표현가능한 정수 타입만 비트 연산이 가능하다. [AND, OR, XOR, NOT]의 논리 연산자는 밑에서 후술하고 여기서는 Shift연산을 알아보기로 하겠다.

  • 먼저 Shift 연산은 비트 이동 연산자 라고 하며 다음과 같이 세 가지 연산자가 있다.

  • 이해를 돕기위해 -176이라는 숫자와 3이라는 숫자를 갖고 각 연산을 살펴보겠다. 우선 각각의 숫자를 비트로 표현하면 다음과 같다.

  • 먼저 -176 << 3을 하게 되면 -176의 비트를 왼쪽으로 3비트 이동시키게 됩니다. 이를 표로 보면 다음과 같다.

  • 참고로 컴퓨터에서 음수는 최상단비트, MSB를 1로 두는데 이는 2의 보수로 음수를 구하기 때문이다. 그래서 << 연산으로 왼쪽으로 3 만큼 이동하면
    왼쪽으로 밀린 빨간색 111은 버려지고 파란색 000 이 채워진다.
    연산의 결과는 -1408이 된다. 실제로 코드로 결과를 살펴보면 다음과 같이 나온다.

  • ">>" 연산 또한 같은 원리이다. 표로 살펴보자.

  • -176의 32비트를 오른쪽으로 3칸 이동하면 밀려진 빨간색 000은 지워지고 남은 부분은 -176의 MSB인 1로 패딩이 된다. 결과를 코드로 보면 다음과 같다.

  • 일부로 음수를 예로든 이유가 바로 <<< 연산 때문이다. <<< 연산은 표에 나와있듯이 << 연산을 수행하고 빈자리를 옮겨지는 수의 MSB가 아닌 그냥 0 으로 채운다는 것이다. 즉, 다음과 같이 된다.

  • ">>" 연산처럼 옮기고 -176의 MSB인 1이 아니라 0을 채우는 것이다. 이는 음수에 대해서 >>> 연산을 할때 부호가 바뀔 수 있음을 의미하기 때문에 굉장히 중요하다. 해당 연산의 결과를 코드로 보면 다음과 같다.

관계 연산자

  • 관계 연산자는 왼쪽과 오른쪽값의 관계에 대하여 확인하고 true / false를 반환하는 연산자이다. 관계 연산자의 종류는 다음과 같다.

  • 각각의 연산자의 사용법을 코드로 살펴보면 다음과 같다.

public class Main
{
    public static void main(String[] args)
    {
        int num1 = 3;
        int num2 = 7;

        System.out.println(num1 < num2); // true
        System.out.println(num1 <= num2); // true

        System.out.println(num1 > num2); // false
        System.out.println(num1 >= num2); // false

        System.out.println(num1 == num2); // false
        System.out.println(num1 != num2); // true
    }
}
  • 우리가 수학에서 흔히 쓰는 개념과 같기 때문에 큰 어려움 없이 사용할 수 있다. 여기서 instanceof는 후에 기술하겠다.

논리 연산자

  • 이제 비트에 대해서 알아봤고 그 비트를 옮기며 연산하는 비트 이동 연산자까지 알아보았다. 다음은 비트 이동 연산자보다 더 많이 쓰이는(?) 논리 연산자에 대해서 알아보자. 우선 논리 연산자에 들어가기 전에 [AND, OR, XOR, NOT] 에 대해서 간단히 정리하고 들어가보자.

  • 사실 이 논리연산은 학교에서 [디지털논리회로] 수업에서 배웠었고 그때 배운뒤로 까먹지않고 잘 쓰고 있다. 각 연산자 마다 진리표나 논리회로는 검색하면 쉽게 찾을 수 있으니 궁금한 사람은 찾아보길 바란다.

  • 이번에는 32비트가 아닌 8비트로 줄여서 작은 수를 예로 들어 각 연산을 살펴보자. 77과 81로 예를 들겠다. 각 숫자를 8비트로 표현하면 다음과 같다.

  • 먼저 & (AND) 연산의 결과를 보면 다음과 같다.

  • 각각 대응하는 비트에 & 연산의 규칙인 두 비트 모두가 1일땐 1 아니면 0을 적용하면 다음과 같은 결과가 나온다. 이를 코드로 보면 다음과 같다.

  • 다음은 | (OR)의 연산 결과이다.

  • 두 비트 중 하나라도 1이면 결과가 1이되는 규칙을 적용하면 이렇게 결과가 나온다.
    결과를 코드로 보면 다음과 같다.

  • 이어서 ^ (XOR) 연산이다.

  • 두 비트 가 서로 다르다면 1, 같으면 0 이 되는 규칙을 적용한 결과이다. 이를 코드로 보면 다음과 같다.

  • 마지막으로 ~ (NOT) 연산이다.

  • ~(NOT) 연산은 이항 연산자가 아닌 이처럼 피연산의 비트를 부정,반전,보수 시킨다. 참고로, 이 결과에 1을 더해주는 것이 바로 2의 보수다. 해당 결과를 코드로 보면 다음과 같다.

  • 여기서 중요하게 생각할 내용이 있다. 실제로 Java에서 조건문을 쓸때는 &&와 ||를 쓰게된다. 그렇다면 이 &와 | 이랑은 뭐가 다를까? 이건 이번에 듣고 있는 [프로그래밍언어론] 에서 들은 개념이 도움이 되었다. 두 조건 간에 AND 연산이나 OR 연산을 하는 경우 굳이 둘다 계산할 필요가 없다.

  • 즉, x AND y (x와 y는 특정 조건식) 를 할때 x 가 0이면 y는 계산할 필요 없이 0이다. x OR y 의 경우도 x가 1이라면 y의 값의 상관없이 결과는 1이다. 이 용어에 대해서 기억이 나질않아서 .. 찾아보고 추가하도록 하겠다. 이렇게 뒤의 연산을 할 필요없게 해주는 연산이 Java에서는 && 와 ||로 제공하고 있다.

instanceof

  • instanceof는 특정 개체가 특정 타입인지 확인하는 연산자이다.
    instanceof는 주로 Polymorphism(다형성), 업-다운 캐스팅과 함께 설명되는 내용이다. 먼저 Polymorphism(다형성)에 대해서 간단히 살펴보자.

  • Polymorphism(다형성)은 상위 클래스 타입의 참조 변수로 하위 클래스의 인스턴스를 참조할 수 있음을 뜻한다.

Human man = new Man();
Human woman = new Woman();
Human baby = new Baby();

man.eat();
woman.eat();
baby.eat();
  • 여기서 human, cat, bird 가 호출하는 eat() 메소드는 Animal의 메소드가 아닌 실제 인스턴스의 메소드이다. 이처럼 다양한 클래스를 하나의 자료형(상위 클래스)으로 선언할 수 있다. 또한, 업캐스팅하여 각 클래스가 동일한 메소드 (예시의 eat()메소드) 를 오버라이딩하여 다양한 실행을 구현할 수 있다.

  • 물론 Animal 클래스에서 선언하지 않은 멤버변수나 메소드는 사용할 수 없다.
    즉, 각 클래스에 선언된 고유한 메소드를 호출하기 위해서는 다시 원래 자료형으로 돌아가야 한다. 이처럼 상위 클래스로 형변환 하는 것을 업캐스팅, 하위 클래스로 형변환 하는 것을 다운캐스팅이라고한다.

  • instanceof 는 다운 캐스팅을 하기 전에 상위 클래스로 형변환된 인스턴스의 원래 자료형을 확인할 때 쓰는 연산자이다.

if(man instanceof Man){}; // = true
  • 이처럼 instanceof는 왼쪽에 있는 변수의 형이 오른쪽 클래스의 자료형인지 확인하여 true / false를 반환한다. 따라서 잘못된 형변환을 할 때의 오류를 막을 수 있다. 만약 자료형이 맞지 않는데 강제로 형을 변환하려 하면 ClassCastException 예외가 발생하여 실행 오류가 발생한다.

assignment(=) operator

  • 수학을 배울때 우리는 = 이 기호를 굉장히 자연스럽게 많이 써왔다. 하지만 프로그래밍 언어에서 이 = 기호는 다르다. 예를 들어, 우리가 익히 연산해왔던
    a = a + 1 같은 식은 수학에서 성립하지 않는다. 그렇다면 어떻게 다를까?

  • 우선 Java를 포함한 프로그래밍 언어에서 ="대입 연산자" 라고 불리운다.
    역할은 어떤 변수에 값을 넣을 때, 할당할 때 사용한다.
    기본적인 사용법은 다음과 같다.

int num = 3;
char ch = 'a';
String s = "test";
  • 이처럼 numch, s에 각각 값을 대입 하여 변수에 값을 할당하는 것이다. 학원에서 아이들에게 이 부분을 가르쳤을때
    "오른쪽에 있는 것을 왼쪽에다가 집어넣는다" 라고 최대한 쉽게 설명했던 기억이 난다.

  • 이 대입 연산자에는 특별한 기능이 있는데 바로 산술 연산자, 비트 연산자, Shift 연산자와 결합하여 사용할 수 있다는 점이다. 이를 복합 대입 연산자 라고 하며 사용법은 다음과 같다.

int num = 3;

num += 1; // -> num = num + 1;
num -= 1; // -> num = num - 1;
num *= 1; // -> num = num * 1;
num /= 1; // -> num = num / 1;
num %= 1; // -> num = num % 1;

num &= 1; // -> num = num & 1;
num |= 1; // -> num = num | 1;
num ^= 1; // -> num = num ^ 1;
//num ~= 1; ~(NOT) 연산은 단항 연산자 이기에 이런 연산은 불가능하다.

num <<= 1; // -> num = num << 1;
num >>= 1; // -> num = num >> 1;
num >>>= 1; // -> num = num >>> 1;
  • 이런 결합기능을 통해 a = a + 1 같은 간단한 코드는 a += 1 로 간결하게 작성할 수 있다.

화살표(->) 연산자

  • 화살표 연산자는 Java 8 부터 지원되는 Lambda Expression(람다 표현식)&& 에서 사용되는 연산자이다. 람다는 자바에서 함수형 프로그래밍**을 가능하도록 등장했기 때문에 함수형 프로그래밍 부터 간단히 살펴보기로 하자.(사실 람다 표현식을 잘 쓰지 않아서 이 화살표 연산자를 쓴적이 많지가 않다..)

  • 함수형 프로그래밍은 사실 유튜브나 블로그에서 처음 접해본 용어였다. 그리고 지금 학교에서 듣는 [프로그래밍 언어론] 수업을 통해 구체적으로 배우고 있다. 함수형 프로그래밍은 간단히 말하자면 함수의 입력만을 의존해서 출력을 만드는 구조로 외부의 상태를 변경하는 것을 줄여 부작용 발생을 최소화 하는 방법론이다.

  • 람다가 등장하기 이전에 Java는 언어 차원에서 함수형 프로그래밍을 지원하지 않았다.(구조적으로 구현은 가능했지만) 그래서 이를 위한 함수형 인터페이스 (단 하나의 메도스만 선언된 인터페이스) 가 Java 8에 도입되었고, 이를 람다식으로 표현이 가능한 것이다.

  • 그렇다면 람다란 무엇일까?
    람다는 프로그래밍 언어에서 익명 함수를 뜻하는 용어다.
    익명 함수란 말 그대로 이름이 없는 함수이다. 간단히 말하면 메소드를 하나의 식으로 표현한 것이다.

    • Java 8 이전에는 "함수"의 개념이 없었다. Java에서는 모든 것을 객체로 관리하는데 메소드로 객체의 행위를 정의하고 상태를 변경한다. 하지만 이 메소드는 일급 함수 가 아니기 때문에 다른 메소드로 전달할 수 없다. 그렇기 때문에 언어 차원에서 함수형 프로그래밍을 지원하지 못했다.내용 참고
  • 간단히 사용법을 살펴보면
    (Parameter 매개 변수) -> {Body 함수 몸체}
    형태로 사용이 가능하다.

  • 사실 람다, 스트림 부분은 이해하지 못하고 있는 부분이 태반이다.. 우선은 개론적인 내용만 정리해두고 추후에 따로 학습하여 정리를 해놓아야겠다.

3항 연산자

  • 앞서 우리는 단항 연산자 (~NOT), 이항 연산자 (두 개의 피연산자를 통해 연산하는 연산자)를 살펴 보았다. 3항 연산자는 말그대로 세 개의 피연산자를 통해 연산을 하기에 3항 연산자이다.

  • 우리가 크기를 비교할때 아무리 간단한 비교라도 if(), else() 구문을 사용하면 코드가 필연적으로 길어진다. 예를 들면 다음과 같은 상황이다.

if(2 < 3)
{
    num1 = 10;
} else
{
    num1 = 20;
}
  • 이처럼 길어지게 된다. 이때 우리는 이를 간결하게 해주는 3항 연산자를 사용할 수 있다. 위 코드를 줄이게 되면 다음과 같다.
    int num = (2 < 3) ? 10: 20;
    = int num = (조건문) ? 참이면 10: 아니면 20;
    처음 이 3항 연산자를 접했을 때 뭐지 싶었는데 이를 우리말로 풀어서 해보면 자연스럽다는 것을 알 수 있다.
    2가 3보다 커? yes -> 10, no -> 20

  • 참고로 3항 연산자를 사용해서 코드가 간결해지더라도 컴파일 속도는 빨라지지 않는다. 그리고 과한 3항 연산자의 사용은 가독성을 떨어질 수 있으므로 보기와 같은 간단한 상황에 적용하는 것이 좋다고 한다.

연산자 우선 순위

  • 사실 연산자 우선 순위에 대해서 알아보고 나서 조금 놀랐다.
    지금까지 연산의 우선순위를 증감 연산자나 산술 연산자에 대해서만 익히 생각해보았고 논리 연산자, 삼항 연산자, 괄호 등 모든 연산자에 대해서는 생각해보지 않았기 때문에 굉장히 신기한 시간이었다.
    검색한 자료를 통해 표를 작성해보았다.

  • 크기 비교에서 대소 비교가 ==과 != 비교 보다 빠른지는 몰랐다..
    이런 우선순위는 어느정도 인지하면 되지 외울 필요까진 없다고 생각한다.
    사실 나는 가독성을 높이기 위해서도 있고 우선순위에 혹시나의 문제도 없게 하기 위해서 괄호( )를 적극적으로 사용한다.

Java 13. switch 연산자

  • 사실 switch 연산자를 그냥 아는대로 만 써왔는데 이번 3주차 과제에서 아마 가장 많이 얻어가는게 이 부분이 아닌가 싶다. switch문이 이렇게 발전했을 줄이야 ...
    기존에는 swtich문을 이런식으로 썼다.
switch(test)
{
    case 1:
        result = 3;
        break;
    case 2:
        result = 3;
        break;
    case 3:
        result = 33;
        break;
}
  • 여기서 Java 12 버전 부터 같은 행위를 하는 다른 케이스들을 묶을 수 있다.(사실 Java11을 쓰고 있기도 하고, 12를 쓸때도 이걸 모르고 지금껏 일일이 나눴다..) 예를 들어 case 1과 case 2가 같은 행위를 한다면
int test = 0;

switch(test)
{
    case 1, 2:
        result = 3;
        break;
    case 3:
        result = 33;
        break;
}
  • 물론 Java 11 에서는 다음과 같이 지원하지 않는다고 에러가 뜬다..

  • 심지어 여기서 하나 더 있는데, 바로 화살표 (->) 를 통해 break; 도 생략할 수 있다는 것이다.

switch(test)
{
    case 1, 2 -> 3;
    case 3 -> 33;
}
  • 여기 까지가 Java 12 부터 지원되는 Enhanced switch 이다. 참고로 더 찾아보니까 break 3; 처럼 쓸 수 있는 break value; 기능을 지원했다고 한다.

  • Java 13 부터는 yield 라는 키워드가 추가 되었다. yield 키워드를 사용하면 코드가 다음과 같다.

switch(test)
{
    case 1, 2 -> 3;
    case 3 ->
    {
        yield 33;
    }
    // case 3 -> yield 33; 블록 밖에서 쓸 수 없다.

}
  • yield는 이번 과제를 통해 처음 보는 친구이다. yield에 대해서 찾아보니 yield는 항상 블록{ } 내부에서만 쓸 수 있다고 한다. 그리고 제일 신기했던건 무려 변수명 으로 사용이 가능하다는 것이다. 원래 예약어는 변수로 쓸 수 없는게 일반적인데 곧 안되는 건지는 모르겠다. (사실 return과의 차이도 자세히는 모르겠다..)

마치며

연산자의 경우 변수와 마찬가지로 필요한 것만 쓰다보니 이렇게 전반적으로 훑어볼 기회는 없었던 것 같다. 특히나 Java 11 만 써오다 보니 버젼업 되면서 변경되는 사항이나 추가되는 기능들에 대해서 관심을 갖지 않았다. Java가 주력 언어라면 앞으로도 버젼에 따른 기능 변화 등 언어에 대한 이슈에 관심을 가져야 하겠음을 느끼는 과제였다.

참고

https://velog.io/@nunddu/Java-Switch-Expression-in-Java-14
https://isooo.github.io/etc/2019/11/13/%EC%9D%BC%EA%B8%89%EA%B0%9D%EC%B2%B4.html
https://donologue.tistory.com/53
https://www.python2.net/questions-156368.htm
https://coding-factory.tistory.com/265
https://khj93.tistory.com/entry/JAVA-%EB%9E%8C%EB%8B%A4%EC%8B%9DRambda%EB%9E%80-%EB%AC%B4%EC%97%87%EC%9D%B4%EA%B3%A0-%EC%82%AC%EC%9A%A9%EB%B2%95
http://www.tcpschool.com/java/java_lambda_concept
https://sas-study.tistory.com/105

profile
For the 1% inspiration.

0개의 댓글