[2주차] String/예외/제네릭/람다/스트림/어노테이션

Dev.Dana·2024년 11월 7일

Java

목록 보기
2/7
post-thumbnail

String

String

문자열 객체. 불변(immutable)이라 한 번 만들어지면 수정이 불가능하다. 변경할 때마다 새로운 객체가 만들어진다.

String literal

“hello” 와 같이 쌍따옴표로 작성된 문자열이다. 자바의 String Pool에 저장된다. 같은 문자열이 여러번 사용되면 하나의 인스턴스만 사용해서 메모리를 절약할 수 있다.

new String(”hello”)

새로운 String 객체를 Heap메모리에 만들어서 사용한다. 같은 내용의 문자열이라도 각 각 다른 객체로 만들어진다.

StringBuilder

가변적(mutal)이면서 빠른 속도로 문자열을 변경 할 수 있다.

동기화 지원은 하지 않는다. ( 동기화 : 여러 스레드가 동시에 접근할때의 안정성 )

StringBuffer

StringBuilder와 비슷하지만 동기화를 지원한다. 그러므로 여러 스레드가 동시에 접근해도 안전하다. 하지만 속도는 StringBuilder보단 느리다.


예외 처리 (Exception Handling)

Exception와 Error의 차이

Exception 예외

프로그램에서 발생할 수 있는 문제이다. 개발자가 처리할 수 있다.

Exception 클래스

  • IOException: 입력/출력 관련 문제.
  • NullPointerException: 객체가 없는데 사용하려고 할 때 발생
  • ArithmeticException: 숫자를 0으로 나눌 때 발생.

Checked Exception과 Unchecked Exception의 차이

  • Checked Exception: 컴파일 시점에 예외 처리를 강제하는 예외. 반드시 try~catch로 처리하거나 throws로 던져야 한다. (ex. IOException, SQLException)
  • Unchecked Exception: 런타임에 발생하며, 꼭 처리하지 않아도 된다. 컴파일러가 예외 처리를 강제하지 않기 때문에 코드 작성시에는 예외가 발생할 가능성이 보이진 않는다. 하지만 프로그램이 실행되는 특정한! 상황에서 발생할 수 있다. (ex. NullPointerException, ArrayIndexOutOfBoundsException)
    컴파일 시점에 확인이 되지 않기 때문에 코드 작성시에 적절한 테스트를 통해 방지하거나 try-catch로 예외 처리를 할 수 있다.

Error

프로그램 외부 또는 심각한 문제… 개발자가 쉽게 처리할 수 없는 것이다.
어플리케이션 코드에서는 일반적으로 Error를 잡아서 처리하지 않고, 처리하는 것을 권장하지도 않는다.

대표적인 Error 유형

  • OutOfMemoryError : JVM이 힙 메모리를 모두 사용해서 더 이상 메모리를 할당할 수 없을 때 발생
  • StackOverflowError : 메서드 호출이 너무 깊어져 스택 메모리가 넘칠 때 발생
  • NoClassDefFoundError : JVM이 클래스를 찾지 못할 때 발생

throw와 throws의 차이

  • throw : 예외를 발생시킬 때 사용한다.
throw new IllegalArgumentException("잘못된 값임!!!!");
  • throws : 메서드에서 예외가 발생할 수 있음을 선언할 때 사용한다.
public void myMethod() throws IOException {
    // 메서드에서 IOException이 발생할 수 있음
}
  • 메서드에서 예외를 처리하지 않고, 상위 호출 메서드로 예외를 throws해서 이 메서드에서 예외 처리를 담당할 수 있도록 위임할 수 있다.

    그럼 왜 상위 계층에서 예외 처리를 하는 것이 더 나을까?
    서비스나 어플리케이션의 구조에 따라서 특정 메서드는 단순히 예외를 던지고, 더 높은 계층의 메서드에서 예외를 처리하는 것이 더 의미 있는 경우가 있다. ... -> 언제일까?

Throwable과 Exception의 차이

Throwable

모든 예외(Exception)와 에러(Error)의 최상위 클래스이다. (=부모 클래스)
Java에서 예오 처리가 가능한 모든 객체의 부모로 프로그램의 정상적인 흐름을 방해할 수 있는 상황을 나타낸다.

Exception

Throwable의 자식 클래스 중 하나. 개발자가 처리할 수 있는 문제만 포함한다.
프로그램 내에서 발생할 수 있는 일반적인 예외 상황을 나타내고 try-catch block을 사용해서 처리할 수 있다.

try-catch-finally

finally 블록은 예외가 발생하든 안하는 항상 실행된다. 주로 자원을 정리하거나 닫을 때 사용된다.

try {
    // 예외가 발생할 수 있는 코드
} catch (Exception e) {
    // 예외 처리
} finally {
    System.out.println("여기는 항상 실행되는 공간!");
}

