Item61. 박싱된 기본 타입보다는 기본 타입을 사용하라

  • java data type은 primitive type과 reference type 이 있으며, primitive type 에는 각각에 대응되는 reference type이 존재하는데 이를 boxing 된 primitive type이라고 한다.
    ( ex) int - Integer , long - Long )

primitive 타입과 boxing된 primitive type간의 차이점

  1. 기본 타입의 값은 언제나 유효하나 (기본값이 존재) , reference type은 null을 가질 수 있다.
  2. 메모리측면에서 기본타입이 더 가볍다
  3. 기본 타입에 비해 reference type은 값에 더해 식별성(identity)라는 속성을 갖는다. 즉 값이 같아도 서로 다르다고 식별될 수 있다.
Read more

Item60.정확한 답이 필요하다면 float와 double은 피하라

  • float 와 double 타입은 정밀한 근사치로 계산하기 떄문에 정확한 계산이 필요한 경우 (금융) 사용하면 안된다.
1
2
3
4
public static void main(String[] args) {
System.out.println(1.03-0.42); // 0.6100000000000001
System.out.println(1.00 - 9 * 0.10); // 0.09999999999999998
}

double, long 타입을 사용해 정확한 계산이 나오지 않는 또다른 예시는 다음과 같다.

1
2
3
4
5
6
7
8
double funds = 1.00;
int itemsBought = 0;
for (double price = 0.10 ; funds >= price ; price += 0.10){
funds -= price;
itemsBought++;
}
System.out.println(itemsBought + "개 구입"); // 3개 구입
System.out.println("잔돈(달러):"+ funds ); // 잔돈(달러):0.3999999999999999

금융 계산 처럼 정확한 계산이 필요한 경우에는 double,long 대신해서

  • 성능이 중요한 경우라면 int,long타입을 사용하고 , 소수점을 직접 관리한다
  • 성능이 중요하지 않은 경우라면 , BigDecimal 타입을 사용하고, BigDecimal API를 활용한다.
1
2
3
4
5
6
7
8
9
10
// BigDecimal을 활용한 정확한 계산
final BigDecimal TEN_CENTS = new BigDecimal("0.10");
BigDecimal funds = new BigDecimal("1.00");
int itemsBought = 0;
for (BigDecimal price = TEN_CENTS ; funds.compareTo(price) >= 0 ; price = price.add(TEN_CENTS)){
funds = funds.subtract(price);
itemsBought++;
}
System.out.println(itemsBought + "개 구입");
System.out.println("잔돈(달러):"+ funds );
Read more

Item59. 라이브러리를 익히고 사용하라

표준 라이브러리를 사용함으로 얻을 수 있는 장점은 다음과 같다.

  1. 코드를 작성한 전문가의 지식과 앞서 사용한 프로그래머들의 경험을 활용할 수 있다.
  2. application 기능 개발에 집중할 수 있다.
  3. 표준 라이브러리를 사용하는 client는 노력하지 않아도 성능이 지속해서 개선된다.
  4. 다음 릴리즈에 기능이 추가될 가능성이 있다.
1
2
3
4
5
6
7
8
// java9의 inputStream에 추가된 transferTo Method

public static void main(String[] args) throws IOException {
try(InputStream in = new URL(args[0]).openStream()){
in.transferTo(System.out); //url 내용을 console에 출력
}
}

특정 기능을 개발하려고 할때, 우선은 표준 라이브러리에서 제공하는지 확인하고, 제공하지 않는다면 서드파티 라이브러리를 활용하고 서드파티 라이브러리에서도 제공하지 않으면 그때 직접 구현하자.

Read more

Item58. 전통적인 for문보다는 for-each문을 사용하라

1
2
3
4
5
6
7
8
// collection 순회
for(Iterator<Element> i = c.iterator(); i.haxNext(); ){
Element e = i.next();
}
// 배열 순회
for(int i = 0; i < a.length ; i++){
//a[i];
}

for문을 사용해 collection과 배열을 순환할 경우, 불필요한 반복자와 인덱스가 사용된다. 또한 collection이냐 배열이냐에 따라 코드 형태와 위와 같이 달라진다.

