Chapter 1. 자바를 시작하기 전에

 

1. 자바(Java Programming Language)

 

[ 자바의 역사 ]

- 썬 마이크로시스템즈(Sun Microsystems, Inc. 이하 썬)에서 개발

- 1991년 오크(Oak)에서 시작 -> 1996년 1월 자바(Java) 공식 발표

- 원래 목표는 소규모 가전제품과 대규모 기업환경을 위한 소프트웨어 개발

 

[ 자바의 특징 ]

- 운영체제에 독립적(운영체제에 관계 없이 실행 가능)

  • 자바가상머신(JVM) 에뮬레이터
  • 자바 응용프로그램은 JVM하고만 통신
  • JVM이 자바 응용프로그램으로부터 전달받은 명령을 해당 운영체제가 이해할 수 있도록 변환하여 전달
  • JVM은 운영체제에 종속적

- 객체지향 프로그래밍 언어

  • 상속
  • 캡슐화
  • 다형성

- 배우기 쉬움

- 자동 메모리 관리(Garbage Collection)

- 네트워크와 분산처리를 지원 : 네트워크 프로그래밍 라이브러리(Java API) 지원

- 멀티쓰레드 지원 : 시스템과 관계 없이 구현 가능. 관련 라이브러리(Java API) 지원, 쓰레드 스케줄링(scheduling)을 자바 인터프리터가 담당

- 동적 로딩(Dynamic Loading)을 지원 : 필요한 클래스만 로딩

- 클래스 라이브러리(Java API)를 통해 프로그래밍에 필요한 요소들을 기본적으로 제공

 

[ JVM ]

자바로 작성된 애플리케이션은 모두 이 가상 컴퓨터(JVM)에서만 실행되기 때문에, 자바 애플리케이션이 실행되기 위해서는 반드시 JVM이 필요하다.

Java 애플리케이션  
    JVM     일반 애플리케이션
OS(Windows) OS(Windows)
컴퓨터(하드웨어) 컴퓨터(하드웨어)

 

자바 애플리케이션은 JVM을 거쳐 OS에 전달되며, 실행 시 해석(interpret)되기 때문에 속도가 느리다단점을 가지고 있다.

그러나 요즘엔 바이트코드(컴파일된 자바코드)를 하드웨어의 기계어로 바로 변환해주는 JIT컴파일러와 향상된 최적화 기술이 적용되어서 속도의 격차를 많이 줄였다.

 

OS마다 JVM은 다르기 때문에 Windows용 JVM, Machintosh용 JVM, Linux용 JVM 등이 있다.

 

 

2. 자바개발환경 구축하기

자바 개발도구(JDK)를 설치하면, 자바가상머신(JVM)과 자바 클래스 라이브러리(Java API) 외에 자바를 개발하는데 필요한 프로그램들이 설치된다.

 

책에서는 Java8을 설치하라고 했지만, 최신 버전은 Java18이라 일단 최신 버전을 설치했다.

 

bin 폴더에는 여러 실행파일이 있는데, 주요 실행파일은 javac.exe, java.exe, javap.exe다.

자바 컴파일러, 자바소스코드를 바이트코드로 컴파일한다.

 

자바 인터프리터, 컴파일러가 생성한 바이트코드를 해석하고 실행한다.

 

자바 역어셈블러, 컴파일된 클래스파일을 원래의 소스로 변환한다.

 

위와 같은 과정을 거치면 Hello.class파일이 역컴파일되어 Hello.java에 저장된다. 그러나 원래의 소스 전체가 아닌 선언부만 저장된다.

'-c' 옵션을 통해 바이트코드로 컴파일된 내용도 볼 수 있다.

그 외의 실행파일(.exe)은 아래와 같다.

 

자동문서생성기, 소스파일에 있는 주석(/**/)을 이용하여 Java API 문서와 같은 형식의 문서를 자동으로 생성한다.

 

압축프로그램, 클래스파일과 프로그램의 실행에 관련된 파일을 하나씩 jar파일(.jar)로 압축하거나 압축해제한다.

 

Java API 문서는 html로 되어있으며, ZIP 방식으로 압축되어 있다.

그러나 Java18에서는 해당 파일이 보이지 않았다. Java API 설명은 아래 링크에서 볼 수 있다.

https://docs.oracle.com/en/java/javase/18/docs/api/index.html

 

Overview (Java SE 18 & JDK 18)

This document is divided into two sections: Java SE The Java Platform, Standard Edition (Java SE) APIs define the core Java platform for general-purpose computing. These APIs are in modules whose names start with java. JDK The Java Development Kit (JDK) AP

docs.oracle.com

 

3. 자바로 프로그램 작성하기

개인적으로는 InteliJ IDEA를 사용하여 프로그램을 작성하였다.

class Hello {
	public static void main(String[] args) { // main 메서드의 선언부
    	System.out.println("Hello, world.");
        }
}

▲ "Hello, world." 출력

 

InteliJ IDEA 에서 작성한 코드

 

 

