본문 바로가기
Everyday Study

2024.07.01 (월) { 메서드와필드, 신택틱슈가, 이터레이터, ArrayList, ArrayCopy, 런타임에리어, 증감연산자, 엔트리포인트, 콜스택, 클래스로더 ... }

by xogns93 2024. 7. 1.

Syntactic Sugar  (신택틱 슈가)

프로그래밍 언어에서 특정 기능이나 작업을 더 쉽게 읽고 쓸 수 있게 하는 문법적 구조를 의미합니다. 이러한 구조는 본질적으로 새로운 기능을 추가하지 않지만, 코드의 가독성을 높이고 더 간결하게 작성할 수 있도록 도와줍니다. 즉, 프로그래밍 언어의 편의성을 높이기 위해 제공되는 문법적 편의 장치입니다.

 

자바의 향상된 for 루프

// 전통적인 for 루프
int[] numbers = {1, 2, 3, 4, 5};
for (int i = 0; i < numbers.length; i++) {
    System.out.println(numbers[i]);
}

// 향상된 for 루프 (신택틱 슈가)
for (int number : numbers) {
    System.out.println(number);
}

Iterator (이터레이터)

자바 컬렉션 프레임워크에서 컬렉션 요소를 순차적으로 접근하는 방법을 제공하는 인터페이스입니다. 이를 통해 컬렉션의 요소를 반복(iterate)하고, 필요에 따라 요소를 제거할 수 있습니다. Iterator는 주로 Collection 인터페이스를 구현한 클래스들(예: ArrayList, HashSet)과 함께 사용됩니다.

 

Iterator 인터페이스의 메서드

Iterator 인터페이스는 다음과 같은 주요 메서드를 제공합니다:

  1. hasNext(): 컬렉션에 다음 요소가 있으면 true를 반환하고, 없으면 false를 반환합니다.
  2. next(): 다음 요소를 반환합니다. 호출하기 전에 반드시 hasNext()를 사용하여 다음 요소가 있는지 확인해야 합니다.
  3. remove(): 현재 요소를 컬렉션에서 제거합니다. 이 메서드는 next()를 호출한 이후에만 호출할 수 있습니다.

 

특징 및 주의사항

  • 일회성: Iterator는 일회용입니다. 한 번 사용한 후에는 재사용할 수 없으며, 새로운 Iterator를 생성해야 합니다.
  • 구조적 수정: Iterator를 사용하여 컬렉션을 순회하는 동안 컬렉션을 직접 수정하면 ConcurrentModificationException이 발생할 수 있습니다. Iterator의 remove() 메서드를 사용하여 안전하게 요소를 제거할 수 있습니다.
  • Fail-Fast: 대부분의 컬렉션 클래스에서 제공하는 Iterator는 fail-fast 동작을 합니다. 즉, 컬렉션이 구조적으로 수정되면(요소 추가/제거) Iterator가 즉시 실패하고 예외를 던집니다.

 


ArrayList

자바의 표준 라이브러리에서 제공하는 동적 배열(dynamic array) 구현 클래스입니다. ArrayList는 크기가 동적으로 조정되며, 요소의 추가와 제거가 간편한 배열을 제공합니다. 자바의 java.util 패키지에 포함되어 있으며, List 인터페이스를 구현합니다.

 

특징

  • 동적 크기 조정: ArrayList는 요소가 추가되거나 제거됨에 따라 크기가 자동으로 조정됩니다.
  • 인덱스 기반 접근: 배열과 마찬가지로 인덱스를 사용하여 요소에 접근할 수 있습니다. 이는 빠른 조회와 업데이트를 가능하게 합니다.
  • 순차적 데이터 저장: 요소는 삽입된 순서대로 저장되며, 중복 요소를 허용합니다.
  • 비동기: ArrayList는 기본적으로 비동기입니다. 여러 스레드에서 동시에 액세스하려면 외부에서 동기화가 필요합니다.

주요 메서드

  • add(E e): 리스트의 끝에 요소를 추가합니다.
  • add(int index, E element): 지정된 위치에 요소를 삽입합니다.
  • get(int index): 지정된 위치의 요소를 반환합니다.
  • remove(int index): 지정된 위치의 요소를 제거합니다.
  • set(int index, E element): 지정된 위치의 요소를 주어진 요소로 대체합니다.
  • size(): 리스트의 요소 개수를 반환합니다.
  • isEmpty(): 리스트가 비어 있는지 여부를 반환합니다.

arraycopy

System.arraycopy()는 Java에서 배열을 복사하는 메서드입니다. 이 메서드는 두 배열 간의 요소를 복사할 때 사용되며, 다음과 같은 형식으로 사용됩니다:

public static void arraycopy(Object src, int srcPos, Object dest, int destPos, int length)

매개변수 설명:

  1. src: 복사할 원본 배열입니다.
  2. srcPos: 원본 배열에서 복사를 시작할 위치(인덱스)입니다.
  3. dest: 복사된 데이터를 저장할 대상 배열입니다.
  4. destPos: 대상 배열에서 복사된 데이터가 저장될 시작 위치(인덱스)입니다.
  5. length: 복사할 요소의 개수입니다.
