String str1 = "hello";
String str2 = "hello";
System.out.println(str1 == str2); // true (같은 객체를 참조)==로 참조를 비교하면 String Pool 내 동일한 객체를 가리키기 때문에 빠릅니다.new String("")은 힙(Heap) 영역에 새로운 String 객체를 생성합니다.String str1 = new String("hello");
String str2 = new String("hello");
System.out.println(str1 == str2); // false (다른 객체를 참조)==를 사용할 경우 예상치 못한 결과를 초래할 수 있습니다. (값 비교는 equals()를 사용해야 합니다.)| 특징 | String Literal | new String("") |
|---|---|---|
| 저장 위치 | String Pool | Heap |
| 객체 생성 | 동일 내용이면 재사용 | 항상 새로운 객체 생성 |
| 메모리 사용 | 효율적 | 비효율적 |
== 비교 결과 | true (같은 객체) | false (다른 객체) |
String, StringBuilder, StringBuffer의 차이는 가변성, 스레드 안전성, 성능에서 크게 구분됩니다. 아래에서 각각의 특성과 차이를 정리하겠습니다.
특징:
String 객체는 생성 이후 내용을 변경할 수 없습니다.String 객체가 생성됩니다.성능:
스레드 안전성:
사용 예:
String s1 = "Hello";
String s2 = s1 + " World"; // 새로운 객체 생성
System.out.println(s2); // Hello World
특징:
성능:
사용 예:
StringBuilder sb = new StringBuilder("Hello");
sb.append(" World"); // 같은 객체에서 수정
System.out.println(sb.toString()); // Hello World
특징:
StringBuilder와 동일하게 동일 객체 내에서 문자열을 수정할 수 있습니다.StringBuilder보다 성능이 느립니다.성능:
사용 예:
StringBuffer sb = new StringBuffer("Hello");
sb.append(" World"); // 같은 객체에서 수정
System.out.println(sb.toString()); // Hello World
| 특징 | String | StringBuilder | StringBuffer |
|---|---|---|---|
| 가변성 | 불변 (Immutable) | 가변 (Mutable) | 가변 (Mutable) |
| 스레드 안전성 | 스레드 안전하지 않음 | 스레드 안전하지 않음 | 스레드 안전 |
| 성능 | 느림 (새 객체 생성) | 빠름 (단일 스레드 환경) | 비교적 느림 (동기화) |
| 사용 환경 | 읽기 전용, 간단 작업 | 단일 스레드 작업 | 멀티스레드 작업 |
public class StringExample {
public static void main(String[] args) {
// String (Immutable)
String str = "Hello";
str += " World"; // 새로운 객체 생성
System.out.println(str); // Hello World
// StringBuilder (Mutable, Not Thread-safe)
StringBuilder sb = new StringBuilder("Hello");
sb.append(" World"); // 같은 객체 내에서 수정
System.out.println(sb); // Hello World
// StringBuffer (Mutable, Thread-safe)
StringBuffer sbf = new StringBuffer("Hello");
sbf.append(" World"); // 같은 객체 내에서 수정
System.out.println(sbf); // Hello World
}
}
이 차이를 이해하면 성능과 메모리 사용을 최적화하면서 적절한 도구를 선택할 수 있습니다.
정의:
특징:
java.lang.Exception을 상속합니다.IOException, SQLException).NullPointerException, ArrayIndexOutOfBoundsException).예:
try {
int result = 10 / 0; // ArithmeticException 발생
} catch (ArithmeticException e) {
System.out.println("예외 발생: " + e.getMessage());
}
처리 가능:
try-catch 블록으로 예외를 처리하거나 throws 키워드를 사용해 호출자에게 전달할 수 있습니다.정의:
java.lang.Exception 클래스는 Throwable 클래스를 직접 상속합니다.역할:
Java의 Throwable 클래스 아래에 두 가지 주요 계층이 있습니다:
1. Error: 시스템 수준의 치명적인 문제를 나타냄.
2. Exception: 애플리케이션 수준에서 처리 가능한 문제를 나타냄.
파일 입출력에서 문제가 발생할 때 던져지는 예외.
예:
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
public class IOExceptionExample {
public static void main(String[] args) {
try {
File file = new File("nonexistent.txt");
FileReader fr = new FileReader(file); // IOException 발생
} catch (IOException e) {
System.out.println("파일을 찾을 수 없습니다: " + e.getMessage());
}
}
}
데이터베이스 작업 중 SQL 문법 오류 또는 연결 실패 시 발생.
예:
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
public class SQLExceptionExample {
public static void main(String[] args) {
try {
Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/test", "user", "pass");
} catch (SQLException e) {
System.out.println("데이터베이스 연결 실패: " + e.getMessage());
}
}
}
public class ClassNotFoundExceptionExample {
public static void main(String[] args) {
try {
Class.forName("com.nonexistent.MyClass"); // ClassNotFoundException 발생
} catch (ClassNotFoundException e) {
System.out.println("클래스를 찾을 수 없습니다: " + e.getMessage());
}
}
}public class NullPointerExceptionExample {
public static void main(String[] args) {
String str = null;
System.out.println(str.length()); // NullPointerException 발생
}
}public class ArithmeticExceptionExample {
public static void main(String[] args) {
try {
int result = 10 / 0; // ArithmeticException 발생
} catch (ArithmeticException e) {
System.out.println("수학 오류 발생: " + e.getMessage());
}
}
}Checked Exception:
IOException, SQLException, ClassNotFoundException.Unchecked Exception (RuntimeException):
NullPointerException, ArithmeticException, ArrayIndexOutOfBoundsException.정의:
특징:
java.lang.Error를 상속합니다.예:
예:
public static void main(String[] args) {
main(args); // StackOverflowError 발생
}
처리 불가능:
| 특징 | Exception | Error |
|---|---|---|
| 상속 계층 | java.lang.Exception | java.lang.Error |
| 처리 가능성 | 처리 가능 (예: try-catch 또는 throws 키워드 사용) | 대부분 처리 불가능 |
| 발생 원인 | 코드 수준의 문제 (예: 잘못된 입력, 파일 미존재 등) | 시스템 수준의 문제 (예: 메모리 부족, JVM 충돌 등) |
| 복구 가능성 | 복구 가능 | 복구 불가능 |
| 예시 | IOException, NullPointerException | OutOfMemoryError, StackOverflowError |
| 컴파일 강제 여부 | Checked Exception은 반드시 처리해야 함 | Error는 처리 강제가 없음 |
throw와 throws는 Java에서 예외 처리와 관련된 키워드이지만, 사용하는 목적과 위치가 다릅니다. 아래에서 각각의 의미와 차이점을 정리하겠습니다.
throw정의:
사용법:
throw new Exception();처럼, 예외 객체를 명시적으로 생성한 후 던질 수 있습니다.RuntimeException)뿐만 아니라 체크 예외도 던질 수 있습니다.예제:
public class ThrowExample {
public static void validateAge(int age) {
if (age < 18) {
throw new IllegalArgumentException("나이는 18 이상이어야 합니다."); // 예외 발생
}
System.out.println("나이가 유효합니다.");
}
public static void main(String[] args) {
validateAge(16); // IllegalArgumentException 발생
}
}
특징:
throws정의:
사용법:
throws 뒤에 예외 클래스를 나열합니다.Checked Exception)는 반드시 throws를 사용해 명시해야 합니다.예제:
import java.io.IOException;
public class ThrowsExample {
public static void readFile() throws IOException {
throw new IOException("파일을 읽을 수 없습니다."); // IOException 던짐
}
public static void main(String[] args) {
try {
readFile(); // 호출된 메서드에서 예외 처리
} catch (IOException e) {
System.out.println("예외 처리: " + e.getMessage());
}
}
}
특징:
| 특징 | throw | throws |
|---|---|---|
| 목적 | 예외를 발생시키기 위해 사용 | 메서드가 던질 수 있는 예외를 선언 |
| 위치 | 메서드 내부 | 메서드 선언부 |
| 사용 대상 | 예외 객체 | 예외 클래스 이름 |
| 처리 강제 여부 | 발생한 순간 예외 처리가 필요 | 호출한 메서드에서 예외 처리 또는 다시 던짐 |
| 예제 | throw new IOException("에러"); | public void method() throws IOException {} |
try-catch-finally는 Java에서 예외를 처리하기 위한 구조입니다. 예외가 발생할 수 있는 코드를 감싸고, 예외 처리 및 리소스 정리를 체계적으로 수행할 수 있도록 합니다.
try {
// 예외가 발생할 가능성이 있는 코드
} catch (ExceptionType1 e1) {
// ExceptionType1을 처리하는 코드
} catch (ExceptionType2 e2) {
// ExceptionType2를 처리하는 코드
} finally {
// 예외 발생 여부와 상관없이 항상 실행되는 코드
}
trycatch 블록으로 제어가 넘어갑니다.catch 또는 finally 블록과 함께 사용해야 합니다.try {
int result = 10 / 0; // ArithmeticException 발생
} catch (ArithmeticException e) {
System.out.println("예외 발생: " + e.getMessage());
}catchtry 블록에서 발생한 특정 예외를 처리합니다.catch 블록을 작성할 수 있습니다.catch 블록을 사용할 경우, 더 구체적인 예외 타입을 먼저 작성해야 합니다.try {
String str = null;
System.out.println(str.length()); // NullPointerException 발생
} catch (NullPointerException e) {
System.out.println("NullPointerException 처리");
} catch (Exception e) {
System.out.println("기타 예외 처리");
}finallytry와 함께 반드시 사용할 필요는 없지만, 예외 발생 여부와 관계없이 실행되어야 하는 코드는 여기에 작성합니다.try {
int[] arr = {1, 2, 3};
System.out.println(arr[5]); // ArrayIndexOutOfBoundsException 발생
} catch (Exception e) {
System.out.println("예외 처리: " + e.getMessage());
} finally {
System.out.println("리소스 정리 수행");
}try-catch-finally 동작public class TryCatchFinallyExample {
public static void main(String[] args) {
try {
int result = 10 / 0; // ArithmeticException 발생
System.out.println("결과: " + result); // 실행되지 않음
} catch (ArithmeticException e) {
System.out.println("예외 발생: " + e.getMessage());
} finally {
System.out.println("리소스 정리: finally 블록 실행");
}
}
}
예외 발생: / by zero
리소스 정리: finally 블록 실행
catch와 finally의 선택:catch는 예외를 처리하고, finally는 예외 처리와 상관없이 항상 실행됩니다.finally의 생략 가능성:catch만으로도 예외 처리가 가능하지만, 리소스 정리가 필요하다면 finally를 추가로 사용해야 합니다.try 블록에서 예외 발생:catch 블록에서 처리됩니다.finally 블록:catch에서 예외가 처리되지 않으면:try 블록이 정상적으로 실행되고, catch 블록은 건너뛰어집니다.finally 블록은 항상 실행됩니다.public class NoExceptionExample {
public static void main(String[] args) {
try {
int result = 10 / 2;
System.out.println("결과: " + result);
} catch (Exception e) {
System.out.println("예외 발생: " + e.getMessage());
} finally {
System.out.println("리소스 정리: finally 블록 실행");
}
}
}
결과: 5
리소스 정리: finally 블록 실행
finally에서 예외를 다시 던지거나, 프로그램 흐름을 조작하면 정상적인 예외 처리 흐름이 깨질 수 있으므로 주의가 필요합니다.finally 블록을 사용하는 것이 권장됩니다.Throwable은 Java에서 모든 예외(Exception)와 오류(Error)의 최상위 클래스입니다. java.lang.Throwable 클래스는 Java의 예외 처리 메커니즘의 근간을 이루며, Error와 Exception 클래스가 이를 상속합니다.
java.lang.ThrowableError: 시스템 레벨에서 발생하는 치명적인 오류 (예: 메모리 부족, 스택 오버플로우).Exception: 프로그램 실행 중 예외적인 상황을 나타냄 (예: 파일 없음, 잘못된 입력).Throwable
├── Exception
│ ├── RuntimeException
│ │ ├── NullPointerException
│ │ ├── ArithmeticException
│ │ └── IndexOutOfBoundsException
│ └── IOException
│ ├── FileNotFoundException
│ └── EOFException
└── Error
├── OutOfMemoryError
├── StackOverflowError
└── VirtualMachineError
getMessage()try {
int result = 10 / 0;
} catch (ArithmeticException e) {
System.out.println(e.getMessage()); // "/ by zero"
}printStackTrace()try {
int result = 10 / 0;
} catch (ArithmeticException e) {
e.printStackTrace();
}toString()try {
String str = null;
str.length();
} catch (NullPointerException e) {
System.out.println(e.toString()); // "java.lang.NullPointerException"
}getCause()try {
throw new IllegalArgumentException("잘못된 인자", new NullPointerException("null 값"));
} catch (IllegalArgumentException e) {
System.out.println(e.getCause()); // java.lang.NullPointerException: null 값
}addSuppressed(Throwable)try {
throw new Exception("Main Exception");
} catch (Exception e) {
e.addSuppressed(new Exception("Suppressed Exception"));
e.printStackTrace();
}IOException: 파일 입출력 예외.SQLException: 데이터베이스 예외.OutOfMemoryError: 메모리 부족 오류.StackOverflowError: 스택 메모리 초과 오류.Object
└── Throwable
├── Error (치명적인 시스템 오류)
│ ├── OutOfMemoryError
│ ├── StackOverflowError
│ └── VirtualMachineError
└── Exception (프로그램 예외)
├── RuntimeException (Unchecked Exception)
│ ├── NullPointerException
│ ├── ArithmeticException
│ └── IndexOutOfBoundsException
└── Checked Exception
├── IOException
└── SQLException
| 특징 | Throwable |
|---|---|
| 상위 클래스 | Object |
| 서브클래스 | Error, Exception |
| 역할 | 예외와 오류를 모두 표현 |
| 사용 목적 | 예외 처리와 디버깅 지원 |
| 주요 메서드 | getMessage(), printStackTrace(), getCause() |
public class ThrowableExample {
public static void main(String[] args) {
try {
int result = 10 / 0; // 예외 발생
} catch (Throwable t) { // Exception과 Error 모두 처리 가능
System.out.println("Throwable로 예외 처리: " + t.getMessage());
t.printStackTrace();
}
}
}
Throwable은 모든 예외와 오류의 최상위 클래스입니다.Exception: 예외적인 상황으로 복구 가능.Error: 치명적인 시스템 오류로 복구 불가능.getMessage(), printStackTrace() 등)를 제공합니다.Throwable을 이해하면 예외 처리와 오류 디버깅을 보다 체계적으로 할 수 있습니다.
제네릭은 데이터 타입을 일반화하여 코드의 재사용성을 높이고 타입 안정성을 보장하는 기능입니다.
다양한 타입에 대해 동일한 코드를 재사용한 경험이 있습니다. 특정 Key와 Value를 반환하는 Pair 클래스의 경우 동작은 같으나 타입만 다르므로 제네릭을 통해 중복 코드를 방지할 수 있었습니다.
타입 안정성을 보장한 경험이 있습니다. ArrayList 대신 ArrayList을 사용하여 런타임에 발생할 수 있는 ClassCastException을 방지할 수 있었습니다.
람다식은 메소드를 하나의 식으로 표현한 것을 말합니다. 그리고 람다식은 함수의 이름이 없기 때문에 익명 함수라고 부르며, 메소드의 매개 변수로 전달되거나 메소드의 결과로 반환될 수 있는 특징이 있어서 함수를 변수로 다룰 수 있다는 장점이 있습니다.
컬렉션의 저장 요소를 하나씩 참조해서 람다식으로 처리할 수 있도록 해 주는 내부 반복자를 말합니다. 외부 반복자는 개발자가 컬렉션을 조작하면서 처리 흐름을 제어하지만, 내부 반복자는 컬렉션 내부에서 흐름이 진행됩니다.
함수형 프로그래밍의 장점 때문에 탄생했다고 생각합니다. 함수형 프로그래밍은 how가 아닌 what에 집중하므로 코드의 가독성이 높아지고, 함수를 변수에 할당할 수 있으므로 함수의 재사용이 용이합니다.
또한 함수들이 순수 함수로 구성되어 있으므로 외부의 값이 변경되는 등의 사이드 이펙트가 발생하지 않습니다. 순수 함수는 함수에 동일한 인자가 주어졌을 때, 항상 같은 값을 리턴하는 함수입니다. 이로 인해 외부의 상태를 변경하지 않습니다.
어노테이션은 인터페이스를 기반으로 한 문법으로 주석처럼 코드에 달아 클래스에 특별한 의미를 부여하거나 기능을 주입할 수 있습니다.
Java 리플렉션을 통해 어노테이션을 런타임 중에 조회할 수 있고, 이 어노테이션의 메타 데이터를 사용하여 런타임 중에 특별한 로직을 작성할 수 있게 됩니다.
힙 영역에 로드된 클래스 타입의 객체를 통해, 원하는 클래스의 인스턴스를 생성할 수 있게 지원하고, 인스턴스의 필드와 메소드를 접근 제어자와 상관 없이 사용할 수 있도록 지원하는 자바 API입니다.
System.out.println 클래스는 성능이 좋지 않다고 하는데 이유가 무엇일까요?println 메서드는 블로킹 IO이므로 해당 메서드를 호출하는 스레드는 그 작업이 끝날 때까지 다른 작업을 수행하지 못하고 기다려야 합니다. 또한, println 메서드 내부에 synchronized block이 있어서 여러 스레드가 동시에 System.out 객체의 println 메서드를 호출할 수 없습니다. 이는 thread-safe하다는 점은 있지만 락 베이스로 동작하므로 성능이 떨어집니다.
즉, 싱글 스레드일 경우 블로킹 IO로 인한 성능 이슈가 있고 멀티 스레드일 경우 한 스레드 외의 다른 스레드는 블로킹 된다는 성능 이슈가 존재합니다.