본문 바로가기
Java & Spring

자바 정리 (1)

by WhoamixZerOne 2024. 1. 22.

이 글은 자바의 정석 3판을 정리한 내용입니다.
자바의 정석 3판

 

✔ 자바 언어의 특징

  • 자동 메모리 관리(GC, Garbage Collection)
  • 멀티 스레드 지원
  • 다수의 라이브러리 지원
  • 운영체제에 독립적(자바 가상 머신, JVM)

https://tcpschool.com/java/java_intro_programming

✔ 변수(Variable)

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

 

1. 변수의 초기화

변수를 사용하기 전에 반드시 초기화(Initialization) 해야 한다.

메모리는 여러 프로그램이 공유하는 자원이기 때문에 전에 다른 프로그램에 의해 저장된 쓰레기 값(Gargabe value)이 남아있을 수 있다.

로컬(지역) 변수(Local Variable)는 읽기 전에 꼭 초기화해야 한다.(안 하면 컴파일 에러)

 

2. 변수의 명명 규칙

변수뿐 만 아니라 자바 프로그래머들에게 권장하는 규칙들이 있다.

 

컨벤션(Convention)

권장하는 규칙들은 아래의 링크에서 참조하면서 사용한다.

프로그래머들만의 암묵적인 약속으로 규칙에 따른 개발을 하는데 일부분은 상황에 맞게 미리 규칙을 정해놓고 적용하면 된다.

 

3. 변수의 타입

자료형은 크게 '기본형', '참조형' 두 가지로 나눌 수 있는데,

기본형 변수는 실제 값을 저장하고 참조형 변수는 어떤 값이 저장되어 있는 주소(Memory Address) 값을 저장한다.

 

1) 기본형 타입(Primitive Type) 8개

  • 문자 - char
  • 숫자
    • 정수 - byte, short, int, long (default type - int)
    • 실수 - float, double (default type - double)
  • 논리 - boolean

2) 참조형 타입(Reference Type)

  • 8개의 기본형을 제외한 나머지 타입(사용자 정의 타입으로 무한 개)
  • 객체의 주소(Memory Address)를 저장(32bit - 4byte, 64bit - 8byte)

3) 상수 & 리터럴(Constant & literal)

  • 변수(Variable) - 단 하나의 값을 저장할 수 있는 메모리상의 공간
  • 상수(Constant) - 한 번만 값을 저장 가능한 변수
  • 리터럴(literal) - 그 자체로 값을 의미(리터럴은 개발자가 직접 입력한 고정된 값. 따라서 리터럴 자체는 변하지 않는다.)

4) 기본형 표현 범위

1 bit = 2진수 1자리 / 1 byte = 8bit

  • 기본형의 종류와 크기
종류 \ 크기 1 byte 2 byte 4 byte 8 byte
논리형 boolean      
문자형   char    
정수형 byte short int long
실수형     float double
  • 기본형의 크기와 범위
자료형 저장 가능한 값의 범위 bit byte
boolean false, true 8 1
char '\u0000' ~ '\uffff' (0~2^{16}-1, 0~65535) 16 2
byte -128 ~ 127 (-2^{7} ~ 2^{7}-1) 8 1
short -32,768 ~ 32,767 (-2^{15} ~ 2^{15}-1) 16 2
int -2,147,483,648 ~ 2,147,483,647 (-2^{31} ~ 2^{31}-1, 약 ±20억) 32 4
long -9,223,372,036,854,775,808 ~ 9,223,372,036,854,775,807 (-2^{63} ~ 2^{63}-1) 64 8
float 1.4E-45 ~ 3.4E38 (1.4*10^{-45} ~ 3.4*10^{38}) 32 4
double 4.9E-324 ~ 1.8E308 (4.9*10^{-324} ~ 1.8*10^{308}) 64 8

저장 가능한 값의 범위를 넘으면 오버플로우가 발생한다.

※ 오버플로우(overflow) : 해당 타입이 표현할 수 있는 값의 범위를 넘어선 것

 

n 비트로 표현할 수 있는 값의 개수 : 2^{n} 개

ex) 1byte(8bit) 2^{8} = 256개

 

n 비트로 표현할 수 있는 부호 없는 정수의 범위 (0 ~ 2^{n}-1)

ex) 1byte(8bit) 0~2^{8}-1 = 255개

 

n 비트로 표현할 수 있는 부호 있는 정수의 범위 (-2^{n-1} ~ 2^{n-1}-1)