Hello.java 작성 -> javac.exe 컴파일 -> Hello.class 생성 -> java.exe 실행 -> "Hello, world." 출력

 

 

자바에서 모든 코드는 반드시 클래스 안에 존재해야 하며, 서로 관련된 코드들을 그룹으로 나누어 별도의 클래스를 구상하게 된다. 그리고 이 클래스들이 모여 하나의 Java 애플리케이션을 이룬다.

 

하나의 Java 애플리케이션에는 main 메서드를 포함한 클래스가 반드시 하나는 있어야 한다. (main 없으면 실행 X)

 

소스파일의 이름public class의 이름과 일치해야 한다. (만약 public class가 없다면 어떤 클래스의 이름으로 해도 상관 X) 다시 말해 하나의 소스파일에 두 개 이상의 public class가 있어서는 안 된다. 이때 이름은 대소문자를 구분한다.

(자세한 사항은 교재 p.10 참조. 에러 해결 방법은 p.11에 있음.)

 

C:\Users\astnv\IdeaProjects\Helloworld>java Hello 실행 시

  1. 프로그램의 실행에 필요한 클래스(*.class파일)을 로드한다.
  2. 클래스파일을 검사한다. (파일형식, 악성코드 체크)
  3. 지정된 클래스 (Hello)에서 main(String[] args)를 호출한다.

 

자바에서 주석/**///의 방법으로 작성한다. 단, 큰따옴표(") 안에서는 주석이 아닌 문자열로 인식된다.

 

 

 

Chapter 2. 변수(Variable)

 

1. 변수와 상수

(변수타입) (변수이름)의 형식으로 변수를 선언한다.

(변수타입) (변수이름) = (리터럴)의 형식으로 변수를 선언하고 초기화한다.

 

변수를 초기화(initialization) 하지 않으면 쓰레기 값이 저장된다. 반드시 초기화 할 것.

 

파이썬에서는 a, b = b, a 와 같이 두 변수의 값을 swap 할 수 있지만, 자바에서는 임시 변수(tmp 등)를 선언하여 두 변수의 값을 교환한다.

 

[ 변수의 명명규칙 ]

  1. 대소문자가 구분되며 길이에 제한이 없다. (True와 true는 구분됨)
  2. 예약어를 사용해서는 안 된다. (교재 p.25에 예약어 목록)
  3. 숫자로 시작해서는 안 된다.
  4. 특수문자는 '_'와 '$'만을 허용한다.
  5. 클래스의 이름첫 글자는 항상 대문자로 한다. 변수와 메서드의 이름의 첫 글자는 항상 소문자로 한다.
  6. 여러 단어로 이루어진 이름은 단어의 첫 글자대문자로 한다. (ex. StringBuffer, lastIndexOf)
  7. 상수의 이름은 모두 대문자로 한다. 여러 단어로 이루어진 경우 '_'로 구분한다. (ex. MAX_NUMBER)

 

2. 변수의 타입

자료형은 크게 '기본형'과 '참조형'이 있다. '기본형'은 실제 값(data)을 저장하고, '참조형'은 어떤 값이 저장되어 있는 주소(memory address)를 값으로 갖는다. 단, 자바는 C언어와 달리 참조형 변수 간의 연산이 불가능하다.

 

참조형 변수를 선언할 때는 변수의 타입으로 클래스 이름을 사용하므로 클래스의 이름이 참조형 변수의 타입이 된다. 즉, 새로운 클래스를 작성한다는 것은 새로운 참조형을 추가하는 것이다.

 

(클래스 이름) (변수 이름);

ex.) Date today = new Date() // Date클래스 타입의 참조변수 today 선언. Date 객체를 생성해서, 그 주소를 today에 저장

 

참조형 변수는 위와 같이 선언 및 초기화한다. 타입이 기본형이 아니라면 모두 참조형 변수다. 참조 변수는 null 또는 객체의 주소를 값으로 가진다.

연산자 new의 결과는 생성된 객체의 주소다.

 

상수(constant)는 변수와 다르게, 한 번 저장하면 값을 변경할 수 없다.

 

final (변수타입) (변수이름) = (리터럴)

 

상수는 선언과 동시에 초기화 해야하며, 앞에 final 키워드를 붙인다.

 

▼ 리터럴의 접미사

종류 리터럴 접미사
논리형 false, true 없음
정수형 123, 0b0101, 077, 0xFF, 100L l, L (long type의 경우)
접미사가 없으면 int형
byte와 short는 따로 접미사 존재 X
16진수의 경우 접두사로 '0x' 또는 '0X'를, 8진수는 '0'을 사용
실수형 3.14, 3.0e8, 1.4f, 0x1.0p-1 f (float의 경우), d (double)의 경우
접미사가 없으면 double형
p는 16진 지수 형태 표기
문자형 'A', '1', '\n' 없음
문자열 "ABC", "123", "A", "true" 없음

 

정수형 리터럴 중간에 _를 넣어 큰 숫자 편하게 읽기 가능하다.

