본문 바로가기
Everyday Study

2024.07.04 (목) { Thread 클래스, final 키워드, 익명 클래스, 로컬 클래스, 루즈 커플링, 펑셔널인터페이스, 람다 표현식, 오토박싱, 캐스팅 }

by xogns93 2024. 7. 4.

Thread 클래스

Thread 클래스는 Java의 표준 라이브러리에 포함된 멀티스레딩을 위한 클래스입니다. 이 클래스는 여러 작업을 병렬로 실행할 수 있게 도와줍니다.

주요 메서드

  • start(): 새로운 스레드를 시작하고, 새로운 호출 스택을 생성합니다. 이 메서드는 스레드의 run() 메서드를 호출합니다.
  • run(): 스레드가 실행할 코드를 포함하는 메서드입니다. Runnable 인터페이스를 구현하거나 Thread 클래스를 상속받아 오버라이드할 수 있습니다.
  • join(): 현재 스레드를 대기 상태로 만들고, 다른 스레드의 종료를 기다립니다.
  • sleep(long millis): 현재 스레드를 일정 시간 동안 대기 상태로 만듭니다.
  • interrupt(): 스레드를 중단하도록 요청합니다.

Runnable 인터페이스는 Java 표준 라이브러리에 포함된 인터페이스로, java.lang 패키지에 속해 있습니다. Runnable 인터페이스는 단 하나의 메서드를 가지고 있으며, 스레드에서 실행할 코드를 정의하는 데 사용됩니다.

 

run(): 이 메서드는 스레드가 실행할 작업을 정의합니다. 이 메서드는 일반적으로 Thread 객체의 start() 메서드에 의해 호출됩니다.

package java.lang;
@FunctionalInterface
public interface Runnable {
	 public abstract void run();
}

Thread 클래스와의 연동

Runnable 인터페이스는 Thread 클래스와 함께 사용됩니다. Thread 클래스는 Java 표준 라이브러리의 일부로, 스레드를 생성하고 관리하는 역할을 합니다. Thread 클래스는 Runnable 인터페이스를 구현한 객체를 생성자 매개변수로 받아서 사용할 수 있습니다.

 


final 키워드

1.  final 변수

변수에 final 키워드를 사용하면 해당 변수는 상수(Constant)로 선언됩니다. 이는 한 번만 초기화할 수 있으며, 이후에 값을 변경할 수 없습니다.

public class FinalVariableExample {
    public static void main(String[] args) {
        final int x = 10;
        // x = 20; // 컴파일 에러: 값을 변경할 수 없음
    }
}

특징:

  • 초기화 이후 값의 변경이 불가능합니다.
  • 반드시 선언과 동시에 초기화해야 합니다.
  • 상수로 사용되는 경우 코드의 가독성과 유지보수성을 높이는 데 도움이 됩니다.

 

2.  final 메서드

메서드에 final 키워드를 사용하면 해당 메서드는 서브클래스에서 오버라이드(재정의)할 수 없습니다.

public class Parent {
    public final void printMessage() {
        System.out.println("Hello, World!");
    }
}

public class Child extends Parent {
    // 오버라이드할 수 없음
    // public void printMessage() { ... }
}
  • 특징:
    • 서브클래스에서 해당 메서드를 재정의할 수 없습니다.
    • 보안, 안정성, 효율성을 강화하는 데 도움이 됩니다.

3.  final 클래스

클래스에 final 키워드를 사용하면 해당 클래스는 상속될 수 없습니다.

public final class FinalClass {
    // 클래스 내용
}

// 아래 코드는 컴파일 에러 발생: FinalClass를 상속할 수 없음
// public class SubClass extends FinalClass { ... }
  • 특징:
    • 상속을 금지하여 클래스의 기능을 변경하지 못하게 합니다.
    • 보안, 안정성, 효율성을 강화하는 데 도움이 됩니다.
    • 주로 라이브러리 클래스나 기반 클래스로 사용될 때 유용합니다.

