Java에 대하여 - 1

SUM·2025년 1월 1일

Java

목록 보기
1/5

1. 자바의 특징 7가지

(1) 객체 지향 언어

  • 절차 지향 언어와 다르게 하나의 기능을 객체형태로 만들어 객체들을 결합하여 하나의 프로그램을 만드는 것.
    위 내용은 컴퓨터로 예를 들자면 아래와 같다,
    - 컴퓨터 = 완성된 프로그램
    - 메모리, HDD, CPU등 = 객체
  • '자바'는 객체 지향 언어의 대표적인 언어이다.

(2) 인터프리터 언어

  • 자바는 컴파일 언어인 동시에 인터프리터 언어이다.
  • 자바는 텍스트 소스를 컴파일하여 클래스파일로 만든 다음 자바 런타임이 클래스 파일을 인터프리트 하면서 실행된다.

(3) 독립적인 플랫폼

  • 어떠한 운영체제라도 독립적으로 자바언어를 사용할 수 있다.
  • 그 이유는 JVM에 의해서 실행되기 때문

(4) 자동 메모리 관리

  • 자바는 개발자가 직접 메모리에 접근할 수 없으며 자바가 직접 관리한다.
  • 객체 생성시 자동적으로 메모리 영역을 찾아서 할당. 또한 사용하지 않는 객체를 제거시켜 준다.
  • C언어는 개발자가 직접 코드를 작성해야 했지만 자바는 이러한 작업을 자동으로 해주기 때문에 메모리 관리에 신경쓰지 않아도 된다.

(5) 멀티 스레드 지원

  • 하나의 프로그램 단위가 여러 스레드를 동시에 수행할 수 있다.
  • 운영체제마다 멀티 쓰레드를 이용하는 API가 다르나 자바의 경우는 자바 API를 사용하기 때문에 쉽게 구현 가능하다.

(6) 동적이다

  • 객체간의 상호 작용을 정의하기 때문에 필요하지 않는 객체는 생성되지 않고, 필요한 객체만 생성하여 사용한다.
  • 오류가 발생하면 발생한 오류의 클래스만 수정하면 되므로 전체를 수정할 필요가 없다. 즉 유지보수를 쉽고 빠르게 진행할 수 있다.

(7) 안전하고 강력하다

  • 모든 메모리 접근을 자바 시스템이 관리하기 때문에 시스템 붕괴의 우려가 없다.
  • 자바는 포인터 개념이 없고 유형 정의가 강고하여 실행 전에 클래스 파일을 이용한 프로그램 검사가 가능하다.

2. 자바의 단점

자바(Java)는 강력하고 널리 사용되는 프로그래밍 언어지만, 몇 가지 단점도 존재합니다. 아래는 자바의 주요 단점을 정리한 내용입니다:

  1. 성능
  • 자바는 JVM(Java Virtual Machine)에서 동작하는 인터프리터 기반 언어로, C/C++ 같은 네이티브 언어에 비해 성능이 느릴 수 있습니다.

  • 가비지 컬렉션(Garbage Collection)이 실행되는 동안 애플리케이션이 멈출 가능성(Stop-the-world)이 있어 성능 저하를 유발할 수 있습니다.

  1. 메모리 사용
  • 자바는 객체 지향 언어로, 객체 생성이 빈번하게 일어나며 이로 인해 메모리 사용량이 많아질 수 있습니다.

  • 자동 메모리 관리(Garbage Collection)가 장점이지만, 비효율적으로 작동할 경우 메모리 누수 문제가 발생할 수 있습니다.

  1. 복잡한 코드
  • 자바는 장황한(Syntax-heavy) 코드 스타일을 가지고 있어 간단한 작업도 비교적 긴 코드로 작성해야 하는 경우가 많습니다.

  • 예를 들어, 람다 표현식 도입 전에는 익명 클래스와 같은 복잡한 문법이 요구되었습니다.

  1. 플랫폼 의존성
  • 자바는 "Write Once, Run Anywhere(WORA)"를 표방하지만, 플랫폼 및 JVM 버전에 따라 예상치 못한 호환성 문제가 발생할 수 있습니다.

  • 특정 운영 체제나 환경에서 JVM 설정을 세부적으로 조정해야 할 때가 있습니다.

  1. 가비지 컬렉션의 비예측성
  • 가비지 컬렉션은 개발자가 직접 메모리 관리를 하지 않아도 되는 장점이 있지만, 특정 시점에 대규모로 실행되면 애플리케이션 성능에 영향을 줄 수 있습니다.
  1. 배포 크기
  • 자바 애플리케이션은 JVM이 필요하기 때문에 실행 환경에서 추가적인 설치가 필요합니다.

  • JAR(Java Archive) 파일 및 라이브러리가 포함된 프로젝트는 크기가 커질 수 있습니다.