public class ArrayCopyExample {
    public static void main(String[] args) {
        int[] srcArray = {1, 2, 3, 4, 5};
        int[] destArray = new int[5];

        // srcArray의 인덱스 0부터 3개 요소를 destArray의 인덱스 1부터 복사
        System.arraycopy(srcArray, 0, destArray, 1, 3);

        // 복사 후 결과 출력
        System.out.println("복사 후 destArray:");
        for (int num : destArray) {
            System.out.print(num + " ");
        }
    }
}

 

  • srcArray: 복사할 원본 배열입니다. {1, 2, 3, 4, 5}로 초기화되었습니다.
  • destArray: 복사된 결과를 저장할 대상 배열입니다. 길이가 5인 배열로 초기화했기 때문에 초기 값은 {0, 0, 0, 0, 0}입니다.

 

위 예시에서 System.arraycopy() 메서드는 srcArray 배열에서 인덱스 0부터 3개의 요소를 destArray 배열의 인덱스 1부터 복사합니다. 실행 결과는 다음과 같습니다:

복사 후 destArray:
0 1 2 3 0

 



첫번째는 i가 10으로 시작되었고, 1을 증가 시킨 후 10을 곱했으니, 11 * 10이 돼서 110이 된다. 

두번째는 i가 11인 상황에서 5를 곱한 후, i의 값을 1 감소시켰으니, 55가 된다.

세번째는 i의 값이 10인 상태에서 전위 감소로 9가 된 후 2를 곱했으니 18이 된다.

네번째는 9에서 다시 전위 증가로 10이 된 상태에서 4를 곱했으니 40이 된다.

 


java 런타임 데이터 에리어

Java에서 런타임 데이터 에리어는 프로그램 실행 중 JVM이 사용하는 메모리 영역들을 의미합니다. 주요 구성 요소는 다음과 같습니다:

  1. 메소드 에리어(Method Area): 클래스 수준의 데이터(메타데이터)가 저장되는 영역입니다. 이 영역에는 다음과 같은 것들이 포함됩니다.
    • 클래스(또는 인터페이스) 메타데이터: 클래스와 인터페이스의 구조적 정보(예: 클래스 이름, 부모 클래스 이름, 인터페이스 목록 등).
    • 필드 정보: 클래스에 정의된 변수들(스태틱 변수 포함)의 정보.
    • 메서드 정보: 클래스에 정의된 메서드의 코드와 메서드의 바이트코드, 메서드에 대한 정보(이름, 리턴 타입, 매개변수 타입 등).
    • 스태틱 필드(변수): 클래스 수준에서 관리되는 변수로, 클래스 로딩 시점에 메모리에 로드되고 프로그램 종료 시까지 유지됩니다.
    • 런타임 상수 풀 (Runtime Constant Pool) : 상수 값(리터럴) 및 메서드, 필드 참조를 저장하는 메모리 영역으로, 클래스 파일 상수 풀의 런타임 표현입니다.
  2. 힙(Heap) = 오브젝트 에리어: 모든 객체와 배열이 저장되는 런타임 데이터 영역입니다. JVM은 가비지 컬렉션을 통해 힙을 관리합니다. ( Java 프로그램에서 생성된 모든 객체는 Heap 영역에 할당, 객체의 생성과 소멸은 자바 가비지 컬렉션(Garbage Collection)에 의해 관리 )
  3. 스택(Stack): 각 스레드마다 생성되며, 메서드 호출 시마다 프레임이 추가됩니다. 각 프레임에는 지역 변수, 피연산자 스택, 그리고 메서드 호출 및 반환에 대한 정보가 포함됩니다.
  4. PC 레지스터(Program Counter Register): 현재 실행 중인 JVM 명령의 주소를 나타냅니다. 각 스레드는 자신의 PC 레지스터를 가지고 있습니다.
  5. 네이티브 메서드 스택(Native Method Stack): 자바가 아닌 네이티브 메서드(C, C++ 등) 호출을 위한 스택입니다. 각 스레드는 네이티브 메서드 스택을 가지고 있습니다.

String Constant Pool

  • 위치: String Constant Pool은 JVM의 힙 메모리 내에 존재합니다. 자바 7 이전에는 메서드 영역(PermGen)에 있었지만, 자바 7 이후부터는 힙 메모리의 일부로 변경되었습니다.
  • 역할: 동일한 문자열 리터럴을 공유하고 중복 저장을 방지하여 메모리 사용을 효율화합니다.
  • 동작 방식:
    • 새로운 문자열 리터럴이 생성될 때, JVM은 먼저 String Constant Pool에서 해당 문자열이 이미 존재하는지 확인합니다.
    • 존재하면, 기존 문자열에 대한 참조를 반환합니다.
    • 존재하지 않으면, 새로운 문자열을 String Constant Pool에 추가하고 그에 대한 참조를 반환합니다.

메서드 에리어의 중요 구성 요소

  1. 클래스 로더: JVM 내에서 클래스를 로드하는 데 사용되는 클래스 로더 메타데이터.
  2. 클래스 데이터: 클래스 변수, 메서드 데이터, 인터페이스 등 클래스 구조에 관한 데이터.
  3. 런타임 상수 풀(Runtime Constant Pool): 상수 및 메서드, 필드 참조를 포함하는 메모리 영역으로 클래스 파일의 상수 풀(Constant Pool)의 런타임 표현입니다.

메서드 에리어에서 주목할 점

  • 스태틱 필드: 클래스 로딩 시 메모리에 로드되어 모든 인스턴스가 공유하는 변수입니다. 이는 메서드 에리어에 저장됩니다.
  • 메서드 바이트코드: JVM이 실행할 수 있는 바이트코드로 변환된 메서드의 구현입니다.
  • 클래스 초기화 코드: 클래스 로딩 시 초기화되는 코드도 이 영역에 포함됩니다.

