Chapter 09 Java.lang 패키지와 유용한 클래스

Hyeonjun·2022년 10월 16일
0

자바의 정석

목록 보기
7/7
post-thumbnail

1. Java.lang 패키지

  • java.lang 패키지의 클래스들은 import 없이도 사용할 수 있다.
  • String, System 클래스도 java.lang 패키지에 포함되기 때문.

1.1 Object 클래스

equals(Object o)

public boolean equals(Object obj) {
      return (this == obj);
}
  • 매개변수로 객체의 참조변수를 받아 비교하여 그 결과를 boolean값으로 알려준다.
  • 참조변수의 값으로 판단한다.
public boolean equals(Object anObject) {
    if (this == anObject) {
        return true;
    }
    if (anObject instanceof String) {
        String aString = (String)anObject;
        if (coder() == aString.coder()) {
            return isLatin1() ? StringLatin1.equals(value, aString.value)
                              : StringUTF16.equals(value, aString.value);
        }
    }
    return false;
}
  • String의 경우는 equals를 오버라이딩해서 문자열을 비교한다.
  • 문자열의 코드에 따라 비교함.
  • 전체적인 로직은 각 문자를 하나씩 비교하며 같은지 확인한다.

hashCode()

@HotSpotIntrinsicCandidate
public native int hashCode();
  • 해싱 기법에 사용되는 ‘해시함수(hash function)’을 구현한 것이다.
  • 해싱은 다량의 데이터를 저장하고 검색하는데 유용하다.
  • 해시함수를 찾고자 하는 값을 입력하면, 그 값이 저장된 위치를 알려주는 해시코드를 반환한다.
  • 일반적으로 해시코드가 같은 두 객체가 존재하는 것이 가능하지만, Object 클래스에 정의된 hashCode 메서드는 객체의 주소값으로 해시코드를 만들어 반환하기 때문에 32bit JVM에서는 서로 다른 두 객체는 결코 같은 해시코드를 가질 수 없었지만, 62bit JVM에서는 8byte 주소값으로 해시코드(4byte)를 만들기 때문에 해시코드가 중복될 수 있다.
  • 클래스의 인스턴스 변수의 값으로 객체의 같고 다름을 판단해야하는 경우라면 equals메서드 뿐만 아니라 hashCode메서드도 적절히 오버라이딩 해야한다.
  • 같은 객체라면 hashCode 메서드를 호출했을 때의 결과값인 해시코드도 같아야 하기 때문이다.
  • 해싱기법을 사용하는 HashMap이나 HashSet과 같은 클래스에 저장할 객체라면 반드시 hashCode 메서드를 오버라이딩 해야한다.
public static int hashCode(byte[] value) {
    int h = 0;
    int length = value.length >> 1;
    for (int i = 0; i < length; i++) {
        h = 31 * h + getChar(value, i);
    }
    return h;
}
  • String클래스는 문자열의 내용이 같으면 동일한 해시코드를 반환한다.
  • System.identityHashCode(Object o)는 Object클래스의 hashCode 메서드처럼 객체의 주소값으로 해시코드를 생성하므로 모든 객체에 대해 다른 해시코드값을 받을 수 있다.

toString()

public String toString() {
    return getClass().getName() + "@" + Integer.toHexString(hashCode());
}
  • 인스턴스에 대한 정보를 문자열로 제공할 목적으로 정의한다.
  • 인스턴스 변수에 저장된 값들을 문자열로 표현한다는 뜻.
  • 따로 오버라이드 하지 않는다면, 클래스의 이름과 16진수 해시값을 출력하게 된다.
/**
	* This object (which is already a string!) is itself returned.
	*
	* @return  the string itself.
	*/
public String toString() {
    return this;
}
  • String클래스의 toString()은 String 인스턴스가 갖고있는 문자열을 반환하도록 오버라이딩 되어있다.

clone()

