LANGUAGE/Java, Spring

Java의 정석 정리 (01~03장)

axelrod입니다 2024. 12. 2. 18:35

 


Ch 01. 자바를 시작하기 전에

 

1.3  자바 언어의 특징

1.  운영체제에 독립적 : emulator인 JVM을 통해서 해당 운영체제가 이해할 수 있도록 변환하여 전달

2. 객체지향언어(OOP) : 상속, 캡슐화, 다형성 적용

3. 자동 메모리 관리(Garbage Collection) 

4. Multi Thread 지원 : 관련 라이브러리 지원, 쓰레드에 대한 스케쥴링을 자바 인터프리터가 담당

 

3.1 Hello.java

- 자바의 모든 코드는 반드시 클래스 안에 존재해야 하며, 서로 관련된 코드들을 그룹으로 나누어 별도의 클래스를 구성하게 된다.

- main 메서드의 선언부는 java.exe에 의해 호출될 수 있도록 미리 약속된 부분이므로 항상 똑같이 적는다.

- Java 애플리케이션은 main 메서드의 호출로 시작해서 main 메서드의 첫 문장부터 마지막 문장까지 수행을 마치면 종료된다.

- 소스 파일의 이름은 public class의 이름과 일치해야 한다. 소스파일 내에 public class가 없다면, 소스파일 내의 어떤 클래스의 이름으로 해도 상관없다.

 

3.3 자바프로그램의 실행과정

1. 프로그램의 실행에 필요한 클래스(*.class파일)를 로드한다.

2. 클래스파일을 검사한다. (파일형식, 악성코드 체크)

3. 지정된 클래스에서 main(String[] args)를 호출한다.

 

3.4 주석(comment)

- 범위 주석 : /*와 */ 사이의 내용은 주석으로 간주된다

- 한 줄 주석 : //부터 라인 끝까지의 내용은 주석으로 간주된다

 

 


 

Ch 02. 변수 variable

 

1.1 변수란?

변수 : 단 하나의 값을 저장할 수 있는 메모리 공간.

 

1.2 변수의 선언과 초기화

- 변수를 선언하면, 메모리의 빈 공간에 변수타입에 알맞은 크기의 저장공간이 확보되고, 이 저장공간은 변수이름을 통해 사용할 수 있게 된다.

- 변수에 값을 저장할 때는 대입 연산자를 이용한다. ex) int age = 25;

- 타입이 같은 경우 콤마를 구분자로 여러 변수를 한 줄에 선언할 수 있다.

- 두 변수의 값을 교환하기 위해선 임시 저장소를 사용한다.

 

1.3 변수의 명명규칙

1. 대소문자가 구분되며 길이에 제한이 없다.

2. 예약어를 사용하지 않는다.

3. 숫자로 시작하지 않는다.

4. 특수문자는 '_'와 '$'만을 허용한다.

 

2.1 변수의 타입

- 기본형 (primitive type) : 계산을 위한 실제 값을 저장한다. boolean, char, byte, short, int, long, float, double

- 참조형 (reference type) : 객체의 주소를 저장한다. 8개의 기본형을 제외한 나머지 타입

 

새로운 클래스를 작성한다는 것은 새로운 참조형을 추가하는 셈이다.

Date today = new Date(); // Date 객체를 생성해서, 그 주소를 today에 저장

 

 

2.2 상수와 리터럴 (constant & literal)

- 상수 : '상수'는 변수와 마찬가지로 값을 저장할 수 있는 공간이지만, 한 번 값을 저장하면 다른 값으로 변경할 수 없다. 변수의 타입 앞에 키워드 final을 붙여주면 된다. 상수의 이름은 모두 대문자로 하는 것이 관례이다.

ex) final int MAX_SPEED = 10;

 

- 리터럴 : 리터럴은 기존에 알고 있던 상수의 다른 이름이다. 즉 자체로 값을 의미한다.

int year = 2014;

final int MAX_VALUE = 100;

 

위의 코드에서 year은 변수, MAX_VALUE는 상수, 2014와 100은 리터럴이다.

 

리터럴에 접미사를 붙여서 타입을 구분한다. 

