ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • Java의 정석(3판)에서의 정리와 생각해볼 거리들
    Java 2019. 7. 30. 23:33
    728x90

    1. Arrays.toString()

      char 배열이 아닌 int 같은 배열을 toString 하기 위해서는 java.util.Arrays의 toString() 메소드가 필요함

      책에 나와 있는 사용 방법

    // import java.util.*; 를 한 상태

    int [] iArr = { 100, 95, 80, 70, 60 };
    System.out.println(Arrays.toString(iArr));


    2. 배열의 복사 (System.arraycopy vs java.util.Arrays.copyOf)

      Java의 정석 3판에서는 배열의 복사를 System.arraycopy()를 이용하도록 작성되어있다.

      성능상으로 어떻게 동작할지 등 궁금해서 찾아보았음.

      http://www.ssiso.net/cafe/club/club1/board1/content.php?board_code=javaStudy%7CmethodList&idx=2455&club=javaStudy&cp=3&cb=1&search=&search_word=

      https://codeday.me/ko/qa/20190517/571181.html

      위 두가지 링크를 보면 어느정도 생각해볼 수 있을 것 같은데, 일반적인 코드로는 System.arraycopy가 조금 느린 것 같다. JIT 컴파일러가 최적화를 시켜주기 어려운 것이 그 이유라는 것 같음. 두번째 링크를 보면 빈 루프로 트릭을 쓰면 속도가 빨라질 수 있다는데 해석이 번역기로 되어있는 것 같아서 내용 파악이 어려움. JIT 컴파일러에 대해선 차후 자세히 공부해보자.


    3. Java String은 Immutable하다.

      Java에서의 String은 불변하다. 즉 값을 변경하려고 하면 기존에 있는 String 객체의 값이 바뀌는 것이 아니라, 변경된 String 객체를 새로 만들어서 반환해준다는 차이점이 있다. 이는 결국 String 객체를 새로 만드는데 리소스를 사용하게된다.

      반면 StringBuffer은 Immutable하지 않다. 때문에 값을 변경하려고 해도 새로운 String 객체를 생성하는 것이 아니라, 기존의 String 객체에서 값만 변경한다.

      ** 때문에 같은 코드에서 문자열의 값을 변경하는 루프를 도는 코드가 있다면 String과 StringBuffer를 사용했을 때 StringBuffer가 더 빠르다.

    참고 : 자바에서 Immutable이 뭔가요?(https://hashcode.co.kr/questions/727/%EC%9E%90%EB%B0%94%EC%97%90%EC%84%9C-immutable%EC%9D%B4-%EB%AD%94%EA%B0%80%EC%9A%94)


    4. StringBuilder vs StringBuffer ?

      둘다 String과 비교했을 때 Immutable 하지 않아서 문자열의 값을 변경해도 새로 객체를 생성하지 않는다는 차이가 있었다. (3에서 설명한 내용처럼)

      그럼 이 둘 사이에는 어떤 차이가 있을까?

      그 차이는 멀티쓰레드 환경에서 동기화를 해주고 안해주고의 차이가 있다.

      StringBuilder : 동기화 해주지 않음

      StringBuffer : 동기화를 해줌

      위와 같은 차이는 다음과 같은 결과를 만들어낸다.

      StringBuilder는 동기화를 해주지 않기 때문에 (StringBuffer보다) 빠를 수 있다. 하지만 경쟁상태일 때 데이터의 정합성이 깨질 우려가 있다.

      StringBuffer는 동기화를 해주기 때문에 (StringBuilder보다) 느릴 수 있다. 하지만 경쟁상태에서도 데이터의 정합성을 지킬 수 있다.

    참고 : Java에서 String, StringBuilder, StringBuffer의 차이(https://novemberde.github.io/2017/04/15/String_0.html) - 해당 링크로 가면 StringBuffer와 StringBuilder의 동기화 여부에 대한 차이를 확인 가능

    참고2 : 위 3가지 클래스의 속도 차이에 대한 비교설명(https://jeong-pro.tistory.com/85)


    5. 인스턴스 메소드(non-static method)와 static 메소드는 정말 실행시간이 차이가 날까?

      자바의 정석 3판 p.278 부분을 보면 다음과 같은 내용이 있다.

    static을 안 붙인 메서드(인스턴스메서드)는 실행 시 호출되어야할 메서드를 찾는 과정이 추가적으로 필요하기 때문에 시간이 더 걸린다.

      해당 내용에 대해 찾아보니 인스턴스 메소드를 호출할 때는 virtual function lookup을 통해 실제 호출해야할 메소드를 검색하는 과정과, 숨어있는 this 파라미터를 전달하는 오버헤드가 있다고 한다.

      실제 오버헤드가 존재하는지를 확인하려고 좀 더 찾아보니 스텍오버플로우에 나와있는 여러 답변에서 현존하는 컴파일러들에서는 이를 적절히 최적화해주기 때문에 속도차이가 나지 않는다고 한다. 17년 12월 실험했던 벤치마크상으로는 static 메소드가 0.75% 정도 빠르긴 했다. 결론적으로 책에도 나와있는 것처럼 어떤 것을 선택할 것이냐가 아니라, 모든 인스턴스에서 사용하는 메소드이면서 인스턴스 변수에 접근하지 않는다면 static을 사용하는게 좋겠다. (참고 링크)


      ** virtual function lookup은 뭘까?

         Java에서는 다형성을 유지하기 위해 부모 클래스에서 상속받은 메소드를 자식의 메소드로 오버라이딩 된 상태에서 메소드를 호출할 경우 어떤 메소드를 호출할지 검색하는 과정이 필요하다. 그 동작을 virtual function lookup을 말하는 것이다. (참고 - C++의 virtual 함수. 가상화. JAVA랑 C++의 차이점) 참고링크로 가면 코드로 잘 설명을 해주고 있다. C++에서는 virual 키워드로 직접 지정해주던 것을 Java에서는 자동으로 해주고 있다.


    6. 다형성에서 조상타입의 레퍼런스 변수로 자손타입의 인스턴스를 참조할 수 있는 것처럼, 인터페이스를 구현한 클래스의 인스턴스를 인터페이스의 레퍼런스 변수로 참조하는 것이 가능하다.

    또한 메소드의 리턴타입으로 인터페이스의 타입을 지정하는 것도 가능함.


    ex)

    Fightable 은 인터페이스이고, Fighter은 Fightable을 implement하고있다.


    Fightable method() {

    Fighter f = new Fighter();

    return f;

    }


    위 예제에서 f에는 Fightable 인터페이스를 구현한 Fighter의 인스턴스가 반환된다.

    책에서는 다음 문장 그대로 반복해서 외우라고함.

      " 리턴타입이 인터페이스라는 것은 메서드가 해당 인터페이스를 구현한 클래스의 인스턴스를 반환한다는 것을 의미한다. "



    7. Java의 Exception에는 Runtime Exception과 그 외의 익셉션들이 있다.

     * 그림 출처 : http://esanglee.blogspot.com/2016/05/exception-handling.html


    Runtime Exception은 unchecked 예외이기 때문에 반드시 try-catch를 작성해줄 필요는 없다. (컴파일이 문제없이 된다는 의미이지 실행중에 발생하는 예외는 어쩔 수 없이 발생한다.) 반면 Runtime Exception이 아닌 Exception들은 반드시 try-catch를 작성해주거나 최소 메소드 선언부에 throws로 호출한 메소드로 예외를 던져줘야한다. 이것을 checked 예외라고 한다.

    하지만 예외가 발생해도 어찌할 도리가 없는 checked 예외들도 존재한다. 그런 예외들마다 불필요한 try-catch문을 작성해야되는데 처리할 방법이 마땅치 않은데 예외를 처리해주는 것이 매우 귀찮을 것이다.

    이것을 막기 위해서 chained exception을 사용하면 되는데 사용 방법은 checked 예외를 RuntimeException의 생성자의 인자로 넘겨주는 방법이다. 다음과 같이 작성하면 된다.

    throw new RuntimeException(new checkedException("checkedException"));

    이렇게 작성해주면 checked 예외인 checkedException을 처리해주지 않고도 컴파일이 가능하다.


    8. System.identityHashCode 메소드를 사용하면 오버라이딩 되지 않은 Object 객체의 hashCode의 값을 가져올 수 있다.

    사용 방법은 API 그대로이다.

    System.identityHashCode(Object x)

    인자로는 레퍼런스 변수를 넣어주면되는데, 자세한 내용은 API 문서 참조(링크)

    의문이 드는 것은 2가지가 있는데, 먼저 첫째로 왜 Object에 있는 hashCode를 사용하면 되는데 따로 만들었을까?

    확실하지는 않지만 내 추측대로면 Object의 hashCode는 오버라이딩이 될 수 있고, 필요에따라 오버라이딩을 해야하는 경우가 있는데, 그 상황에서 해당 객체의 원래 hashCode를 알고싶을때 사용하려는 것 같다.

    두번째는 자바의 정석 3판 p.454의 [참고]에 나와있는 내용인데 다음과 같이 써있다.

    System.identityHashCode(Object x)의 호출결과는 실행 할 때마다 달라질 수 있다.

    이 내용은 검색해봐도 실행시마다 달라질 수 있다는 내용의 원인에 대해선 찾지 못했다. hashCode와의 차이에 대해서가 주로 검색되어서 이유는 알 수 없었음. (추후 알게되면 수정 예정)




Designed by Tistory.