@HotSpotIntrinsicCandidate
protected native Object clone() throws CloneNotSupportedException;
  • 자신을 복제하여 새로운 인스턴스를 생성한다.
  • 어떤 인스턴스에 대해 작업을 할 때, 원래의 인스턴스는 보존하고 clone 메서드를 이용해서 새로운 인스턴스를 생성하여 작업을 하면 작업 이전의 값이 보존되므로 작업에 실패해서 원래의 상태로 되돌리거나 변경되기 전의 값을 참고하는데 도움이 될 수 있다.
  • Object 클래스에 정의된 clone()은 인스턴스 변수의 값만 복사하므로 참조타입의 인스턴스 변수가 있는 클래스는 와전한 인스턴스 복제가 아니다. (얕은복사)
  • Cloneable 인터페이스를 구현한 클래스에서만 clone()을 호출할 수 있다.
    public class ArrayList<E> extends AbstractList<E>
            implements List<E>, RandomAccess, Cloneable, java.io.Serializable
  • clone()을 오버라이딩 하면서 접근 제어자를 protected에서 public으로 변경한다.
  • 그래야 상속관계가 없는 다른 클래스에서도 clone()을 호출할 수 있다.
    public Object clone() {
        try {
            ArrayList<?> v = (ArrayList<?>) super.clone();
            v.elementData = Arrays.copyOf(elementData, size);
            v.modCount = 0;
            return v;
        } catch (CloneNotSupportedException e) {
            // this shouldn't happen, since we are Cloneable
            throw new InternalError(e);
        }
    }
  • 인스턴스의 데이터를 보호하기 위해 Cloneable 인터페이스가 구현되어 있는지 확인하고, 없다면 사용할 수 없다.
    • CloneNotSupportedException

공변 반환타입

  • JDK 1.5부터 추가되었다.
  • 오버라이딩할 때 조상 메서드의 반환타입을 자손 클래스의 타입으로 변경을 허용하는 것이다.
  • 공변 반환타입을 사용하면, 조상의 타입이 아닌, 실제로 반환되는 자손 객체의 타입으로 반환할 수 있어서 번거로운 형변환이 줄어든다는 장점이 있다.

얕은 복사와 깊은 복사

  • 얕은 복사
    • 객체에 저장된 값(주소)을 복사하는 것
    • 원본을 변경하면 복사본도 영향을 받는다.
  • 깊은 복사
    • 원본이 참조하고 있는 객체까지 복제하는 것
    • 원본과 복사본이 서로 다른 객체를 참조하기 때문에 원본의 변경이 복사본에 영향을 미치지 않는다.

getClass()

  • 자신이 속한 클래스의 Class 객체를 반환하는 메서드

Class객체

Class객체는 클래스의 모든 정보를 담고있으며 클래스당 1개만 존재한다.

클래스파일이 ClassLoader에 의해 메모리에 올라갈 때, 자동으로 생성된다.

ClassLoader은 실행 시에 필요한 클래스를 동적으로 메모리에 로드하는 역할을 한다.

먼저 기존에 생성된 클래스 객체가 메모리에 존재하는지 확인하고, 있으면 객체의 참조를 반환하고 없으면 classpath에 지정된 경로를 따라서 클래스 파일을 찾는다.

몾찾으면 ClassNoFoundException이 발생하고, 찾으면 해당 클래스 파일을 읽어서 Class객체로 변환한다.

그러니까 파일 형태로 저장되어 있는 클래스를 읽어서 Class클래스에 정의된 형식으로 변환하는 것이다.

즉, 클래스 파일을 읽어서 사용하기 편한 형태로 저장해 놓은 것이 클래스 객체이다.

Class객체를 얻는 방법

Class pClazz1 = new Person().getClass();  // 생성된 객체로부터 얻는 방법
Class pClazz2 = Person.class;             // 클래스 리터럴(*.class)로 부터 얻는 방법
Class pClazz3 = Person.forName("Person"); // 클래스 이름으로 부터 얻는 방법
  • 특히 forName()은 특정 클래스 파일을 메모리에 올릴 때 주로 사용한다.
  • Class객체를 이용하면 보다 동적인 코드를 작성할 수 있다.
  • reflection API
    • Spring에서 사용하는 모습을 볼 수 있음.
      • Bean과 관련한 동작 등 동적으로 수행하는 경우
    • 프레임워크에서 구체적이지 않은 객체를 받아 동적으로 수행한다고 이해할 수 있다.