요약

  • final 키워드는 Java에서 변수, 메서드, 클래스에 적용될 수 있습니다.
  • 변수: 상수로 만들어 값을 변경할 수 없게 합니다.
  • 메서드: 오버라이드(재정의)를 금지하여 안정성과 성능을 개선합니다.
  • 클래스: 상속을 금지하여 클래스의 변형을 막고, 안정성을 높입니다.
  • final 키워드는 코드의 안정성과 유지보수성을 높이는 데 중요한 역할을 합니다.

 


익명 클래스(anonymous class)

이름이 없는 클래스입니다. 보통 일회용으로 사용되며, 주로 특정한 기능을 간단히 구현할 때 유용합니다. 자바에서는 익명 클래스를 사용할 때 주로 인터페이스나 추상 클래스를 구현하는 데 사용합니다.

 

인터페이스 구현:

// 인터페이스 정의
interface Greeting {
    void sayHello();
}

// 익명 클래스 사용 예제
public class Main {
    public static void main(String[] args) {
        // 익명 클래스로 인터페이스 구현
        Greeting greeting = new Greeting() {
            @Override
            public void sayHello() {
                System.out.println("Hello, World!");
            }
        };

        // 메서드 호출
        greeting.sayHello();
    }
}

 

추상 클래스 구현:

// 추상 클래스 정의
abstract class Animal {
    abstract void makeSound();
}

// 익명 클래스 사용 예제
public class Main {
    public static void main(String[] args) {
        // 익명 클래스로 추상 클래스 구현
        Animal dog = new Animal() {
            @Override
            void makeSound() {
                System.out.println("Woof!");
            }
        };

        // 메서드 호출
        dog.makeSound();
    }
}

 

Runnable 인터페이스 구현:

// Runnable 인터페이스를 구현한 익명 클래스 사용 예제
public class Main {
    public static void main(String[] args) {
        // 익명 클래스로 Runnable 구현
        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("Thread is running");
            }
        });

        // 스레드 시작
        thread.start();
    }
}

 


로컬 클래스

메서드 내에 정의된 클래스로, 특정 메서드 내에서만 사용 가능한 클래스입니다. 로컬 클래스는 특정 메서드의 로직을 보조하는 역할을 하며, 그 메서드의 지역 변수와 매개변수에 접근할 수 있습니다. 로컬 클래스는 메서드가 호출될 때마다 다시 정의되고 생성되며, 해당 메서드가 종료되면 로컬 클래스의 인스턴스도 소멸합니다.

 

 

  • 메서드 내부에서만 정의: 로컬 클래스는 특정 메서드 내에서만 정의되고 사용할 수 있습니다.
  • 지역 변수와 매개변수 접근: 로컬 클래스는 자신이 정의된 메서드의 final 또는 effectively final인 지역 변수와 매개변수에 접근할 수 있습니다.
  • 스코프 제한: 로컬 클래스는 자신이 정의된 메서드의 스코프를 벗어나지 못합니다.

로컬클래스는 세미콜론 ; 왜 안붙나 - 정의 코드 라서

클래스 정의는 중괄호 {}로 둘러싸인 블록으로 끝나므로 세미콜론이 필요 없습니다:

 


인터페이스는 루즈 커플링(loose coupling)을 달성하기 위해 설계되었습니다. 루즈 커플링은 시스템의 각 구성 요소가 서로 독립적으로 변경될 수 있도록 하는 소프트웨어 설계 원칙입니다. 이는 시스템의 유지 보수성과 확장성을 높이는 데 중요한 역할을 합니다.

인터페이스를 사용하면 클래스 간의 의존성을 줄이고, 서로 독립적으로 작동할 수 있게 합니다. 인터페이스는 구현을 정의하지 않고, 어떤 동작이 필요하다는 것을 명시하기 때문에 구체적인 구현이 아닌 추상적인 계약(Contract)에 의존하게 됩니다.

 