boolean, char, string은 접미사가 없다.

정수형 중 long타입은 L을 붙인다.

실수형 중 float은 f를 Double은 D를 붙인다. (double은 생략 가능하다.)

 

타입이 달라도 저장범위보다 좁은 타입의 값을 저장하는 것은 허용된다. 그러나 리터럴의 타입이 변수의 타입보다 저장범위가 넓으면 컴파일 에러가 발생한다.

 

 

2.3 형식화된 출력 printf()

println()은 값을 그대로 출력한다. 값을 다른 형식으로 출력하고 싶을 때 printf()를 사용한다. 예를 들어 소수점 둘째자리까지만 출력하거나, 정수를 16진수로 출력하는 경우 등이 있다.

값을 변경할 경우 '지시자(specifier)'를 넣어서 출력한다.

%b boolean
%d, %o, %x 10진 정수, 8진 정수, 16진 정수
%f 부동 소수점(floating-point)
%e, %E 지수(exponent)
%c, %s char, string

 

ex)

System.out.printf("[%20s]%n", url); // [   www.codechobo.com] (최소 20글자 출력공간 확보. 우측정렬)
System.out.printf("d=%f%n", d); // d=1.123457
System.out.printf("d=%14.10f%n", d); // 전체 14자리 중 소수점 아래 10자리까지 출력

 

 

3.6 음수의 2진 표현 - 2의 보수법

1. 음수의 절대값을 2진수로 변환한다.

2. (1)에서 구한 2진수의 1을 0으로 0은 1로 바꾼다. (1의 보수)

3. (2)의 결과에 1을 더한다. 

 

 

4.2 문자형 char

- char 타입의 크기는 2byte이므로, 16자리의 2진수로 표현할 수 있는 정수의 개수인 65536개의 코드를 사용할 수 있다. 실제로 char 타입의 변수에는 유니코드가 저장되고 표현형식 역시 정수형과 동일하다. 다만, 정수형과 달리 음수를 나타낼 필요가 없어서 값의 범위가 다른다.

 

short타입의 표현범위 : -2^15 ~ 2^15-1 (-32768 ~ 32767)

char타입의 표현범위 : 0 ~ 2^16-1 (0 ~ 65535)

 

- 문자를 코드로 변환하는 것을 인코딩, 코드를 문자로 변환하는 것을 디코딩이라고 한다.

 

4.3 정수형 int

- 표현형식과 범위

어떤 진업의 리터럴을 변수에 저장해도 실제로는 2진수로 바뀌어 저장된다. 2진수가 저장되는 형식은 크게 정수형과 실수형이 있으며, 정수형은 다음과 같이 저장된다.

s n - 1 bit

 

모든 정수형은 부호있는 정수형이므로 첫 번째 비트를 '부호 비트(sign bit)'로 사용하고 나머지는 값을 표현하는데 사용한다. 

그래서 정수형은 타입의 크기만 알면 최대값과 최소값을 쉽게 계산할 수 있다

  • n비트로 표현할 수 있는 정수의 개수 : 2^n개 (= 2^n-1개 + 2^n-1개)
  • n비트로 표현할 수 있는 부호있는 정수의 범위 : -2^n-1 ~ 2^n-1 - 1

정수형 변수를 선언할 때는 int타입으로 하고, int의 범위(약 +-20억)를 넘어서면 long을 사용한다.

 

 

- 정수형의 오버플로우

4bit 2진수의 최대값인 '1111'에 1을 더하면 10000이 되지만 4자리의 2진수만 저장할 수 있기 때문에 '0000'이 된다. 연산과정에서 해당 타입이 표현할 수 있는 값의 범위를 넘어서는 것을 overflow라고 한다. 에러가 발생하는 것은 아니지만 예상 값을 얻지 못한다.

 

- 부호있는 정수의 오버플로우

부호있는 정수는 부호비트가 0에서 1이 될 때 오버플로우가 발생한다. 부호없는 정수의 경우 표현범위가 '0~15'이므로 이 값이 계속 반복되고, 부호있는 정수의 경우 표현범위가 '-8~7'이므로 이 값이 무한이 반복된다.

 

 