반면 향상된 for문을 사용할때는 반복자와 인덱스 변수를 사용하지 않고, collection과 배열일때 모두 코드 형태가 동일하다.

1
2
3
for ( Element e : elements ){
// e
}
  • for-each문은 collection과 배열은 물론 Iterable interface를 구현한 객체라면 무엇이든 순회할 수 있다.
Read more

Item57. 지역변수의 범위를 최소화하라

  • 지역변수의 유효 범위를 최소로 줄이면 코드 가독성과 유지보수성이 높아지고 오류 가능성은 낮아진다.

지역변수의 유효범위를 최소화하는 방법

  • 지역변수의 범위를 줄이는 방법은 처음쓰일때 선언하는 것이다. 미리 선언해두면 블록 스코프가 끝났을떄도 살아있는 경우가 있다.
  • 지역변수는 선언과 동시에 초기화해야 한다. 초기화에 필요한 정보가 충분하지 않다면 충분할떄까지 선언을 미루어야 한다.
Read more

Item56. 공개된 API요소에는 항상 문서화 주석을 작성하라

문서가 잘 갖춰지지 않은 API는 사용하기 헷갈려서 오류의 원인이 되기 쉽다.

method용 문서화 주석에는 해당 method 와 client 사이의 규약을 명료하게 기술해야 한다.

  • how가 아닌 what 을 기술해야 한다. (어떻게 동작하는지보다 무엇을 하는지를 기술해야 한다.)
  • client가 해당 method를 호출하기 위한 precondition을 모두 나열해야 한다.
  • client가 해당 method를 호출한 뒤에 postcondition을 모두 나열해야 한다.
  • 시스템의 어떠한 변화를 가져오는 경우, 문서에 밝혀야 한다.

@param,@return,@thorws 태그

  • 매개변수에 @param 태그 , 반환타입이 void가 아니라면 @return 태그 , 발생할 가능성이 있는 모든 예외에 @throws 태그를 달아야 한다.
  • 관례상 @param 태그 , @return 태그의 설명은 해당 매개변수가 뜻하는 값이나 반환값을 설명하는 명사구를 쓴다.
  • @throws 태그의 설명은 if로 시작해서 해당 예외를 던지는 조건을 설명하는 절이 뒤따른다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14

/***
* Returns the element at the specified position in this list. --> method가 무엇을 하는지 기술하는 요약 설명
(this:호출된 method가 가르키는 객체)
* <p> This method is <i> not </i> guaranteed to run in constant time.
* In some complementations it may run in time proportional to element position
*
* @param index idnex of element to return; must be non-negative and less than the size of this list
* @return the element at the specified position in this list
* @throws IndexOutOfBoundsException if the index is out of range
* ( {@code index< 0 || index >= this.size() })
*/

E get(int index);

javadoc utility는 문서화 주석을 HTML로 변환함으로 문서화 주석안의 HTML 요소들이 최종 HTML 문서에 반영된다.
{@code} 태그는 코드용 폰트로 렌더링, 이스케이프 기능을 제공한다.

  • 문서화 주석의 첫 번쨰 문장은 해당 요소의 요약 설명으로 간주되며, 이 요약 설명은 반드시 대상의 기능을 고유하게 기술해야 한다. 즉 한 class안에서 요약 설명이 똑같은 멤버가 둘 이상이면 안 된다. (다중정의된 method라고 하여도 허용되지 않는다.)
  • 첫번째 문장의 마무리는 마침표여야 한다. 중간에 의도치 않는 마침표가 들어간 경우에는 @literal 태그 (아래에 설명 ) 를 활용하자
1
2
3
4
/**
* A suspect , such as Colonel Mustard or {@listeral Mrs.} Peacok.
*/
public class Suspect{...}
  • java 10부터는 이 요약 설명부분이 @summary 태그로 분리되었으니, 중간에 마침표를 별도로 literal태그로 감쌀필요가 없다
1
2
3
4
/**
* {@summary A suspect , such as Colonel Mustard or Peacok.}
*/
public class Suspect{...}
  • 요약 설명은 method와 생성자의 경우에는 주어가 없는 동사구 여야 한다.
1
- ArrayList (int intialCapacity) : Constructs an empty list with the specified initial capacity
  • 요약 설명은 class,interface,field의 경우에는 대상을 설명하는 명사절이여야 한다.