3. 자바의 실행 과정

PC에 JDK가 설치되었다면 자바 언어로 작성한 소스 파일을 만들고 컴파일을 할 수 있다.
자바 소스파일 확장명은 .java이다.

소스파일(.java)은 javac(java compiler) 실행파일에 의해 컴파일되어 바이트코드(.class)파일이 된다. 바이트코드 파일을 특정 운영체제가 이해하는 기계어로 번역하고 실행시키는 명령어는 java이다. java 명령어는 JDK와 함께 설치된 자바 가상 머신을 구동시켜 바이트코드 파일을 완전한 기계어로 번역하고 실행시킨다.

바이트코드(Bytecode) 파일은 운영체제와 상관없이 모두 동일한 내용으로 생성되지만, 자바 가상 머신(JVM, Java Virtual Machine)은 운영체제에서 이해하는 기계어로 번역해야 하므로 운영체제별로 다르게 설치된다. 그래서 운영체제별로 설치하는 JDK가 다른 것이다.

Java Bytecode에 대해서 알아보자

4. 자바 가상 머신의 구성

자바 바이트 코드를 실행시키기 위한 가상의 기계인 자바 가상 머신은 다음과 같은 구성을 가지고 있다.

(1) 자바 인터프리터(interpreter)

자바 컴파일러에 의해 변환된 자바 바이트 코드를 읽고 해석하는 역할을 함

(2) 클래스 로더(class loader)

자바는 동적으로 클래스를 읽어오므로, 프로그램이 실행 중인 런타임에서야 모든 코드가 자바 가상 머신과 연결됨. 이렇게 동적으로 클래스를 로딩해주는 역할을 하는 것이 바로 클래스 로더임

(3) JIT 컴파일러(Just-In-Time compiler)

자바 컬파일러가 프로그램이 실행 중인 런타임에 자바 컴파일러가 생성한 자바 바이트 코드를 기계어로 변환하는데 사용하는 것임.

동적 번역(dynamic translation)이라고도 불리는 이 기법은 프로그램의 실행 속도를 향상시키기 위해 개발됨

(4) 가비지 컬렉터(Garbage Collector)

자바의 메모리 관리 방법 중의 하나로 JVM(자바 가상 머신)의 Heap 영역에서 동적으로 할당했던 메모리 중 필요 없게 된 메모리 객체(garbage)를 모아 주기적으로 제거하는 프로세스를 말함

참고 : Garbage Collector란? - 정리 예정

5. 컴파일의 두 가지 방식

(1) 인터프리터 방식

  • 소스 코드를 한 줄씩(또는 한 문장씩) 해석하고, 바로 실행한다.
  • 실행해봐야 오류를 확인할 수 있어, 오류 발생 시 해당 시점에 프로그램이 중지된다.
  • 일반적으로 컴파일된 코드보다 실행 속도가 느리다.

(2) 컴파일러 방식

  • 프로그램 전체를 스캔해서 이를 모두 기계어로 번역한다.
  • 초기 스캔 시간이 오래 걸린다.
  • 전체 코드를 검사한 후 오류 메시지를 생성해서, 실행 전에 오류를 발견할 수 있다.

