From 1275ceb33fa91b1213b0050f46e6a7ea6d65eedd Mon Sep 17 00:00:00 2001 From: suubinkim <0vbbbv0@gmail.com> Date: Sun, 6 Oct 2024 18:25:25 +0900 Subject: [PATCH 1/2] =?UTF-8?q?2=EC=9E=A5=20=ED=8F=B4=EB=8D=94=20=EC=83=9D?= =?UTF-8?q?=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../2\354\236\245/item1.md" | 0 .../2\354\236\245/item2.md" | 0 .../2\354\236\245/item3.md" | 0 .../2\354\236\245/item4.md" | 0 .../2\354\236\245/item5.md" | 0 "\352\271\200\354\210\230\353\271\210/delete_me" | 0 6 files changed, 0 insertions(+), 0 deletions(-) rename "\352\271\200\354\210\230\353\271\210/item1.md" => "\352\271\200\354\210\230\353\271\210/2\354\236\245/item1.md" (100%) rename "\352\271\200\354\210\230\353\271\210/item2.md" => "\352\271\200\354\210\230\353\271\210/2\354\236\245/item2.md" (100%) rename "\352\271\200\354\210\230\353\271\210/item3.md" => "\352\271\200\354\210\230\353\271\210/2\354\236\245/item3.md" (100%) rename "\352\271\200\354\210\230\353\271\210/item4.md" => "\352\271\200\354\210\230\353\271\210/2\354\236\245/item4.md" (100%) rename "\352\271\200\354\210\230\353\271\210/item5.md" => "\352\271\200\354\210\230\353\271\210/2\354\236\245/item5.md" (100%) delete mode 100644 "\352\271\200\354\210\230\353\271\210/delete_me" diff --git "a/\352\271\200\354\210\230\353\271\210/item1.md" "b/\352\271\200\354\210\230\353\271\210/2\354\236\245/item1.md" similarity index 100% rename from "\352\271\200\354\210\230\353\271\210/item1.md" rename to "\352\271\200\354\210\230\353\271\210/2\354\236\245/item1.md" diff --git "a/\352\271\200\354\210\230\353\271\210/item2.md" "b/\352\271\200\354\210\230\353\271\210/2\354\236\245/item2.md" similarity index 100% rename from "\352\271\200\354\210\230\353\271\210/item2.md" rename to "\352\271\200\354\210\230\353\271\210/2\354\236\245/item2.md" diff --git "a/\352\271\200\354\210\230\353\271\210/item3.md" "b/\352\271\200\354\210\230\353\271\210/2\354\236\245/item3.md" similarity index 100% rename from "\352\271\200\354\210\230\353\271\210/item3.md" rename to "\352\271\200\354\210\230\353\271\210/2\354\236\245/item3.md" diff --git "a/\352\271\200\354\210\230\353\271\210/item4.md" "b/\352\271\200\354\210\230\353\271\210/2\354\236\245/item4.md" similarity index 100% rename from "\352\271\200\354\210\230\353\271\210/item4.md" rename to "\352\271\200\354\210\230\353\271\210/2\354\236\245/item4.md" diff --git "a/\352\271\200\354\210\230\353\271\210/item5.md" "b/\352\271\200\354\210\230\353\271\210/2\354\236\245/item5.md" similarity index 100% rename from "\352\271\200\354\210\230\353\271\210/item5.md" rename to "\352\271\200\354\210\230\353\271\210/2\354\236\245/item5.md" diff --git "a/\352\271\200\354\210\230\353\271\210/delete_me" "b/\352\271\200\354\210\230\353\271\210/delete_me" deleted file mode 100644 index e69de29..0000000 From 3922fb6c7724b0d8b140cfd13eb5c5eb040673a5 Mon Sep 17 00:00:00 2001 From: suubinkim <0vbbbv0@gmail.com> Date: Tue, 4 Feb 2025 19:10:21 +0900 Subject: [PATCH 2/2] docs: Update to item54 --- .../5\354\236\245/item32.md" | 125 ++++++++++++++++++ .../8\354\236\245/item53.md" | 87 ++++++++++++ .../8\354\236\245/item54.md" | 77 +++++++++++ 3 files changed, 289 insertions(+) create mode 100644 "\352\271\200\354\210\230\353\271\210/5\354\236\245/item32.md" create mode 100644 "\352\271\200\354\210\230\353\271\210/8\354\236\245/item53.md" create mode 100644 "\352\271\200\354\210\230\353\271\210/8\354\236\245/item54.md" diff --git "a/\352\271\200\354\210\230\353\271\210/5\354\236\245/item32.md" "b/\352\271\200\354\210\230\353\271\210/5\354\236\245/item32.md" new file mode 100644 index 0000000..5ea9e69 --- /dev/null +++ "b/\352\271\200\354\210\230\353\271\210/5\354\236\245/item32.md" @@ -0,0 +1,125 @@ +## 제네릭과 가변인수를 함께 쓸 때는 신중하라 + + +### 가변인수 메서드 + +하나의 메서드가 가변적인 수의 매개변수를 받을 수 있도록 지원 + + + +가변인수(Varargs)를 사용하는 메서드를 호출하면 Java 컴파일러가 자동으로 배열을 생성 + +```java +public static void varargsExample(String... args) { + // args는 배열로 처리됨 + System.out.println("Length: " + args.length); +} +``` +호출시 아래처럼 컴파일 + +```java +varargsExample(new String[]{"A", "B", "C"}); +``` + +### 이 배열은 내부로 감춰져야 하는데, 클라이언트에 공개되면서 문제가 발생할 수 있다. + + + +가변인수 매개변수에 제네릭이나 매개변수화 타입이 포함되면 컴파일 경고 발생 + +warning: [unchecked] Possible heap pollution from + +parameterized vararg type List + +매개변수화 타입의 변수가 타입이 다른 객체를 참조하면 힙 오염이 발생한다. + + + +ex) +```java +static void dangerous(List... stringLists) { + List intList = List.of(42); + Object[] objects = stringLists; + objects[0] = intList; // 힙 오염 발생 + String s = stringLists[0].get(0); // ClassCastException +} +``` + +마지막 줄에 컴파일로가 생성한 형변환이 숨어 있기 때문에 ClassCastException 발생. +```java +String s = (String)stringLists[0].get(0); // ClassCastException +``` + + + + + +-> 이처럼 타입 안전성이 깨지니 제네릭 가변인수 배열 매개변수에 값을 저장하는 것은 안전하지 않다. + + + +### 제네릭 가변인수 메서드를 선언할 수 있게 한 이유? + +실무에서 유용! + +자바 라이브러리에서도 이런 메서드를 여럿 제공 (타입 안전) + +ex. Arrays.asList(T... a), Collections.addAll(Collection c, T... elements) + + + +타입 안전한 메서드의 조건 + +1. 메서드가 제네릭 배열에 아무것도 저장하지 않는다. + +2. 그 배열의 참조가 밖으로 노출되지 않는다. + +-> 가변인수(Varargs)를 사용하는 메서드가 전달된 데이터를 단순히 읽고 사용만 할 경우에는 문제가 발생하지 않는다 + +(가변인수의 목적 : 메서드 호출 시 임의의 개수의 인수를 간편하게 전달) + + + +### @SafeVarargs + +- 메서드 작성자가 그 메서드가 타입 안전함을 보장하는 장치 + +컴파일러는 이 약속을 믿고 그 메서드가 안전하지 않을 수 있다는 경고를 더 이상 하지 않는다. + +메서드가 안전한 게 확실하지 않다면 절대 @SafeVarargs 애너테이션을 달아서는 안 된다. + + + +### 제네릭 가변인수 매개변수를 안전하게 사용하는 메서드 +```java +@SafeVarargs +static List flatten(List... lists) { + List result = new ArrayList<>(); + for (List list : lists) + result.addAll(list); + return result; +} +``` + +flatten 메서드는 임의 개수의 리스트를 인수로 받아, 받은 순서대로 그 안의 모든 원소를 하나의 리스트로 옮겨 담아 반환한다. +이 메서드에는 @SafeVarargs 어노테이션이 달려있으니 선언하는 쪽과 사용하는 쪽 모두에서 경고를 발생시키지 않는다. +제네릭이나 매개변수화 타입의 varargs 매개변수를 받는 모든 메서드에 @SafeVarargs 어노테이션을 달자 + + +### 제네릭 varargs 매개변수를 List로 대체한 예 - 타입 안전 +```java +static List flatten(List> lists) { + List result = new ArrayList<>(); + for (List list : lists) + result.addAll(list); + return result; +} +``` + + + +### 정리 + +메서드에 제네릭 (혹은 매개변수화된) varargs 매개변수를 사용하고자 한다면, 먼저 그 메서드가 타입 안전한지 확인한 다음 + +@SafeVarargs 애너테이션을 달아 사용하는 테 불편함이 없게끔 하자. \ No newline at end of file diff --git "a/\352\271\200\354\210\230\353\271\210/8\354\236\245/item53.md" "b/\352\271\200\354\210\230\353\271\210/8\354\236\245/item53.md" new file mode 100644 index 0000000..afd86f6 --- /dev/null +++ "b/\352\271\200\354\210\230\353\271\210/8\354\236\245/item53.md" @@ -0,0 +1,87 @@ +## 가변인수는 신중히 사용하라 + +- 가변인수 메서드 : 명시한 타입의 인수를 0개 이상 받을 수 있음 + +```java +static int sum(int...args){ + int sum=0; + for(int arg:args) + sum+=arg; + return sum; + } +``` + +### 인수가 1개 이상이어야 할때 + +- 잘못 구현한 예 + +-- 인수를 0개 넣어 호출하면 컴파일이 아닌 런타임에 실패. 코드 지저분 + +```java +static int min(int...args){ + if(args.length=0) + throw new IllegalArgumentException("인수가 1개 이상 필요합니다."); + + int min=args[0]; + for(int i=1;i 성능에 민감하면 걸림돌이 될 수 있음 + +-> 다중정의를 활용하면 성능 최적화 가능 +```java +public void foo() { } +public void foo(int a1) { } +public void food(int a1, int a2) { } +public void foo(int a1, int a2,int a3) { } +public void foo(int a1, int a2, int a3, int... rest) { } +``` + + +- EnumSet의 정적 팩터리도 이 기법을 사용해 열거 타입 집합 생성 비용을 최소화함. +```java +EnumSet weekend = EnumSet.of(DayOfWeek.SATURDAY, DayOfWeek.SUNDAY); +``` + +```java +public static > EnumSet of(E e) { + EnumSet result = noneOf(e.getDeclaringClass()); result.add(e); + return result; +} + + ... +public static > EnumSet of(E e1, E e2, E e3, E e4, E e5){ + EnumSet result = noneOf(e1.getDeclaringClass()); + result.add(e1); + result.add(e2); + result.add(e3); + result.add(e4); + result.add(e5); + return result; +} + +@SafeVarargs public static > EnumSet of(E first, E... rest) { + EnumSet result = noneOf(first.getDeclaringClass()); + result.add(first); + for (E e : rest) result.add(e); + return result;} +``` + + diff --git "a/\352\271\200\354\210\230\353\271\210/8\354\236\245/item54.md" "b/\352\271\200\354\210\230\353\271\210/8\354\236\245/item54.md" new file mode 100644 index 0000000..20e192d --- /dev/null +++ "b/\352\271\200\354\210\230\353\271\210/8\354\236\245/item54.md" @@ -0,0 +1,77 @@ +## null이 아닌, 빈 컬렉션이나 배열을 반환하라 + + + +- 따라하면 안 되는 코드 (컬렉션이 비었으면 null을 반환) +```java +private final List cheesesInStock = ...; +/** +* @return 매장 안의 모든 치즈 목록을 반환한다. +* 단, 재고가 하나도 없다면 null을 반환한다. + */ + public List getCheeses() { + return cheesesInStock.isEmpty() ? null + : new ArrayList<>(cheesesInStock); + } +``` + + + +-- null을 반환하게 되면 클라이언트는 항상 방어 코드를 넣어줘야함, 방어코드를 빼먹으면 오류가 발생할 수 있 + +getCheeses()를 사용하는 클라이언트는 null 확인을 추가로 해야한다. +```java +if (cheeses != null && cheeses.contains(Cheese.STILTON)) { +... } +``` + + + +### null 대신 빈 컬렉션 할당시 비용. + +1. 빈 컬렉션을 반환해도 사실상 신경 쓸 만한 성능 차이가 나지 않는다. + +2. 빈 컬렉션과 배열은 새로 할당하지 않고도 반환할 수 있다. + +- 빈 불변 컬렉션 반환 : Collections.emptyList(), Collections.emptySet(), Collections.emptyMap() + +: 최적화가 필요할 때만 사용. + + + +### 빈 컬렉션을 반환하는 올바른 예 +```java +public List getCheeses() { + return new ArrayList<>(cheesesInStock); +} +``` + + + +### 빈 배열을 반환하는 올바른 예 +```java +public Cheese[] getCheeses() { + return cheesesInStock.toArray(new Cheese[0]); +} +``` + + + +- 최적화 +```java +private static final Cheese[] EMPTY_CHEESE_ARRAY = new Cheese[0]; + +public Cheese[] getCheeses() { + return cheesesInStock.toArray(EMPTY_CHEESE_ARRAY); +} +``` + + + + + +### 핵심 정리 + +null이 아닌, 빈 배열이나 컬렉션을 반환하라. null을 반환하는 API는 사용하기 어렵고 오류 처리 코드도 늘어난다. + +그렇다고 성능이 좋은 것도 아니다. \ No newline at end of file