리터럴의 타입이 변수의 타입보다 작은 경우에는 타입이 일치하지 않아도 상관 없지만, 클 경우 컴파일 에러가 발생한다.

byte short의 경우, 따로 리터럴이 존재하지 않으므로 int타입의 리터럴을 사용한다.

 

원래 Sring은 클래스이므로 객체를 생성하는 연산자 new를 사용해야 하지만, 일반 변수의 초기화처럼 선언 가능하다.

 

String name = new String("Java");

String name = "Java"; // 두 표현 모두 가능

 

String의 경우 덧셈 연산자를 통해 문자열과 문자열을 결합할 수 있다.

 

출력의 경우 println()printf()가 있는데, println()은 값을 그대로 출력하며, printf()는 '%d'과 같은 지시자를 통해 출력의 형식을 결정할 수 있다.또한 println()은 출력 후 줄바꿈을 하지만, printf()는 줄바꿈을 하지 않기 때문에 줄바꿈을 하려면 '%n'과 같은 지시자를 따로 넣어줘야 한다. ('\n'도 가능하지만 안 되는 OS가 존재하지 %n가 안전함)

 

int age = 14, year = 2017;

System.out.printf("age : %d, year : %d%n", age, year);

>> age : 14, year : 2017

 

▼ 자주 사용되는 지시자

지시자 설명
%b 불리언(boolean) 형식으로 출력
%d 10진(decimal) 정수의 형식으로 출력
%o 8진(octal) 정수의 형식으로 출력
%x, %X 16진(hexa-decimal) 정수의 형식으로 출력
%f 부동 소수점(floating-point)의 형식으로 출력
%e, %E 지수(exponent) 표현식의 형식으로 출력
%c 문자(character)로 출력
%s 문자열(string)로 출력

'%d'를 사용할 때에는, 출력될 값이 차지할 공간을 숫자로 지정할 수 있다. (줄 간격 맞추기 가능)

%5d, %-5d, %05d를 사용할 경우 각각 [     10], [10     ], [00010]으로 출력되는 것을 알 수 있다.

 

'%o'와 '%x', '%X'를 사용할 때 '#'을 사용하면 접두사 '0x', '0X' 와 '0'이 각각 붙는다.

ex)%#x를 이용할 곁투 0xff... 출력

 

자바에서 char 타입을 %d로 출력할 경우, 출력하려는 변수의 int()의 형변환이 필요하다.

 

'%f'의 경우, 기본적으로 소수점 아래 6자리까지만 출력하기 때문에 7자리에서 반올림한다.또한 전체 자리 수소수점 아래의 자리수를 지정할 수도 있다. 이때 전체자리에서 남는 빈자리는 공백으로, 소수점 아래의 자리의 빈자리는 0으로 채운다.

 

double d = 1.23456789;

System.out.printf("d = %14.10f%n", d);

>> d =     1.2345678900

 

이때, 만약 %014.10f"로 작성할 경우 모든 부분의 빈자리는 0으로 채운다.만약 형을 지정하지 않고 println()으로 출력할 경우 float 형의 변수는 7자리에서 반올림한 값을 출력한다. double형의 경우 16자리에서 반올림한 값을 출력한다.

 

'%s'에도 숫자를 추가하여 원하는 만큼의 출력 공간을 확보하거나, 문자열의 일부만 출력할 수 있다.%20s는 20글자를 표현하고 우측정렬을 하기 때문에 좌측에 공백을 작성하며, %-20s는 20글자를 표현하고 좌측정렬을 하기 때문에 우측에 공백을 작성한다. 이때 변수에 저장된 문자열이 숫자보다 크다면 그냥 공백 없이 작성된다.%.8s의 경우, 왼쪽에서 8글자만 출력한다. 그러나 %-.8s나 %-0.8S 등은 작동하지 않았다.

 

Scanner 클래스를 이용하면 사용자로부터 화면에서 입력을 받을 수 있다. 이때 이 Scanner를 사용하기 위해서는 java.util.을 import 해야한다. 이때 *는 전체 호출을 의미한다.

import java.util.*;

class ScannerEx{
	public static void main(String[] args){
    	Scanner scanner = new Scanner(System.in);
        
        System.out.print("두자리 정수를 하나 입력해주세요.>");
        String input = scanner.nextLine(); // 입력받은 내용을 input에 저장
        int num = Integer.parseInt(input); // 입력받은 문자열을 숫자로 변환
        
        System.out.println("입력 내용 : " + input);
        System.out.printf("num = %d%n", num);
        }
}

nextLine()이라는 메서드를 호출하면, 입력대기 상태에 있다가 입력을 마치고 엔터키를 누르면 입력한 내용이 String 타입의 input 변수에 저장된다. 이 문자열을 int타입의 정수로 변환하기 위해 Interger.parseInt를 이용한다. 이때 공백이 입력되어 있으면 int 형변환이 제대로 일어나지 않을 수 있으니 주의하자.

 