ex) 1byte(8bit) -2^{8-1} ~ 2^{8-1}-1 → -2^{7} ~ 2^{7}-1 → -128 ~ 127

 

int(-2^{31} ~ 2^{31}-1) :  -2,147,483,648 ~ 2,147,483,647 (약 20억, 4byte, 2³²)

2^{31} = 2^{10} * 2^{10} * 2^{10} * 2^{1} → 2*10^{9} (2^{10}≒1024≒10^{3})

S 31bit

S : 부호비트(Sign bit)

0 31bit
1 31bit

0 : 양수, 1 : 음수

 

  • 실수형의 범위와 정밀도
자료형 정밀도 bit byte
float 7 자리 32 4
double 15 자리 64 8

※ 정밀도 : 오차 없는 자리 수

 

float : 대략 -3.4E38 ~ 3.4E38, 7자리 정밀도 (4byte, 2³²)

S E(8) M(23)

S : 부호, E : 지수, M : 가수

1+8+23 = 32bit(4byte)

 

double : 대략 -1.7E308 ~ 1.7E308, 15자리 정밀도 (8byte, 2⁶⁴)

S E(11) M(52)

1+11+52 = 64bit(8byte)

 

5) 형변환(캐스팅, Casting)

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

자동 형변환

  • 컴파일러가 자동으로 형변환
  • 작은 타입에서 큰 타입일 시 자동 형변환
  • 형변환(캐스트) 연산자 생략 가능

ex) 자동 형변환

// byte → int
byte b = 10;
int i = b; // 형변환 생략 가능(원래는 int i = (int) b;)

 

대입뿐만 아니라 연산과정에서도 자동 형변환이 이루어진다.

ex) 산술 연산 자동 형변환

int i = 3;
double d = 1.0 + i;  // double d = 1.0 + (double) i; 자동 형변환

byte b = 10;
short s = 20;
int i = b + s;  // int i = (int) b + (int) s;
// byte, short, char는 int 형으로 자동 형변환

 

형변환을 하는 이유는 서로 다른 두 타입을 일치시키기 위해서인데, 형변환을 생략하면 컴파일러가 알아서 자동적으로 형변환을 한다.

 

"기존의 값을 최대한 보존할 수 있는 타입으로 자동 형변환"

  • 기본형의 자동 형변환이 가능한 방향

1byte           2byte          4byte     8byte      4byte        8byte

byte  →  short, char  →  int  →  long  →  float  →  double

 

명시적 형변환

  • 큰 타입에서 작은 타입일 시 명시적으로 형변환 필요
  • 형변환(캐스트) 연산자 생략 불가능
  • 큰 타입에서 작은 타입으로의 형변환은 값 손실 문제가 발생 가능

ex) 명시적 형변환

// int → byte
int i = 10;
byte b = (byte) i; // 생략 불가능

 

형변환 예외적인 상황

ex) 예외 상황

byte b = 100; // OK 100이 byte 범위인 -128~127에 해당

int i = 100;
byte b = i;  // Error
/*
i는 int 타입인 4byte인데 byte는 1byte라서 에러
첫 번째는 100은 리터럴 값이라 가능
따라서 byte b = (byte) i;로 해야 가능
*/

✔ 연산자(Operator)

  • 연산자 우선순위 & 결합규칙
종류 결합규칙 연산자 우선순위
단항 연산자 <━━━━━ ++, --, +, -, ~, !, (type) 높음
























낮음
산술 연산자 ━━━━━━━━> *, /, %
━━━━━━━━> +, -
━━━━━━━━> <<, >>
비교 연산자 ━━━━━━━━> <, >, <=, >=, instanceof
━━━━━━━━> ==, !=
논리 연산자 ━━━━━━━━> &
━━━━━━━━> ^
━━━━━━━━> |
━━━━━━━━> &&
━━━━━━━━> ||
삼항 연산자 ━━━━━━━━> ?:
대입 연산자 <━━━━━ =, +=, -=, *=, /=, %=,
<<=, >>=, &=, ^=, |=

 

1. 산술 변환

연산 전에 피연산자의 타입을 일치시키는 것

 

1. 두 피연산자의 타입을 같게 일치시킨다.(큰 타입으로 일치 → 값 손실을 최소화하기 위해)

ex)

8byte  4byte

long  +  int  →  long + long  →  long

4byte  4byte

float  + int  → float + float →  float

