💡 String literal과 new String(””) 차이
- String 리터럴은 String constant pool 이라는 영역에 저장 (자바는 문자 리터럴을 특별히 관리)
- String constant pool 은 동일한 문자열에 대해서는 하나의 참조를 재사용
- new String(””) 은 매번 새로운 String 객체를 Heap 영역에 저장
String str1 = new String("Hello");
String str2 = "Hello";
String str3 = "Hello";
System.out.println(str1.equals(str2)); // (1) true
System.out.println(str1 == str2); // (2) false
System.out.println(str2 == str3); // (3) true
- String constant pool에서는 같은 값이 존재한다면 새로 정의한 변수여도 같은 주소값을 가리키게 된다
💡 String, StringBuilder, StringBuffer의 차이
(String)
- String 은 기본적으로 객체의 값을 변경할수 없다. 그러므로 문자열 변경에 메모리 사용과 효율이 비효율적이다.
- String 클래스에 내부를 보면 문자열이 저장되는 필드가 private final byte[] value; 으로 정의 되어 있음
String str = "hello";
str = str + " world";
System.out.println(str); // hello world
- trim 이나 toUpperCase 메소드를 사용하여 문자열을 변경해도 새로운 공간에 할당된다.
(StringBuilder)
- StringBuilder, StringBuffer는 기본으로 문자열을 저장할수 있는 버퍼가 있음
- 버퍼의 Default 사이즈는 16개 문자 저장 가능, 객체 생성 할때 버퍼 사이즈 설정 가능
- StringBuilder, StringBuffer 에 내부를 보면 문자열이 저장되는 필드가 private byte[] value; 로 정의 됨 (final X)
- 동시 접근이 가능하기 때문에 멀티스레드 환경에서 안전하지 않다.
(StringBuffer)
- StringBuffer 메서드에서 synchronized 키워드를 사용하기 때문에 멀티스레드 환경에서도 안전하다.
(StringBuilder, StringBuffer 동시 처리 테스트)
import java.util.*;
public class Main extends Thread{
public static void main(String[] args) {
StringBuffer stringBuffer = new StringBuffer();
StringBuilder stringBuilder = new StringBuilder();
new Thread(() -> {
for(int i=0; i<10000; i++) {
stringBuffer.append(1);
stringBuilder.append(1);
}
}).start();
new Thread(() -> {
for(int i=0; i<10000; i++) {
stringBuffer.append(1);
stringBuilder.append(1);
}
}).start();
new Thread(() -> {
try {
Thread.sleep(2000);
System.out.println("StringBuffer.length: "+ stringBuffer.length()); // thread safe 함
System.out.println("StringBuilder.length: "+ stringBuilder.length()); // thread unsafe 함
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
}
}
결과 : StringBuffer.length: 20000 StringBuilder.length: 19628
- StringBuilder는 Thread safe 하지 않아서 각기 쓰레드가 객체에 접근해서 변경을 하면 결과가 씹힌다.
- 반면 StringBuffer는 한 쓰레드가 append() 를 수행하고 있을경우 다른 쓰레드가 append() 를 수행을 동시에 하지못하도록 잠시 대기를 시켜주고 순차적으로 실행하게 한다.
💡 Exception과 Error 의 차이
(Exception)
- Exception (예외) 는 체크 예외, 언체크 예외가 있다
- 체크 예외는 컴파일 시점에 확인, 반드시 예외처리 해야함, 예외 발생 시 롤백 안함
- 언체크 예외는 런타임 시점에 확인, 예외처리 선택 가능, 예외 발생 시 롤백 (언체크 예외의 부모가 RuntimeException인 이유)
- Exception (예외) 는 개발자가 임의로 Throw 하거나 Catch 할수 있다
- Checked Exception이 발생할 가능성이 있는 메서드라면 반드시 로직을 try-catch로 감싸서 throws로 언체크 예외를 던져야한다.
(Error)
- Error (오류) 는 시스템이 종료되어야 할 수준의 상황과 같이 수습할 수 없는 심각한 문제를 의미, 개발자가 미리 예측하여 방지할 수 없다. (메모리 꽉 찬걸 개발자가 예측할순 없죠...)
- Error의 종류에는
1. 논리 에러 : 흔히 말하는 버그, 프로그램이 동작하는데 결과가 예상과 다른 경우
2. 런타임 에러 : 실행 중 발생하는 에러
3. 컴파일 에러 : 컴파일 시점에 발생하는 에러
(예외 처리)
- try는 예외 발생 가능성이 있는 로직이 포함된 블럭, catch는 예외가 발생했을 때 처리되는 로직이 포함된 블럭, finally는 예외가 발생하던 안하던 최종적으로 처리되는 로직
- finally의 사용 예시로는 DB 커넥션 풀을 사용하고, 로직이 성공하던 말던 DB의 커넥션 풀을 반환하는것
- throw : 예외를 강제로 발생 시킴
- throws : 예외를 상위 메서드로 던지는 것, 메서드나 생성자 선언부에 throws를 선언하므로써 이 메서드를 사용하기 위해서 어떤 예외 처리를 하여아 하는지 명시
public class Test {
public static void main(String[] args) throws Exception {
Test test = new Test();
test.throwException(13);
}
public void throwException(int number) throws Exception {
if (number > 12) {
throw new Exception("Number is over than 12");
}
System.out.println("Number is + " + number);
}
}
💡 제네릭
- 제네릭이란?
➜ 데이터의 타입(data type)을 일반화한다(generalize) 즉 사용할 타입을 미리 결정하지 않고 실제 사용하는 시점에 타입을 결정하여 사용
- 제네릭은 컴파일 시점의 전처리 기술
- 제네릭 사용 전 (Object를 사용할때 마다 매번 형 변환 해주어야 함)
class Box {
Object item;
void setItem(Object item) {this.item = item;}
Object getItem() {return item;}
}
public class JavaChapter {
public static void main(String[] args) {
Box box = new Box();
box.setItem("AAA");
String a = (String) box.getItem(); // 형 변환을 해주어야 함
System.out.println(a);
}
}
- 제네릭 사용 후
class Box<T> {
T item;
void setItem(T item) {this.item = item;}
T getItem() {return item;}
}
public class JavaChapter {
public static void main(String[] args) {
Box<String> sbox = new Box<>();
Box<Integer> ibox = new Box<>();
sbox.setItem("AAA");
ibox.setItem(1234);
System.out.println(sbox.getItem()); // 형 변환 필요 없음
System.out.println(ibox.getItem());
}
}
(참고)
https://jehuipark.github.io/java/java-generic
[JAVA] 제네릭(Generic)이란
List<Interger> list1 = new ArrayList<>();List list2 = new ArrayList<>();Map<String, String> map = new ArrayList<>();
jehuipark.github.io
💡 람다(lambda)
- 람다는 메서드를 하나의 식으로 만든것
- 람다 함수는 값으로 사용할수도 있고, 파라미터로 전달 및 변수에 대입이 가능하다 (일급 객체)
* 사용법
// (매개변수) -> { 실행문 }
//정상적인 유형
() -> {}
() -> 1
() -> { return 1; }
(int x) -> x+1
(x) -> x+1
x -> x+1
(int x) -> { return x+1; }
x -> { return x+1; }
(int x, int y) -> x+y
(x, y) -> x+y
(x, y) -> { return x+y; }
(String lam) -> lam.length()
lam -> lam.length()
(Thread lamT) -> { lamT.start(); }
lamT -> { lamT.start(); }
//잘못된 유형 선언된 type과 선언되지 않은 type을 같이 사용 할 수 없다.
(x, int y) -> x+y
(x, final y) -> x+y
- 람다는 표준 함수형 인터페이스(java.util.function) 를 구현한 것 ➜ 람다식을 변수나 매개변수에 할당하기 위해서는 그에 해당하는 인터페이스 타입이 필요하기 때문
+ 함수형 인터페이스란? 추상 메서드가 단 하나만 있는 인터페이스
+ 표준 함수형 인터페이스 종류 : Consumer<T>, Supplier<T>, Function<T, R>, Predicate<T>, BiFunction<T, U, R>
+ 함수형 인터페이스의 자세한 내용 : https://inpa.tistory.com/entry/%E2%98%95-%ED%95%A8%EC%88%98%ED%98%95-%EC%9D%B8%ED%84%B0%ED%8E%98%EC%9D%B4%EC%8A%A4-API
+ 사실 이런식으로 람다의 반환값을 할당할수 있다.
public static void main(String[] args) {
Consumer<String> c1 = t -> System.out.println("입력값 : "+ t);
c1.accept("홍길동");
}
💡스트림(Stream)
- 스트림이란? 컬렉션이나 배열 등의 데이터를 효율적으로 처리하기 위해 추가된 API
- 스트림 생성 + 중간 연산 + 최종 연산으로 이루어짐
example
.stream() // 스트림 생성
.filter(x -> x < 2) // 중간 연산
.count(); // 최종 연산
- 시작 명령어
메서드 | 설명 | 예제 |
stream() | 컬렉션(Collection)에서 스트림 생성 | list.stream() |
parallelStream() | 병렬 스트림 생성 | list.parallelStream() |
Arrays.stream(array) | 배열로부터 스트림 생성 | Arrays.stream(intArray) |
Stream.of(T... values) | 주어진 값으로 스트림 생성 | Stream.of("A", "B", "C") |
Stream.empty() | 비어 있는 스트림 생성 | Stream.empty() |
Stream.generate(Supplier) | 무한 스트림 생성 (요소를 Supplier로 제공) | Stream.generate(Math::random) |
Stream.iterate(seed, UnaryOperator) | 초기 값과 함수로 무한 스트림 생성 | Stream.iterate(0, n -> n + 1) |
Files.lines(Path) | 파일의 각 줄을 스트림으로 생성 | Files.lines(Paths.get("file.txt")) |
- 중간 연산 명령어
메서드 | 설명 | 예제 |
filter(Predicate) | 조건에 맞는 요소만 포함 | stream.filter(x -> x > 5) |
map(Function) | 각 요소를 변환 | stream.map(x -> x * 2) |
flatMap(Function) | 요소를 스트림으로 변환 후 하나의 스트림으로 병합 | stream.flatMap(List::stream) |
distinct() | 중복 제거 | stream.distinct() |
sorted() | 기본 정렬 | stream.sorted() |
sorted(Comparator) | 사용자 정의 정렬 | stream.sorted(Comparator.reverseOrder()) |
peek(Consumer) | 각 요소를 처리 (주로 디버깅 용도) | stream.peek(System.out::println) |
limit(long maxSize) | 스트림의 요소 수 제한 | stream.limit(5) |
skip(long n) | 처음 n개의 요소를 건너뜀 | stream.skip(3) |
- 최종 연산 명령어
메서드 | 설명 | 예제 |
forEach(Consumer) | 각 요소에 대해 작업 수행 | stream.forEach(System.out::println) |
toArray() | 스트림을 배열로 변환 | Object[] arr = stream.toArray() |
reduce(BinaryOperator) | 모든 요소를 하나로 축소 | stream.reduce(0, Integer::sum) |
collect(Collector) | 스트림을 컬렉션으로 변환 | stream.collect(Collectors.toList()) |
count() | 요소의 개수 반환 | long count = stream.count() |
anyMatch(Predicate) | 조건에 맞는 요소가 하나라도 있는지 검사 | stream.anyMatch(x -> x > 5) |
allMatch(Predicate) | 모든 요소가 조건을 만족하는지 검사 | stream.allMatch(x -> x > 5) |
noneMatch(Predicate) | 모든 요소가 조건을 만족하지 않는지 검사 | stream.noneMatch(x -> x > 5) |
findFirst() | 첫 번째 요소 반환 | Optional<T> first = stream.findFirst() |
findAny() | 아무 요소나 반환 | Optional<T> any = stream.findAny() |
max(Comparator) | 최대값 반환 | stream.max(Comparator.naturalOrder()) |
min(Comparator) | 최소값 반환 | stream.min(Comparator.naturalOrder()) |
💡어노테이션
- 어노테이션은 한국어로 주석이라는 뜻, 프로그램의 메타 데이터를 주고 코드 동작에는 직접 영향 X
- 코드에 추가적인 의미를 부여하거나, 반복 작업을 줄임
- 컴파일 타임, 로드 타임, 런타임에 해석될 수 있음
- 어노테이션은 리플렉션을 이용하여 런타임에 해석될수 있음
- 동작 원리 : Javac(자바 컴파일러) 안에 있는 Annotation Processor가 Annotation이 붙은 것들을 수집해서 byte code를 만들고 .class 파일을 만든다.
- Meta Annotation은 어노테이션을 만들기 위해 사용하는 어노테이션
- @Target : Annotation이 어디에 위치할 수 있는지 제한 ex) class, interface, constructor....
- @Documented : Javadoc이 만들어질 때 해당 Annotation 을 문서화 하도록 만드는 Annotation
- @Retention : 어노테이션 값들을 언제까지 유지할 것인지
* Source : 컴파일 시 사라짐
* Class : 컴파일된 .class 파일에 포함되지만 런타임에는 사용되지 않음.
* Runtime : 런타임에서도 유지되며 리플렉션으로 접근 가능.
- 사용자 정의 어노테이션 만드는 예시
// 문자열 필드의 길이가 특정 범위 내에 있는지 검증하는 어노테이션 : @ValidLength
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface ValidLength {
int min() default 0;
int max() default Integer.MAX_VALUE;
}
+ 리플렉션이란 ? 구체적인 클래스 타입을 알지 못하더라도 그 클래스의 메서드, 타입, 변수들에 접근할 수 있도록 해주는 자바 API
(참고)
[JAVA] 람다식(Lambda)의 개념 및 사용법
람다함수란?
medium.com
'Java' 카테고리의 다른 글
[JAVA] 가비지 컬렉션 (2) | 2025.01.30 |
---|---|
[JAVA] 동시성 프로그래밍 (0) | 2025.01.24 |
[JAVA] 컬렉션 총정리 (0) | 2025.01.13 |
[JAVA] 자바 기본, 자바 객체 지향 (1) | 2025.01.03 |