1.2 Stirng 클래스

String클래스는 문자열을 저장하고 이를 다루는데 필요한 메서드를 제공한다.

변경 불가능한(immutable) 클래스

  • String 클래스에는 문자열을 저장하기 위해 문자열 배열 참조 변수(char[]) value를 인스턴스 변수로 정의하고 있다.
  • 인스턴스 생성 시 생성자의 매개변수로 입력받는 문자열은 이 인스턴스 변수(value)에 문자형 밸열(char[])로 저장되는 것이다.
  • 한번 생성된 String인스턴스가 갖고 있는 문자열은 읽어 올 수만 이쏙, 변경할 수는 없다.
    • 즉, 인스턴스 내의 문자열이 바뀌는 것이 아닌 새로운 문자열이 담긴 String 인스턴스가 생성되는 것이다.
  • 덧셈 연산자를 사용해서 문자열을 결합하는 것은 매 연산 시 마다 새로운 문자열을 가진 String인스턴스가 생성되어 메모리 공간을 차지하게 되므로 가능한 한 결합횟수를 줄이는 것이 좋다.
  • 문자열간의 결합이나 추출 등 문자열을 다루는 작업이 많이 필요한 경우에는 String클래스 대신 StringBuffer클래스를 사용하는 것이 좋다.
  • StringBuffer인스턴스에 저장된 문자열은 변경이 가능하다.

문자열의 비교

  • 문자열을 만드는 방법
    1. 문자열 리터럴을 지정하는 방법
      • 이미 존재하는 것을 재사용하는 것이다.
    2. String클래스의 생성자를 사용해서 만드는 방법
      • new 연산자에 의해 메모리 할당이 이루어지기 때문에 항상 새로운 인스턴스가 생성된다.
      • 즉, new 연산자를 통해 같은 문자열의 String 객체를 만들더라도, 두 객체의 주소값이 다르다.

문자열 리터럴

  • 자바 소스파일에 포함된 모든 문자열 리터럴은 컴파일 시에 클래스 파일에 저장된다.
  • 이때 같은 내용의 문자열 리터럴은 한번만 저장된다.
  • 문자열 리터럴도 String인스턴스이고, 한번 생성하면 내용을 변경할 수 없으니 하나의 인스턴스를 공유하면 되기 때문이다.
  • 클래스파일에는 소스파일에 포함된 모든 리터럴의목록이 있다.
    • 해당 클래스 파일이 클래스 로더에 의해 메모리에 올라갈 때, 이 리터럴의 목록에 있는 리터럴들이 JVM내에 있는 ‘상수 저장소(constant pool)’에 저장된다.

빈 문자열(empty string)

  • 길이가 0인 배열을 생성할 수 있고, 이 배열을 내부적으로 가지고 있는 문자열이 바로 빈 무낮열이다.
  • 하지만 문자인 char은 빈 문자를 넣을 수 없다.
    • primitive type이니까.
  • String은 참조형 타입의 기본값이 null보다는 빈 문자열로, char형은 기본값인 ‘\u9999’대신 공백으로 초기화 하는 것이 보통이다.

String 클래스의 생성자와 메서드

  • contains(CharSequence s), replace(CharSequence old, CharSequence nw)는 JDK 1.5부터 추가되었다.

join()과 StringJoiner

  • join()은 여러 문자열 사이에 구분자를 넣어서 결합한다.
  • 구분자로 문자열을 자르는 split()과 반대 작업을 한다.

유니코드의 보충문자

  • String의 메서드에는 확장된 유니코드를 다루기 위해 int형을 쓰는 경우가 있다.
  • 유니코드의 경우 20bit를 가지면서 char타입으로 다루지 못한다.
  • 이를 다루기 위해 int형을 쓰게 된다.
  • int ch로 값을 받는 경우는 유니코드의 보충문자를 지원하는 것들이다.
  • 확장된 유니코드(20bit)가 적용된 것은 JDK 1.5부터이다.