루즈 커플링의 장점

  1. 유지 보수성 향상: 각 구성 요소가 독립적으로 변경될 수 있어 유지 보수가 쉬워집니다.
  2. 확장성 향상: 새로운 기능이나 구성 요소를 쉽게 추가할 수 있습니다.
  3. 재사용성 향상: 인터페이스를 통해 정의된 기능은 여러 클래스에서 재사용될 수 있습니다.
  4. 테스트 용이성: 인터페이스를 사용하면 Mock 객체를 통해 단위 테스트를 쉽게 작성할 수 있습니다.

구체, 구현체(implementation)

인터페이스(interface)나 추상 클래스(abstract class)를 실제로 구현한 클래스를 의미합니다. 인터페이스나 추상 클래스는 메서드의 시그니처만을 정의하고, 실제 동작(구현)은 포함하지 않습니다. 이러한 메서드의 실제 동작을 정의하는 클래스가 바로 "구현체"입니다. 반면에 인스턴스(instance)는 클래스를 기반으로 생성된 객체를 의미합니다.

개념 설명

  • 인터페이스(interface): 행동을 정의하는 추상 타입으로, 메서드 시그니처만을 포함하고 구현은 포함하지 않습니다.
  • 구현체(implementation): 인터페이스의 메서드를 구체적으로 구현한 클래스입니다.
  • 인스턴스(instance): 클래스(구현체)를 기반으로 생성된 객체입니다.

펑셔널인터페이스 (Functional Interface)

단 하나의 추상 메서드만을 가지고 있는 인터페이스입니다. 펑셔널 인터페이스는 람다 표현식 및 메서드 참조와 함께 사용되어 코드의 간결함과 가독성을 높이는 데 유용합니다. 펑셔널 인터페이스의 예로는 Runnable, Callable, Comparator, Function, Consumer 등이 있습니다.

펑셔널 인터페이스의 특징

  • 단일 추상 메서드: 오직 하나의 추상 메서드만을 정의할 수 있습니다.
  • @FunctionalInterface 어노테이션: 선택적으로 사용되며, 인터페이스가 펑셔널 인터페이스임을 명시합니다. 이 어노테이션을 사용하면 두 개 이상의 추상 메서드를 정의하려고 할 때 컴파일 오류가 발생합니다.

대체로 구체로 사용된다 -> 람다 익스프레션

신택스가 익명클래스보다 훨씬 심플하다

 


람다 표현식(lambda expression)

자바에서 함수형 프로그래밍을 지원하기 위한 중요한 기능 중 하나입니다. 람다 표현식은 익명 함수(anonymous function)로, 메서드를 간결하게 표현할 수 있는 방법을 제공합니다. 람다 표현식은 함수형 인터페이스(Functional Interface)를 구현하는데 사용되며, 메서드의 본문을 명시적으로 작성하지 않고도 메서드의 기능을 제공할 수 있습니다.

 

 

  • parameter: 메서드의 매개변수를 나타냅니다. 매개변수가 없는 경우 괄호만 사용할 수 있습니다.
  • ->: 람다 화살표(arrow)로, 매개변수와 본문(expression)을 구분짓는 역할을 합니다.
  • expression: 메서드의 본문을 나타내며, 이는 값으로 평가됩니다.

람다 표현식의 특징

  1. 메서드 본문의 간결성: 메서드 본문을 한 줄로 간결하게 표현할 수 있습니다.
  2. return 키워드 생략: 람다 표현식에서는 별도로 return 문을 작성하지 않아도 됩니다.
  3. 세미콜론 생략: 단일 표현식이므로 세미콜론(;)을 추가할 필요가 없습니다.
// 기존 방식의 메서드 정의
interface MyInterface {
    void myMethod();
}

