이것이 자바다 정리 #7 기본 API 클래스
이것이 자바다 책을 참고하였습니다.
API들은 경로
\jre\lib\rt.jar
라는 압축 파일에 저장되어 있다.
자바 API 도큐먼트에서 API의 내용을 확인할 수 있다.
API 클래스의 필드, 생성자, 메소드 등을 확인할 수 있다.
자바 프로그램의 기본적인 클래스를 담고 있는 패키지로 import
없이 사용할 수 있다.
Object
System
Class
String
StringBuffer
, StringBuilder
Math
Wrapper
Byte
, Short
, Character
, Integer
, Float
, Double
, Boolean
, Long
주로 컬렉션 클래스들이 있다.
Arrays
Calendar
Date
Objects
null
) 여부 등을 조사할 때 사용StringTokenizer
Random
모든 클래스가 기본적으로 상속하는 최상위 부모 클래스이다.
public boolean equals(Object obj) { ... }
파라미터가 Object
타입이라 모든 클래스를 전부 파라미터로 받을 수 있다. 또한, Object
클래스는 모든 클래스의 최상위 클래스이기 때문에 모든 클래스는 .equals()
메소드를 갖는다.
Object
클래스의 .equals()
메소드는 기본적으로 ==
연산자와 동일한 동작을 갖는다. 하지만 .equals()
메소드는 Object
에서 기본으로 제공하는 형태로는 거의 쓰이지 않고 메소드의 내용을 오버라이드하여, .equals()
메소드의 동작을 두 객체가 논리적으로 동일한지 판단하는데 쓴다.
이를테면 String
클래스에서는 .equals()
가 객체의 주소를 비교하지 않고, 오직 문자열의 내용을 비교하도록 오버라이딩 되어있다.
.equals()
메소드를 오버라이드할 때는 인자로Object
타입의 객체를 받기 때문에 사실상 어떠한 클래스의 객체도 올 수 있다. 그래서instanceof
연산자로 해당 객체가 내가 원하는 클래스의 객체인지 확인하는 작업이 가장 먼저 필요하다.
@Override
public boolean equals(Object obj) {
if(obj instanceof TargetObject) {
TargetObject targetObject = (TargetObject) obj;
if(this.id == targetObject.id) {
return true;
}
}
return false;
}
.hashCode()
는 객체 식별에 사용되는 유일한 정수값을 반환한다..equals()
와 .hashCode()
를 둘 다 오버라이드 해야 한다.HashSet
, HashMap
, Hashtable
는 .hashCode()
와 .equals()
두 메소드 모두를 이용하여 논리적 동등을 비교한다..hashCode()
를 수행하고 같은 반환값을 가지는지 먼저 확인하고 .equals()
를 통해 다시 한번 같은 반환값을 가지는지 확인한다.그렇다면 객체 해시코드를 반환하는
.hashCode()
메소드를 오버라이드 해버리면 유일한 값은 어떻게 찾아와야 할까? 그 해답은 바로System.identityHascode()
메소드를 사용하면 기존의.hashCode()
와 같은 기능을 이용할 수 있다.
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
로만 논리적 동치 판단을 하도록 구성하였다.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
변수이다. .get()
메소드로 값을 가져올 때 사용한 키는 key2
변수이다.id
필드의 값은 같고, .equals()
와 .hashCode()
를 그에 맞게 구현했기에 논리적 동치가 가능하다..hashCode()
메소드나 .equals()
메소드가 호출되면 콘솔에 출력하도록 프로그래밍 하였다..hashCode()
가 비교를 위해 2번 호출되는 것을 볼 수 있다..equals()
는 한번만 호출되어도 비교가 가능하여 1번 호출된 것을 볼 수 있다.key1
과 key2
가 HashMap
내부 구현에 따라 논리적 동치로 판단되고 저장했던 문자열인 홍길동
이 출력된 것을 볼 수 있다..toString()
메소드는 객체의 문자 정보를 출력한다.클래스명@16진수해시코드
를 리턴한다.Object obj = new Object();
System.out.println(obj.toString());
// java.lang.Object@de6ced
보통 번지수는 사용자의 관심사가 아니어서 의미없는 정보이기 때문에, 오버라이드하여 사용자의 관심사인 필드의 내용 등을 출력하는 방식으로 많이 고쳐쓴다.
Cloneable
인터페이스를 구현해야 한다..clone()
메소드를 쓰면 CloneNotSupportedException
예외가 발생한다.CloneNotSupportedException
예외처리가 필요하기 때문에 try-catch
구문이 필요하다.
getter
의 결과로 복제된 클래스를 내보내면, 클래스의 불변성을 지킬 수 있다.
CloneableClass
클래스 작성,.getCloneableClass()
메소드 호출 시에Object
에서 상속받은.clone()
메소드를 그대로 사용하여 반환한다.
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
객체가 같은 객체인지 해시코드를 출력해본다.
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()));
}
}
CloneableClass
의 객체들은 각각 다른 주소를 가지고 있지만, 내부에 있는 Integer
클래스 필드는 같은 객체를 가리키고 있다.Integer
클래스가 각각 다른 객체를 가리키게 만드려면 깊은 복제(deep clone)가 필요하다..clone()
메소드를 재정의하여 직접 참조 객체를 복사하는 코드를 작성해야 한다.
CloneableClass
클래스 재작성
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
클래스 재작성
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()
메소드를 통해 테스트Object
의 .finalize()
를 재정의할 수 있다.System.gc()
메소드를 호출하여 가급적 빨리 쓰레기 수집기를 돌릴 수 있다.finalize()
메소드가 호출되는 시점 자체도 명확하지 않다.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()
와 같은 형식으로 구현되어 있다.
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()
메소드를 오버라이드하면 된다.
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 + '\'' +
'}';
}
}
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;
을 하면 순서가 반대로된 리스트가 반환된다.