Home Java Overview
Post
Cancel

Java Overview

Java

목차

JVM과 bytecode

Java는 JVM(Java Virtual Machine) 위에서 실행되는 언어다. Java 소스 코드는 바로 운영체제가 실행하는 기계어가 아니라 bytecode로 컴파일된다.

1
2
3
4
5
Java source
-> javac
-> bytecode(.class)
-> JVM
-> OS와 CPU

JVM은 운영체제 위에서 실행되는 런타임이다. 그래서 Java 프로그램은 JVM이 설치된 여러 운영체제에서 같은 bytecode를 실행할 수 있다.

1
2
3
4
Windows JVM
Linux JVM
macOS JVM
-> 같은 .class 실행 가능

JVM은 단순 실행기만이 아니다. 메모리 관리, garbage collection, class loading, JIT compilation 같은 기능을 제공한다.

1
2
3
class loader  class 파일 로딩
JIT compiler  자주 실행되는 bytecode를 native code로 최적화
GC            더 이상 참조되지 않는 객체 메모리 회수

class와 object

class는 객체를 만들기 위한 설계도이고, object는 그 설계도로 만들어진 실제 값이다.

1
2
3
4
5
6
7
class User {
  String name;

  User(String name) {
    this.name = name;
  }
}
1
User user = new User("shin");

위 코드에서 User는 class이고, new User("shin")으로 만들어진 값은 object다.

Java의 타입은 크게 primitive type과 reference type으로 나눌 수 있다.

1
2
primitive type  int, long, boolean 같은 값 자체
reference type  객체를 가리키는 참조

예를 들어 int x = 10은 값 자체를 다루고, User user는 객체를 직접 담는 것이 아니라 객체를 가리키는 참조를 담는다.

1
user variable -> User object

이 차이는 null, 메모리, equality를 이해할 때 중요하다.

interface와 inheritance

inheritance는 부모 class의 속성과 동작을 자식 class가 물려받는 것이다. interface는 어떤 기능을 제공해야 하는지 약속하는 타입이다.

1
2
3
interface Repository {
  void save(String value);
}
1
2
3
4
5
class MemoryRepository implements Repository {
  public void save(String value) {
    System.out.println(value);
  }
}

interface가 필요한 이유는 구현을 바꿔도 사용하는 쪽 코드를 안정적으로 유지하기 위해서다.

1
2
Service -> Repository interface -> MemoryRepository
                              -> DatabaseRepository

상속은 코드 재사용에 유용하지만 부모-자식 결합을 강하게 만든다. Java에서는 구현 상속보다 interface를 통한 역할 분리를 더 자주 권장한다.

1
2
inheritance  is-a 관계와 코드 재사용
interface    역할과 계약 정의

collection

collection은 여러 값을 담는 자료구조다. Java에서 자주 쓰는 collection은 List, Set, Map이다.

1
2
3
List  순서가 있고 중복 허용
Set   중복을 허용하지 않음
Map   key-value 저장

예시는 다음과 같다.

1
2
3
List<String> names = List.of("kim", "lee", "kim");
Set<String> uniqueNames = Set.of("kim", "lee");
Map<Long, String> users = Map.of(1L, "shin");

collection을 고를 때는 조회 방식과 중복 허용 여부를 생각해야 한다.

1
2
3
순서가 필요한가?
중복을 허용하는가?
key로 빠르게 찾을 것인가?

예를 들어 ID로 사용자를 찾는 구조라면 List<User>보다 Map<Long, User>가 더 적절할 수 있다.

exception

exception은 프로그램 실행 중 발생한 예외 상황을 표현하는 객체다.

1
2
3
4
5
try {
  int result = 10 / 0;
} catch (ArithmeticException e) {
  System.out.println("cannot divide by zero");
}

Java exception은 checked exception과 unchecked exception으로 나눌 수 있다.

1
2
checked exception    컴파일러가 처리 여부를 요구
unchecked exception  RuntimeException 계열, 컴파일러가 강제하지 않음

checked exception의 장점은 호출자가 실패 가능성을 명시적으로 다루게 한다는 점이다. 단점은 코드가 장황해지고, 의미 없이 throws가 전파될 수 있다는 점이다.

예외는 정상적인 조건 분기용으로 남용하면 안 된다. 예외는 보통 파일 없음, 네트워크 실패, 잘못된 상태처럼 예외적인 흐름을 표현하는 데 적합하다.

concurrency

concurrency는 여러 작업을 같은 시간대에 진행하는 것처럼 다루는 개념이다. Java에서는 Thread, ExecutorService, CompletableFuture, synchronized, lock, atomic class 등을 사용한다.

1
2
3
4
Thread thread = new Thread(() -> {
  System.out.println("worker");
});
thread.start();

동시성에서 가장 중요한 문제는 공유 상태다. 여러 스레드가 같은 값을 동시에 수정하면 race condition이 생길 수 있다.

1
2
3
4
thread A -> count 읽기
thread B -> count 읽기
thread A -> count + 1 저장
thread B -> count + 1 저장

이 경우 두 번 증가해야 하는 값이 한 번만 증가할 수 있다. 그래서 synchronized나 atomic type을 사용한다.

1
2
AtomicInteger count = new AtomicInteger(0);
count.incrementAndGet();

Java 동시성은 “스레드를 많이 만들면 빠르다”가 아니라, 작업 특성과 공유 상태를 이해하고 안전하게 조정하는 문제다.

This post is licensed under CC BY 4.0 by the author.