4.4 실수형 float, double

타입 저장 가능한 값의 범위(양수) 정밀도 bit byte
float 1.4 x 10^-45 ~ 3.4 x 10^38 7자리 32 4
double 4.9 x 10^-324 ~ 1.8 x 10^308 15자리 64 8

 

- 오버플로우

정수형과 달리 실수형에서는 오버플로우가 발생하면 변수의 값은 무한대가 된다. 정수형에는 없는 언더플로우가 있는데, 실수형으로 표현할 수 없는 아주 작은 값, 즉 양의 최소값보다 작은 값이 되는 0을 말한다.

 

- 실수형의 저장형식

실수형은 값을 부동소수점수(floating-point)의 형태로 저장한다. 부동소수점수는 실수를 '+-M x 2^E'와 같은 형태로 표현하는 것을 말하며, 부호(sign), 지수(Exponent), 가수(Mantissa), 모두 세 부분으로 이루어져 있다.

 

float : 1 + 8 + 23 = 32 (4byte)

S(1) E(8) M(23)

 

double : 1 + 11 + 52 = 64 (8byte)

S(1) E(11) M(52)

 

- 부동소수점의 오차

실수 중에는 '파이'와 같은 무한소수가 존재하므로, 값을 저장할 때 오차가 발생할 수 있다.게다가 10진수가 아닌 2진수로 저장하기 때문에 10진수로는 유한소수이더라도, 2진수로 변환하면 무한소수가 되는 경우도 있다. 

 


5.1 형변환 casting

형변환이란 변수 또는 상수의 타입을 다른 타입으로 변환하는 것이다. 방법은 간단하다. 변수나 리터럴 앞에 변환하고자 하는 타입을 괄호화 함께 붙여주면 된다.

ex) (타입)피연산자

 

여기에 사용되는 괄호는 '캐스트 연산자', '형변환 연산자'라고 한다.

float타입의 값을 int타입으로 변환할 때 소수점 이하의 값은 반올림이 아닌 버림으로 처리된다.

public class CastingEx1 {
    public static void main(String[] args) {
        double d = 85.4;
        int score = (int)d;

        System.out.println("score = " + score);
        System.out.println("d = " + d);
    }
}

 

 

5.5 정수형과 실수형 간의 형변환

정수는 소수점이하의 값이 없으므로 비교적 변환이 간단하다. 그저 정수를 2진수로 변환한 다음 정규화를 거쳐 실수의 저장형식으로 저장될 뿐이다. 

실수형은 정수형보다 큰 저장범위를 갖고 있기 때문에 무리가 없다. 주의할 점은 실수형의 정밀도 제한으로 인한 오차가 발생할 수 있다. 그래서 10진수로 8자리 이상의 값을 실수형으로 변환할 때는 double로 형변환해야 오차가 발생하지 않는다.

 

실수형을 정수형으로 변환하면, 실수형의 소수점이하 값은 버린다. 만일 실수의 소수점을 버리고 남은 정수가 정수의 저장범위를 넘는 경우 오버플로우가 발생한다.

 

 

5.6 자동 형변환

경우에 따라 편의상의 이유로 형변환을 생략할 수 있다. 그렇다고 형변환이 이루어지지 않는 것이 아니고, 컴파일러가 생략된 형변환을 자동적으로 추가한다.

float f = 1234; // float f = (float)1234;

 

 

그러나 변수가 저장할 수 있는 값의 범위보다 더 큰 값을 저장하려는 경우에 형변환을 생략하면 에러가 발생한다.

byte b = 1000; // byte의 범위(-128 ~ 127) 초과. 에러

 


 

Ch 03 연산자 operator

 

 

1.1 연산자와 피연산자

- 연산자(operator) : 연산을 수행하는 기호

- 피연산자(operand) : 연산자의 작업 대상(변수, 상수, 리터럴, 수식)

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

 

(대입 연산자와 단항 연산자(++, --)는 결합규칙이 오른쪽에서 왼쪽으로 진행된다.)

 

 

1.5 산술 변환 (usual arithmetic conversion)

연산 전에 피연산자 타입의 일치를 위해 자동 형변환 되는 것을 산술 변환이라고 한다. 산술 변환의 규칙은 다음과 같다.