8byte      4byte

double  +  float  →  double + double  →  double

 

2. 피연산자의 타입이 int보다 작은 타입이면 int로 변환된다.

(JVM 피연산자 스택(operand stack)이 피연산자를 4byte 단위로 저장하기 때문에 기본 타입인 int가 가장 효율적으로 처리할 수 있는 타입이다)

ex)

1byte    2byte

byte  +  short  →  int + int  →  int

2byte    2byte

char  +  short  → int + int  →  int

 

※주목할 점(연산결과의 타입)

연산결과의 타입은 피연산자의 타입과 일치한다.

int와 int의 나눗셈 연산결과는 실수가 아니라 int라는 점이다.

ex)

int / int → int

5 / 2 → 2

double d = 5 / 2; // double d = 2; 정수 2가 대입
System.out.println(d);  // 2.0

// double 타입으로 형변환 필요
double d = (double) 5 / 2; // double d = 5.0 / 2;
double d = 5.0 / (double) 2; // double d = 5.0 / 2.0; 자동 형변환
double d = 2.5;
System.out.println(d);  // 2.5

첫 번째는 연산결과의 타입이 int이기 때문에 int 타입으로 2가 대입된다.

형변환을 해줘야 원하는 실수 값이 대입된다.

✔ 조건문과 반복문

1. Switch

  • 조건식 결과는 정수 or 문자열이어야 한다
  • case문의 값은 정수 상수, 문자열 리터럴만 가능하며 중복되지 않아야 한다(JDK 1.7부터 문자열 리터럴 허용)
  • Java 12 버전에서 스위치 표현식(Switch Expression) Preview가 추가되었다
  • Java 13 버전에서 스위치 표현식(Switch Expression)에 'yield' 키워드가 추가되었다
  • Java 14 버전에서 스위치 표현식(Switch Expression)이 표준화되었다

 

  • 기존에 사용하던 switch 문
int month = 6;
boolean isLeapYear = false;

switch (month) {
    case 1:
    case 3:
    case 5:
    case 7:
    case 8:
    case 10:
    case 12:
        System.out.println(31);
        break;
    case 2:
        System.out.println(!isLeapYear ? 28 : 29);
        break;
    case 4: case 6: case 9: case 11:
        System.out.println(30);
        break;
    default:
        throw new IllegalArgumentException();
}

 

 

  • Switch Expression(Arrow, Inline)

람다식 사용 및 case 문을 한 번에 정의할 수 있다.(break를 사용하지 않아도 된다)

if, for문과 마찬가지로 실행문이 하나일 땐 블록을 생략할 수 있지만 여러 개면 블록을 사용해야 한다.

int month = 6;
boolean isLeapYear = false;

switch (month) {
    case 1, 3, 5, 7, 8, 10, 12 -> System.out.println(31);
    case 2 -> System.out.println(!isLeapYear ? 28 : 29);
    case 4, 6, 9, 11 -> System.out.println(30);  // 30
    default -> throw new IllegalArgumentException();
}

 

  • Switch Expression(yield)

기존에는 값을 사용하기 위해서 변수에 값을 대입해서 사용했지만 'yield' 키워드를 사용해서 값을 반환할 수 있다.

'yield' 키워드는 항상 블록 내부에서만 사용할 수 있다.(Java 12 버전이라면 'yield' 대신에 'break'를 사용해서 반환해야 한다)

int month = 2;
boolean isLeapYear = false;

int lastDate = switch (month) {
    case 1, 3, 5, 7, 8, 10, 12 -> 31;
    case 2 -> {
        int day = !isLeapYear ? 28 : 29; 
        yield day;
    }
    case 4, 6, 9, 11 -> {
    	System.out.println(30);
        yield 30;
    }
    default -> throw new IllegalArgumentException();
};
System.out.println("lastDate = " + lastDate);  // 28

첫 번째 케이스처럼 람다식으로 반환할 수 있고, 두 번째 케이스처럼 'yield' 키워드를 사용해 반환할 수 있다.

 

 

 

🔗 Reference

'Java & Spring' 카테고리의 다른 글

자바 정리 (3)  (0) 2024.02.19
자바 정리 (2)  (1) 2024.02.06
Java 버전 관리 도구  (0) 2023.11.18
[Spring] 인터셉터(Interceptor) 적용  (0) 2022.04.17
JPA @MappedSuperclass  (0) 2022.03.07

댓글