위에서는 형변환 과정을 거쳤지만, 입력을 문자열이 아닌 정수형으로 받고 싶다면 String input 부분을 int input으로 변경하면 된다. 또한 메서드 역시 nextLine()이 아니라 nextInt()를 사용한다.

 

 

3. 진법

1 byte = 8bit

 

'워드(word)'는 'CPU가 한 번에 처리할 수 있는 데이터의 크기'를 의미한다. ex) 64 비트 CPU에서는 1 워드 = 64 비트

 

  • 2진수 -> 8진수 : 뒤에서부터 3자리씩 끊어 해당하는 8진수로 변환
  • 2진수 -> 16진수 : 뒤에서부터 4자리씩 끊어 해당하는 16진수로 변환
  • 10진수 -> n진수 : n으로 나눈 뒤 나머지값을 옆에 적고, 몫이 n보다 작아질 때까지 반복한다. 이후 나머지를 아래에서 위 순서대로 적으면 된다.
  • n진수 -> 10진수 : 각 자리의 수에 해당 단위 값을 곱해서 모두 더한다.
  • 10진 소수점수 -> 2진 소수점수 : 10진 소수에 2를 곱한 뒤, 소수부에만 다시 2를 곱해 소수부가 0이 될 때까지 반복한다. 소수부가 전부 0이 되면 연산 과정의 정수부만 위에서 아래 순서대로 적고 앞에 '0.'을 붙이면 된다.
  • 2진 소수점수 -> 10진 소수점수 : 각 자리에 2^(-n)을 곱한 뒤 더해 변환

 

n비트의 2진수로는 2^n개의 값을 표현할 수 있다. 이때 음수를 2진 표현으로 나타낼 경우, 2^(n-1)개의 양수와 음수를 표현할 수 있게 되는데, 0으로 시작하면 양수, 1로 시작하면 음수로 간주한다.

 

이때 음수2의 보수법으로 작성 가능하다. 단, 이 경우 첫번째 비트를 0에서 1로 바꾸는 것만으로는 음수와 양수의 변환이 불가능하다.

 

'n의 보수'란 더했을 때 n이 되는 수를 말한다. 즉, 2의 보수법이란 양수와 음수를 더했을 때 각 자리의 수를 2로 만드는 방식이다.

 

따라서, 10진 음의 정수를 2진수로 표현하려면 먼저 음의 정수의 절대값을 2진수로 변환한 뒤, 해당 값의 2의 보수를 구하면 된다. 또는, '1의 보수'를 구한 뒤 해당 값에 1을 더하면 된다.

 

 

4. 기본형(primitive type)

 

▼ 기본형 변수의 타입

논리형 boolean(1 byte)
true와 false 중 하나를 값으로 가진다.
조건식과 논리적 계산에 사용
문자형 char(2 byte)
문자(유니코드)를 저장하는데 사용
변수에 하나의 문자만 저장 가능
정수형 byte(1 byte), short(2 byte), int(4 byte), long(8 byte)
정수를 저장하는데 사용(주로 int)
byte는 이진 데이터, short는 C언어와의 호환
n비트의 경우 1비트는 양수/음수 표현에 사용되어 실제로는 n-1비트로 숫자를 표현
실수형 float(4 byte), double(8 byte)
실수를 저장하는데 사용(주로 double)
float는 실수값을 부동소수점(floating-point) 방식으로 저장

boolean을 제외하고 나머지 기본형은 서로 연산과 변환이 가능하다.

 

char의 경우, 문자가 아닌 유니코드를 저장하기 때문에 'A'를 저장하는 것과 65를 저장하는 것은 동일하다. 이때 char형은 int()를 통해 형변환이 가능하다.

 

▼ 특수 문자 리터럴 표기

특수문자 문자 리터럴
tab \t
backspace \b
실제로 출력하면 문자를 지우고 출력함
form feed \f
new line \n
carriage return \r
역슬래쉬(\) \\
작은따옴표 \'
큰따옴표 \"
유니코드(16진수)문자 \u유니코드 (ex. char a = '\u0041')
0x대신 \u입력 후 뒤에 16진수 작성

char은 2byte(16bit)기 때문에 2^16개의 코드를 사용할 수 있다. 다만, short 타입은 음수를 표현하기 때문에 두 자료형이 표현할 수 있는 크기는 동일하나 그 범위는 다르다. 따라서 두 자료형의 오버플로우 위치도 다르다.

 

'A' ──(인코딩)──> 65

'A'<──(디코딩)──65

 

문자를 코드(숫자)로 변환하는 것을 '문자 인코딩', 코드(숫자)를 문자로 치환하는 것을 '문자 디코딩'이라고 한다.

 

문자를 저장할 때에는 인코딩을 하여 코드를 저장하고, 읽어올 때에는 디코딩을 하여 저장된 코드를 다시 문자로 되돌린다. 인코딩 방식에 따라 디코딩 방식이 달라지며, 인코딩 방식에는 아스키(ASCII), 확장 아스키(Extended ASCII)와 한글, 코드 페이지(code page, cp), 유니코드(Unicode, UTF-8, UTF-16, UTF-32)가 있다.

 