(3) JIT(Just-In-Time) 컴파일러

  • JIT 컴파일러는 두 가지의 방식을 혼합한 방식으로 생각할 수 있는데, 실행 시점에서 인터프리트 방식으로 기계어 코드를 생성하면서 그 코드를 캐싱하여, 같은 함수가 여러 번 불릴 때 매번 기계어 코드를 생성하는 것을 방지한다.
  • 한 번 기계어로 컴파일된 코드는 이후에는 인터프리터가 줄단위로 해석할 필요 없이 빠르게 실행 가능하다.

(4) 자바(Java)에서의 실행 엔진(Execution Engine)

  • 자바 소스 코드(.java)는 먼저 바이트 코드(.class) 로 컴파일된다.
  • 이후 JVM(자바 가상 머신) 이 메모리에 올라온 바이트 코드를 해석하고 실행한다.
    • 인터프리터: 바이트 코드를 한 줄씩 읽어 해석해 실행한다.
    • JIT 컴파일러: 자주 실행되는 부분(핫스팟)을 기계어로 변환해, 이후에는 인터프리팅 없이 바로 실행되도록 한다.

“정확히는 JIT 컴파일러가 ‘번역 안 할래’가 아니라, 이미 자주 실행되는 부분을 미리 기계어로 컴파일해두므로 인터프리터가 해당 부분을 굳이 한 줄씩 해석하지 않아도 된다는 뜻이다.”

(4) 정리

  • 컴파일러 방식: 전체 코드 분석 → 한 번에 기계어 변환 → 빠른 실행, 하지만 초기 컴파일 시간이 길고, 오류는 컴파일 후에 한꺼번에 확인.
  • 인터프리터 방식: 코드를 줄단위로 해석하며 즉시 실행 → 실행해봐야 오류 확인 가능 → 전체적으로 실행 속도가 느린 편.
  • JIT 컴파일: 인터프리터 방식과 함께 사용 → 실행 중 자주 사용되는 코드를 골라 기계어로 컴파일하여 전체 실행 속도를 향상.

6. Java 8, 11, 17, 21 버전

(1) Java 8

  • 2014년 출시, LTS 버전(~2030.12 지원)

  • 대규모 릴리즈, Lambda, Stream API 제공

  • Optional, 새로운 날짜,시간 API 제공 (ex: LocalDateTime)

  • Oracle이 Java를 인수한 후 첫번째 LTS 출시 버전

(2) Java 11

  • 2018년 출시, LTS 버전(~2032.01 지원)

  • String과 File 기능 향상

  • String: isBlank(), strip() File: writeString(), readString()
    var 키워드 사용 가능

  • Open JDK와 Oracle JDK가 통합

  • G1 GC가 기본 GC로 설정

