반응형
- 유튜브의 알고리즘에 나와서 봤던 영상을 보고 정리 유튜브 링크
- 자바 코드를 작성할 때 주의하지 않으면 발생할 수 있는 문제들을 예시코드로 보여주고 설명해준다.
- 이런걸 stub toe problem이라고 표현하는게 재미있다.
- 예시 코드를 보여주고 결과를 예측해보라고 할 때 나도 풀어봤는데 그래도 얼추 맞추긴 했다.
- 그래도 평소 신경쓰지 않았던 부분들을 다시 공부할 수 있었다.
Remove 메서드
// List 예제
List<Integer> numbers = new ArrayList<>(List.of(1, 2, 3));
System.out.println(numbers); // 출력: [1, 2, 3]
numbers.remove(1); // 인덱스 1의 요소(2)를 제거
System.out.println(numbers); // 출력: [1, 3]
// 타입을 Collection으로 변경
Collection<Integer> numbers = new ArrayList<>(List.of(1, 2, 3));
System.out.println(numbers); // 출력: [1, 2, 3]
numbers.remove(1); // 값이 1인 요소를 제거
System.out.println(numbers); // 출력: [2, 3]
- List와 Collection의 API 차이로 발생한 출력값 변경
List
에서remove(1)
을 호출하면 인덱스 1의 원소 제거- 하지만
Collection
에서remove(1)
을 호출하면 값이 1인 원소를 제거한다.
타입추론
// Collection 타입
Collection<Integer> numbers = new ArrayList<>(List.of(1, 2, 3));
System.out.println(numbers); // 출력: [1, 2, 3]
numbers.remove(1); // 값이 1인 요소를 제거
System.out.println(numbers); // 출력: [2, 3]
// var
var numbers = new ArrayList<>(List.of(1, 2, 3));
System.out.println(numbers); // 출력: [1, 2, 3]
numbers.remove(1); // ??
System.out.println(numbers); // 출력: [1, 3]
- 타입 추론을 쓸 때는 실제로 어떤 타입으로 추론될지 확인해야 한다.
var
로 추론한 타입이ArrayList
라서 remove(1)은 인덱스 1의 원소를 제거했다.
Arrays.asList()
List<Integer> numbers = Arrays.asList(1, 2, 3);
try {
numbers.add(4); // UnsupportedOperationException 발생
} catch (UnsupportedOperationException e) {
System.out.println("add unsupported");
}
try {
numbers.set(2, 2); // 작동함 - 요소 변경은 가능
} catch (UnsupportedOperationException e) {
System.out.println("set unsupported");
}
Arrays.asList()
로 만든 리스트는 크기가 고정되어 있어 새 원소를 추가할 수 없다.- 하지만 기존 요소의 값은 변경 가능
- 불변 리스트를 만들려는 의도로 사용할 수 없다.
List<Integer> numbers = List.of(1, 2, 3);
try {
numbers.add(4); // UnsupportedOperationException 발생
} catch (UnsupportedOperationException e) {
System.out.println("add unsupported");
}
try {
numbers.set(2, 2); // UnsupportedOperationException 발생
} catch (UnsupportedOperationException e) {
System.out.println("set unsupported");
}
- Java 9부터 도입된
List.of()
,Set.of()
,Map.of()
와 같은 팩토리 메서드들은 완전히 불변 컬렉션을 생성한다.
Stream side effect
List<String> names = List.of("Dory", "Gill", "Bruce", "Nemo", "Darla", "Marlin", "Jacques");
List<String> inUpperCase = new ArrayList<>();
names.stream()
.map(String::toUpperCase)
.forEach(name -> inUpperCase.add(name));
System.out.println("Size before: " + names.size()); // 7
System.out.println("Size after: " + inUpperCase.size()); // 7
names.parallelStream() // 병렬 스트림으로 변경할 경우 예측 불가능한 결과 발생
.map(String::toUpperCase)
.forEach(name -> inUpperCase.add(name));
- Stream 람다는 순수 함수(Pure Function)로 구성해야한다.(외부 상태 변경 X, 외부 변경 가능 상태에 의존 X)
Stream lazy evaluation
int[] factor = new int[] { 2 };
List<Integer> numbers = List.of(1, 2, 3);
var stream = numbers.stream()
.map(n -> n * factors[0]);
// 스트림이 실행되기 전에 외부 상태 변경
factors[0] = 0;
// 스트림이 실행 - 변경된 factor 값 사용
stream.forEach(System.out::print)
// 올바른 방식 - 외부 상태에 의존하지 않음
int factor = 2; // 로컬 변수로 값 고정
List<Integer> betterResult = numbers.stream()
.map(n -> n * factor)
.toList();
- 스트림은 최종 연산이 호출될 때까지 실제로 실행되지 않는다.
- 그 전에 외부 상태를 변경하면 의도하지 않은 결과가 발생할 수 있다.
- 불변 컬렉션 팩토리 메서드나 스트림의
toList()
같은 새로운 기능들을 별 생각 없이 사용하고 있었는데, 단순히 편의성이 아니라 안전성을 위해 중요하다는 것을 새삼 배웠다. - 마찬가지로 스트림을 사용할 때 순수함수 여부와 사이드 이펙트를 크게 생각하지 않았었다.
- 코드 작성할 때 내가 의도한 대로 동작하는지 늘 확인하고, 정말 보장하는지 알 수 있도록 꾸준한 학습이 필요할 것 같다.
반응형