1) 두 연산자의 타입을 같게 일치시킨다. (보다 큰 타입으로 일치) ex) long + int → long + long → long

2) 피연산자의 타입이 int보다 작을 경우 int로 변환된다. ex) byte + short → int + int → int

 

 

2.1 증감 연산자 ++, --

전위형 값이 참조되기 전에 증감 j = ++i;
후위형 값이 참조된 후 증감 i = i++;

증감연산자가 수식이나 메서드 호출에 포함되지 않고 독립적으로 쓰인 경우에는 전위형과 후위형에 차이가 없다. 

 

더보기
public class OperatorEx1 {
    public static void main(String[] args) {
        int i = 5;
        i++;
        System.out.println(i);

        i = 5;
        ++i;
        System.out.println(i);
    }
}

// output = 6 6

 

public 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);
    }
}

// output
// j=i++; 실행 후, i=6, j=5
// j=i++; 실행 후, i=6, j=6

 

하나의 식에서 증감연산자의 사용을 최소화하고, 식에 두 번 이상 포함된 변수에 증감연산자를 사용하는 것은 피해야 한다. 

 

 

2.2 부호 연산자 +, -

부호 연산자 '-'는 피연산자의 부호를 반대로 변경한 결과를 반환한다. 반면에 부호 연산자 '+'는 하는 일이 없으며, 쓰이는 경우도 거의 없다. 부호 연산자는 boolean형과 char

더보기
public class OperatorEx4 {
    public static void main(String[] args) {
        int i = -10;
        i = +i;
        System.out.println(i);

        i = -10;
        i = -i;
        System.out.println(i);
    }
}

 

 

3.1 사칙 연산자

int형과 float을 나눌 경우 int타입보다 범위가 넓은 float타입으로 일치시킨 후에 연산을 수행한다. 즉 10과 4.0f를 나누면 2.5f가 된다. 

 

피연산자가 정수형인 경우, 나누는 수로 0을 사용할 수 없다. 만일 0으로 나누면 컴파일은 정상적으로 실행되지만 오류(ArithmeticException)가 발생한다.

부동 소수점값인 0.0f, 0.0d로 나누는 것은 가능하지만 결과는 무한대이다.

 

ex code)

더보기
public class OperatorEx5 {
    public static void main(String[] args) {
        int a = 10;
        int b = 4;

        System.out.printf("%d + %d = %d%n", a, b,  a + b);
        System.out.printf("%d - %d = %d%n", a, b,  a - b);
        System.out.printf("%d * %d = %d%n", a, b,  a * b);
        System.out.printf("%d / %d = %d%n", a, b,  a / b);
        System.out.printf("%d / %f = %f%n", a, (float)b, a / (float)b);
    }
}

 

x y x / y x % y
유한수 +- 0.0 +- Infinity Nan
유한수 +- Infinity +- 0.0 x
+- 0.0 +- 0.0 Nan Nan
+- Infinity 유한수 +- Infinity NaN
+- Infinity +- Infinity NaN NaN

 

 

더보기
// error: incompatible types: possible lossy conversion from int to byte

public class OperatorEx6 {
    public static void main(String[] args) {
        byte a = 10;
        byte b = 20;
        byte c = a + b;
        System.out.println(c);
    }
}

위의 코드에서 a와 b는 모두 int형보다 작은 byte형이기 때문에 연산자 +는 두 피연산자를 int형으로 변환한 다음 연산을 수행한다.

이후 4byte의 값을 1byte의 변수형에 저장하려고 했기 때문에 에러가 발생한다.

 

크기가 작은 자료형의 변수를 큰 자료형의 변수에 저장할 때는 자동으로 캐스팅 되지만, 큰 자료형의 값을 작은 자료형의 변수에 저장하려면 명시적으로 캐스팅을 해야한다.

 

 

더보기
public class OperatorEx7 {
    public static void main(String[] args) {
        byte a = 10;
        byte b = 30;
        byte c = (byte)(a * b);
        System.out.println(c);
    }
}