엔트리 포인트

Java 프로그램의 엔트리 포인트(entry point) 메서드프로그램 실행의 시작 지점을 의미합니다. 대부분의 Java 프로그램에서 엔트리 포인트는 main 메서드 ' public static void main(String[] args) ' 입니다. 이 메서드는 JVM이 프로그램을 시작할 때 호출하는 메서드

 

 

  • Static 특성: main 메서드는 static으로 선언되어 있습니다. 따라서 프로그램이 시작될 때 JVM은 클래스의 로딩과 동시에 main 메서드를 호출할 수 있습니다. 인스턴스를 생성하지 않고도 클래스 이름을 통해 직접 호출할 수 있습니다 (ClassName.main(args)).
  • 로컬 변수: main 메서드 내에서 선언된 변수들은 메서드 내의 로컬 변수로 간주됩니다. 이는 메서드가 실행될 때 생성되고, 메서드가 종료되면 제거됩니다.

 

콜 스택 (Call Stack)

  • 콜 스택은 메서드 호출을 관리하는 자료구조로, 메서드가 호출될 때 마다 해당 메서드의 정보를 저장합니다. 이 정보에는 메서드의 매개변수, 지역 변수, 복귀 주소 등이 포함됩니다.
  • 동작: 메서드가 호출될 때마다 콜 스택에 호출된 메서드의 정보가 쌓이고, 메서드가 종료되면 해당 메서드의 정보가 스택에서 제거됩니다. 이를 스택 프레임(Stack Frame)이라고도 합니다.
  • 재귀 호출: 재귀 함수는 같은 함수를 다시 호출하므로, 콜 스택에 여러 개의 동일한 메서드 호출 정보가 쌓일 수 있습니다. 이 때문에 재귀 호출이 깊이가 너무 깊으면 스택 오버플로우(Stack Overflow)가 발생할 수 있습니다.

 


 

 

오브젝트 메서드 = 인스턴스 메서드 , 오브젝트 필드 = 인스턴스 필드

스태틱 메서드 = 클래스 메서드, 스태틱 필드 = 클래스 필드

 

오브젝트 메서드 (Instance Method)

오브젝트 메서드는 클래스의 인스턴스(객체)에 속하는 메서드입니다. 이 메서드는 인스턴스의 필드(멤버 변수)에 접근할 수 있고, 인스턴스별로 다른 동작을 수행할 수 있습니다. 오브젝트 메서드는 객체가 생성된 후에 호출할 수 있습니다.

특징:

  • 소속: 클래스의 인스턴스에 속합니다.
  • 호출 방법: 객체를 통해 호출됩니다.
  • 액세스: 인스턴스 변수와 다른 인스턴스 메서드에 접근할 수 있습니다.
  • 키워드: this 키워드를 사용하여 현재 인스턴스를 참조할 수 있습니다.

 

스태틱 메서드 (Static Method)

스태틱 메서드는 클래스 자체에 속하는 메서드입니다. 이 스태틱 메서드는 클래스의 인스턴스를 생성하지 않고도 호출할 수 있습니다. 일반적으로 유틸리티 함수나 인스턴스 독립적인 작업을 수행할 때 사용됩니다.

특징:

  • 소속: 클래스 자체에 속합니다.
  • 호출 방법: 클래스 이름을 통해 호출됩니다.
  • 액세스: 인스턴스 변수나 인스턴스 메서드에 접근할 수 없습니다. 대신 스태틱 변수와 다른 스태틱 메서드에만 접근할 수 있습니다.
  • 키워드: this 키워드를 사용할 수 없습니다.

 

주요 차이점

  1. 소속:
    • 오브젝트 메서드: 클래스의 인스턴스에 속합니다.
    • 스태틱 메서드: 클래스 자체에 속합니다.
  2. 호출 방법:
    • 오브젝트 메서드: 객체를 통해 호출됩니다.
    • 스태틱 메서드: 클래스 이름을 통해 호출됩니다.
  3. 액세스:
    • 오브젝트 메서드: 인스턴스 변수와 메서드에 접근할 수 있습니다.
    • 스태틱 메서드: 인스턴스 변수와 메서드에 접근할 수 없고, 스태틱 변수와 메서드에만 접근할 수 있습니다.
  4. 키워드:
    • 오브젝트 메서드: this 키워드를 사용할 수 있습니다.
    • 스태틱 메서드: this 키워드를 사용할 수 없습니다.

사용 시기

  • 오브젝트 메서드: 인스턴스별로 다른 동작을 수행해야 하거나, 인스턴스 변수에 접근해야 할 때 사용합니다.
  • 스태틱 메서드: 인스턴스와 독립적으로 동작해야 하거나, 유틸리티 성격의 메서드를 작성할 때 사용합니다.

인스턴스 필드  (오브젝트 필드)

인스턴스 필드는 클래스의 각 객체마다 별도로 유지되는 변수입니다. 즉, 클래스의 각 인스턴스는 자체의 인스턴스 필드 값을 가집니다.

  • 정의: 클래스 내에서 static 키워드 없이 선언된 필드.
  • 메모리: 각 객체마다 별도로 저장됩니다.
  • 접근: 인스턴스를 통해 접근합니다.

스태틱 필드  (클래스 필드)