해당 타입이 표현할 수 있는 값의 범위를 넘어서는 것오버플로우(overflow)라고 한다. 오버플로우가 발생한다고 오류가 발생하는 것은 아니지만, 예상했던 결과를 얻지 못한다. 따라서 오버플로우가 발생하지 않도록 충분한 크기의 타입을 선택해서 사용해야 한다. 오버플로우가 발생하는 지점에서는 최대값과 최소값이 맞닿아있다. ±1을 통해 서로가 서로로 변환 가능하다.

 

부호있는 정수의 오버플로우는 오버플로우가 발생하는 시점이 다르다. 앞자리가 0에서 1이 될 때 오버플로우가 발생한다.

 

실수형은 소수점수도 표현하기 때문에, '얼마나 0에 가깝게 표현할 수 있는가'가 중요하다. 다만 실수형애서는 오버플로우가 발생하면 변수의 값은 무한대가 된다. 또한 실수형에는 정수형에 없는 언더플로우(underflow)가 있는데, 이는 실수형으로 표현 불가능한 아주 작은 값, 양의 최소값(float의 경우 1.4e-45, double의 경우 4.9e-324)보다 작은 값이 되는 경우다. 이때는 변수가 0이 된다.

 

실수형은 '부호(S) 지수(E), 가수(M)'의 세 부분으로 이루어져 있기 때문에 정수형보다 더 큰 값을 저장할 수 있지만, 오차가 발생할 수 있다는 단점이 있다. 따라서 실수형에는 값의 범위 뿐만 아니라 정밀도(precision)도 중요하다. 정밀도란 '해당 자리까지는 정확한 값'이라는 의미기도 하다.

float형의 정밀도는 7자리이고, double형의 정밀도는 15자리다. 연산속도의 향상이나 메모리를 절약하려면 float를, 더 큰 값의 범위나 높은 정밀도가 필요하다면 double을 사용하면 된다.

 

실수형은 ± M × 2^E로 표기한다. 정수와 달리 2의 보수법을 사용하지 않기 때문에 양수와 음수 전환은 부호 비트만 바꿔주면 된다. 이때 지수값을 나타내는 E는 float의 경우 8bit의 저장 공간을 갖는데(-127~128) 이때 -127128음의 무한대양의 무한대라는 특수한 값을 표기하는데 사용된다. 지수는 기저법으로 저장되는데, 이때 기저법이란 특정값(기저)를 더했다가 읽어올때 빼는 것이다.

 

또한 2진수로 저장하기 때문에 10진수일 때는 유한소수였지만 2진수로 변환하면서 무한소수가 될 수 있다. (마지막 자리가 5가 아니면 무한소수) 이러한 10진 소수점수 -> 2진 소수점수 변환 과정을 정규화라고 한다. 정규화된 2진 소수점수는 1.xxx × 2^n 형태다. float의 경우 가수(M)의 크기는 23bit이므로 1. 을 제외한 23자리의 2진수만 가수(M)에 저장되며, 그 아래는 잘린다.

 

 

5. 형변환

형변환이란, 변수 또는 상수 타입을 다른 타입으로 변환하는 것이다. 서로 다른 값을 연산하거나 처리할 경우, 먼저 두 값을 같은 타입으로 변환할 필요가 있다.

 

(타입) 피연산자

 

이때 괄호()는 '캐스트 연산자' 또는 '형변환 연산자'라고 하며 형변환을 '캐스팅'이라고도 한다. 캐스팅 연산자는 실제 변수의 값을 바꾸는 것이 아니라, 형변환만 할 뿐이므로 변환된 값을 유지하고 싶다면 변수에 다시 저장해야 한다.

 

정수형에서 큰 타입에서 작은 타입으로 형변환을 할 때에는, 작은 타입보다 값이 큰 경우 값 손실(loss of data)이 발생한다. 반대로 작은 타입에서 큰 타입으로 형변환을 할 때에는 값 손실이 발생하지 않고, 나머지 빈 공간이 0이나 1로 채워진다. (음수인 경우 1로 채움)

 

실수형에서 형변환을 하는 경우, float를 double로 바꿀 때 지수는 float의 기저인 127을 지수 값에서 뺀 후, 다시 double의 기저인 1023을 더한 값을 저장한다. 가수의 남는 자리는 0으로 채워진다.

 

반대로 double을 float으로 바꿀 때, 지수에서 double의 기저인 1023을 뺀 후 float의 기저인 127을 더한다. 가수의 23자리만 저장되고 나머지는 버려진다. 이때 주의할 점은 24번째 자리에서 반올림이 발생할 수 있다. 24번째 자리가 1인 경우 반올림하여 23번째 자리의 값이 1 증가한다. 또한 float의 범위를 넘는 값을 float으로 형변환 할 경우 ±∞ 혹은 ±0을 반환한다.

 