문자 인코딩 변환

  • getBytes(String charsetName)을 사용하면, 문자열의 문자 인코딩을 다른 인코딩으로 변경할 수있다.
  • UTF16 to CP949같은 것들

String.format()

  • 형식화된 문자열을 만들어내는 간단한 방법
  • printf()와 사용법이 완전히 같다.

기본형 값을 String으로 변환

int i = 100
String str = i + "";
String str = String.valueOf(i);
  • primitive type은 빈 문자열””만 더해주면 된다.
  • valueOf()를 사용한다면 더 좋은 성능을 가질 수 있다.

String을 기본형 값으로 변환

int i = Integer.parseInt("100");
int j = Integer.valueOf("100");
  • 사실 Integer.valueOf()의 반환 타입은 Integer인데, 오토박싱에 의해 Integer가 int로 자동 변환된다.
  • 이전에는 parseInt()와 같은 메서드를 많이 썼는데, 메서드의 이름을 통일하기 위해 valueOf()가 추가되었다.
public static Integer valueOf(String s) throws NumberFormatException {
    return Integer.valueOf(parseInt(s, 10));
}
  • valueOf(String s)는 메서드 내부에서 그저 parseInt(String s)를 호출할 뿐이므로 두 메서드는 반환 타입만 다를 뿐 같은 메서드이다.
  • 문자열을 primitive type으로 변환하는 경우, 공백 또는 문자가 포함되어있다면 NumberFormatException이 발생하게 된다.
    • 이를 방지하기 위해 .trim()을 사용하기도 한다.
  • 이처럼 문자열을 숫자로 변환하는 과정에서는 예외가 발생하기 쉽기 때문에 주의를 기울여야 하고, 예외가 발생했을 때의 처리를 적저맇 해주어야 한다.
  • +가 포함된 문자열이 parseInt()로 변환 가능하게 된 것은 JDK 1.7부터이다.
  • Integer클래스의 static int parseInt(String s, int radix)를 사용하면 16진수 값으로 표현된 문자열도 쉽게 변환할 수 있기 때문에 대소문자 구별없이 a~f도 사용할 수 있다.

1.3 StringBuffer클래스와 StringBuilder클래스

StringBuffer

  • 내부적으로 문자열 편집을 위한 버퍼를 가지고 있으며, StringBuffer인스턴스를 생성할 때 그 크기를 지정할 수 있다.
  • 편집할 문자열의 길이를 고려하여 버퍼의 길이를 충분히 잡아주는 것이 좋다.
  • 편집중인 문자열이 버퍼의 길이를 넘어서게 되면 버퍼의 길이를 늘려주는 작업이 추가로 수행되어야 하기 때문에 작업 효율이 떨어진다.

StringBuffer의 생성자

  • StringBuffer클래스의 인스턴스를 생성할 때, 적절한 길이의 char형 배열이 생성되고, 이 배열은 문자열을 저장하고 편집하기 위한 공간으로 사용된다.

StringBuffer의 변경

  • StringBuffer의 append()의 반환 타입이 StringBuffer이며, 자기 자신 인스턴스를 리턴하므로 메서드 체이닝이 가능하다.

StringBuffer의 비교

  • StringBuffer은 equals()를 오버라이딩 하지 않았고, 그 때문에 equals()로 비교하더라도 등가 비교 연산자==로 비교한 것과 같은 결과를 얻는다.
  • 근데 toString()은 구현했으니, String으로 값을 비교하도록 하자.

StringBuilder란?

  • StringBuffer는 멀티쓰레드에 안전(thread safe)하도록 동기화되어 있다.
  • 멀티쓰레드 작업이 필요 없는 경우 StringBuilder를 사용해 성능을 향상시킬 수 있다.

1.4 Math클래스

올림, 버림, 반올림

Math.ceil(1.1) = 2.0
Math.floor(1.1) = 1.0
Math.round(1.1) = 1.0
Math.round(1.5) = 2

예외를 발생시키는 메서드