정적 필드(스태틱 필드)는 클래스에 속하는 변수로, 해당 클래스의 모든 객체가 공유합니다. 정적 필드(스태틱 필드)는 클래스가 메모리에 로드될 때 한 번 초기화됩니다.

  • 정의: 클래스 내에서 static 키워드와 함께 선언된 필드.
  • 메모리: 클래스 로더에 의해 메모리에 한 번만 로드됩니다.
  • 접근: 클래스 이름으로 접근하며, 인스턴스를 통해서도 접근할 수 있습니다.
public class Car {
    // 정적 필드: 클래스에 속하며 모든 인스턴스가 공유
    public static int numberOfCars = 0;

    // 인스턴스 필드: 각 객체에 속하며 독립적
    private String model;
    private int year;

    // 생성자: 객체가 생성될 때 호출
    public Car(String model, int year) {
        this.model = model;
        this.year = year;
        numberOfCars++;  // 새로운 객체가 생성될 때마다 정적 필드를 증가시킴
    }

    // 인스턴스 메서드: 인스턴스 필드에 접근
    public String getModel() {
        return model;
    }

    public int getYear() {
        return year;
    }

    // 정적 메서드: 정적 필드에 접근
    public static int getNumberOfCars() {
        return numberOfCars;
    }

    public static void main(String[] args) {
        // 정적 메서드 호출: 클래스 이름을 통해 직접 접근
        System.out.println(Car.getNumberOfCars()); // 출력: 0

        // 새로운 인스턴스 생성
        Car car1 = new Car("Toyota", 2020);
        Car car2 = new Car("Honda", 2019);

        // 정적 메서드 호출: 객체를 통해서도 접근 가능
        System.out.println(Car.getNumberOfCars()); // 출력: 2

        // 인스턴스 메서드 호출: 각 객체의 인스턴스 필드에 접근
        System.out.println(car1.getModel()); // 출력: Toyota
        System.out.println(car2.getModel()); // 출력: Honda
    }
}

주요 차이점

  • 소속:
    • 인스턴스 필드: 각 객체에 속합니다.
    • 정적 필드: 클래스에 속합니다.
  • 메모리 할당:
    • 인스턴스 필드: 각 객체가 생성될 때마다 별도로 할당됩니다.
    • 정적 필드: 클래스가 처음 로드될 때 한 번만 할당됩니다.
  • 공유:
    • 인스턴스 필드: 객체마다 독립적입니다.
    • 정적 필드: 모든 객체가 공유합니다.
  • 접근 방법:
    • 인스턴스 필드: 객체를 통해 접근합니다.
    • 정적 필드: 클래스 이름을 통해 직접 접근할 수 있으며, 객체를 통해서도 접근 가능합니다.

요약

  • 인스턴스 필드는 각 객체에 속하며, 객체마다 별도로 존재합니다.
  • 정적 필드는 클래스에 속하며, 클래스의 모든 인스턴스가 공유합니다.

 

 

 


클래스 로더 (Class Loader)

클래스 로더는 JVM이 자바 클래스 파일(.class)을 메모리에 로드하고, 이를 실행 가능한 상태로 만드는 역할을 합니다.

 

파싱 (Parsing)

파싱은 소스 코드를 읽고 분석하여, 의미 있는 구조로 변환하는 과정입니다. 컴파일러나 인터프리터가 소스 코드를 처리할 때 첫 번째로 수행하는 단계입니다. 파싱은 일반적으로 두 단계로 나뉩니다:

  1. 렉싱(Lexing): 소스 코드를 토큰(token)이라는 기본 단위로 분할합니다. 각 토큰은 키워드, 식별자, 리터럴, 연산자 등과 같은 의미 있는 단위입니다.
  2. 파싱(Parsing): 토큰을 분석하여 문법적 구조(syntax tree 또는 parse tree)를 생성합니다. 이는 소스 코드의 계층적 구조를 나타내며, 프로그램의 문법을 검증하는 역할을 합니다.

Class 클래스

Java에서 Class 클래스는 JVM에서 클래스를 표현하는 메타클래스입니다. 즉, Java의 모든 클래스와 인터페이스는 JVM 내에서 Class 클래스의 인스턴스로 표현됩니다.

 


Java의 this 키워드와 C 언어 포인터의 차이점

  1. 용도:
    • Java의 this: 객체 지향 프로그래밍에서 객체 자신을 가리키는 참조 변수입니다.
    • C 언어 포인터: 메모리 주소를 저장하고, 메모리에 직접 접근하여 값을 읽거나 쓸 수 있는 기능을 제공합니다.
  2. 접근 방식:
    • Java의 this: Java는 객체를 자동으로 관리하며, this를 통해 객체의 메서드와 변수에 접근할 수 있습니다.
    • C 언어 포인터: C는 메모리를 직접 관리하며, 포인터를 통해 메모리 위치를 직접 참조하고 조작할 수 있습니다.
  3. 안전성:
    • Java의 this: Java는 가비지 컬렉션을 통해 메모리 관리를 자동화하여 메모리 누수를 방지합니다.
    • C 언어 포인터: C는 메모리 할당과 해제를 명시적으로 관리해야 하므로, 잘못된 포인터 조작으로 인한 메모리 오류가 발생할 수 있습니다. (delete 해줘야함 까먹으면 메모리 누수)
  4. 활용:
    • Java의 this: 주로 클래스 내부에서 인스턴스 변수와 메서드를 참조하고, 다른 생성자를 호출할 때 사용됩니다.
    • C 언어 포인터: 메모리 직접 조작을 통해 복잡한 자료 구조나 동적 메모리 할당을 구현할 때 사용됩니다.