1
- Instant : An instantaneous point on the time-line.
Read more

Item55. Optional 반환은 신중히 하라

Optional API

jdk 8 이전에는 method가 특정 조건에서 값을 반환할 수 없을 때 취할 수 있는 선택지는 null을 반환하거나 , 예외를 던지는 방법이 있었다.

예외를 던지는 경우에는 진짜 예외적인 상황에만 사용해야 하며, null을 반환하는 경우에는 null처리로직이 client코드에 들어가야한다는 단점이 있었다.

jdk 8 이후부터는 Optional API 가 추가되었다.

Optional<T> 는 null 이 아닌 T 타입의 참조값을 하나 담거나, 혹은 아무것도 담지 않을 수 있다.

보통은 T를 반환해야 하지만 특정 조건에서는 아무것도 반환하지 않아야 할때, T 대신 Optional<T>를 반환하도록 선언하면 된다.

1
2
3
4
5
6
7
8
9
10
11
12
13
// collection에서 최댓값을 구하는 method로 collection이 비어있으면 IllegalArgumentException을 던진다. 
public static <E extends Comparable<E>> E max(Collection<E> c){
if(c.isEmpty()){
throw new IllegalArgumentException("빈 collection");
}
E result = null;
for (E e : c) {
if(result == null || e.compareTo(result)>0){
result = Objects.requireNonNull(e);
}
}
return result;
}

위 method를 Optional을 반환하도록 변경하면 다음과 같다.

1
2
3
4
5
6
7
8
9
10
11
12
public static <E extends Comparable<E>> Optional<E> max(Collection<E> c){
if(c.isEmpty()){
return Optional.empty();
}
E result = null;
for (E e : c) {
if(result == null || e.compareTo(result)>0){
result = Objects.requireNonNull(e);
}
}
return Optional.of(result);
}
  • 빈 optional은 Optional.empty()
  • null이 아닌 값이 들어있는 optional은 Optional.of(value)
  • null일 수 있는 값이 들어있는 optional은 Optional.ofNullable(value)를 사용하면 된다.

주의사항은 optional을 반환하는 method에는 당연히 null을 반환하면 안된다. 이는 API 도입 취지를 완전히 무시하는 행동이다.

추가로 Stream의 max연산을 비롯한 상당수의 종단 연산이 Optional 반환을 지원한다.

1
2
3
public static <E extends Comparable<E>> Optional<E> max(Collection<E> c){
return c.stream().max(Comparator.naturalOrder());
}
Read more

Item54. null이 아닌 빈 collection 이나 배열을 반환하라

다음은 주변에서 흔히 볼 수 있는 method이다.

1
2
3
4
5
private final List<Cheese> cheeseInStock = null;

public List<Cheese> getCheese(){
return cheeseInStock.isEmpty() ? null : new ArrayList<>(cheeseInStock);
}

null을 반환해야할 특별한 이유가 있는 게 아니라면 위와 같이 빈 collection 대신에 null을 반환하는 코드는 이를 사용하는 client가 아래와 같이 null처리 로직을 작성하게끔 만든다.

1
2
3
4
List<Cheese> cheese = shop.getCheese();
if(cheese != null){
// ...
}

collection이나 배열같은 container가 비었을 떄 null을 반환하는 method를 사용할 때면 항상 이와 같은 방어 코드를 넣어주어야 한다.

null을 반환하는 족에서는 빈 container 를 만드는데도 비용이 드니 null을 반환하는게 낫다는 주장도 있지만 이는 잘못된 주장이다. 빈 container를 만드는데는 성능 차이가 미미하며, 굳이 빈 collection 과 배열은 새로 할당하지 않고도 반환할 수 있다.

Read more

Item53. 가변인수는 신중히 사용하라

가변인수

가변인수 method는 명시한 타입의 인수를 0개 이상 받을 수 있다.
가변인수 method를 호출하면 가장 먼저 인수의 개수와 길이가 같은 배열을 만들고, 인수들을 이 배열에 저장하여 가변인수 method에 건네준다.

1
2
3
4
5
6
7
static int sum(int... args){
int sum = 0;
for (int arg : args) {
sum += arg;
}
return sum;
}