제네릭 (Generic)

제네릭은 클래스나 메서드에 사용할 타입을 미리 지정하지 않고, 객체를 생성하거나 메서드를 호출 할 때 타입을 지정해서 사용하는 기능이다.

장점 : 타입 안정성을 보장해주고 코드의 중복을 줄일 수 있다.

제네릭 클래스 만들기

public class Box<T> {
    private T content;

    public void setContent(T content) {
        this.content = content;
    }

    public T getContent() {
        return content;
    }
}

제네릭 클래스를 정의할때는 위와 같이 타입을 미리 지정하지 않고 타입 매개변수 T를 사용한다.

Box<String> stringBox = new Box<>();
stringBox.setContent("Hello");

Box<Integer> intBox = new Box<>();
intBox.setContent(123);

Box 클래스는 어떤 타입이든 사용할 수 있도록 설계되어서, 위에 보이는 코드처럼 String이든 Integer든 객체를 생성할 때 타입을 지정해서 넣어준다.

제네릭 사용경험 : ArrayList

ArrayList<String> list = new ArrayList<>();
list.add("Hello");
// list.add(123); // 컴파일 오류, String 타입만 허용

제네릭을 통해 ArrayList가 String타입의 객체만 저장할 수 있도록 지정해두었다.

  • 이게 왜 제네릭인가요? 제네릭을 사용하지 않으면 ArrayList 같은 컬렉션은 기본적으로 Object 타입을 사용해서 모든 종류의 객체를 저장할 수 있다. 이렇게 되면 값을 꺼내서 사용할때마다 타입을 명시적 변환(Casting)해야 하고, 잘못된 타입 변환이 발생할 경우에는 런타임 오류가 발생할 수 있다.
    ArrayList list = new ArrayList(); // 제네릭을 사용하지 않았을 때
    list.add("Hello");
    list.add(123); // 다른 타입도 추가 가능!
    
    String str = (String) list.get(0); // 타입 변환
    int num = (int) list.get(1); // 타입 변환
    위의 코드처럼 list에 모든 타입의 객체를 추가할 수 있어서 캐스팅이 필요하다.

💡
ArrayList
HashMap<K, V>
HashSet


람다(Lambda)란?

자바에서 코드를 더 간결하고 읽기 쉽도록 만든 함수 표현식이다.

(x, y) -> x + y; // 두 수를 더하는 람다식

스트림(stream)이란?

데이터의 흐름을 다루는 도구.

주로 필터링, 변환, 집계등의 작업을 할 때 사용된다.

List<String> names = Arrays.asList("Alice", "Bob", "Charlie");
names.stream().filter(name -> name.startsWith("A")).forEach(System.out::println);

람다와 스트림이 왜 생겨났을까?

코드의 간결성과 가독성을 높이고 병렬 처리를 쉽게 하기 위해 도입되었다.

어노테이션 (annotation)

코드에 메타데이터를 추가해 특별한 의미나 동작을 부여한다.

목적 : 코드의 명확성을 높이고 컴파일러나 런타임 환경에서 특정 기능을 트리거할 수 있다.

리플렉션이란?

실행중에 클래스의 구조(필드, 메서드)를 탐색하고 수정할 수 있는 기능이다.

어노테이션을 통해 메타데이터를 읽어 특정 동작을 실행할 수 있다.

System.out.println이 성능이 좋지 않은 이유?

위 클래스는 I/O작업이기 때문에 데이터를 출력할 때 마다 리소스를 사용하기에 속도가 느리다. 많은 데이터를 출력할 때 더욱이 성능 저하가 발생할 수 있다.

입출력 작업은 속도가 많이 소요되기 때문에 대량의 데이터를 다룰때에는 로그를 비동기식으로 처리하거나 적절한 로그 라이브러리를 사용하는 것이 좋다.

  • 동기화(Synchronization) : System.out.println은 동기화된 메서드. 즉, 여러 스레드가 동시에 호출할 경우 한 번에 하나의 스레드만 접근할 수 있다는 의미이다. 스레드 안전성을 보장하지만 많은 출력 작업이 동시 발생할 때 병목 현상을 유발할 수 있다.

대신 뭘 써야할까?

  1. Logger : Java의 Logger클래스나 Log4j같은 로깅 라이브러리를 사용하는 것이 좋다. 이런 라이브러리들은 비동기 로깅이 가능하다.
  2. BufferedWriter : 버퍼링을 통해 데이터를 일괄로 쓰기 때문에 효율적인 파일출력이 가능하다. 한번에 데이터를 모아서 쓰기 때문에 sout호출보다 속도가 훨씬 빠르다.

profile
어제의 나보단 나은 오늘의 내가 되기를

0개의 댓글