(3) Java 17

  • 2021년 출시, LTS 버전(~2029.09 지원)

  • Spring Boot 3.x.x 버전은 JDK 17 이상 부터 지원

  • Switch에 대한 패턴 매칭 (Preview), recode class도입, 텍스트 블록 기능(""" 사용)을 추가해 코드를 간결하고 효율적으로 작성할 수 있도록 도움

(4) Java 21

  • 가상 스레드: Java 플랫폼에 경량 가상 스레드를 도입

  • 가상 스레드의 도입으로 몇 개의 운영 체제 스레드만 사용하여 수백만 개의 가상 스레드를 실행하는 것이 가능해짐. 기존 Java 코드를 수정할 필요 X

  • UTF-8이 기본값으로 사용

Java21을 사용한 이유

  • Virtual Thread
    : 경량 스레드를 사용하려면 예전엔 코틀린을 사용해야 했지만, 21버전에서는 가상 스레드로 경량 스레드를 구현할 수 있었기 때문에 사용함 : 기술적인 니즈
  • Spring Boot 3.0 부터는 자바 17 이상을 지원

참고 : Java의 미래, Virtual Thread

7. JRE(Java Runtime Environment)와 JDK(Java Development Kit)의 차이

JRE란 번역하면 자바 실행환경으로 자바 프로그램을 실행하는데 필요한 것이다.

즉, 자바 프로그램을 실행시키는데는 문제가 없지만 자바 프로그램을 코딩할 때 jdk가 아니라 jre를 사용하면 문제점이 생길 수 있다.

예를 들어 컴파일이 정상적으로 되지 않을 수도 있다.

JDK(Java Development Kit)란?
번역하면 자바 개발 키트이다. 간단하게 설명하면 자바를 개발하는데 필요한 기능들이 들어간 것이다.

여기에는 물론 자바를 실행하는데 필요한 jre도 포함되어 있어서 jdk를 다운로드 받으면 jre 또한 포함되어 있다.

자바 프로그램 개발을 위해서는 바로 이 jdk를 다운로드 받아 자바 기능을 사용하고 컴파일 해야하는 것이다.


8. 동일성과 동등성

  • 동일성은 두 객체가 같은 메모리 주소값을 가지고 있다는 것을 의미합니다.

  • 동등성은 두 객체가 논리적으로 같은 내용(값)을 가지고 있다는 것을 의미합니다.

  • 두 객체가 동일하다면 당연히 같은 주소를 참조하므로 동등한 내용(값, 동등성)을 가지지만, 두 객체가 동등하다면 같은 내용(값)을 가지고 있다 하더라도 메모리 주소는 다를 수 있기 때문에 동일하지 않을 수 있습니다.

9. equals(), ==

int와 boolean과 같은 Primitive Type의 비교는 ==이라는 연산자를 사용하여 비교한다. '==' 연산자는 객체의 메모리 주소를 비교하기 때문에 두 객체의 참조가 같은지, 즉 동일한 객체를 가리키고 있는지를 확인한다.

하지만 String처럼 Reference Type의 값을 비교할때는 == 대신 equals()라는 메소드를 사용해 비교한다. 동등성의 비교를 위해서는 'equals()' 메소드를 사용해야하기 때문이다. 왜냐하면 'equals()' 메소드는 객체의 내용이나 상태를 기반으로 두 객체가 같은지를 판단하기 때문이다.

(다만 equals()가 재정의되지 않은 Object 등은 동등성 비교가 되지 않고, equals()의 기본 작동 방식인 참조 비교를 할 수 있음. String은 재정의가 되어 있기 때문에 동등성 비교 가능)

참고 : 자바 문자열 비교 == equals() 차이점

10. HashCode

(1) HashCode란?

  • 정의
    자바의 Object 클래스가 제공하는 hashCode() 메서드는 객체를 구별하기 위한 정수 해시 값(hash code)을 반환한다.

  • 용도
    주로 HashMap, HashSet, Hashtable해시 기반 자료구조에서 객체를 빠르게 조회하거나 저장할 때 사용한다.
    객체가 저장될 위치(버킷)를 결정하는 데 활용되므로, 같은 객체(동등한 객체)는 반드시 동일한 해시 코드를 가져야 데이터가 정상적으로 관리된다.

  • 특징

    • 객체가 논리적으로 같다면(equals()가 true를 반환한다면) 반드시 동일한 해시 코드를 반환하도록 구현해야 한다.
    • 반대로, 해시 코드가 같다고 해서 무조건 같은 객체(동등 객체)라고 단정할 수는 없다(충돌 가능).

(2) equals()와 hashCode()의 차이점

  1. equals() 메서드

    • 용도: 두 객체가 “논리적으로 같은 객체”인지 비교
    • 기본 구현: Object의 기본 equals()는 “동일한 참조(주소)인가?”만 확인(즉, == 동작과 유사)
    • 재정의 시점: 해당 객체의 “내용”(필드 값 등)이 같을 경우 ‘동등하다’라고 판단해야 하는 상황에서 재정의
  2. hashCode() 메서드

    • 용도: 객체를 식별할 수 있는 정수 값을 반환 → 해시 기반 자료구조에서 위치 계산에 활용
    • 기본 구현: 일반적으로 JVM에 의한 객체의 메모리 주소를 기반으로 해시 값을 생성
    • 재정의 시점: equals()를 재정의할 때, 반드시 해시 기반 자료구조에 사용할 수 있도록 hashCode()같이 재정의해야 함
  3. 왜 함께 재정의해야 하는가?

    • equals()에서 “동등”이라 판별된 객체는 동일한 hashCode()를 가져야, HashMap/HashSet 등에서 정상적으로 동작함
    • 이를 어기면, 논리적으로 같은 객체라도 해시 코드가 달라 서로 다른 위치에 저장되는 등 예상치 못한 오류가 발생할 수 있음

(3) 정리

  • Object 클래스: 자바에서 모든 클래스의 최상위 부모로, equals(), hashCode(), toString() 등의 기본 메서드를 제공한다.
  • equals(): 객체의 논리적 동등성 비교
    • 재정의 시, “어떤 조건을 만족하면 두 객체가 같다고 볼 것인지”를 명확히 기술해야 한다.
  • hashCode(): 해시 코드(정수) 반환
    • 해시 기반 자료구조에서 중요한 역할을 수행하며, equals()와 논리적으로 일관되어야 한다.
  • 결론:
    • equals()hashCode()는 객체 동등성 관리와 해시 기반 자료구조의 올바른 동작을 위해 함께 재정의한다.
    • 자바 개발자는 이 두 메서드의 작동 원리를 이해하고, 필요한 경우 정확하게 구현해야 한다. 이를 제대로 구현하지 않으면 의도치 않은 버그가 발생할 수 있다.

이처럼 equals()hashCode()를 어떻게 정의하느냐에 따라, 객체를 어떻게 “같다”고 판단하고 어떻게 해시 자료구조에 저장할지가 결정된다. 이는 자바 프로그래밍에서 객체의 신뢰성 있는 비교와 효율적 자료구조 활용에 필수적이다.

참고 : 자바의 Object 클래스와 equals, hashCode 메서드의 중요성

11. toString()

toString() 메소드는 해당 인스턴스에 대한 정보를 문자열로 반환한다. 이 메서드는 인스턴스에 대한 정보를 문자열로 제공할 목적으로 정의되어 있는 것이다.

이때 반환되는 문자열은 클래스 이름과 함께 구분자로 @가 사용되며, 그 뒤로 16진수 해시 코드(hash code)가 추가된다. 해시 코드 값은 인스턴스의 주소를 해싱하여 변환한 값으로, 고유 숫자로서 인스턴스마다 모두 다르게 반환된다.

실제로 toString() 메서드 내부를 본다면 다음과 같이 구현되어있다.

toString() 오버라이딩

하지만 만일 객체의 이름이나 주소값이 아닌 객체의 고유 정보를 출력하고 싶을 때가 있다.

예를 들어 다음 Person 객체를 출력하면 원론적인 값이 아닌, Person의 이름이나 나이 같은 고유 정보를  출력하고 싶을 때 바로 오버라이딩(Overriding)을 통해 toString() 메소드를 재정의 해주면 된다.

메서드 오버라이딩을 할때 바로 뒤에서 배울 접근제어자 지정에 대해 조심해야 할 점이 있다.바로 부모 메서드에 정의된 접근제어자의 범위보다 낮은 범위의 제어자를 지정할수 없다는 것인데, Object 클래스의 toString 메서드의 접근제어자는 protected void toString() 라고 정의되어있어서, 이것을 오버라이딩 했을때 같은 protected 나 public 으로 설정해야 오버라이딩이 된다. 만일 private 나 default 제어자로 오버라이딩을 하려고 하면 컴파일 에러가 생긴다.

배열 요소 출력을 위한 재정의

예를들어 배열에 다음 MyInt 라는 객체 자료를 저장해서 출력하고 싶다고 하자.

MyInt 클래스는 정수를 인자로 받아 100을 곱한 수를 저장하는 특별한 객체 자료형이다. 이를 배열에 적재하고 배열을 출력해보자.

import java.util.Arrays;

class MyInt {
    final int num;

    MyInt(int num) {
        this.num = num * 100;
    }
}

public class Main {
    public static void main(String[] args) {

        Object[] arr = new Object[5];
        arr[0] = new MyInt(0);
        arr[1] = new MyInt(1);
        arr[2] = new MyInt(2);
        arr[3] = new MyInt(3);
        arr[4] = new MyInt(4);

        System.out.println(Arrays.toString(arr));
    }
}

우리는 [100, 200, 300, 400, 500] 으로 출력되기를 기대했지만, 엉뚱하게 객체 정보값이 출력되어 버렸다.
배열을 스트링으로 출력하기 위해 Arrays.toString() 메서드를 사용했어도 말이다.

이런식으로 출력된 이유는 Arrays.toString() 메서드는 배열 자체를 스트링화 한것이고, 배열 내의 각각의 원소들은 스트링화 하지 않기 때문에 발생한 현상이다.

따라서 배열 요소들도 정수값으로 출력되게 하고 싶다면, 그 객체 요소의 클래스 정의문에 toString() 을 재정의해야 한다.

즉, 핵심은 배열의 원소들이 int형 같은 primitive 타입이 아닌 객체와 같은 reference 타입일 경우, 배열을 출력할때 안에 있는 값을 출력하기 위해선 반드시 일일히 각 객체의 클래스마다 toString() 을 재정의해야 된다는 소리이다.

import java.util.Arrays;

class MyInt {
    final int num;

    MyInt(int num) {
        this.num = num * 100;
    }

    @Override
    public String toString() {
        return Integer.toString(num);
    }
}

public class Main {
    public static void main(String[] args) {

        Object[] arr = new Object[5];
        arr[0] = new MyInt(1);
        arr[1] = new MyInt(2);
        arr[2] = new MyInt(3);
        arr[3] = new MyInt(4);
        arr[4] = new MyInt(5);

        System.out.println(Arrays.toString(arr));
    }
}

반면, Integer 클래스에서는 toString 메서드가 미리 오버라이딩 되어 있기 때문에 별다른 작업없이 배열을 출력하면 Wrapper 객체가 문자열로 변환된 것이다.

참고 : 자바 toString 오버라이딩 - 완벽 이해하기

12. Primitive Type(원시 타입)과 Reference Type(참조 타입)

위에서 이야기한 Wrapper(Primitive type을 객체 형태로 다룰 수 있게 하는 클래스)는 Reference Type에 속한다. 그럼 과연 Primitive와 Reference의 차이가 무엇일까?

우선 둘은 데이터타입의 유형을 의미한다.

  • 원시 타입 은 정수, 실수, 문자, 논리 리터럴 등의 실제 데이터 값을 저장하는 타입

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

반드시 사용하기 전에 선언되어야하며, 자료형의 길이는 운영체제에 독립적이며 변하지 않는다. stack 메모리에 저장된다.

  • 참조 타입은 객체(Object)를 참조(주소를 저장) 하는 타입으로 메모리 번지 값을 통해 객체를 참조하는 타입

    Integer, Long, Double, Float, Boolean, Byte, Short, Char, 배열, 열거, 클래스, 인터페이스

즉, Java에서 최상위 클래스인 java.lang.Object 클래스를 상속하는 모든 클래스들을 말한다.

참조타입의 객체는 실제 데이터는 힙영역에 참조타입 변수는 스택영역에서 실제 객체의 주소를 저장합니다.
해서 객체 사용시마다 스택 영역에서 객체의 주소를 불러와 사용하게 됩니다.

이후 Garbage Collector가 돌면서 메모리를 해제한다.

13. Java는 Call by Value일까 아님 Call by Reference일까?

자바는 엄밀히 말해 ‘Call by Value’만을 사용한다.

Call by Value는 함수가 호출될 때 값을 전달해주고, Call by Reference는 함수가 호출될 때 주소를 전달해준다.

그래서 Call by Value는 함수를 호출할 때 전달된 값의 복사본을 생성하기 때문에 값을 어떻게 지지고 볶든 local value의 성격을 지니기 때문에 함수가 종료한 후에 값이 변화되지 않지만, Call by Reference는 주소를 바로 참조하기 때문에 호출한 함수에서의 변경이 함수가 종료된 이후에도 남는다.

그런데 JAVA는 int, float, double등 primitive type에 대해서는 Call by Value이고, array나 class instance등은 Call by Reference로 작동한다라고들 한다. 이게 무슨 이야기인지, 아래 예제를 보도록 하자.

public class CallByValueTest {
    public static void main(String args[]) {
        Obj o1 = new Obj(1);
        Obj o2 = new Obj(2);
        System.out.println("Before ==> " + o1.value + "," + o2.value);  // 1, 2
        changeValue(o1, o2);
        System.out.println("After  ==> " + o1.value + "," + o2.value);  // 3, 2
    }
    public static void changeValue(Obj param1, Obj param2) {
        // param1, param2 는 각각 o1, o2가 들고 있던 "참조값"의 복사본
        param1.value = 3;   // 복사된 참조를 통해 실제 Obj(힙)에 가서 value 변경
        param2 = param1;    // 복사된 참조들끼리의 재할당이므로, 메서드 밖의 o2엔 영향 없음
    }
    static class Obj {
        int value;
        Obj(int i) { this.value = i; }
    }
}

  • param2 = param1;로 인해 메서드 내부에서는 param2param1과 같은 객체를 가리키게 되었지만,
    메서드가 끝나는 순간 로컬 변수 param1/param2는 사라진다.
  • 호출한 쪽o2는 여전히 new Obj(2)를 가리키고 있으므로 o2.value는 2가 남는다.

이처럼 참조 타입도 ‘참조(주소)의 값’을 복사해 전달하므로, 메서드 안에서 재할당해도 원래의 참조 변수에는 영향이 없다.

왜 헷갈릴까?

  1. 기본 타입(int, float, double 등)을 메서드에 넘길 경우:

    • 값 자체(숫자 5, 10, 3.14 등)가 복사되어 넘어간다.
    • 메서드 안에서 그 복사본을 바꿔도 원본에는 영향을 주지 않는다.
  2. 참조 타입(배열, 클래스 인스턴스 등)을 메서드에 넘길 경우:

    • 객체가 넘어가는 것이 아니라, 객체를 가리키는 참조(주소) 자체가 “값”으로 복사되어 넘어간다.
    • 메서드 안에서 그 복사된 참조를 통해 실제 객체(힙 영역에 있는 인스턴스)의 속성(필드)을 바꾸면, 당연히 원본 객체도 바뀐 것처럼 보인다(실제로 같은 객체를 가리키므로).
    • 하지만 메서드 안에서 “참조 변수” 자체를 새로 할당하거나 변경해도, 호출한 쪽의 참조 변수는 전혀 바뀌지 않는다. (복사본의 참조만 바뀔 뿐)

이 점 때문에,

  • “어? 객체(필드)가 바뀌네? -> Call by Reference인가?” 라고 착각하기 쉽다.
  • 사실은 “같은 객체를 가리키는 참조값으로 복사했기 때문”이지, 참조 자체가 메서드 밖 변수와 ‘공유’되는 것은 아니다.

참고1 : 자바의 Call by Value와 Call by Reference 이해하기

참고2 : JAVA는 Call by Value일까 Call By Reference일까?

14. 상수(Constant)와 리터럴(Literal)

상수(Constant)
1. final 키워드를 붙인 변수로, 한 번 설정한 후에는 값을 변경할 수 없다.
2. 선언과 동시에 초기화해야 하며, 주로 대문자로 이름을 짓는다.
3. 의미 있는 이름을 붙여 코드 가독성과 유지보수성을 높인다.
4. 예시:

final int MAX_SPEED = 100;
// MAX_SPEED = 200; // 컴파일 에러 (상수 값은 변경 불가)

리터럴(Literal)
변하지 않는 데이터 자체를 의미, 예: 10, 3.14, "JAVA".

예시:

	int number = 10;       // 정수 리터럴
   long bigNumber = 10L;  // long 리터럴
   float pi = 3.14f;      // float 리터럴
   String text = "JAVA";  // 문자열 리터럴
   char ch = 'A';         // 문자 리터럴

참고 : 상수와 리터럴 ( constant 와 literal ) 이란?


15. 자바의 메인메소드(public static void main(String[] args))

(1) public

  • 어느 곳에서든 접근할 수 있도록 설정한다.
  • 자바 애플리케이션이 실행될 때, 외부에서 이 메서드를 찾아 진입점으로 사용한다.

(2) static

  1. 클래스 로딩 시점에 초기화

    • static 영역은 프로그램 시작 시 클래스가 로딩될 때 이미 메모리에 적재된다.
    • 따라서 static 멤버(필드·메서드)는 객체 없이도 바로 사용 가능하다.
  2. 객체 생성 없이 호출 가능

    • static 메서드는 클래스명만으로 호출할 수 있다.
    • 예: MyClass.myStaticMethod();
    • main 메서드도 이 원리로, 자바 애플리케이션 시작 시점에 객체 없이 바로 호출된다.
  3. 인스턴스 멤버와 구분

    • static 메서드 내부에서는 인스턴스 멤버(필드나 메서드)에 직접 접근할 수 없다.
    • 모든 인스턴스가 공유하므로, 동시성 문제 등 주의가 필요하다.

(3) void

  • 반환값이 없다는 뜻이다.
  • 메서드 실행이 끝난 뒤, 호출한 쪽에 어떤 값도 돌려주지 않는다.

(4) main(String[] args)

  • 자바 애플리케이션이 시작될 때 가장 먼저 호출되는 메서드다.
  • String[] args는 문자열 배열을 의미한다.
  • 이 배열에는 커맨드 라인에서 전달된 여러 문자열이 담긴다(인덱스별로 구분된다).
  • 변수명 args는 관례적 표현이며, 다른 이름으로 변경해도 무방하다.

이로써 public static void main(String[] args)는 자바 프로그램의 진입점이자 시작점이며, 어디서든 접근 가능하고, 객체 없이 호출되며, 값을 반환하지 않는다는 뜻을 담고 있다.

참조 : 메인메소드 public static void main(String[] args) 를 이해하자

16. 직렬화(Serialization)

Java에서 직렬화(Serialization)란 자바의 객체를 바이트의 배열로 변환하여 파일, 메모리, 데이타베이스 등을 통해서 스트림(송수신)이 가능하도록 하는 것을 의미한다.

즉, Java 시스템 내부에서 사용되는 객체 또는 데이터를 외부의 Java 시스템에서도 사용할 수 있도록 바이트(byte) 형태로 데이터 변환하는 것을 뜻한다.

물론, Java 시스템간의 데이터 교환을 위해서 XML, JSON 과 같은 직렬화를 사용해도 되지만
Java Serializable을 사용하는 가장 큰 이유는 개발자의 편의를 위해서이다.

복잡한 데이터 구조를 가진 클래스의 객체라도 직렬화 기본 조건만 지키면 큰 작업 없이 바로 직렬화/역직렬화를 가능할 뿐만 아니라 Data Type이 자동으로 맞춰지기 때문에 관련 부분에 대해 큰 신경을 쓰지 않아도 되기 때문이다.

Java에선 직렬화를 위해 Serializable이라는 Marker interface를 지원하고 있다.

 package java.io;
 
 public interface Serializable {
 }

참고 : Serialize에 좀 더 깊이 알아보자


추가 참고

  • 이것이 자바다
profile
백엔드 개발자 SUM입니다.

0개의 댓글