정수형과 실수형 사이의 형변환 할 경우, 정수를 2진수로 변환한 뒤 정규화를 거쳐 실수의 저장 방식으로 저장한다. 단, 이때 실수형의 정밀도 제한으로 오차가 발생할 수 있다. (int형의 경우 최대 10자리의 정밀도 요구, but float은 7자리의 정밀도 제공)

 

실수형을 정수형으로 형변환할 경우, 실수형의 소수점 이하 값은 버려진다. 이때는 버림이기 때문에 반올림이 발생하지 않는다.

 

일반적으로 컴파일러가 생략된 형변환을 자동적으로 추가해주기도 하지만, 변수가 저장할 수 있는 값의 범위보다 더 큰 값을 저장하는 경우에는 형변환을 생략하면 에러가 발생한다. 그러나 이때 명시적 형변환을 추가하면 오류가 발생하지 않는다. 또한 큰 값과 작은 값을 연산할 때에는 자동으로 큰 쪽의 형으로 자동 형변환이 되기도 한다. 이렇게 하는 경우가 값손실의 위험이 더 적기 때문이다. 즉, 기존의 값을 최대한 보존할 수 있는 타입으로 자동 형변환한다.

 

[ 요약 ]

  1. boolean을 제외한 나머지 7개의 기본형은 서로 형변환이 가능하다.
  2. 기본형과 참조형은 서로 형변환 할 수 없다.
  3. 서로 다른 타입의 변수간의 연산은 형변환을 하는 것이 원칙이지만, 값의 범위가 작은 타입에서 큰 타입으로 형변환은 생략할 수 있다.

 

 

Chapter 3. 연산자(Operator)

 

1. 연산자(operator)

연산자(operator) : 연산을 수행하는 기호(+, -, *, / 등)

피연산자(operand) : 연산자의 작업 대상(변수, 상수, 리터럴, 수식)식(式, expression) : 연산자와 피연산자를 조합하여 계산하고자 하는 바를 표현한 것. 작성한 식을 프로그램에 포함시키려면 식의 끝에 ';'를 붙여 문장으로 만들어야함식을 평가(evaluation) : 식을 계산하여 결과를 얻는 것. 식의 평가 결과는 저장되어야 의미가 있다.

 

피연산자의 개수에 따라 단항 연산자, 이항 연산자, 삼항 연산자로 분류된다.

 

▼ 연산자의 기능별 분류

종류 연산자 설명
산술 연산자 +   -   *   /   %   <<   >> 사칙 연산(+, -, *, /)과 나머지 연산(%)
비교 연산자 >   <   >=   <=   ==   != 크고 작음와 같고 다름을 비교
논리 연산자 &&   ||   !   &   |   ^   ~ '그리고(AND)'와 '또는(OR)'으로 조건을 연결
대입 연산자 = 우변의 값을 좌변에 저장
기타 (type)   ?:   instanceof 형변환 연산자, 삼항 연산자, instanceof 연산자

 

▼ 연산자 우선순위의 예와 설명

설명
-x + 3 단항 연산자(부호 연산자) > 이항 연산자
x + 3 * y 곱셈 및 나눗셈 > 덧셈과 뺄셈
x + 3 > y - 2 산술 연산자(+, -) > 비교 연산자(>)
x > 3 && x < 5 비교 연산자(>, <) > 논리 연산자(&&)
result = x + y * 3; 곱셈 및 나눗셈 > 덧셈과 뺄셈 >>>>>> 대입 연산자

 

▼ 주의해야 할 연산자 우선순위의 예와 설명

설명
x << 2 + 1 덧셈 연산자 > 쉬프트 연산자(<<)
왼쪽의 식은 x << (2 + 1)과 동일
data & 0xFF == 0 비교 연산자(==) > 비트 연산자(&)
왼쪽의 식은 data & (0xFF == 0)과 동일
x < -1 || x > 3 && x < 5 AND('&', '&&') > OR ('|', '||')
왼쪽의 식은 x < -1 || (x > 3 && x < 5)과 동일

 

하나의 식에 같은 우선순위의 연산자들이 여러 개 있는 경우, 왼쪽에서 오른쪽의 순서로 연산을 수행.

단, 대입 연산자(=, +=, -=, *=, /=, %=, <<=, >>=, &=, ^=, |=)와 단항 연산자(++, --, +, -, ~, !, (type))는 그 반대인 오른쪽에서 왼쪽으로 진행한다.

 

연산 타입의 결과는 피연산자의 타입과 일치한다. 즉, int형과 int형을 나누면 결과는 int형으로 반환된다.

 

 

2. 단항 연산자

단항 연산자에는 증감 연산자와 부호 연산자, 논리 부정 연산자, 비트전환 연산자가 있으며, 그중 증감 연산자에는 전위형과 후위형이 있다.

 

▼ 전위형과 후위형의 비교