// 람다 표현식 사용
public class Main {
    public static void main(String[] args) {
        // 기존 방식
        MyInterface obj1 = new MyInterface() {
            @Override
            public void myMethod() {
                System.out.println("Hello from anonymous class!");
            }
        };
        obj1.myMethod();

        // 람다 표현식
        MyInterface obj2 = () -> System.out.println("Hello from lambda expression!");
        obj2.myMethod();
    }
}

 

 

설명

  • MyInterface는 단일 추상 메서드 myMethod를 가진 함수형 인터페이스입니다.
  • obj1은 익명 클래스를 사용하여 MyInterface를 구현한 객체를 생성하고, myMethod를 오버라이드하여 구현합니다.
  • obj2는 람다 표현식을 사용하여 MyInterface의 인스턴스를 생성합니다. 람다 표현식 () -> System.out.println("Hello from lambda expression!")은 매개변수가 없고, 메서드 본문이 "Hello from lambda expression!"인 간결한 형태입니다.

람다 표현식의 장점

  • 간결성과 가독성: 복잡한 익명 클래스 대신 간단하게 표현할 수 있습니다.
  • 지연 실행: 필요한 시점에만 실행할 수 있습니다.
  • 함수형 프로그래밍 지원: 함수를 값처럼 다루는 함수형 프로그래밍 스타일을 지원합니다.

오토박싱(Auto-Boxing)

자바에서 기본 자료형(primitive type)을 해당하는 래퍼 클래스(wrapper class) 객체로 자동 변환해주는 기능입니다. 이와 반대로, 언박싱(Auto-Unboxing)은 래퍼 클래스 객체를 다시 기본 자료형으로 자동 변환해주는 기능을 말합니다.

기본 자료형과 래퍼 클래스

자바의 기본 자료형과 그에 대응되는 래퍼 클래스는 다음과 같습니다:

  • int -> Integer
  • double -> Double
  • char -> Character
  • boolean -> Boolean
  • float -> Float
  • long -> Long
  • short -> Short
  • byte -> Byte

오토박싱 예제

기본 자료형을 래퍼 클래스로 자동 변환하는 예제입니다.

public class AutoBoxingExample {
    public static void main(String[] args) {
        // 오토박싱: 기본 자료형 -> 래퍼 클래스 객체
        int primitiveInt = 5;
        Integer wrapperInt = primitiveInt; // 오토박싱

        // 출력
        System.out.println("primitiveInt: " + primitiveInt);
        System.out.println("wrapperInt: " + wrapperInt);
    }
}

요약

  • 오토박싱(Auto-Boxing): 기본 자료형을 대응하는 래퍼 클래스 객체로 자동 변환하는 기능.
  • 언박싱(Auto-Unboxing): 래퍼 클래스 객체를 기본 자료형으로 자동 변환하는 기능.
  • 오토박싱과 언박싱은 컬렉션 프레임워크 사용 시나 기본 자료형을 객체처럼 사용하고자 할 때 유용하게 사용됩니다.

로컬변수 -> 직접 구현체로 만듬 - 로컬 변수로 직접 구현체를 만든다는 것은 메서드 내부에서 인터페이스나 추상 클래스의 구현체를 익명 클래스(Anonymous Class)나 람다(Lambda)를 사용하여 생성하는 것을 의미합니다.

 

디폴트 메서드는 앱스트랙트메서드가 아님 -> 디폴트 메서드(default method)는 인터페이스에 정의된 메서드로, 메서드 본문(구현)을 포함하고 있어 추상 메서드가 아닙니다.

 

  • 디폴트 메서드:
    • 구현을 포함하고 있는 메서드.
    • 인터페이스에 선언되며, default 키워드로 정의.
    • 인터페이스를 구현하는 클래스에서 반드시 재정의할 필요는 없음.
  • 추상 메서드:
    • 구현이 없는 메서드로, 반드시 재정의해야 함.
    • 인터페이스 또는 추상 클래스에 선언되며, abstract 키워드로 정의.
    • 인터페이스를 구현하거나 추상 클래스를 상속받는 클래스에서 반드시 구현해야 함.
  • 디폴트 메서드는 인터페이스에 구현을 포함하고 있어, 해당 인터페이스를 구현하는 클래스에서 반드시 재정의할 필요는 없습니다.
  • 추상 메서드는 구현을 포함하지 않으며, 해당 인터페이스 또는 추상 클래스를 구현하거나 상속받는 클래스에서 반드시 구현해야 합니다.

