만쥬의 개발일기
article thumbnail

아이템 60 - 정확한 답이 필요하다면 float와 double은 피하라

float와 double 타입은 과학 / 공학 계산용으로 설계되었다.
해당 자료형들은 부동소수 타입으로, 정확한 결과를 위해서는 사용을 지양해야 한다.

실수를 표현하는 방법 두가지

실수를 표현하는 방법은 다음 두가지이다.

  • 고정 소수점 방식: 소수점에 이용하는 범위를 고정해 수의 표현 범위가 크지 않다.
  • 부동 소수점 방식: 소수점을 이용하지 않아 그만큼 다른 수를 표현할 수 있도록 더 넓은 표현 범위를 가진다.

부동 소수점 방식을 사용하는 자료형인 double, float등 에서는 실수 연산에서는 소수점 단위 값을 정확히 표현하는 것이 아닌 근사값으로 처리해 오차가 발생한다.

정확한 계산을 위해서는

따라서 정확한 계산을 위해서는 BigDecimal 또는 int, long을 사용해야 한다.

BigDecimal 사용법

BigDecimal num = new BigDecimal("0.01");

위와 같이 문자열 형태로 생성자에 전달해 초기화를 해주어야 한다.

BigDecimal에서 제공하는 메서드는 다음과 같다.

// 최대, 최소
num1.min(num2);
num1.max(num2);

// 소수점 맨 끝의 0 까지 비교
num1.equals(num2);

// 소수점 맨 끝의 0을 무시하고 비교
num1.compareTo(num2);

// 사칙연산
num1.add(num2);
num1.subtract(num2);
num1.multiply(num2);
num1.divide(num2);
num1.remainder(num2);

BigDecimal을 사용하면 쓰기가 불편하고 훨씬 느리기 때문에 아쉽다. 따라서 열여덟자리 이하 십진수로 표현할 수 있따면, 소수점을 정수로 올려 int나 long을 쓰는 것이 해결법이 될 수 있다.

아이템 61 - 박싱된 기본 타입보다는 기본 타입을 사용하라

자바의 데이터 타입은 크게 두 가지로 나뉜다.

  • 기본 타입
    • int
    • double
    • boolean
  • 참조 타입
    • String
    • List
    • 각 기본타입에 대응하는 참조타입 (Integer,Double,...)

오토박싱과 오토언박싱 덕분에 두 타입을 크게 구분하지 않고 사용 가능하지만, 어떤 타입을 사용하는지는 상당히 중요하다.

기본 타입 vs 박싱된 기본 타입

  1. 기본 타입은 값만 가지고 있으나 박싱된 기본타입은 식별성이란 속성을 갖는다.
    1. 즉, 값이 같아도 서로 다르다고 식별할 수 있다.
  2. 기본 타입의 값은 언제나 유효하나, 박싱된 기본 타입은 null값을 가질 수 있다.
  3. 기본 타입이 박싱된 기본 타입보다 시간과 메모리 사용면에서 더 효율적이다.

박싱된 기본 타입의 잘못된 사용 예시

static Integer i; 

public static void main(String[] args) { 
    if (i == 42) { // NullPointerException 발생
         System.out.println("unbelievable"); 
    } 
}

inetger와 int를 비교할 때 오토 언박싱이 일어나고, 박싱된 기본 타입의 초깃값은 null이기 때문에 null인 참조를 언박싱하며 nullpointerexception이 발생한다.

박싱된 기본타입은 언제?

  • 컬렉션의 원소, 키, 값으로 쓸 때 (컬렉션은 기본 타입 사용 불가)
  • 타입 매개변수로 사용할 때
  • 리플렉션을 통해 메서드를 호출할 때

결론

가능하면 기본 타입을 사용하자 .

아이템 62 - 다른 타입이 적절하다면 문자욜 사용을 피하라

String은 텍스트를 표현하도록 설계되었고, 다른 타입을 표현할 때는 쓰지마라.

여러 요소가 혼합된 데이터를 하나의 문자열로 표현하는 것은 좋지 않다.

String compoundKey = className + "#" + i.next();

위와 같이 각 요소를 개별로 접근하려면 문자열을 파싱해야 하기에 느리고 귀찮고 오류 가능성도 커진다.

또한 적절하게 equals나 compareTo 같은 메서드를 제공할 수도 없다.

따라서 다음과 같이 private 정적 멤버 클래스로 선언하는 것이 좋다 .

public class Compound { 
    private CompoundKey compoundKey; 

    private static class CompoundKey { 
        private String className; 
        private String delimiter; 
        private int index; 

        public CompoundKey(String className, String delimiter, int index{ 
            this.className = className; 
            this.delimiter = delimiter; 
            this.index = index; 
        } 
    } 
}

결론

아무때나 문자열 쓰지마라.

아이템 63 - 문자열 연결은 느리니 주의하라

문자열 연결 연산자 + 는 여러 문자열을 하나로 합쳐준다.

그러나 문자열 연결 연산자로 문자열 n개를 잇는 시간은 $O(n^2)$ 이다 .

문자열은 불변이므로 두 문자열을 연결할 경우 양쪽의 내용을 모두 복사해야 하기 때문이다.

문자열을 연결할 때는 String대신 StringBuilder의 append 메서드를 사용하자.

약 4000배 차이

String '+'연산의 방식

String의 + 연산은 a의 길이 + b의 길이를 가지는 새로운 배열을 만들어서 a와 b를 모두 복사해넣고 사용한다.

이를 반복문으로 반복해서 + 연산 실행 시 다음과 같이 된다.

 

StringBuilder 연산 방식

반면에 StringBuilder는 미리 일정한 크기의 배열을 잡아두고 거기에 String을 붙여나가는 방식이다.

배열의 크기가 가변이기 때문에 배열이 가득차게 되면 기존보다 2배 더 크게 새로운 배열을 만든다.

 

만약 멀티쓰레드 방식이라면 synchronized를 가진 StringBuffer를 사용하고, join 또한 StringBuilder를 내부적으로 사용하므로 빠르다.

+ 연산은 반복문에서 최대한 지양할 것.

profile

만쥬의 개발일기

@KangManJoo

포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!