타입 설명 사용 예
전위형 값이 참조되기 전에 증가시킨다. j = ++i;
후위형 값이 참조된 후에 증가시킨다. j = i++;

 

증감연산자를 메소드나 수식에 포함시키면 차이를 조금 더 확실히 알 수 있다.

 

class OperatorEx2{
	public static void main(String[] args){
    	int i = 5, j = 0;
        
        j = i++;
        System.out.println("j = i++; 실행 후, i = " + i + ", j = " + j);
        
        i = 5;
        j = 0;
        
        j = ++i;
        System.out.println("j = ++i; 실행 후, i = " + i + ", j = " + j);
        }
}

이 경우 위쪽의 실행 결과는 i = 6, j  = 5이고 아래쪽의 실행 결과는 i = 6, j = 6이다.

즉, 위에서는 후위형(i++)이므로 먼저 j에 i = 5가 참조된 후 i가 6으로 변하고, 아래서는 전위형(++i)이므로 먼저 i가 6으로 변한 뒤 j에 i = 6이 참조된다.

 

 

3. 산술 연산자

산술 연산자에는 사칙 연산자나머지 연산자가 있다.

피연산자가 정수형인 경우, 나누는 수로 0을 사용할 수 없다. (사용할 경우 실행 시 에러 발생)

피연산자가 실수형인 경우, 나누는 수로 0을 사용할 경우 무한대를 출력한다.

 

▼ 피연산자가 유한수가 아닌 경우의 연산결과

x y x / y x % y
유한수 ± 0.0 ± ∞ NaN
유한수 ± ∞ ± 0.0 x
± 0.0 ± 0.0 NaN NaN
± ∞ 유한수 ± NaN
± ∞ ± ∞ NaN NaN

 

byte형끼리 산술 연산을 할 경우, 결과는 int형이기 때문에 해당 결과를 byte형 변수에 저장하기 위해서는 다시 byte로 형변환이 필요하다.

 

int형끼리 산술 연산을 할 경우, 결과는 int형이기 때문에 해당 결과를 long형 변수에 저장하더라도 int형 변수의 표현으로 이미 저장되었기 때문에 의도와 다른 값이 나올 수 있다. 이 경우 올바른 값을 얻기 위해서는 int형끼리 산술 연산을 하는 것이 아니라, 둘 중 하나의 타입을 long형으로 형변환 해야한다. 이때 만약 int형 변수의 값을 벗어나는 오버플로우가 일어나면 마찬가지로 long형 변수에 저장하더라도 의도와는 다른 값이 나온다.

 

유니코드간의 산술 연산가능하다. 단, 연산에 변수가 포함될 경우에는 형변환을 필요로 하는데, 변수는 컴파일러가 미리 계산할 수 없기 때문이다.

 

 

4. 비교 연산자

비교 연산자에는 대소 비교 연산자(<, >, <=, >=)와 등가 비교 연산자(==, !=)가 있다. 결과값은 모두 true 아니면 false를 반환한다. 대소 비교 연산자는 참조형에 사용할 수 없지만, 등가 비교 연산자참조형에 사용 가능하다.

 

비교 연산자도 이항 연산자이므로 연산을 수행하기 전에 형변환을 통해 두 피연산자의 타입을 맞춘다.

 

실수형의 경우 근사값으로 저장되기 때문에 등가 비교 연산자의 결과라 정수형과 달리 false가 나올 수 있다. 특히 0.1f는 2진수로 변환하는 과정에서 오차가 발생한다. double타입의 상수인 0.1도 저장할 때 오류가 발생하지만, float타입의 리터럴인 0.1f보다 적은 오차로 저장된다.

 

문자열을 비교할 때에는 '==' 대신 equals()라는 메서드를 사용해야 한다. "abc" == "abc"의 경우에는 true를 반환하지만, 만약 s1이라는 string 변수에 "abc"를 초기화한뒤 s1 == "abc" 연산을 진행하면 false를 반환한다. 이는 내용은 동일하지만 String s1 = "abc"의 s1 객체와 또다른 String 객체인 "abc"는 다른 객체이기 때문이다.

즉! 문자열(string)은 객체로 취급하기 때문에 '==' 연산의 사용이 불가능하다!!

만약 대소문자를 구별하지 않고 비교하고 싶다면 equalsIgnoreCase()를 사용하면 된다.

 

 

5. 논리 연산자

|| (OR 결합) : 둘 중 하나라도 true면 true (하나라도 true면 나머지 연산 하지 않음)

&& (AND 결합) : 둘 다 true여아 true (하나라도 false면 나머지 연산 하지 않음)

 

논리 부정 연산자(!)는 true면 false를, false면 true를 반환한다.

 

비트 연산자는 피연산자를 비트단위로 논리 연산한다.

 

| (OR 연산자) : 피연산자 중 한 쪽의 값이 1이면, 1을 결과로 얻는다. 그 외에는 0을 얻는다.

& (AND 연산자) : 피연산자 양 쪽이 모두 1이어야만 1을 결과로 얻는다. 그 외에는 0을 얻는다.