위의 코드를 실행하면 44가 출력된다. 10*30의 결과는 300이지만, 큰 자료형에서 작은 자료형으로 변환하면 손실이 발생하므로 값이 바뀔 수 있다. 300은 byte의 범위를 넘기 때문에 44가 byte형 변수 c에 저장된다.

int형을 byte형으로 변환하는 경우 앞의 24자리를 없애고 하위 8자리(1byte)만 보존한다. 

 

 

더보기
public class OperatorEx8 {
    public static void main(String[] args) {
        int a = 1_000_000;
        int b = 2_000_000;
        long c = a * b;
        System.out.println(c);
    }
}

위의 코드는 예상과 전혀 다른 -1454759936이 출력된다. 'a*b'의 결과가 이미 int타입의 값이므로 long형으로 자동 형변환되어도 값은 변하지 않는다.

올바른 결과를 얻으려면 변수 a 또는 b 타입을 long형으로 형변환해야 한다.

 

 

더보기
public class OperatorEx9 {
    public static void main(String[] args) {
        long a = 1_000_000 * 1_000_000;
        long b = 1_000_000 * 1_000_000L;

        System.out.println("a="+a);
        System.out.println("b="+b);
    }
}

위의 코드를 실행하면 a는 -727379968이 나오고 b는 1000000000000로 예상한 값이 나온다. a는 int타입의 최대값인 약 2*10^9를 넘으므로 오버플로우가 발생했기 때문이다. 이미 오버플로우가 발생한 값을 아무리 long타입의 변수에 저장해도 소용이 없다.

 

 

더보기
public class OperatorEx10 {
    public static void main(String[] args) {
        int a = 1000000;

        int result1 = a * a / a;
        int result2 = a / a * a;

        System.out.printf("%d * %d / %d = %d%n", a, a, a, result1);
        System.out.printf("%d / %d * %d = %d%n", a, a, a, result2);
    }
}

위의 코드를 실행하면 result1은 -727이 나오고, result2는 1000000이 나온다. result1처럼 먼저 곱한 경우엔 int의 범위를 넘어서기 때문에 예상했던 값과 다른 결과가 도출된다.

 

 

더보기
public class OperatorEx11 {
    public static void main(String[] args) {
        char a = 'a';
        char d = 'd';
        char zero = '0';
        char two = '2';

        System.out.printf("'%c' - '%c' = %d%n", d, a, d-a);
        System.out.printf("'%c' - '%c' = %d%n", two, zero, two - zero);
        System.out.printf("'%c'=%d%n", a, (int)a);
        System.out.printf("'%c'=%d%n", d, (int)d);
        System.out.printf("'%c'=%d%n", zero, (int)zero);
        System.out.printf("'%c'=%d%n", two, (int)two);
    }
}

사칙연산의 피연산자로 숫자뿐만 아니라 문자도 가능하다. 문자는 실제로 해당 문자의 유니코드로 바뀌어 저장되므로 문자간의 사칙연산은 정수간의 연산과 동일하다. 주로 문자간의 뺄셈을 하는 경우가 대부분이며, 문자 '2'를 숫자로 변환하려면 문자 '0'을 빼주면 된다.

'2' - '0' → 50 - 48 → 2

 

 

더보기
public class OperatorEx12 {
    public static void main(String[] args) {
        char c1 = 'a'; // a의 유니코드 값인 97이 저장된다.
        char c2 = c1; // c1에 저장되어 있는 값이 저장된다.
        char c3 = ' '; // 공백으로 초기화.

        int i = c1 + 1; // 'a'=1 -> 91+1 -> 98

        c3 = (char)(c1 + 1);
        c2++;
        c2++;

        System.out.println("i="+i);
        System.out.println("c2="+c2);
        System.out.println("c3="+c3);
    }
}

위의 코드를 보면, 'c1+1'은 '97+1'이 되어 98이 된다. c2++은 형변환 없이 증가시키므로 두번 증가시켜 '99'가 되고 'c'가 된다.

 

 

더보기
public class OperatorEx13 {
    public static void main(String[] args) {
        char c1 = 'a';
        char c2 = c1 + 1; // compile error
        char c2 = 'a' + 1; // compile error

        System.out.println(c2);
    }
}