결론

Java의 this 키워드는 객체 지향 프로그래밍에서 현재 객체를 가리키는 참조 변수로, 객체의 메서드와 변수에 접근하는 데 사용됩니다. 반면, C 언어의 포인터는 메모리 주소를 직접 저장하고, 메모리 위치를 조작하는 데 사용됩니다. 각 언어의 특성과 사용 목적에 따라 this와 포인터는 다르게 작동하며, 각각의 언어 환경에서 중요한 역할을 합니다.

 


Virtual Function Table, v테이블 ( 가상 함수 테이블 )

주로 C++과 같은 언어에서 사용

Java에서는 가상 함수 테이블(v테이블)과 같은 메커니즘을 사용하지만, 이는 JVM(Java Virtual Machine) 내부에서 동작하는 개념입니다.

 


새도우(Shadow)와 관련된 개념

새도우(Shadowing)는 변수의 범위(scope)에서 발생할 수 있는 현상을 말합니다. 예를 들어, 메서드 내에서 매개변수와 동일한 이름의 지역 변수를 선언하면, 매개변수는 지역 변수에 의해 가려지는 것입니다. 이 경우에도 this를 사용하여 인스턴스 변수에 접근할 수 있습니다.

 

this는 주로 변수 이름 충돌을 방지하고, 인스턴스 변수에 접근하기 위한 용도로 사용

 


.class 파일 생성 시점

  1. 컴파일 시점:
    • 자바 소스 코드(.java 파일)는 JDK(Java Development Kit)에 포함된 javac 컴파일러를 사용하여 컴파일됩니다.
    • 컴파일 과정에서 javac은 각 클래스 파일에 해당하는 바이트코드를 생성합니다.
    • 예를 들어, MyClass.java라는 소스 코드 파일을 컴파일하면 MyClass.class 파일이 생성됩니다.
  2. 컴파일 후 실행:
    • 컴파일이 완료된 후에는 자바 가상 머신(JVM)이 .class 파일을 실행할 수 있습니다.
    • JVM은 .class 파일을 로드하고, 이를 해석하여 프로그램을 실행합니다.
  3. 런타임 환경:
    • 프로그램 실행 중에는 클래스가 필요할 때마다 해당 클래스의 .class 파일이 JVM에 의해 로드됩니다.
    • 클래스의 정적(static) 멤버나 정적 초기화 블록은 클래스가 처음 사용될 때 로드되고 실행될 수 있습니다.

this 키워드는 오브젝트 에리어( heap 에리어 ) 에 있는 스타트 어드레스를 가리킨다. 

this 키워드는 객체의 인스턴스를 가리키는 참조입니다. 객체 지향 프로그래밍에서 this는 현재 객체의 주소(메모리 상의 위치)를 나타내며, 이를 통해 해당 객체의 멤버 변수와 메소드에 접근할 수 있습니다.

자바에서 객체는 힙(heap) 메모리에 할당됩니다. 따라서 this는 객체가 힙에 할당된 위치를 가리키는 것으로 이해할 수 있습니다. 

 


오버플로우 (Overflow)

  • 의미: 일반적으로 데이터가 특정 범위를 넘어가는 현상을 의미합니다. 주로 숫자 데이터 타입에서 발생하는 경우가 많습니다.
  • 예시: 정수형 데이터 타입의 변수가 해당 데이터 타입이 표현할 수 있는 최대 값보다 큰 값을 저장하려고 할 때 발생합니다. 이 경우, 변수에 저장할 수 있는 범위를 넘어서게 되어 오버플로우가 발생합니다.
int num = Integer.MAX_VALUE; // int 타입의 최대값
num = num + 1; // 오버플로우 발생

주의사항: Java에서 정수 오버플로우는 예외를 발생시키지 않고, 해당 데이터 타입이 표현할 수 있는 범위 내에서 계산된 값으로 최소값으로부터 다시 시작합니다.

 

오버라이딩 (Overriding)

  • 의미: 상속 관계에서 부모 클래스의 메서드를 자식 클래스에서 재정의하여 사용하는 것을 말합니다. 부모 클래스의 메서드 시그니처(이름, 매개변수 타입, 반환 타입)가 동일해야 합니다.
  • 특징:
    • 자식 클래스에서 오버라이딩된 메서드는 부모 클래스의 동일한 메서드를 대체하게 됩니다.
    • 다형성(polymorphism)을 구현하는 중요한 메커니즘 중 하나로, 상속 관계에서 부모 클래스의 메서드를 자식 클래스에서 유연하게 재정의하여 사용할 수 있습니다.
// 부모 클래스
class Animal {
    public void makeSound() {
        System.out.println("Animal makes a sound");
    }
}

// 자식 클래스
class Dog extends Animal {
    @Override
    public void makeSound() {
        System.out.println("Dog barks");
    }
}

위 예제에서 Dog 클래스는 Animal 클래스의 makeSound 메서드를 오버라이딩하여 자신의 동작인 "Dog barks"로 재정의하였습니다.

 

차이점 요약

  • 오버플로우: 데이터가 특정 범위를 초과하여 값이 계산될 때 발생하는 현상.
  • 오버라이딩: 상속 관계에서 부모 클래스의 메서드를 자식 클래스에서 재정의하여 사용하는 것.

 


