> For the complete documentation index, see [llms.txt](https://n00nietzsche.gitbook.io/jake-seo/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://n00nietzsche.gitbook.io/jake-seo/books/this-is-java/7.md).

# 7. 기본 API 클래스 1

## API란?

* Application Programming Interface의 약자이다.
* 컴포넌트들을 결합하기 위한 매개체 인터페이스이다.
* 호출을 위한 수단이다.
* 내부 구현을 볼 수 없는 경우도 많지만, 자바 내부의 표준 API들은 내부 구현을 볼 수 있다.

> API들은 경로 `\jre\lib\rt.jar`라는 압축 파일에 저장되어 있다.
>
> [자바 API 도큐먼트](http://docs.oracle.com/javase/8/docs/api)에서 API의 내용을 확인할 수 있다. API 클래스의 필드, 생성자, 메소드 등을 확인할 수 있다.

## java.lang 패키지

자바 프로그램의 기본적인 클래스를 담고 있는 패키지로 `import` 없이 사용할 수 있다.

### java.lang 대표 내장 클래스

* `Object`
  * 자바 클래스의 최상위 클래스로 사용
* `System`
  * 표준 입력 장치(키보드)로부터 데이터를 입력받을 때 사용
  * 표준 출력 장치(모니터)로 출력하기 위해 사용
  * 자바 가상 기계를 종료시킬 때 사용
  * 쓰레기 수집기를 실행 요청할 때 사용
* `Class`
  * 클래스를 메모리로 로딩할 때 사용
* `String`
  * 문자열을 저장하고 여러가지 정보를 얻을 때 사용
* `StringBuffer`, `StringBuilder`
  * 문자열을 저장하고 내부 문자열을 조작할 때 사용
* `Math`
  * 수학 함수를 이용할 때 사용
* `Wrapper`
  * `Byte`, `Short`, `Character`, `Integer`, `Float`, `Double`, `Boolean`, `Long`
  * primitive type을 감싸는(Wrapping) 클래스
  * 기본 타입의 데이터를 갖는 객체를 만들 때 사용&#x20;
    * primitive 타입에 null이 불가한 특성 등을 해결할 때
    * 편의 메소드를 이용하고 싶을 때
  * 문자열을 기본 타입으로 변환할 때 사용
  * 입력값 검사에 사용

## java.util 패키지

주로 컬렉션 클래스들이 있다.

* `Arrays`
  * 배열을 조작(비교, 복사, 정렬, 찾기)할 때 사용
* `Calendar`
  * 운영체제의 날짜와 시간을 얻을 때 사용
* `Date`
  * 날짜와 시간 정보를 저장하는 클래스
* `Objects`
  * 객체 비교, 널(`null`) 여부 등을 조사할 때 사용
* `StringTokenizer`
  * 특정 문자로 구분된 문자열을 뽑아낼 때 사용
* `Random`
  * 난수를 얻을 때 사용

## Object 클래스 (java.lang)

모든 클래스가 기본적으로 상속하는 최상위 부모 클래스이다.

### 객체 비교 (Object.equals())

```java
public boolean equals(Object obj) { ... }
```

파라미터가 `Object` 타입이라 모든 클래스를 전부 파라미터로 받을 수 있다. 또한, `Object` 클래스는 모든 클래스의 최상위 클래스이기 때문에 모든 클래스는 `.equals()`메소드를 갖는다.

`Object` 클래스의 `.equals()` 메소드는 기본적으로 `==` 연산자와 동일한 동작을 갖는다. 하지만 `.equals()` 메소드는 `Object`에서 기본으로 제공하는 형태로는 거의 쓰이지 않고 메소드의 내용을 오버라이드하여, `.equals()` 메소드의 동작을 두 객체가 논리적으로 동일한지 판단하는데 쓴다.

이를테면 `String` 클래스에서는 `.equals()`가 객체의 주소를 비교하지 않고, 오직 문자열의 내용을 비교하도록 오버라이딩 되어있다.

> `.equals()` 메소드를 오버라이드할 때는 인자로 `Object` 타입의 객체를 받기 때문에 사실상 어떠한 클래스의 객체도 올 수 있다. 그래서 `instanceof` 연산자로 해당 객체가 내가 원하는 클래스의 객체인지 확인하는 작업이 가장 먼저 필요하다.

```java
@Override
public boolean equals(Object obj) {
  if(obj instanceof TargetObject) {
    TargetObject targetObject = (TargetObject) obj;
    if(this.id == targetObject.id) {
      return true;
    }
  }

  return false;
}
```

### 객체 해시코드(hashCode())

* `.hashCode()`는 객체 식별에 사용되는 유일한 정수값을 반환한다.
  * 유일하기 때문에, 모든 객체는 다른 정수값을 반환한다.
* 논리적 동등을 완벽하게 비교하려면 `.equals()`와 `.hashCode()`를 둘 다 오버라이드 해야 한다.
* 컬렉션 프레임워크 `HashSet`, `HashMap`, `Hashtable`는 `.hashCode()`와 `.equals()` 두 메소드 모두를 이용하여 논리적 동등을 비교한다.
  * `.hashCode()`를 수행하고 같은 반환값을 가지는지 먼저 확인하고 `.equals()`를 통해 다시 한번 같은 반환값을 가지는지 확인한다.

> 그렇다면 객체 해시코드를 반환하는 `.hashCode()` 메소드를 오버라이드 해버리면 유일한 값은 어떻게 찾아와야 할까? 그 해답은 바로 `System.identityHascode()` 메소드를 사용하면 기존의 `.hashCode()`와 같은 기능을 이용할 수 있다.

### HashMap 코드로 살펴보기

#### Key 클래스

```java
public class Key {
    int id;
    String name;

    public Key(int id, String name) {
        this.id = id;
        this.name = name;
    }

    @Override
    public int hashCode() {
        System.out.println("Key.hashCode of " + name);
        return id;
    }

    @Override
    public boolean equals(Object obj) {
        System.out.println("Key.equals of " + name);
        if(obj instanceof Key) {
            Key argKey = (Key) obj;
            return this.id == argKey.id;
        }

        return false;
    }
}
```

* `Key`라는 클래스를 새로 작성하여 `.equals()` 메소드와 `.hashCode()` 메소드를 위와 같이 오버라이딩 하였다.
* 클래스 멤버 중 `id`로만 논리적 동치 판단을 하도록 구성하였다.

#### Main 클래스

```java
public class Main {
    public static void main(String[] args) {
        Key key1 = new Key(1, "firstKey");
        Key key2 = new Key(1, "secondKey");

        HashMap<Key, String> hashMap = new HashMap<>();

        // key1로 홍길동이란 값 삽입
        hashMap.put(key1, "홍길동");

        // key2로 해쉬맵에 있는 일치하는 값 찾기
        String result = hashMap.get(key2);
        System.out.println("result = " + result);
    }
}
```

* `HashMap` 객체를 생성하고, `Key`의 타입은 위에서 직접 만든 `Key` 클래스로 하였다.
* `.put()` 메소드로 값을 넣을 때 사용한 키는 `key1` 변수이다.&#x20;
* `.get()` 메소드로 값을 가져올 때 사용한 키는 `key2` 변수이다.
* 하지만, 둘의 `id` 필드의 값은 같고, `.equals()`와 `.hashCode()`를 그에 맞게 구현했기에 논리적 동치가 가능하다.

#### 실행 결과

![](https://images.velog.io/images/jakeseo_me/post/a728134a-1524-4535-8468-4fe2596bfeec/image.png)

* `.hashCode()` 메소드나 `.equals()` 메소드가 호출되면 콘솔에 출력하도록 프로그래밍 하였다.
  * `.hashCode()`가 비교를 위해 2번 호출되는 것을 볼 수 있다.
  * `.equals()`는 한번만 호출되어도 비교가 가능하여 1번 호출된 것을 볼 수 있다.
* 결과적으로 `key1`과 `key2`가 `HashMap` 내부 구현에 따라 논리적 동치로 판단되고 저장했던 문자열인 `홍길동` 이 출력된 것을 볼 수 있다.

### 객체 문자 정보(toString())

* `.toString()`메소드는 객체의 문자 정보를 출력한다.
  * 기본 구현은 객체의 주소인 `클래스명@16진수해시코드`를 리턴한다.

```java
Object obj = new Object();
System.out.println(obj.toString());

// java.lang.Object@de6ced
```

> 보통 번지수는 사용자의 관심사가 아니어서 의미없는 정보이기 때문에, 오버라이드하여 사용자의 관심사인 필드의 내용 등을 출력하는 방식으로 많이 고쳐쓴다.

### 객체 복제(clone())

* 원본 객체의 필드값과 동일한 값을 가지는 새로운 객체를 생성하는 메소드이다.
  * 원본 객체의 안전을 보장하기 위해 많이 사용된다.
* 이 메소드를 사용하려면 클래스에서 `Cloneable` 인터페이스를 구현해야 한다.
  * 구현하지 않으면, 설계자가 복제를 허용하지 않는다는 뜻이다.
    * 구현하지 않은 채로 `.clone()` 메소드를 쓰면 `CloneNotSupportedException` 예외가 발생한다.
* `CloneNotSupportedException` 예외처리가 필요하기 때문에 `try-catch` 구문이 필요하다.

> `getter`의 결과로 복제된 클래스를 내보내면, 클래스의 불변성을 지킬 수 있다.

#### 얕은 복제(thin clone)

* 단순히 필드 값만 복사해서 객체를 복제하는 것을 말한다.
  * 참조 객체를 복사할 때는 동일한 객체를 생성하는 것이 아닌 단순히 주소만 복사한다.

#### 얕은 복제(thin clone) 코드 테스트

> `CloneableClass` 클래스 작성, `.getCloneableClass()` 메소드 호출 시에 `Object`에서 상속받은 `.clone()` 메소드를 그대로 사용하여 반환한다.

```java
public class CloneableClass implements Cloneable {
    Integer id;

    public CloneableClass(Integer id) {
        this.id = id;
    }

    public CloneableClass getCloneableClass() throws CloneNotSupportedException {
        return (CloneableClass) clone();
    }

    public Integer getId() {
        return id;
    }
}
```

* 위와 같이 그냥 기본으로 `Object`에서 상속받은 `.clone()` 메소드를 사용하면 얕은 복제가 된다.

> `Main` 클래스 작성, `System.identityHashCode()`로 두 객체가 가지고 있는 `id` 필드의 `Integer` 객체가 같은 객체인지 해시코드를 출력해본다.

```java
public class Main {
    public static void main(String[] args) throws CloneNotSupportedException {
        CloneableClass cloneableClass = new CloneableClass(1);
        CloneableClass clonedCloneableClass = cloneableClass.getCloneableClass();

        System.out.println(cloneableClass);
        System.out.println(clonedCloneableClass);

        System.out.println(System.identityHashCode(cloneableClass.getId()));
        System.out.println(System.identityHashCode(clonedCloneableClass.getId()));
    }
}
```

![](https://images.velog.io/images/jakeseo_me/post/767b6072-5eb2-420e-b098-90461014d4ba/image.png)

* 위는 얕은 복제를 한 객체를 직접 출력해보는 소스와 결과다.
* `CloneableClass`의 객체들은 각각 다른 주소를 가지고 있지만, 내부에 있는 `Integer` 클래스 필드는 같은 객체를 가리키고 있다.
  * `Integer` 클래스가 각각 다른 객체를 가리키게 만드려면 깊은 복제(deep clone)가 필요하다.

#### 깊은 복제(deep clone)

* 참조하고 있는 객체도 새로 생성하여 복제한다.
* 깊은 복제를 원할 때는 `.clone()` 메소드를 재정의하여 직접 참조 객체를 복사하는 코드를 작성해야 한다.

#### 깊은 복제(deep clone) 코드 테스트

> `CloneableClass` 클래스 재작성

```java
public class CloneableClass implements Cloneable {
    Integer id;

    public CloneableClass(Integer id) {
        this.id = id;
    }

    @Override
    protected Object clone() throws CloneNotSupportedException {
        System.out.println("deep clone");
        return new CloneableClass(new Integer(id));
    }

    public CloneableClass getCloneableClass() throws CloneNotSupportedException {
        return (CloneableClass) this.clone();
    }

    public Integer getId() {
        return id;
    }
}
```

* `clone()` 메소드를 오버라이드하여, 매번 `clone()` 메소드가 호출될 때마다 `Integer` 객체를 새롭게 생성하도록 바꾸었다.

> `Main` 클래스 재작성

```java
public class Main {
    public static void main(String[] args) throws CloneNotSupportedException {
        CloneableClass cloneableClass = new CloneableClass(15);
        CloneableClass clonedCloneableClass = cloneableClass.getCloneableClass();

        System.out.println(cloneableClass);
        System.out.println(clonedCloneableClass);

        System.out.println(System.identityHashCode(cloneableClass.getId()));
        System.out.println(System.identityHashCode(clonedCloneableClass.getId()));

        System.out.println(cloneableClass.getId());
        System.out.println(clonedCloneableClass.getId());
    }
}
```

* 오버라이드된 `clone()` 메소드가 정상적으로 새로운 `Integer` 객체를 만들어내는지 다시한번 `System.identityHashCode()` 메소드를 통해 테스트

![](https://images.velog.io/images/jakeseo_me/post/53eaab22-4fbe-40bd-978a-36495833113b/image.png)

* 서로 다른 hash 값을 반환하는 것을 확인했다.

### 객체 소멸자(finalize())

* 객체가 가비지 콜렉터에 의해 소멸당하기 전에 마지막으로 실행시키는 메소드이다.
* 객체 소멸 전에 마지막으로 사용했던 자원을 닫고 싶거나 데이터를 저장하고 싶다면 `Object`의 `.finalize()`를 재정의할 수 있다.
  * 가비지 콜렉터는 객체가 쓰레기가 된 그 순간에 바로 작동하는 것이 아니다.
    * `System.gc()` 메소드를 호출하여 가급적 빨리 쓰레기 수집기를 돌릴 수 있다.
  * 가비지 콜렉터는 객체를 순서대로 소멸시키지도 않는다.
    * 그래서 `finalize()` 메소드가 호출되는 시점 자체도 명확하지 않다.
      * 그래서 사실은 리소스 회수는 그냥 일반 메소드에 명시적으로 호출하는 것이 좋다.

## Objects 클래스

### 대략적 정적 메소드들의 기능

* 객체 비교
* 해시코드 생성
* null 여부
* 객체 문자열 리턴

### 정적 메소드 목록

* `int compare(T a, T b, Comparator<T> c)`: Comparator를 이용하여 두 객체 a와 b를 비교한다.
* `boolean deepEquals(Object a, Object b)`: 두 객체의 깊은 비교(배열의 항목까지 비교)
* `boolean equals(Object a, Object b)`: 두 객체의 얕은 비교(주소만 비교)
* `int hash(Object... values)`: 매개값이 저장된 배열의 해시코드 생성
* `boolean isNull(Object obj)`: 객체가 null인지 조사
* `boolean nonNull(Object obj)`: 객체가 null이 아닌지 조사
* `T requireNonNull(T obj)`: 객체가 null이면 예외 발생
* `T requireNonNull(T obj, String message)`: 객체가 null인 경우 예외 발생(주어진 예외 메시지 포함)
* `T requireNonNull(T obj, Supplier<String> messageSupplier)`: 객체가 null인 경우 예외 발생(람다식이 만든 예외 메시지 포함)
* `String toString(Object o)`: 객체의 `.toString()` 리턴 값을 리턴
* `String toString(Object o, String nullDefault)`: 객체의 `.toString()` 리턴 값 리턴, 첫번째 매개값이 null인 경우, 두번째 매개값 리턴

### 객체 비교(`compare(T a, T b, Comparator<T>c)`)

`Objects.compare(T a, T b, Comparator<T>c)` 메소드는 두 객체를 비교자(Comparator)로 비교해서 `int` 값을 리턴한다. `java.util.Comparator<T>`는 제네릭 인터페이스 타입으로 두 객체를 비교하는 `compare(T a, T b)` 메소드 시그니처가 있다.

`.compare()` 메소드는 `int` 타입을 리턴하며, 일반적으로 `a`와 `b`를 비교하여 `a`가 `b`보다 작으면 `음수(Minus)`, 같으면 `0(Zero)`, 크면 `양수(Plus)`를 리턴하도록 구현 클래스를 만든다.

> `Wrapper` 클래스에는 일반적으로 위와 같이 2가지의 인자를 비교하여 작으면 음수, 같으면 0, 크면 양수를 리턴하는 메소드가 구현되어 있다. `Integer.compare()`와 같은 형식으로 구현되어 있다.

```java
public class ComparatorTest {
    @Test
    public void integerComparatorTest() {
        Integer a = 10;
        Integer b = 20;

        int compareResult = Integer.compare(a, b);
        System.out.println("compareResult = " + compareResult);
        Assertions.assertThat(compareResult).isEqualTo(-1);

        int compareToResult = a.compareTo(b);
        System.out.println("compareToResult = " + compareToResult);
        Assertions.assertThat(compareResult).isEqualTo(-1);

        int compareImplementationResult = compareImplementation(a, b);
        System.out.println("compareImplementationResult = " + compareImplementationResult);
        Assertions.assertThat(compareImplementationResult).isEqualTo(-1);
    }

    public int compareImplementation(int a, int b) {
        if(a > b) {
            return 1;
        } else if( a == b) {
            return 0;
        }

        return -1;
    }
}
```

> 나중에는 위의 `.compare()` 메소드를 이용하여 컬렉션에 대한 정렬 등을 수행할 수도 있다. 또한 직접 만든 클래스를 정렬하고 싶다면, `Comparator<>` 혹은 `Comparable<>` 인터페이스를 상속하여, `.compare()` 메소드를 오버라이드하면 된다.

### Comparable 상속을 이용한 컬렉션 정렬 예제

#### Student 클래스

```java
public class Student implements Comparable<Student>{
    private int number;
    private String name;

    public void setNumber(int number) {
        this.number = number;
    }

    public void setName(String name) {
        this.name = name;
    }

    @Override
    public int compareTo(Student student) {
        if(this.number > student.number) return -1;
        return 0;
        // Integer.compare(this.number, student.number); 와 같다
        // 반대로 정렬하고 싶다면 부호만 반대로 바꾸어주면 된다.
    }

    @Override
    public String toString() {
        return "Student{" +
                "number=" + number +
                ", name='" + name + '\'' +
                '}';
    }
}
```

#### StudentTest 클래스

```java
public class StudentTest {
    @Test
    public void studentSort() {
        ArrayList<Student> studentArrayList = new ArrayList<>();

        Student student2 = new Student();
        student2.setName("김이번");
        student2.setNumber(2);

        studentArrayList.add(student2);

        Student student1 = new Student();
        student1.setName("김일번");
        student1.setNumber(1);

        studentArrayList.add(student1);

        Student student3 = new Student();
        student3.setName("김삼번");
        student3.setNumber(3);

        studentArrayList.add(student3);

        Collections.sort(studentArrayList);

        System.out.println("studentArrayList = " + studentArrayList);
    }
}
```

나는 컬렉션의 `.sort()`에 대해 간단히 `comapre` 메소드가 `음수`일 때만 변화를 준다고 이해하고 있다. 왜냐하면 `음수` 외에 다른 어떤 값을 주어도 변화가 일어나지 않기 때문이다. 무조건 `음수`만 반환하게 `return -1;`을 하면 순서가 반대로된 리스트가 반환된다.