위의 코드는 에러가 발생하지 않는다. 'a'+1이 리터럴 간의 연산이기 때문이다. 상수 또는 리터럴 간의 연산은 실행과정동안 변하는 값이 아니기 때문에, 컴파일 시에 컴파일러가 계산해서 그 결과로 대체함으로써 코드를 효율적으로 만든다.

컴파일 전의 코드 컴파일 후의 코드
char c2 = 'a' + 1;
int sec = 60 * 60 * 24;
char c2 = 'b';
int sec = 86400;

 

수식에 변수가 들어가 있는 경우에는 컴파일러가 미리 계산을 할 수 없기 때문에 직접 캐스팅을 해야 한다. 그렇지 않으면 컴파일 에러가 발생한다. 또는 코드의 가독성과 유지보수를 위해 명시하기도 한다.

 

 

더보기
public class OperatorEx14 {
    public static void main(String[] args) {
        char c = 'a';
        for(int i=0; i<26; i++){
            System.out.println(c++);
        }
        System.out.println(); // 줄바꿈

        c = 'A';
        for(int i=0; i<26; i++){
            System.out.println(c++);
        }
        System.out.println();

        c = '0';
        for(int i=0; i<10; i++){
            System.out.println(c++);
        }
        System.out.println();
    }
}

 

public class OperatorEx15 {
    public static void main(String[] args) {
        char lowerCase = 'a';
        char upperCase = (char)(lowerCase - 32);
        System.out.println(upperCase);
    }
}

문자 a의 코드값은 10진수로 97, b는 98, c는 99 ... z는 122이며, A는 65, B는 66 ... Z는 90이다. 그리고 문자 0의 코드값은 48이다.

이 사실을 이용하면 대문자와 소문자를 서로 변환하는 프로그램을 만들 수 있다.

소문자를 대문자로 변경하려면, 코드값에서 32를 빼면 되고, 소문자로 변환하려면 32를 더하면 된다.

 

더보기
public class OperatorEx16 {
    public static void main(String[] args) {
        float pi = 3.141592f;
        float shortPi = (int)(pi * 1000) / 1000f;
        System.out.println(shortPi);
    }
}

위의 코드에서 'pi * 1000'은 float형인 3141.592f가 된다. 그 다음 형변환을 통해 3141이 된다. 그 다음 int와 float의 연산이므로, int가 float으로 변환된 다음 연산을 수행한다. 결과는 3.141f가 된다. 

 

 

더보기
public class OperatorEx17 {
    public static void main(String[] args) {
        double pi = 3.141592;
        double shortPi = (int)(pi * 1000 + 0.5) / 1000.0;

        System.out.println(shortPi);
    }
}

소수점 버림이 아닌 반올림을 하려면 위의 코드처럼 반올림을 위해 0.5를 더해준다. double과 double의 나눗셈이므로 결과는 double인 3.142가 된다. 만일 1000.0이 아닌 1000으로 나누었다면, 3.142가 아닌 3을 결과로 얻었을 것이다.

 

 

더보기
public class OperatorEx18 {
    public static void main(String[] args) {
        double pi = 3.141592;
        double shortPi = Math.round(pi * 1000) / 1000.0;
        System.out.println(shortPi);
    }
}

Math.round()를 사용하면 더 간단히 반올림할 수 있다. 

 

 

4.2 등가비교 연산자 ==, !=

더보기
public class OperatorEx22 {
    public static void main(String[] args) {
        float f = 0.1f;
        double d = 0.1;
        double d2 = (double)f;

        System.out.printf("10.0==10.0f  %b%n", 10.0==10.0f);
        System.out.printf("0.1==0.1f  %b%n", 0.1==0.1f);
        System.out.printf("f=%19.17f%n", f);
        System.out.printf("d=%19.17f%n", d);
        System.out.printf("d2=%19.17f%n", d2);
        System.out.printf("d==f  %b%n", d==f);
        System.out.printf("d==d2  %b%n", d==d2);
        System.out.printf("d2==f  %b%n", d2==f);
        System.out.printf("(float)d==f  %b%n", (float)d==f);
    }
}

