개요
회사 업무로 진행하는 이펙티브 자바 세미나 자료 내용이다. 책과 블로그를 참고하였으며, 자세한 코드는 여기서 확인할 수 있다.
9장은 내용이 많아서 1편과 2편으로 나누어서 포스팅할 예정이다.
item 57. 지역변수의 범위를 최소화하라.
코드 가독성과 유지보수성을 위해 지역변수의 유효 범위를 최소화하자.
1. 가장 처음 쓰일 때 선언하기
2. 선언과 동시에 초기화하기
3. while <<<<< for
4. 메서드를 작게 유지하자! → 단순히 기능별로 메서드를 쪼개면 된다.
지역변수의 범위를 줄이는 가장 강력한 기법 : 가장 처음 쓰일 때 선언하기
- 미리 선언부터 해두면 코드가 어수선해져 가독성이 떨어진다.
- 변수를 실제로 사용하는 시점엔 타입과 초깃값이 기억나지 않을 수도 있다.
- 지역변수를 생각없이 선언하다 보면 변수가 쓰이는 범위보다 너무 앞서 선언하거나, 다 쓴 뒤에도 여전히 살아있게 되기 쉽다.
지역변수는 선언과 동시에 초기화해야 한다.
- 초기화에 대한 정보가 충분하지 않다면 충분해질 때까지 선언을 미뤄야 한다.
- try-catch 문은 이 규칙에서 예외다. 변수를 초기화하는 표현식에서 검사 예외를 던질 가능성이 있다면 try 블록 안에서 초기화해야 한다. → 그렇지 않으면 예외가 블록을 넘어 메서드에까지 전파된다.
- 변수 값을 try 블록 바깥에서도 사용해야 한다면 try 블록 앞에 선언해야 한다.
✅ 개발할 때 그렇진않지만, 알고리즘 문제를 풀 때는 확실히 이 문제에 대해서 짚고 넘어가야 할 안좋은 습관이 있다. 지역변수를 쓸 때는 항상 사용하는 곳에서 초기화하여 사용하도록 하자.
while보다는 for, 변수 범위를 최소화해준다.
/* 1. 컬렉션/배열 순회 시 관용구 */
for (Element e: c) {
// ...
}
/* 2. 반복자가 필요할 때 */
for (Iterator<Element> i = c.iterator(); i.hasNext();) {
Element e = i.next();
}
Iterator<Element> i = c.iterator();
while (i.hasNext()) {
doSomething(i.next());
}
Iterator<Element> i2 = c2.iterator();
while (i.hasNext()) {
doSomethingElse(i2.next());
}
item 58. 전통적인 for문보다는 for-each 문을 사용하라.
for vs. for-each
- 인덱스를 굳이 쓰지않는다면, 외부로 노출시킬 필요는 없다.
- 사용하지 않는 것들을 공개해서 괜히 실수를 유발하지 말라는 것이다. for-each를 사용함으로써 방어적인 코딩을 통해 안전한 코드를 만들자는 철학이다.
- for-each를 사용하면 인덱스를 노출하지 않으면서 안전한 코드를 작성할 수 있다!while 보다 낫지만, 여전히 인덱스가 잘못 쓰일 위험이 있다.
Collection 정의
Collection<Element> collection = new ArrayList<>();
collection.add(new Element("e1"));
collection.add(new Element("e2"));
collection.add(new Element("e3"));
전통적인 for문
// 전통적인 for문을 사용한 CASE -> index를 노출한다.
for(Iterator<Element> i = collection.iterator(); i.hasNext();) {
Element element1 = i.next();
System.out.println(element1.toString());
}
for each로 리팩토링
// for-each로 리팩토링
for(Element element : collection) {
System.out.println(element.toString());
}
- 간단한 반복 구성, 그렇다고 속도도 느리지않고, 최적화된 속도를 따른다.
- 컬렉션 중첩에서는 더욱 이점이 커진다.
- Iterator를 건드릴 필요도 없기 때문에 실수할 일도 적다.
for문을 잘못 사용하였을 때
enum Suit {CLUB, DIAMOND, HEART, SPADE}
enum Rank {ACE, DEUCE, THREE, FOUR, FIVE, SIX, SEVEN, EIGHT, NINE, TEN, JACK, QUEEN, KING}
Collection<Suit> suits = Arrays.asList(Suit.values());
Collection<Rank> ranks = Arrays.asList(Rank.values());
for(Iterator<Suit> i = suits.iterator(); i.hasNext();) {
for(Iterator<Rank> j = ranks.iterator(); j.hasNext();) {
System.out.println(i.next() + ", " + j.next());
}
}
- 구현 의도와 맞지 않는 결과
- 외부 루프의 Suit 이 Rank보다 짧아서 NoSuchElementException을 던지게 된다.
→ 만약 enum 상수의 개수가 같아서 예외를 던지지 않는다면, 나중에 enum 개수가 변경된 후에 없던 에러가 생기는 현상을 겪을 것이다.
for문을 잘못 사용하였을 때 - refactoring
enum Face {ONE, TWO, THREE, FOUR, FIVE, SIX}
Collection<Face> faces = EnumSet.allOf(Face.class);
// 일반적으로 동작하지만 복잡한 코드
for(Iterator<Face> i = faces.iterator(); i.hasNext();) {
Face elementI = i.next();
for(Iterator<Face> j = faces.iterator(); j.hasNext();) {
System.out.println(elementI + ", " + j.next());
}
}
// for-each의 사용으로 코드가 깔끔해 보인다.
for (Face faceI:faces) {
for (Face faceJ:faces) {
System.out.println(faceI + ", " + faceJ);
}
}
for-each를 사용할 수 없는 경우
- 파괴적인 필터링(destructive filtering): 반복을 돌며 원소를 하나씩 지워나가는 필터링
- Collection의 removeIf() 를 통해 구현하자.
- ramoveIf(): 특정 조건을 만족하는 원소를 지을 수 있다.
- Collection의 removeIf() 를 통해 구현하자.
- 변형(transforming)
- 컬렉션을 순회하며 원소희 값 일부/전체를 교체해야 한다면 리스트의 반복자나 배열의 인덱스를 사용하자
- 컬렉션에 변동이 있을 때에는 for-each로 구현할 수 없다!
- 병렬 반복(parallel iteration)
- 여러 컬렉션을 병렬로 순회해야 한다면, 반복자와 인덱스 변수를 사용해 엄격하게 제어하는 편이 좋다.
- 병렬 순회: 두 개 이상의 컬렉션을 동시에 순회할 때를 말한다. 반복되는 리스트들의 크기가 같은지 주의 깊게 확인해야 한다.
List<Element> elements = new ArrayList<>();
elements.add(new Element("e1"));
elements.add(new Element("e2"));
elements.add(new Element("e3"));
List<String> descriptions = new ArrayList<>();
descriptions.add("설명1");
descriptions.add("설명2");
descriptions.add("설명3");
// 병렬 반복을 위한 인덱스 기반 for문
for (int i = 0; i < elements.size(); i++) {
Element element = elements.get(i);
String description = descriptions.get(i);
System.out.println(element + ": " + description);
}
item 59. 라이브러리를 익히고 사용하라
- 라이브러리는 알고리즘에 능통한 여러 전문가가 오랜 시간에 걸쳐 만들어낸 메서드다. 이를 제대로 알고 사용하자.
- 개발자들이 많이 쓸 것같은 기능이 있다면, 개발하기보다 라이브러리를 찾아보자‼️
중요도 및 장점
- 라이브러리를 잘 확인하고 사용하자
- Java 7부터는 Random을 쓰지 않는 게 좋다.
- 대신, ThreadLocalRandom으로 대체하면 대부분 잘 작동한다.
- 포크-조인 풀이나 병렬 스트림에서는 SplittableRandom을 사용하자.
- 핵심적인 일과 크게 관련없는 문제를 해결하느라 시간을 낭비하지 않아도된다. 라이브러리를 활용하자!
- 라이브러리 성능은 지속해서 개선된다.
- 기능이 점점 많아진다.
- 많은 사람에게 낯익은 코드가 된다.
많은 프로그래머들은 직접 구현해서 사용한다. 문제!
어떤 라이브러리가 있는지 모르기 때문이다!
메이저 릴리스마다 주목할 만한 수많은 기능이 라이브러리에 추가된다. 자바는 메이저 릴리스마다 새로운 기능을 설명하는 웹페이지를 공시하는데, 읽어볼만 하다.
알아두면 좋은 라이브러리
- 적어도 java.lang, java.util, java.io와 그 하위 패키지는 필수로 알아두자.
- java.util.concurrent의 동시성 기능
item 60. 정확한 답이 필요하다면 float과 double은 피하라
float과 double은 과학과 공학 계산용으로 설계되었다.
- 넓은 범위의 수를 빠르게 정밀한 ‘근사치’로 계산하도록 설계되었다.
- 따라서 정확한 결과가 필요할 때는 사용하면 안 된다.
- 특히 금융 관련 계산과 맞지 않는다. → 0.1, 10의 음의 거듭제곱 수 등을 표현할 수 없다.
돈계산 해보기
// 1.03 달러 중 42센트를 썼다. 남은 돈은?
System.out.println(1.03 - 0.42); // 0.6100000000000001
// 1달러로 10센트짜리 사탕 9개를 구매하였다. 남은 돈은?
System.out.println(1.00 - 9 * 0.10); // 0.09999999999999998
- 반올림을 해도 틀린 답이 나올수 있다. 사탕 예제 356p.
결론! 금융 계산에는 BigDecimal, int 혹은 long을 사용해야 한다.
장점
- 8 가지 반올림 모드를 이용하여 반올림을 완벽히 제어할 수 있다. ⇒ 법으로 정해진 반올림을 수행해야 하는 비즈니스 계산에서 아주 편리하다.
단점
- 기본 타입보다 쓰기가 훨씬 불편하다.
- 느리다.
결론
성능이 중요하고 소수점을 직접 추적할 수 있고 숫자가 너무 크지 않다면 int/long을 사용하자!
- int - 9 자리 10진수
- long - 18자리 10진수
- BigDecimal - 그 이상
// 문자열로 초기화
BigDecimal a = new BigDecimal("1.03");
BigDecimal b = new BigDecimal("0.42");
// 사칙 연산
System.out.println(a.add(b)); // 1.03 + 0.42 = 1.45
System.out.println(a.subtract(b)); // 1.03 - 0.42 = 0.61
// 기본 상수
System.out.println(BigDecimal.ONE);
System.out.println(BigDecimal.TEN);
// 소수점 처리
System.out.println(a.setScale(0, RoundingMode.UP)); // 2
System.out.println(a.setScale(0, RoundingMode.CEILING)); // 2
System.out.println(a.setScale(0, RoundingMode.HALF_UP)); // 1
'언어 > JAVA' 카테고리의 다른 글
[JAVA] 날짜/시간 클래스 알아보기(Date, LocalDateTime, ZoneDateTime) (1) | 2024.03.08 |
---|---|
[JAVA] Effective Java. 6장 열거 타입과 애너테이션 (2) | 2024.01.12 |
[JAVA] 익명 클래스(Anonymous Class), 람다식(Lambda) (1) | 2023.10.22 |
[JAVA] JAVA String | StringBuilder | 문자열 결합 연산 | 문자열 연산의 복잡도 (0) | 2023.05.19 |