Java란?
Java는 1995년에 썬 마이크로시스템즈(Sun Microsystems)에서 제임스 고슬링(James Gosling)을 중심으로 한 개발 팀에 의해 만들어졌다.
플랫폼 독립적인 객체 지향 프로그래밍 언어로써 한 번 작성되면 다양한 운영체제에서 실행 할 수 있는게 특징이자 장점이다.
Java의 특징 및 단점
Java의 특징
- 플랫폼 독립성
- Write Once, Run Anywhere라는 모토처럼 한 번 작성한 Java 코드는 다양한 운영 체제에서 실행 할 수 있다. 소스 코드를 컴파일할 때 기계어가 아닌 바이트코드로 변환되기 때문에 가능해졌다.
- JVM이 각 운영체제에 맞춰 설치되기 때문에 동일한 바이트코드를 다양한 운영 체제에서 해석하고 실행할 수 있다.
- 객체 지향 프로그래밍
- Java는 객체 지향 언어로 클래스와 객체를 기반으로 설계된다.
- 상속, 다형성, 캡슐화, 추상화와 같은 OOP 개념을 통해 코드의 재사용성을 높이고 유지보수성을 강화하는 장점이 있다.
- 대규모 프로젝트에서 구조화된 코드 작성이 용이하다.
- 메모리 관리와 보완성
- GC(Garbage Collection)을 통해 개발자가 직접 메모리 해제를 하지 않아도 자동으로 메모리를 최적화 해 준다.
- JVM 자체의 보안 관리 기능 덕분에 애플리케이션이 불필요한 자원에 접근하는 것을 방지하여 보안성이 강화된다.
Java의 단점
- 속도
- JVM이 바이트코드를 해석하는 과정에서 C++같은 순수 컴파일 언어보다는 성능이 느릴 수 있다. → 하지만 JIT(Just In Time)컴파일러가 자주 실행되는 코드는 기계어로 바로 컴파일하여 성능을 개선한다.
- 메모리 사용량
- JVM이 실행되는 동안 필요한 메모리 자원이 비교적 크기 때문에 가벼운 환경에서는 성능이 저하될 수 있다.
- 긴 코드
- 객체 지향적인 특성으로 인해 간단한 작업도 클래스를 정의하고 메서드를 구현해야하기 때문에 다른 언어에 비해 코드가 길어질 수 있다.
Java의 실행 과정, JIT 컴파일 방식
- 소스코드 작성 및 컴파일
- Java 코드를 작성한 후 컴파일러가 바이트코드로 변환한다. 이 바이트코드는 .class파일로 저장된다.
- JVM에서 해석 및 실행
- 각 운영체제에 맞게 설치된 JVM은 이 바이트코드를 해석하고 기계어로 변환하여 실행한다. → 다양한 운영체제에서 동일한 바이트코드를 실행
- JIT 컴파일
- 자주 실행되는 코드 블록을 기계어로 변환하여 캐시에 저장한다.
- 반복 실행 시에 코드를 해석하는 시간을 줄이고 실행속도를 향상시킨다.
📌
소스코드(.java) → 컴파일러 → 바이트코드(.class) → JVM 해석 및 JIT 컴파일 → 프로그램 실행
Java 주요 버전 (Java 8, 11, 17)
Java는 정기적으로 업데이트 되고, 각 버전마다 주요 기능이 추가된다.
특히 Java 8, 11, 17은 현업에서 많이 사용되는 버전 LTS (Long-Term Support) 들이다.
→ LTS 버전이 일반적인 버전보다 더 오랜 기간 보안 패치, 버그 수정, 성능 개선 등의 업데이트가 제공되기 때문에 기업들이 안정적인 애플리케이션 환경을 유지할 수 있어 많이 선택한다.
- Java 8 :
- 람다 표현식과 스트림 API가 추가되어 함수형 프로그래밍 요소가 강화되었다.
- NullPointerException을 방지하기 위해 Optional 클래스가 도입되었습니다.
- Java 11 :
- 모듈 시스템과 HTTP 클라이언트 API가 추가되었다.
- Java 17 :
- 패턴 매칭 기능과 스위치 표현식 개선이 포함되었다.
JDK와 JRE의 차이점
- JDK : Java 애플리케이션을 개발할 때 필요한 컴파일러, 디버거, 그리고 JRE가 포함된 개발 툴킷
JDK : 개발 도구 + JRE
- JRE : JVM과 필요한 라이브러리만 포함되어 있어 Java 애플리케이션을 실행하는 데 필요한 환경만을 제공한다. ⇒ 개발 기능은 없고 실행 기능만 있음
JRE : JVM + 라이브러리
동일성(Identity)과 동등성(Equality)
Java에서는 객체를 비교할 때 동일성과 동등성의 개념이 중요하다.
- 동일성 (== 연산자) : 두 객체의 메모리 주소를 비교하여 같은 객체인지 확인
동일성 : == 사용, 메모리 주소 비교
- 동등성 (equals() 메서드) : 객체가 같은 값을 가지고 있는지 확인하고, 객체의 내용 자체를 비교
동등성 : equals() 사용, 객체의 내용 비교
equals()와 hashCode()의 관계
Java에서 객체의 동등성을 비교할 때 equals()와 hashCode()를 함께 재정의해야 한다.
→ HashMap이나 HashSet 같은 자료구조를 사용할 때 주로 사용된다.
- equals()는 객체의 값을 비교하고, hashCode()는 객체의 해시 코드를 반환한다.
- 두 객체가 같은 값이라면 동일한 해시 코드를 반환해야 자료구조 내에서 올바르게 검색될 수 있다.
- 찾고싶은 정보를 더 빨리 찾기 위해 hashcode()를 먼저 사용해서 고유 번호를 찾은 후 equals()로 진짜 값이 맞는지 한 번 더 확인한다.
toString() 메서드
- 객체를 사람이 읽기 쉬운 문자열 형태로 변환해 주는 역할
메인 메서드의 static 선언 이유
- Java 프로그램의 시작점인 메인 메서드 main은 프로그램이 실행될 때 JVM에 의해 먼저 호출된다.
- 프로그램을 실행할 때 클래스가 인스턴스화되지 않아도 main 메서드를 호출할 수 있어야 한다. static메서드는 클래스의 인스턴스 없이 호출이 가능하기 때문에 main에는 항상 static이 붙어있다 !!
상수(Constant)와 리터럴(Literal)
- 상수(Constant) :
- 한 번 설정한 뒤 값이 변하지 않는 변수
- final 키워드를 사용해 선언하고 주로 고정된 값을 정의할 때 사용
- ex. final int MAX_VALUE = 100;
- 리터럴(Literal) :
- 코드 내에서 고정된 값으로 직접 사용되는 값입니다.
- 숫자 10, 문자열 "Hello" 등이 리터럴이라고 할 수 있다.
- 현업에서는 하드코딩 한다고 불리운다.
Primitive Type과 Reference Type
- Primitive Type(기본형)
- 값을 직접 저장하는 자료형으로 int, float, boolean 등이 있다.
- 메모리에는 값 자체가 저장됩니다.
- Reference Type(참조형)
- 객체의 메모리 주소를 저장하는 자료형. 클래스 타입의 변수나 배열 등이 참조형이다.
- 변수는 실제 값을 가진 객체의 주소를 저장한다.
Java는 Call by Value일까, Call by Reference일까?
Java는 Call by Value 방식을 사용한다. 메서드 호출 시 인수의 값이 복사되어 전달된다.
하지만 참조형 변수가 전달될 때는 객체의 주소 값이 복사되어 전달되기 때문에 메서드 내부에서 상태를 변경할 수 있다. 이 때 변수 자체가 가리키는 객체의 주소는 변경되지 않는다.
Object-Oriented Programming (OOP, 객체 지향 프로그래밍)
오버로딩(Overloading)과 오버라이딩(Overriding)의 차이
- 오버로딩(Overloading)
- 같은 이름의 메서드를 매개변수의 개수나 타입을 다르게 정의하여 여러 번 선언하는 것.
- 컴파일 시점에서 어떤 메서드가 호출될지 결정됩니다.
- 오버라이딩(Overriding)
- 부모 클래스의 메서드를 자식 클래스에서 재정의
- 런타임 시점에서 어떤 메서드가 호출될지 결정되고, 다형성을 구현할 때 중요한 역할을 한다.
void print(String text) { ... }
void print(int number) { ... }
@Override
void print(String text) { ... }
다형성(Polymorphism)이란 무엇이고, 왜 필요할까요?
다형성이란 같은 인터페이스를 구현한 다양한 객체가 서로 다른 동작을 수행할 수 있도록 만드는 특성
다형성을 통해 코드의 유연성과 재사용성이 높아지고, 코드의 수정 없이도 다양한 객체를 쉽게 교체할 수 있다 !
상속(Inheritance)이란?
기존 클래스의 속성과 메서드를 물려받아 새로운 클래스를 만드는 것.
자식 클래스는 부모 클래스의 특성을 이어받아 확장하거나 변경할 수 있다.
코드의 재사용성과 확장성을 높여주는 장점이 있지만 상속 구조가 길어질수록 코드 간 결합도가 높아져서 유지보수가 어려워 질 수 있다.
상속과 조합(Composition)의 차이
상속(Inheritance)은 클래스 간에 “is-a” 관계를 나타내며, 부모 클래스의 특성을 자식 클래스가 상속받아 사용한다.
조합(Composition)은 클래스 간에 “has-a” 관계를 나타내며, 다른 클래스의 객체를 자신의 필드로 사용하여 기능을 조합한다.
- 상속은 클래스의 타입을 확장할 때 사용되고, 조합은 클래스의 기능을 결합할 때 사용된다.
instanceof 키워드란 무엇이며, 사용할 때의 문제점
nstanceof : 객체가 특정 클래스의 인스턴스인지 확인할 때 사용
if (obj instanceof String) {
System.out.println("obj는 String 타입입니다.");
}
- instanceof는 특정 클래스의 타입을 확인하여 동작을 결정하므로 코드가 특정 타입에 의존적이 되는 문제가 발생할 수 있다. → 객체 지향 프로그래밍에서 권장되는 다형성(Polymorphism) 개념과 충돌
인터페이스(Interface)와 추상 클래스(Abstract Class)
인터페이스란?
- 인터페이스는 추상 메서드(구현되지 않은 메서드)의 집합으로 클래스가 구현해야하는 기능을 명세한다.
- 인터페이스에 정의된 메서드는 모두 추상적이며 실제 구현은 없다.
- 다형성을 높이고, 코드의 일관성을 유지할 수 있다.
인터페이스와 추상클래스 비교
| 비교 항목 | 인터페이스 (Interface) | 추상 클래스 (Abstract Class) |
|---|
| 목적 | 기능을 명세하고 구현을 강제하여 다형성을 높임 | 공통 속성 및 기능을 공유하고, 확장을 위한 기반 제공 |
| 메서드 | 기본적으로 추상 메서드만 포함 (default 및 static 제외) | 추상 메서드와 일반 메서드 모두 포함 가능 |
| 필드 | 상수만 가능 (public static final이 자동으로 적용) | 일반 변수와 상수 모두 가능 |
| 다중 상속 | 여러 개의 인터페이스를 구현 가능 | 하나의 추상 클래스만 상속 가능 |
| 접근 제어자 | 모든 메서드와 필드는 기본적으로 public | 필드와 메서드에 접근 제어자 설정 가능 |
| 상속 관계 | "can do" 관계를 표현하며, 구현하는 클래스가 기능을 제공해야 함 | "is-a" 관계를 표현하며, 상속받는 클래스가 부모의 특성을 가짐 |
인터페이스와 추상 클래스의 사용 시점
- 인터페이스 사용 시점
- 여러 클래스가 공통 기능을 구현해야 할 때 (ex. Runnable, Comparable 인터페이스)
- 클래스가 다양한 기능을 혼합하여 구현해야 할 때 (다중 구현이 필요할 때)
- 특정 기능을 제공하는 클래스를 모아서 공통된 타입으로 처리할 때
- 추상 클래스 사용 시점
- 공통 속성이나 메서드를 공유하면서, 일부 기능만 자식 클래스에서 구현하도록 할 때
- “is-a” 관계를 명확히 나타낼 때 (ex. Vehicle 추상 클래스 → Car, Bike 클래스)
- 기본 동작을 정의하면서 일부만 자식 클래스가 오버라이드하도록 할 때
final 키워드란?
final 키워드는 Java에서 변경되지 않도록 고정하고 싶을때 사용되는 키워드
변수, 메서드, 클래스에 붙일 수 있고 각각 다른의미를 지닌다.
1. 변수에 final을 붙이는 경우
변수에 붙이면 한 번 값이 정해졌을때 절대로 바꿀 수 없게 된다. ⇒ 상수처럼 작동
final int MAX_SCORE = 100;
만약 바꾸려고 하면 컴파일 오류가 발생한다.
2. 메서드에 final을 붙이는 경우
메서드에 붙이면 이 메서드를 자식 클래스가 오버라이드하지 못한다. (재정의 X)
class Animal {
final void makeSound() {
System.out.println("소리나요.");
}
}
class Dog extends Animal {
}
final이 붙은 메서드는 그대로 사용해야 하며, 내용을 바꾸는 것이 불가능하다.
→ 메서드가 원래 의도대로 안전하게 사용되도록 보장할 수 있음
3. 클래스에 final을 붙이는 경우
클래스에 붙이면 그 클래스는 다른 클래스가 상속할 수 없게 된다.
→ 이 클래스가 마지막 클래스가 되는 것 !
final class Vehicle {
}
class Car extends Vehicle {
}
final 클래스는 더 이상 확장되지 않도록 보호되기 때문에, 클래스를 그대로 사용해야 하고, 자식 클래스를 만들 수 없다.