위 코드의 결과를 보면, '10.0==10.0f'는 true인데, '0.1==0.1f'는 false이다. 정수형과 달리 실수형은 근사값으로 저장되므로 오차가 발생할 수 있기 때문이다. 

10.0f는 오차없이 저장할 수 있는 값이라서 double로 형변환해도 그대로 10.0이 되지만, 0.1f는 저장할 때 2진수로 변환하는 과정에서 오차가 발생한다. 

 

float타입의 값을 double타입으로 형변환하면 부호화 지수는 달라지지 않고 그저 가주의 빈자리를 0으로 채우기 때문에, 오차가 적어지지 않는다.

 

float타입의 값과 double타입의 값을 비교하려면 double타입의 값을 float으로 형변환한 다음에 비교해야 한다. 

 

 

4.3 문자열의 비교

두 문자열을 비교할 때는 비교연산자 대신 equals() 메서드를 사용해야 한다. 

 

ex code)

더보기
public class OperatorEx23 {
    public static void main(String[] args) {
        String str1 = "abc";
        String str2 = new String("abc");

        System.out.println("abc"=="abc");
        System.out.println(str1=="abc");
        System.out.println(str2=="abc");
        System.out.println(str1.equals("abc"));
        System.out.println(str2.equals("abc"));
        System.out.println(str2.equalsIgnoreCase("ABC"));
    }
}

 

***추가***

원래 String은 클래스이므로, 아래와 같이 new를 사용해서 생성해야 한다.

String str = new String("abc"); // String클래스의 객체를 생성
String str = "abc"; // 위의 문장을 간단히 표현

 

 

5.1 논리 연산자

|| (OR결합) : 피연산자 중 어느 한 쪽만 true이면 true를 결과로 얻는다
&& (AND결합) : 피연산자 양쪽 모두 true이어야 true를 결과로 얻는다.

 

ex code)

 

더보기
public class OperatorEx24 {
    public static void main(String[] args) {
        int x = 0;
        char ch = ' ';

        x = 15;
        System.out.println(10 < x && x < 20); // true

        x = 6;
        System.out.println(x % 2 == 0 || x % 3 == 0 && x % 6 != 0); // true

        System.out.println((x % 2 == 0 || x % 3 == 0) && x % 6 != 0); // false

        ch = '1';
        System.out.println('0' <= ch && ch <= '9'); // true

        ch = 'a';
        System.out.println('a' <= ch && ch <= 'z'); // true

        ch = 'A';
        System.out.println('A' <= ch && ch <= 'Z'); // true

        ch = 'q';
        System.out.println(ch == 'q' || ch == 'Q'); // true
    }
}

 

 

ex code)

하나의 문자를 입력받아서 숫자인지 영문자인지 확인한다. 조건 if는 괄호 안의 연산결과가 참인 경우 블럭{} 내의 문장을 수행한다.

더보기
import java.util.Scanner;

public class OperatorEx25 {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        char ch = ' ';

        System.out.println("문자를 하나 입력하세요. > ");

        String input = scanner.nextLine();
        ch = input.charAt(0);

        if('0' <= ch && ch <= '9'){
            System.out.printf("입력하신 문자는 숫자입니다. %n");
        }
        if('a' <= ch && ch <= 'z'){
            System.out.printf("입력하신 문자는 영문자입니다. %n");
        }
    } // main
}

 

 

ch < 'a' || ch > 'z'
!('a' <= ch && ch <= 'z')

두 코드는 같은 의미다.

 

5.2 비트 연산자

| (OR연산자) : 피연산자 중 한 쪽의 값이 1이면 1을 결과로 얻는다. 그 외에는 0을 얻는다.
& (AND연산자) : 피연산자 양 쪽이 모두 1이어야만 1을 결과로 얻는다. 그 외에는 0을 얻는다.
^ (XOR연산자) : 피연산자의 값이 서로 다를 때만 1을 결과로 얻는다. 같을 때는 0을 얻는다.

 

 

비트연산에서도 피연산자의 타입을 일치시키는 '산술 변환'이 일어날 수 있다.

 

ex code)

더보기
import static java.lang.Long.toBinaryString;