int addExact(int x, int y)        // x+y
int substractExact(int x, int y)  // x-y
int multiplyExcact(int x, int y)  // x*y
int increamentExact(int a)        // x++
int decrementExact(int a)         // x--
int negateExact(int a)            // -x
int toIntExact(long value)        // (int)value
  • 메서드의 이름에 Exact가 포함된 메서드를 JDK 1.8부터 새롭게 추가했다.
  • 정수형 간의 연산에서 발생할 수 있는 오버플로우(overflow)를 감지한다.
  • 오버플로우가 발생하면 예외(ArithmeticException)를 발생시킨다.

삼각함수와 지수, 로그

sin()
cos()
tan()
toRadians()
toDegrees()
log10()

StricMath클래스

  • Math클래스는 최대한의 성능을 얻기 위해 JVM이 설치된 OS의 메서드를 호출해서 사용한다.
  • OS에 의존적인 계산을 하는 것이다.
  • 성능을 조금 포기하고 OS에 독립적인 결과를 얻을 수 있도록 만든 클래스이다.

1.5 래퍼(wrapper) 클래스

  • 자바는 primitive타입으로 인해 완전한 객체지향이 아니라는 말을 듣지만, 이를 통해 성능을 얻었다.
  • 때로는 기본형(primitive type) 변수도 객체로 다뤄야하는 경우가 있다.
  • 이때 사용하는 것이 래퍼(wrapper) 클래스이다.
  • 래퍼 클래스들은 모두 equals()가 오버라이딩 되어있어서 주소값이 아닌 객체가 가지고 있는 값을 비교한다.
  • 그래서 실행결과를 보면 equals()를 이용한 두 Integer객체의 비교 결과가 true라는 것을 알 수 있다.
  • 오토박싱이 된다 해도 Integer객체에 비교연산자를 사용할 수 없다.
    • 대신 compareTo()를 제공한다.
  • toString()도 오버라이딩 되어있어서 객체가 가지고 있는 값을 문자열로 변환하여 반환한다.
  • MAX_VALUE, MIN_VALUE, SIZE, BYTES.TYPE등의 static 상수를 공통적으로 가지고 있다.
    • BYTES는 JDK1.8부터 추가되었다.

Number클래스

Number클래스는 추상 클래스로 내부적으로 숫자를 멤버변수로 갖는 래퍼 클래스들의 조상이다.

  • 기본형 중에서 숫자와 관련된 래퍼 클래스들은 모두 Number클래스의 자손이라는 것을 알 수 있다.
  • BigInteger, BigDecimal을 자손으로 가진다.
  • BigInteger
    • long보다 큰 범위의 정수
  • BigDecimal
    • double보다 큰 범위의 부동 소수점수

오토박싱 & 언박싱 (autoboxing & unboxing)

  • JDK 1.5 이전에는 기본형과 참조형 간의 연산이 불가능했기 때문에, 래퍼 클래스로 기본형을 객체로 만들어서 연상해야 했다.
int i = 5;
Integer iObj = new Integer(7);
int sum = i + iObj //.intValue()를 컴파일러가 자동으로 넣어준다.
  • 컴파일러가 자동으로 변환하는 코드를 넣어줘서 기본형과 참조형간의 연산이 가능하다.
  • 오토박싱 Autoboxing
    • 기본형 값을 래퍼 클래스의 객체로 자동 변환해주는 것
  • 언박싱 Unboxing
    • 래퍼 클래스의 객체를 기본형 값으로 변환하는 것
ArrayList<Integer> list = new ArrayList<>();
list.add(10);            // 오토박싱  10 -> new Integer(10)
int value = list.get(0); // 언박싱    Integer(10) -> 10
  • 컴파일러가 제공하는 편리한 기능일 뿐 자바의 원칙이 바뀐 것은 아니다.
  • 생성자가 없는 클래스에 컴파일러가 기본 생성자를 자동으로 추가해 주드싱 개발자가 간략하게 쓴 구문을 컴파일러가 원래의 구문으로 변경해주는 것 뿐이다.
profile
더 나은 성취

0개의 댓글