스트링 리터럴

스트링 리터럴은 Java 프로그램에서 문자열을 표현하는 방식 중 하나입니다. 예를 들어 "Hello"와 같은 문자열 상수는 스트링 리터럴입니다. Java에서는 스트링 리터럴을 사용할 때 JVM 내부에서 관리되는 문자열 상수 풀(Constant Pool)에 저장됩니다.


'List' 인터페이스

주요 메서드

  • add(E e): 리스트의 끝에 요소를 추가합니다.
  • add(int index, E element): 지정된 위치에 요소를 추가합니다.
  • remove(int index): 지정된 위치의 요소를 제거합니다.
  • get(int index): 지정된 위치의 요소를 반환합니다.
  • set(int index, E element): 지정된 위치의 요소를 주어진 요소로 변경합니다.
  • size(): 리스트의 요소 개수를 반환합니다.
  • isEmpty(): 리스트가 비어있는지 여부를 반환합니다.
  • contains(Object o): 리스트에 특정 요소가 포함되어 있는지 여부를 반환합니다.
  • clear(): 리스트의 모든 요소를 제거합니다.

add(E e) 메서드

add(E e) 메서드는 Java 컬렉션 프레임워크의 인터페이스나 클래스에 정의된 메서드입니다. 이 메서드는 컬렉션에 요소를 추가하는 역할을 합니다. 예를 들어, List 인터페이스에는 다음과 같이 정의되어 있습니다.

boolean add(E e);

JVM의 내부 동작

스트링 리터럴과 add(E e) 메서드의 관계는 다음과 같습니다.

  1. 스트링 리터럴과 문자열 상수 풀:
    • Java에서 문자열 리터럴은 컴파일 시점에 문자열 상수 풀에 저장됩니다.
    • 따라서 동일한 문자열 리터럴을 여러 번 사용해도 같은 객체를 참조하게 됩니다.
  2. add(E e) 메서드와 객체 참조:
    • add(E e) 메서드를 호출할 때, 메서드의 매개변수로 전달되는 객체(e)는 메서드가 속한 클래스나 인터페이스에 의해 정의된 데이터 타입(E)에 맞게 전달됩니다.
    • 예를 들어, List<String>의 경우 add(String e) 메서드를 호출할 때 e는 String 객체여야 합니다.
  3. 자동으로 처리되는 부분:
    • JVM이 자동으로 처리한다는 것은, 예를 들어 List<String>에 문자열을 추가할 때 실제로는 내부적으로 문자열 객체를 생성하고 add 메서드를 호출하는 코드를 작성하지 않아도 되는 것을 의미합니다.
    • Java에서는 이런 처리를 통해 개발자가 직접적으로 객체를 생성하고 관리하는 부분을 줄이고, 간편하게 객체를 사용할 수 있도록 합니다.

메서드 오버로딩(Method Overloading)

객체 지향 프로그래밍 언어에서 사용되는 개념으로, 동일한 메서드 이름을 가지지만 매개변수의 타입, 개수, 순서가 다른 여러 개의 메서드를 정의할 수 있는 기능을 말합니다. 메서드 오버로드를 통해 같은 이름의 메서드를 다양한 매개변수 조합에 대해 사용할 수 있습니다.

 

메서드 오버로드의 특징

  1. 메서드 이름 동일:
    • 오버로드된 메서드들은 모두 같은 이름을 가집니다. 메서드 이름은 동일하지만 매개변수의 시그니처(타입, 개수, 순서)가 달라야 합니다.
  2. 다양한 매개변수 조합:
    • 오버로드된 메서드는 매개변수의 타입, 개수, 순서가 다르게 정의될 수 있습니다.
    • 반환 타입이나 접근 지정자는 오버로딩과 관련이 없습니다. 매개변수의 시그니처가 다르면 반환 타입이나 접근 지정자가 다르더라도 오버로드할 수 있습니다.
  3. 정적 메서드와 인스턴스 메서드:
    • 정적 메서드(static method)와 인스턴스 메서드(instance method) 모두 오버로드할 수 있습니다. 단, 동일 클래스 내에서만 오버로딩이 가능합니다.
  4. Compile-time 다형성:
    • 메서드 오버로드는 컴파일 시점에 결정됩니다. 메서드 호출 시 컴파일러는 전달된 매개변수의 타입을 기준으로 적절한 오버로드된 메서드를 선택합니다.
public class Calculator {
    // 정수형 매개변수를 받는 메서드 오버로드
    public int add(int a, int b) {
        return a + b;
    }

    // 실수형 매개변수를 받는 메서드 오버로드
    public double add(double a, double b) {
        return a + b;
    }

    // 문자열을 매개변수로 받는 메서드 오버로드
    public String add(String a, String b) {
        return a.concat(b);
    }
}

위 예시에서 Calculator 클래스는 add라는 이름의 메서드를 세 번 오버로드하고 있습니다. 각각의 메서드는 다른 매개변수 타입을 받아서 다른 연산을 수행합니다.

 

주의사항

  • 메서드 오버로드는 매개변수의 타입, 개수, 순서가 서로 달라야 합니다. 메서드 이름만 같고 매개변수의 시그니처가 같은 경우에는 오버로드가 되지 않습니다.
  • 매개변수의 타입만 다르고 나머지가 동일한 경우에는 컴파일러가 혼란스러워할 수 있으므로 주의해야 합니다.