가변인수 개수가 1개 이상이여야만 할떄가 있다. 예를 들면 최솟값을 찾는 method에서 인수0개가 들어오면 exception이 터져야 한다.

1
2
3
4
5
6
7
8
9
10
11
12
static int min(int... args){
if(args.length == 0){
throw new IllegalArgumentException("인수가 1개 이상 필요합니다.");
}
int min = args[0];
for(int i = 1; i< args.length ; i++) {
if (args[i] < min) {
min = args[i];
}
}
return min;
}

위 방식의 문제점은 가변인수의 인수를 넣지 않아도 호출되어 runtime에 실패한다는 점이다. 또한 가변인수에 대한 유효성 검사도 명시적으로 해야 한다.

이를 더 나은 방법으로 runtime이 아닌 compile time에 에러가 나오도록 수정하면 다음과 같다.

1
2
3
4
5
6
7
8
9
static int min(int firstArg , int... remainingArgs){
int min = firstArg;
for (int arg : remainingArgs) {
if(arg<min){
min = arg;
}
}
return min;
}

매개변수를 2개 받아서, 첫번째 매개변수는 필수여야하는 인수를 넣고, 두번째는 가변인수로 받아서 인수가 1개 미만일떄는 compile error가 터진다

Read more

Item52. 다중정의는 신중히 사용하라

매개변수가 동일한 부모 타입일떄 overloading의 문제점

다음과 같이 collection을 집합, 리스트 , 그외로 구분하고자 만든 CollectionClassifier 프로그램이 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class CollectionClassifier {

public static String classify(Set<?> s){
return "set";
}
public static String classify(List<?> lst){
return "list";
}
public static String classify(Collection<?> c){
return "others";
}

public static void main(String[] args) {
Collection<?>[] collections = {
new HashSet<String>(),
new ArrayList<BigInteger>(),
new HashMap<String,String>().values()
};

for (Collection<?> c : collections) {
System.out.println(classify(c));
}
}
}

collection에 set,list,map을 넣어주면 예상결과는 set,list,others인데 실제로는 others만 세번 출력된다.
그 이유는 다중정의(overloading)된 classify증에 어느 method를 실행할지가 compile time에 정해지기 떄문이다. compile time에 for문안의 c는 항상 Collection<?> 타입이다. runtime에는 타입이 매번 달라지지만 호출할 method를 선택하는데는 영향을 주지 못한다.

overloading vs overriding

재정의한 method는 동적으로 runtime에 선택되고, 다중정의한 method는 정적으로 compile time에 선택된다. method를 재정의했다면 해당 객체의 runtime 타입이 어떤 method를 호출할지의 기준이 된다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class Wine{
String name() {return "Wine";}
}

class SparklingWine extends Wine{
@Override
String name() {return "SparklingWine";}
}
class Champagne extends SparklingWine{
@Override
String name() {return "Champagne";}
}

public class Overriding {

public static void main(String[] args) {
List<Wine> wineList = List.of(new Wine(), new SparklingWine(), new Champagne());
for (Wine wine : wineList) {
System.out.println("wine.name() = " + wine.name());
}
}
}
1
2
3
4
//출력 결과 
wine.name() = Wine
wine.name() = SparklingWine
wine.name() = Champagne

위와 같이 runtime에 객체 타입을 보고 재정의한 method가 호출된다. 반면에 다중정의한 method사이에서는 객체의 runtime 타입은 전혀 중요치 않다. 선택은 compile time에 오직 매개변수의 compile time 타입에 의해 이뤄진다.

CollectionClassifier program이 runtime에 객체 타입에 따라 반환결과를 다르게 하고 싶다면 아래와 같이 classify method를 모두 하나로 합친 뒤, instanceof 로 명시적으로 runtime에 수정해야 한다.

1
2
3
public static String classify(Collection<?> c){
return c instanceof Set ? "set" : c instanceof List ? "list" : "others";
}
  • overloading시 compile time에 호출 method가 매개변수 타입을 보고 결정되므로, overriding 과는 동작방식이 다르다. 따라서 client가 사용했을때, 혼동을 일으키는 상황을 되도록 주지 않는 것이 좋다.
Read more