public class OperatorEx28 {
    public static void main(String[] args) {
        int x = 0xAB, y = 0xF;

        System.out.println(toBinaryString(x));
        System.out.println(toBinaryString(y));
        System.out.println(toBinaryString(x | y));
        System.out.println(toBinaryString(x & y));
        System.out.println(toBinaryString(x ^ y));
        System.out.println(toBinaryString(x ^ y ^ y));
    }

    static String toBinaryString(int x) {
        String zero = "000000000000000000000000000000000";
        String tmp = zero + Integer.toBinaryString(x);
        return tmp.substring(tmp.length()-32);
    }
}

 

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

 

5.4 비트 전환 연산자

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

'~'에 의해 비트전환이 되면 부호있는 타입의 피연산자는 부호가 반대로 변경된다. 즉, 피연산자의 '1의 보수'를 얻을 수 있다. 그래서 비트전환연산자를 1의보수 연산자라고도 한다.

 

ex code)

더보기
public class OperatorEx29 {
    public static void main(String[] args) {
        byte p = 10;
        byte n = -10;

        System.out.println(toBinaryString(p));
        System.out.println(toBinaryString(~p));
        System.out.println(toBinaryString(~p+1));
        System.out.println(toBinaryString(~~p));
        System.out.println();
        System.out.println(n);
        System.out.println(~(n-1));
    } // main

    static String toBinaryString(int x) {
        String zero = "000000000000000000000000000000000";
        String tmp = zero + Integer.toBinaryString(x);
        return tmp.substring(tmp.length()-32);
    }
}

 

 

5.5 쉬프트 연산자

이 연산자는 피연산자의 각 자리(2진수)를 오른쪽 또는 왼쪽으로 shift한다고 해서 shift operator로 이름 붙여졌다.

예를 들어 '8<<2'는 10진수 8의 2진수를 왼쪽으로 2자리 이동한다. 자리이동으로 저장범위를 벗어난 값들은 버려지고 빈자리는 0으로 채워진다.

 

'<<' 연산자의 경우 피연산자의 부호에 상관없이 각 자리를 왼쪽으로 이동시키며 빈칸을 0으로 채우면 되지만, '>>'연산자는 부호있는 정수는 부호를 유지하기 위해 왼쪽 피연산자가 음수인 경우 빈자리를 1로 채운다. (물론 양수일 때는 0으로 채운다.)

 

쉬프트 연산자의 좌측 피연산자는 산술변환이 적용되어 int보다 작은 타입은 int로 자동 변환되고 연산결과 역시 int타입이 된다. 그러나 쉬프트 연산자는 다른 이항연산자들과 달리 타입을 일치시킬 필요가 없기 때문에 우측 피연산자에는 산술변환이 적용되지 않는다.

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

 

 

6.1 조건 연산자

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

첫 번째 피연산자인 조건식의 평가결과에 따라 다른 결과를 반환한다. 조건식의 평가결과가 true이면 식1이, false이면 식2가 연산결과가 된다. 

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

위의 코드에서 'x > y'의 결과가 true이면 x가 result에 저장되고, false면 y가 true에 저장된다.

조건 연산자를 사용하는 것이 if문보다 간략하다. 그러나 가독성이 떨어지므로 필요한 경우에 한번 정도만 중첩하는 것이 좋다.

 

ex code)

더보기
public class OperatorEx32 {
    public static void main(String[] args) {
        int x, y, z;
        int absX, absY, absZ;
        char signX, signY, signZ;

        x = 10;
        y = -5;
        z = 0;

        absX = x >= 0 ? x : -x;
        absY = y >= 0 ? y : -y;
        absZ = z >= 0 ? z : -z;

        signX = x > 0 ? '+' : (x == 0 ? ' ' : '-'); // 조건 연산자 중첩
        signY = y > 0 ? '+' : (y == 0 ? ' ' : '-');
        signZ = z > 0 ? '+' : (z == 0 ? ' ' : '-');

        System.out.printf("x=%c%d%n", signX, absX);
        System.out.printf("y=%c%d%n", signY, absY);
        System.out.printf("z=%c%d%n", signZ, absZ);
    }
}