^ (XOR 연산자) : 피연산자의 값이 서로 다를 때만 1을 결과로 얻는다. 같을 때에는 0을 얻는다.

 

▼ 비트 연산자의 연산결과

x y x | y x & y x ^ y
1 1 1 1 0
1 0 1 0 1
0 1 1 0 1
0 0 0 0 0

비트OR연산자 '|'는 주로 특정 비트의 값을 변경할 때 사용한다.

ex) 0xAB | 0xF = 0xAF

(10101011 | 00001111 = 10101111)

 

비트AND연산자 '&'는 주로 특정 비트의 값을 뽑아낼 때 사용한다.

ex) 0xAB & 0xF = 0xB

(10101011 & 00001111 = 00001011)

 

비트XOR연산자 '^'는 두 피연산자의 비트가 다를 때에만 1이 된다. 같은 값으로 두고 XOR 연산을 수행하면 원래의 값으로 돌아오는 특성이 있어서 간단한 암호화에 사용된다.

ex) 0xAB ^ 0xF = 0xA4

(10101011 ^ 00001111 = 10100100)

0xA4 ^ 0xF = 0xAB

(10100100 ^ 00001111 - 10101011)

 

비트 전환 연산자 '~'는 피연산자를 2진수로 표현했을 때, 0은 1로, 1은 0으로 바꾼다. 논리부정 연산자 '!'와 유사하다. '1의 보수' 연산자라고도 한다.

 

쉬프트 연산자(<<, >>)는 피연산자의 각 자리(2진수로 표현했을 때)를 '오른쪽(>>)' 또는 '왼쪽(<<)'으로 이동(shift)한다고 해서 쉬프트 연산자(shift operator)라고 이름 붙여졌다.

ex) 8 << 2 : 10진수의 8의 2진수를 왼쪽으로 2칸 이동

00001000을 왼쪽으로 2칸 이동하면 00100000이 되고, 이 값은 32다.

 

'<<' 연산자의 경우 피연산자의 부호에 상관없이 왼쪽으로 이동하면서 빈칸을 0으로 채우면 되지만, '>>' 연산자는 오른쪽으로 이동시키기 때문에 피연산자가 음수라면 왼쪽의 빈자리를 1로 채운다.

 

또한, 쉬프트 연산자의 좌측 피연산자는 산술변환이 적용되어 int보다 작은 타입일 경우 자동으로 int타입으로 변하며, 결과도 int형이 된다.

 

  • x << n은 x * 2^n의 결과와 같다.
  • x >> n은 x / 2^n의 결과와 같다.

쉬프트 연산자는 빠르다는 장점이 있지만 가독성이 떨어지기 때문에 보다 빠른 실행속도가 요구되어지는 곳만 쉬프트 연산자를 사용하는 것이 좋다.

 

 

6. 그 외의 연산자

조건 연산자(?:)는 조건식, 식1, 식2 모두 세 개의 피연자를 필요로 하는 삼항 연산자이며, 삼항 연산자는 조건 연산자 하나뿐이다. 즉, 조건 연산자는 유일한 삼항 연산자이다.

 

조건식 ? 식1 : 식2

ex) result = (x > y) ? x : y;

 

위의 문장에서 식 'x > y'의 결과가 true이면 변수 result에는 x의 값이 저장되고, false이면 y의 값이 저장된다.

 

조건 연산자를 중첩해서 사용하면 셋 이상 중의 하나의 결과를 결과로 얻을 수 있다. 단, 가독성이 떨어지므로 꼭 필요한 경우에만 중첩해서 쓰는 것이 좋다. 또한 식1과 식2의 타입이 다른 경우 이항 연산자처럼 산술 변환이 발생한다.

ex) result = x > 0 ? 1 : (x == 0 ? 0 : -1);

x의 값이 양수면 1, 0이면 0, 음수면 -1을 반환한다.

 

대입 연산자는 연산자들 중에서 가장 낮은 우선 순위를 가지고 있기 때문에 식에서 가장 나중에 수행된다.

 

x(lvalue, left value) = 3(rvalue, right value)

 

lvalue는 리터널이나 상수가 될 수 없으며, 변수처럼 값을 변경할 수 있는 것이어야 한다. rvalue는 변수 뿐만 아니라 식이나 상수 등 모두 가능하다.

 

 

 

자바 연습문제는 아래 링크에서 PDF로 제공한다고 한다.

https://cafe.naver.com/javachobostudy

 

남궁성의 코드초보스터디(자바 java... : 네이버 카페

전문가가 지도하는 스터디카페에요. 프로그래밍언어(자바 java, C언어)를 제대로 배우고픈 분들 오세요.

cafe.naver.com

 

'JAVA' 카테고리의 다른 글

Chapter 8, 9 요약  (1) 2022.09.17
Chapter 7 요약  (0) 2022.09.01
Chapter 6 요약  (0) 2022.08.26
Chapter 4, 5 요약  (0) 2022.08.19