메서드 시그니처(Method Signature)

메서드를 식별하는 데 사용되는 정보의 집합입니다. 메서드 시그니처는 메서드의 이름과 매개변수 리스트로 구성되며, 반환 타입은 포함되지 않습니다. 메서드 시그니처는 메서드 오버로드와 관련이 있으며, 같은 클래스 내에서 메서드를 구분할 때 사용됩니다.

 

메서드 시그니처의 구성 요소

메서드 시그니처는 다음과 같은 요소들로 구성됩니다:

  1. 메서드 이름: 메서드의 고유한 식별자입니다. 같은 클래스 내에서는 메서드 이름이 중복될 수 없습니다.
  2. 매개변수 리스트: 메서드가 받는 매개변수의 종류, 순서, 타입입니다. 매개변수의 이름은 시그니처에 포함되지 않습니다. 매개변수 타입과 개수가 다르면 다른 메서드 시그니처로 인식됩니다.
public class Calculator {
    // 메서드 시그니처: add(int, int)
    public int add(int a, int b) {
        return a + b;
    }

    // 메서드 시그니처: add(double, double)
    public double add(double a, double b) {
        return a + b;
    }

    // 메서드 시그니처: add(String, String)
    public String add(String a, String b) {
        return a.concat(b);
    }
}

반환 타입과 메서드 시그니처

메서드 시그니처에는 반환 타입은 포함되지 않습니다. 따라서 반환 타입이 다른 메서드가 있더라도 매개변수의 시그니처가 동일하면 오버로드할 수 없습니다. 예를 들어, 다음과 같은 코드는 컴파일 오류를 발생시킵니다.

public class Example {
    // 컴파일 오류: 메서드 시그니처가 동일함
    public int add(int a, int b) {
        return a + b;
    }

    // 컴파일 오류: 메서드 시그니처가 동일함
    public double add(int a, int b) {
        return (double)(a + b);
    }
}

Java의 String 클래스는 immutable(불변) 객체입니다. 이는 한 번 생성된 문자열 객체는 변경할 수 없다는 의미입니다.

String 클래스가 불변 객체로 설계된 것은 Java에서 문자열 처리의 안전성과 효율성을 높이기 위한 중요한 설계 선택이며, 다중 스레드 환경에서도 안정적으로 사용될 수 있습니다.

 

 

toLowerCase() 메서드는 Java의 String 클래스에서 제공하는 메서드로, 문자열의 모든 알파벳 문자를 소문자로 변환하는 기능을 수행합니다. 이 메서드는 원본 문자열을 변경하지 않고, 대신에 새로운 문자열 객체를 생성하여 반환합니다. 여기서 중요한 점은 String 클래스가 불변(immutable) 객체라는 점입니다.

 

toLowerCase() 메서드의 특징

  1. 불변 객체 유지: String 클래스는 한 번 생성된 문자열 객체의 내용을 변경할 수 없습니다. 따라서 toLowerCase() 메서드는 원본 문자열을 변경하지 않고, 소문자로 변환된 새로운 문자열을 새로 생성하여 반환합니다.
  2. 원본 문자열 보존: toLowerCase() 메서드를 호출한 후에도 원본 문자열은 그대로 유지됩니다. 새로운 문자열 객체에 변환된 결과가 저장되고, 이 객체가 반환됩니다.
  3. 유용성: toLowerCase() 메서드는 문자열 비교나 검색을 수행할 때 대소문자 구분을 무시하고 비교할 수 있도록 도와줍니다. 또한 데이터 정규화나 입력 처리 등에서도 유용하게 사용될 수 있습니다.
String str = "Hello World";
String lowerCaseStr = str.toLowerCase();

System.out.println(str);           // 출력: "Hello World"
System.out.println(lowerCaseStr);  // 출력: "hello world"

위 예시에서 toLowerCase() 메서드는 str 변수에 저장된 문자열 "Hello World"를 소문자로 변환한 새로운 문자열 객체를 생성하고 반환합니다. str 변수에 할당된 원본 문자열은 변하지 않고 그대로 유지됩니다.

 

주의사항

  • toLowerCase() 메서드가 반환하는 문자열 객체를 새로운 변수에 저장하지 않고, 원본 변수에 다시 할당하지 않으면 변환된 결과를 사용할 수 없습니다. 예를 들어, str.toLowerCase(); 만 호출하고 아무 변수에 할당하지 않으면 변환된 문자열이 사라지게 됩니다.
  • 메서드가 반환하는 새로운 문자열 객체는 원래 문자열 객체와 메모리 상에서 서로 다른 객체입니다. 따라서 메모리 사용량이 늘어날 수 있으며, 이는 문자열 연산을 반복적으로 수행할 때 고려해야 할 점입니다.

스태틱(static) 메서드를 사용 - 헬퍼(Helpers) 또는 유틸리티(Utility) 기능을 제공하기 위해

 