캐스팅 ( Casting )

객체나 변수를 다른 타입으로 변환하는 과정을 의미합니다. 캐스팅은 주로 데이터 타입의 호환성 문제를 해결하기 위해 사용됩니다.

자바에서는 두 가지 주요 캐스팅이 있습니다: 기본형 캐스팅 참조형 캐스팅

기본형 캐스팅 (Primitive Casting)

기본형 캐스팅은 숫자 타입 간의 변환을 의미합니다. 기본형 캐스팅은 자동(암시적)과 강제(명시적) 캐스팅으로 나눌 수 있습니다.

1. 암시적 캐스팅 (Implicit Casting)

더 작은 크기의 타입에서 더 큰 크기의 타입으로 변환할 때 자동으로 수행됩니다. 예를 들어, int에서 long으로 변환하는 경우입니다.

int num = 10;
long longNum = num; // 암시적 캐스팅

2. 명시적 캐스팅 (Explicit Casting)

더 큰 크기의 타입에서 더 작은 크기의 타입으로 변환할 때 강제로 수행해야 합니다. 예를 들어, double에서 int로 변환하는 경우입니다.

double num = 10.5;
int intNum = (int) num; // 명시적 캐스팅, 값은 10이 됩니다.

참조형 캐스팅 (Reference Casting)

참조형 캐스팅은 객체의 타입을 변환하는 과정입니다. 이는 상속 관계에 있는 클래스들 사이에서 발생합니다. 참조형 캐스팅에는 업캐스팅과 다운캐스팅이 있습니다.

1. 업캐스팅 (Upcasting)

업캐스팅은 서브클래스 타입을 슈퍼클래스 타입으로 변환하는 것을 의미합니다. 이는 암시적으로 수행됩니다.

class Animal {
    void makeSound() {
        System.out.println("Some sound");
    }
}

class Dog extends Animal {
    void makeSound() {
        System.out.println("Bark");
    }
    void fetch() {
        System.out.println("Fetch");
    }
}

public class Main {
    public static void main(String[] args) {
        Dog dog = new Dog();
        Animal animal = dog; // 업캐스팅
        animal.makeSound();  // "Bark"
    }
}

2. 다운캐스팅 (Downcasting)

다운캐스팅은 슈퍼클래스 타입을 서브클래스 타입으로 변환하는 것을 의미합니다. 이는 명시적으로 수행되어야 하며, 캐스팅이 안전한지 확인하기 위해 instanceof 연산자를 사용하는 것이 일반적입니다.

public class Main {
    public static void main(String[] args) {
        Animal animal = new Dog(); // 업캐스팅
        if (animal instanceof Dog) {
            Dog dog = (Dog) animal; // 다운캐스팅
            dog.fetch(); // "Fetch"
        }
    }
}

 

요약

  • 기본형 캐스팅: 숫자 타입 간의 변환.
    • 암시적 캐스팅: 작은 타입에서 큰 타입으로 자동 변환.
    • 명시적 캐스팅: 큰 타입에서 작은 타입으로 강제 변환.
  • 참조형 캐스팅: 객체 타입 간의 변환.
    • 업캐스팅: 서브클래스를 슈퍼클래스로 변환 (자동).
    • 다운캐스팅: 슈퍼클래스를 서브클래스로 변환 (명시적).