1. 객체지향 프로그래밍 언어
Java는 객체지향 프로그래밍 언어로, 클래스와 객체를 기반으로 코드를 설계한다.
캡슐화, 추상화, 상속, 다형성 등 객체지향 프로그래밍(OOP)의 원칙을 지원한다.
2. 플랫폼 독립성 제공
"Write Once, Run Anywhere(WORA)"
철학을 따르는 언어로,
소스 코드를 바이트 코드로 컴파일한 후, JVM(Java Virtual Machine)에서 실행한다.
운영체제와 하드웨어에 관계 없이 JVM이 설치된 모든 환경에서 실행이 가능하다.
🔍 JVM이란? Java 애플리케이션을 실행하기 위한 가상 환경
3. 효율적인 메모리 관리
Garbage Collector를 통해 메모리를 자동으로 관리하여
프로그래머가 명시적으로 메모리 해제를 하지 않아도 된다.
4. 멀티스레딩 지원
Thread
클래스와 Runnable
인터페이스를 통해 멀티스레딩을 지원한다.
여러 작업을 동시에 수행하여 실행 시간을 줄이고, 시스템 자원을 효율적으로 활용할 수 있다.
5. 동적 로딩 지원
프로그램 실행 시점에 필요한 클래스만 메모리에 로드하여
애플리케이션의 유연성과 확장성을 제공하고, 초기 메모리 사용량을 줄일 수 있다.
🔍 Java는 코드 구조가 모듈화되어 있어 재사용과 유지 보수에 용이하고,
기존 코드의 수정 없이 클래스 상속 등으로 새로운 기능을 쉽게 추가할 수 있다.
6. 풍부한 표준 라이브러리
데이터 구조, 네트워크, 파일 입출력, 그래픽 등 다양한 API를 제공한다.
(ArrayList
, Socket
, FileReader
, Connection
등)
7. 엄격한 데이터 타입 적용
데이터 타입이 엄격히 정의되고, 타입 간의 규칙을 엄격하게 준수한다.
컴파일 시점에 타입 오류를 사전에 감지해 런타임 오류를 줄이고 안정성을 높인다.
1. 복잡한 코드
객체지향 언어의 특성상 단순한 작업도 클래스를 생성해야 하는 등
코드 작성 시 필수적인 구조적 요소들이 많아 설계가 복잡해질 수 있다.
2. 많은 메모리 사용량
JVM과 Garbage Collector를 통해 메모리를 관리하여
프로세스가 사용하는 메모리가 상대적으로 많다.
특히 대규모 애플리케이션에서는 메모리 사용을 최적화하기 어렵다.
3. 상대적으로 느린 실행 시간
Java 애플리케이션 실행 시 JVM 초기화 및 클래스 로딩 과정으로
Python, JavaScript 등의 스크립트 언어에 비해 초기 실행 시간이 길다.
4. 배포 크기 증가
Java 애플리케이션을 실행하기 위해서는 JVM 관련 라이브러리가 필요하기 때문에
서버 환경에서는 부담이 적지만, 클라이언트 환경에서는 불리할 수 있다.
5. 높은 JVM 의존성
Java 애플리케이션은 JVM이 없는 환경에서는 실행할 수 없기 때문에
네이티브 코드를 필요로 하는 특정 상황에서는 제약이 될 수 있다.
6. 가비지 컬렉션으로 인한 성능 저하
Garbage Collection은 메모리 관리의 편리함을 제공하지만,
실행 중 간헐적으로 애플리케이션의 성능을 저하시킬 수 있다.
Java의 실행 과정은 크게 컴파일과 실행 두 단계로 나뉜다.
1. 소스 코드 작성
Java 언어로 .java
파일 작성
2. 컴파일
Java 컴파일러(javac
)가 소스 코드를 바이트 코드(.class
)로 변환
(예: HelloWorld.java
실행 시 HelloWorld.class
파일 생성)
3. 클래스 로드
JVM의 클래스 로더가 .class
파일을 로드하여 메모리에 적재
동적 로딩 방식을 사용해 필요한 클래스만 실행 시점에 로드
4. 바이트 코드 검증
JVM의 바이트 코드 검증기가 .class
파일의 무결성 확인
악의적 코드, 잘못된 명령어 등을 탐지해 실행을 방지
5. 실행
JVM의 실행 엔진이 바이트 코드를 해석하고 실행 (인터프리터 + JIT)
1) JVM 인터프리터로 모든 바이트 코드를 한 줄씩 읽고 해석한 후 즉시 실행
2) JIT(Just-In-Time) 컴파일러가 자주 실행되는 바이트 코드를 네이티브 코드로 변환
3) 변환된 네이티브 코드를 실행하여 속도 향상 및 최적화
Java의 바이트 코드는 Java 컴파일러가 소스 코드를 컴파일하여 생성하는 중간 코드이다.
바이트 코드는 JVM이 이해하고 실행할 수 있는 형태로 설계되었다.
방식 | 인터프리터 | JIT 컴파일러 |
---|---|---|
장점 | 전체 코드를 미리 컴파일하지 않아 빠른 실행 속도 코드를 바로 실행하여 테스트와 디버깅에 유리 | 인터프리터 과정 없이 바로 실행 인라인 함수, 루프 전개 등 성능 최적화 기능 |
단점 | 매번 해석해야 하므로 반복 작업에서 성능 저하 | 네이티브 코드로 변환하는 데 시간 소요 |
적용 시점 | 프로그램 초기 실행 시 사용 | 프로그램 실행 중 특정 코드가 반복적으로 실행될 때 사용 |
Java SE 8: 현대 Java 프로그래밍의 기반을 구축한 버전
함수형 프로그래밍 도입과 API 개선이 큰 특징
java.time
패키지: 더 직관적이고 강력한 날짜/시간 처리 제공Java SE 11: 장기 지원(LTS) 버전으로,
Java 8 이후 많은 개선과 기능 추가가 이루어진 버전
var
키워드로 지역 변수 선언 간소화 및 타입 추론 가능isBlank()
, lines()
, strip()
등 유용한 메서드 추가Java SE 17: 최신 장기 지원(LTS) 버전으로,
새로운 기능과 최적화가 다수 포함된 버전
instanceof
와 switch
문에서 더 간결하고 강력한 패턴 처리Records
: 데이터 객체를 간단하게 정의 가능Sealed
클래스: 상속 가능한 클래스 제한 가능JDK
와 JRE
JDK (Java Development Kit)
Java 애플리케이션을 개발하기 위한 도구 모음
JRE (Java Runtime Environment)
Java 애플리케이션을 실행하기 위한 환경
==
)과 동등성(equals
) (🍀)동일성: 두 객체가 동일한 메모리 주소를 참조하는지 비교
동등성: 두 객체의 내용(값)이 같은지 비교
Object 클래스의 기본 equals()
메서드는 == 연산자와 동일하게 메모리 주소를 비교하지만,
equals()
를 재정의하면 객체의 내용을 비교할 수 있다.
🔍 재정의(Override): 부모 클래스의 메서드를 상속 받은 자식 클래스에서 다시 정의하여
자식 클래스의 요구에 맞게 동작을 변경하는 것
HashCode
Java의 Object 클래스에 정의된 메서드로, 객체의 해시 값을 반환
해시 값은 객체를 식별하기 위해 사용하는 정수 값으로,
HashMap
, HsahSet
과 같은 해시 기반 컬렉션에서 객체를 빠르게 검색, 저장하기 위해 사용
hashCode()
의 주요 특징hashCode()
)을 가져도equals()
로 최종 비교해 충돌된 객체 중 원하는 객체를 정확히 식별한다.⭐ equals()
와 hashCode()
는 함께 동작하도록 설계되어 항상 함께 재정의해야 한다!
해시 기반 컬렉션에서 equals()
값이 같아도 hashCode()
값이 다르면
논리적으로 같은 객체를 올바르게 인식하지 못해 다른 객체로 처리될 수 있다.
특징 | equals() | hashCode() |
---|---|---|
비교 기준 | 객체의 내용(값) 비교 | 객체의 해시 값(정수) 반환 |
역할 | 객체가 논리적으로 같은지 비교 | 객체를 빠르게 검색하거나 그룹화하기 위해 사용 |
사용 시점 | 컬렉션에서 객체의 논리적 동등성 확인 | 해시 기반 컬렉션에서 객체의 위치 계산 |
toString()
(🍀)Java의 Object 클래스에 정의된 메서드로, 객체를 사람이 읽을 수 있는 문자열 형태로 변환
모든 클래스는 기본적으로 toString()
메서드를 상속 받아 사용할 수 있음
기본 구현: 객체의 클래스 이름과 16진수 해시 코드 반환
public String toString() { return getClass().getName() + "@" + Integer.toHexString(hashCode()); }
출력 예시:
Person@1a2b3c4d
재정의: 기본 toString()
출력은 객체에 대한 유용한 정보를 제공하지 않기 때문에
사용자 정의 클래스에서 필요한 필드만 포함하여 재정의하는 것이 일반적
@Override public String toString() { return "Person{name='" + name + "', age=" + age + "}"; }
출력 예시:
Person{name='Alice', age=25}
주요 특징
System.out.println()
또는 문자열 연결 연산자(+
) 사용 시 자동으로 호출SLF4J
, Log4j
등)에 유용Gson
, Jackson
등의 JSON 라이브러리 활용 가능상수: 변하지 않는 값을 저장하는 변수 (🍀)
메모리 상에 값을 한 번 저장하면 수정할 수 없으며, final
키워드를 이용해 선언한다.
상수의 이름은 보통 대문자와 언더스코어(_)로 표기한다.
리터럴: 코드에서 직접 작성된 고정 값 (🍀)
상수나 변수에 저장되지 않은 상태로 코드에 직접 사용된다.
System.out.println("Hello, World!");
기본 자료형(Primitive Type): Java에서 값을 직접 저장할 때 사용하는 자료형
Stack 메모리 공간에 실제 값이 저장되며, 데이터의 크기와 타입이 고정되어 있다.
종류 | 표현식 | 메모리 크기 |
---|---|---|
논리형 | boolean | 1byte |
정수형 | byte | 1byte |
short | 2byte | |
int | 4byte | |
long | 8byte | |
실수형 | float | 4byte |
double | 8byte | |
문자형 | char | 2byte |
참조 자료형(Reference Type): 객체, 배열 등 복합 데이터를 다룰 때 사용하는 자료형
Stack 메모리 공간에 객체의 참조 값(메모리 주소)이 저장되고, 객체 자체는 Heap 메모리에 저장된다.
Java는 Call by Value(값에 의한 호출)만 지원한다.
기본 자료형: 값 자체를 복사하여 전달
메서드 내에서 값을 변경해도 원본 값에 영향을 주지 않는다.
참조 자료형: 객체의 참조 값을 복사하여 전달
객체의 내부 상태(필드 값 등)는 변경할 수 있지만,
참조 자체를 변경해도 원본 객체에는 영향을 주지 않는다.
🔍 Call by Reference: 메서드 호출 시 변수의 메모리 주소를 전달해
호출된 메서드가 원본 값을 직접 변경할 수 있음
객체를 바이트 스트림으로 변환하여
파일을 저장하거나 네트워크를 통해 전송할 수 있도록 하는 기술
java.io.Serializable
인터페이스를 구현해야 직렬화 가능