헬퍼(Helpers) 또는 유틸리티(Utility) 기능은 주로 반복적으로 사용되는 작업이나 기능을 재사용하기 쉽도록 돕는 함수나 클래스를 말합니다. 이들은 특정 작업을 수행하는 데 도움이 되는 다양한 메서드들로 구성될 수 있습니다. 주로 다음과 같은 특징을 가집니다:

  1. 재사용성: 여러 곳에서 반복적으로 사용되는 기능이나 코드를 하나의 잘 정의된 함수나 클래스로 묶어 재사용성을 높입니다.
  2. 단순화: 복잡한 작업을 단순화하여 코드를 간결하게 만들 수 있습니다. 예를 들어, 문자열 처리, 날짜 및 시간 관리, 파일 입출력 등의 작업을 헬퍼 함수나 유틸리티 클래스로 추상화할 수 있습니다.
  3. 유틸리티 클래스: 주로 정적(static) 메서드로 구성된 클래스로, 객체를 생성하지 않고도 호출할 수 있는 기능을 제공합니다.
  4. 유틸리티 함수: 단일 기능을 수행하는 함수로, 특정 작업을 수행하는 데 도움을 주는 작은 함수입니다.

예시

  • 문자열 처리: 문자열을 분리하거나 결합하는 메서드, 대소문자 변환 메서드 등이 유틸리티 기능으로 제공될 수 있습니다.
  • 날짜 및 시간 관리: 날짜 형식을 변환하거나 특정 시간 간격을 계산하는 메서드들이 포함될 수 있습니다.
  • 파일 입출력: 파일을 읽거나 쓰는 기능을 제공하는 메서드들이 유틸리티로 구현될 수 있습니다

널 포인터 예외 (NullPointerException)

프로그래밍 언어에서 발생하는 일반적인 예외 상황 중 하나입니다. 주로 Java와 같은 객체 지향 프로그래밍 언어에서 발생하며, 변수가 null 값을 가진 상태에서 해당 변수를 참조하려고 할 때 발생합니다.

 

1.  객체 초기화하지 않은 변수 참조: 객체를 생성하지 않고, 혹은 null로 초기화된 객체를 참조하려 할 때 발생할 수 있습니다.

String str = null;
int length = str.length(); // NullPointerException 발생

 

2. 메서드에서 null 반환: 메서드가 null을 반환하고 해당 반환 값을 사용하려고 할 때 발생할 수 있습니다.

public String getString() {
    return null;
}

String str = getString();
int length = str.length(); // NullPointerException 발생

 

3. 배열에서 null 인덱스 참조: 배열의 요소가 null인 상태에서 해당 인덱스를 참조하려고 할 때 발생할 수 있습니다.

String[] arr = new String[3];
arr[0] = "Hello";
arr[1] = null;
int length = arr[1].length(); // NullPointerException 발생

 


루핑 인디케이터(looping indicator)

일반적으로 반복문(loop)에서 사용되는 변수나 플래그를 가리킵니다. 이 변수나 플래그는 반복문의 시작, 진행 상태, 종료 조건 등을 제어하는 데 사용됩니다. 예를 들어, for 루프에서의 카운터 변수(i), while 루프에서의 조건식 등이 루핑 인디케이터의 예입니다.

 

반복문에서의 루핑 인디케이트

대표적으로 반복문에서 사용되는 루핑 인디케이트 예시는 다음과 같습니다:

 

1. 카운터 변수: 가장 일반적으로 사용되는 방법으로, 특정 범위 내에서 반복을 제어하기 위해 카운트를 증가시키는 변수입니다.

for (int i = 0; i < 10; i++) {
    // 반복 작업
}

여기서 i는 반복문의 루핑 인디케이트입니다. 초기화(int i = 0), 조건(i < 10), 증감(i++) 부분에서 반복의 시작, 진행 상태, 종료 조건을 모두 제어합니다.

 

2. 조건식: 반복문의 조건식 자체가 루핑 인디케이트 역할을 할 수 있습니다.

while (condition) {
    // 반복 작업
}

여기서 condition은 반복문의 루핑 인디케이트 역할을 합니다. 조건이 참일 때 반복이 계속됩니다.

 

3. 플래그 변수: 반복문이 진행 중인지 여부를 표시하기 위해 불리언 타입의 플래그 변수를 사용할 수도 있습니다.

boolean continueLoop = true;
while (continueLoop) {
    // 반복 작업
    if (someCondition) {
        continueLoop = false; // 반복 종료 조건
    }
}

continueLoop 변수는 반복의 진행 여부를 표시하는 루핑 인디케이트 역할을 합니다.

 


라벨(label)

프로그래밍 언어에서 특정 코드 블록을 식별하기 위해 사용되는 식별자입니다. 주로 반복문이나 조건문에서 특정 위치로 이동하거나, 중첩된 반복문에서 특정 반복문을 식별하는 데 사용됩니다.\

 

라벨의 사용 예시

1. 반복문에서의 라벨 사용:

outerLoop:
for (int i = 0; i < 5; i++) {
    innerLoop:
    for (int j = 0; j < 3; j++) {
        if (i == 2 && j == 1) {
            break outerLoop; // outerLoop 라벨을 사용하여 바깥쪽 반복문을 종료
        }
        System.out.println("i: " + i + ", j: " + j);
    }
}

위 예시에서 outerLoop와 innerLoop는 라벨로 사용되었습니다. break outerLoop;는 outerLoop라벨이 붙은 반복문을 종료하도록 지시합니다.

 

2. switch 문에서의 라벨 사용:

String dayOfWeek = "Monday";
switch (dayOfWeek) {
    case "Monday":
    case "Tuesday":
        System.out.println("Weekday");
        break;
    case "Saturday":
    case "Sunday":
        System.out.println("Weekend");
        break;
    default:
        System.out.println("Unknown day");
        break;
}

위 예시에서 case 레이블은 switch 문의 각 분기를 식별하는 데 사용됩니다.