chapter11
기본 API클래스 (API 클래스)
API(Application Programming Interface)
- 자바 라이브러리
java.lang 패키지
- java.lang 패키지는 자바 프로그램의 기본적인 클래스를 담고 있는 패키지이다. 그렇기 때문에 java.lang 패키지에 있는 클래스와 인터페이스는 import 없이 사용할 수 있다.
java.lang 패키지의 클래스 종류
클래스 | 용도 |
---|---|
Object | - 자바 클래스의 최상위 클래스로 사용 |
System | - 표준 입력 장치(키보드)로 부터 데이터를 입력받을 때 사용 |
- 자바 가상 기계를 종료시킬 때 사용 | |
- 쓰레기 수집기(Garbage Collector)를 실행 요청할 때 사용 | |
Class | - 클래스를 메모리에 로딩할 때 사용 |
String | - 문자열을 저장하고 여러 가지 정보를 얻을 때 사용 |
StringBuffer, StringBuilder | - 문자열을 저장하고 내부 문자열을 조작할 때 사용 |
Math | - 수학 함수를 사용할 때 사용 |
Wrapper (Byte, Short, | - 기본 타입의 데이터를 갖는 객체를 만들 때 사용 |
Character, Integer,Float, | - 문자열을 기본 타입으로 변환 할 때 사용 |
Double, Boolean, Long) | - 입력값 검사에 사용 |
java.util 패키지
- java.util 패키지는 자바 프로그램 개발에 조미료 같은 역할을 하는 클래스들을 담고 있다. java.util 패키지는 컬렉션(Collection)클래스들이 대부분 차지하고 있다.
클래스 | 용도 |
---|---|
Arrays | - 배열을 조작(비교,복사,정렬,찾기)할 때 사용 |
Calendar | - 운영체제의 날짜와 시간을 얻을 때 사용 |
Date | - 날짜와 시간 정보를 저장하는 클래스 |
Objects | - 객체 비교, 널(null) 여부 등을 조사할 때 사용 |
StringTokenizer | - 특정 문자로 구분된 문자열을 뽑아 낼 때 |
Random | - 난수를 얻을 때 사용 |
java.lang 의 Object 클래스
- 클래스를 선언할 때 extends 키워드로 다른 클래스를 상속하지 않으면 암시적으로 java.lang.Object 클래스를 상속하게 된다. 왜냐하면 Object 클래스는 자바 클래스의 최상위 클래스이고, 따라서 자바의 모든 클래스는 Object 클래스의 자식이거나 자손 클래스이다.
- => Object는 자바의 최상위 부모 클래스에 해당한다.
- Object 클래스는 필드가 없고, 메소드로 구성되어 있다. 이 메소드들은 모든 클래스가 Object를 상속하기 때문에 모든 클래스에서 사용 가능하다.
객체 비교(equals())
Object 클래스의 equals() 메소드
public boolean equals(Object obj){...}
=> equals() 메소드의 매개 타입은 Object 인데, 이것은 모든 객체가 매개값으로 대입될 수 있음을 말한다. 그 이유는 Object가 최상위 타입이므로 모든 객체는 Object 타입으로 자동 타입 변환될 수 있기 때문이다.
- Object 클래스의 equals() 메소드는 비교 연산자인 == 과 동일한 결과를 리턴한다(즉 주소값을 비교한다는 뜻). 따라서 동일한 객체이면 true, 서로 다른 객체이면 false를 리턴한다.
=> 그런데 우리가 흔히 쓰는 equals()는 왜 객체 비교가 아닌 논리적으로 비교할까? String 클래스만 하더라도 두 String 객체의 주소비교가 아니라 문자열 비교를 하니 말이다.
=> 정답은 바로 String 클래스가 Object의 equals() 메소드를 재정의(오버라이딩) 해서 번지 비교가 아닌 문자열 비교로 변경했기 때문이다.
=> 이처럼 Object 클래스의 equals() 메소드는 직접 사용되지 않고 하위 클래스에서 재정의하여 논리적으로 동등 비교 할 때 이용된다.
equls()메소드 오버라이딩 하는 법
- 매개값(비교 객체)이 기준 객체와 동일한 타입의 객체인지 먼저 확인해야 하는데 instanceof 연산자를 사용하여 확인하자.
- 비교 객체가 다른 타입이라면 equals() 메소드는 false를 리턴해야 한다.
- 비교 객체가 동일한 타입이라면 기준 객체로 Casting하여 필드값이 동일한지 검사한다.
- 필드값이 동일하면 true를, 동일하지 않다면 false를 리턴한다.
public class Member {
public String id;
public Member(String id){
this.id = id;
}
@Override
public boolean equals(Object obj) {
if(obj instanceof Member){ //obj가 Member 타입인지 확인
Member member = (Member)obj;
if(id.equals(member.id)) // 만약 둘이 같은 문자열이라면 트루 리턴
return true;
//id는 String 객체이므로 문자열이 같은지 아닌지 비교한다.
}
return false; // 매개값이 Member 타입이 아니거나 id 필드값이 다른 경우 false 리턴
}
}
public class MemberExample {
public static void main(String[] args) {
Member member1 = new Member("Kim");
Member member2 = new Member("Kim");
Member member3 = new Member("Gyu");
if(member1.equals(member2)){
System.out.println("member1과 member2의 id가 같습니다. ");
}else{
System.out.println("member1과 member2의 id가 다릅니다. ");
}
if(member1.equals(member3)){
System.out.println("member1과 member3의 id가 같습니다. ");
}else{
System.out.println("member1과 member3의 id가 다릅니다. ");
}
}
}
//실행 결과:
//member1과 member2의 id가 같습니다.
//member1과 member3의 id가 다릅니다.
객체 해쉬코드(hashCode())
- 객체 해시코드란 객체를 식별한 하나의 정수값을 말한다.
- Object의 hashCode() 메소드는 객체의 메모리 번지를 이용해서 해시코드를 만들어 리턴하기 때문에 객체마다 다른 값을 가지고 있다.
=> 논리적 동등 비교시 hashCode()를 오버라이딩할 필요성이 있는데, 컬렉션프레임워크 중 HashSet, HashMap, Hashtable 은 다음과 같은 방법으로 키(key)에 대하여 두 객체가 동등한지 비교한다. (1) 우선 hashCode() 메소드를 실행해서 리턴된 해시코드 값이 같은지를 본다. 해시코드 값이 다르면 다른 객체로 판단하고, 해시코드 값이 같으면 (2) equals() 메소드로 다시 비교한다. 그렇기 때문에 hashCode() 메소드가 true 가 나와도 equals()의 리턴값이 다르면 다른 key라고 판단하고 다른 객체가 된다.
다음 예제는 equals()메소드는 오버라이딩 했지만 hashCode()메소드는 오버라이딩 하지 않아 없다고 판단하여 null값을 반환하는 case이다.
Object 클래스의 hashCode() 메소드는 오버라이딩 하지 않은 경우
package Generic;
public class Key {
private int number;
public Key(int number){
this.number = number;
}
@Override
public boolean equals(Object obj) {
if(obj instanceof Key){
Key key = (Key)obj;
return number == key.number;
}
return false;
//Object 클래스의 hashCode()메소드는 오버라이딩 하지 않았다.
}
결국 Map의 해당되는 Entry를 못찾아 null을 반환하였다.
package Generic;
import java.util.HashMap;
public class KeyExample {
public static void main(String[] args) {
HashMap<Key,String> map = new HashMap<>();
map.put(new Key(1),"김도형");
String result = map.get(new Key(1));
System.out.println(result); // null
}
}
=> 해결책은 Key클래스에 hashCode() 메소드를 오버라이딩해 return number; 로 해놓으면 그때서야 Map 이 정상작동하여 null 이 아닌 김도형을 반환한다. (hashMap의 기본 동작이 키의 hashCode()메소드, 이후 equals()메소드로 키가 같은지 아닌지 검사를 하는데 아까의 같은 경우는 hashCode()를 오버라이딩 하지 않아 Object클래스의 hashCode()가 작동하여(이는 주소값만 다르면 false 이다.) 키를 찾지 못하였고, hashCode()를 제대로 오버라이딩 하면 비로소 논리적 동등비교를 검사할 수 있으므로 정상작동되는 것이다.
객체 문자 정보(toString())
: Object 클래스의 toString() 메소드는 객체의 문자 정보를 리턴한다. 객체의 문자 정보란 객체를 문자열로 표현한 값을 말한다.
(기본적으로 Object 클래스의 toString() 메소드는 “클래스명@16진수해시코드”로 구성된 문자정보를 리턴한다.)
public class Test {
public static void main(String[] args) {
Object obj1 = new Object();
System.out.println(obj1.toString());
}
}
// 실행 결과: java.lang.Object@19dfb72a
- Object 하위 클래스는 toString() 메소드를 재정의(오버라이딩)하여 간결하고 유익한 정부를 리턴하도록 되어 있다.
- 예를 들어 String 클래스는 toStirng() 메소드를 재정의해서 저장하고 있는 문자열을 리턴한다.
- 또 물론 개발자가 재정의하여 사용할 수 있다.
public class SmartPhone{
private String company;
private String os;
SmartPhone(String company, String os){
this.company = company;
this.os = os;
}
@Override
public String toString() {
return company + " , " + os;
}
}
public class SmartPhoneExample {
public static void main(String[] args) {
SmartPhone s1 = new SmartPhone("Samsung","android");
System.out.println(s1.toString());
//실행결과 : Samsung , android
}
}
객체 복제(clone())
- 객체 복제는 원본 객체의 필드값과 동일한 값을 가지는 새로운 객체를 생성하는 것을 말하는데, 쓰는 이유는 복제를 하여 원본 객체를 안전하게 보호하기 위해서다.
- clone()을 사용하면 필드가 원시 타입(Primitive type)일 경우 값이 복사가 되고, 참조 타입(Reference type)일 경우 객체의 번지가 복사된다.
- 따라서 일부 참조 타입의 경우엔 복사가 제대로 이루어지지 않는다. 배열의 경우 복사된 객체나 원본 객체중 하나가 element의 값을 바꾸면 나머지 한 객체의 배열 값도 바뀌게 된다. 왜냐하면 같은 배열을 참조하기 때문이다. 클래스의 필드 값도 같은 이유이다.
- 이와 같이 참조 타입의 일부는 불완전한 복사가 되는 걸 얕은 복제(thin clone)이라 한다.
- 얕은 복제(thin clone)
clone()을 사용한 클래스 Member
public class Member implements Cloneable {
public String name;
public int age;
public int[] scores;
public Car car;
Member(String name, int age, int[] scores, Car car){
this.name = name;
this.age = age;
this.scores = scores;
this.car = car;
}
public Member getClone() {
Member cloned = null;
try {
cloned = (Member)this.clone(); // clone()으로 복사.
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return cloned;
}
}
class Car{
private String model;
public Car(String model){
this.model = model;
}
}
clone()을 사용한 예제
public class MemberExample {
public static void main(String[] args) {
Member member = new Member("김도형", 27, new int[]{1,2,3}, new Car("Hyundai"));
Member newMember = member.getClone();
newMember.name= "김유정";
newMember.age = 31;
newMember.scores[0] = 0;
newMember.car.setModel("Volkswagen");
//Member
System.out.println(member.name);
System.out.println(member.age);
for(int i=0; i<member.scores.length; i++){
System.out.print(member.scores[i]+" ");
}
System.out.println(member.car,getModel());
//newMember
System.out.println(newMember.name);
System.out.println(newMember.age);
for(int i=0; i<newMember.scores.length; i++){
System.out.print(newMember.scores[i]+" ");
}
System.out.println(newMember.car.getModel());
}
}
//실행결과
//김도형
//27
//0 2 3 Volkswagen
//김유정
//31
//0 2 3 Volkswagen
// => clone()은 필드가 기본 타입일 경우 값 복사가 일어나고, 필드가 참조 타입일 경우에는 객체의 번지가 복사되어
// 배열과 Car 객체는 제대로 복사가 되지 않고 있다.
- 깊은 복제(deep clone)
- 얕은 복제는 일부 참조 타입의 경우 불완전한 복제가 이루어진다는 걸 알 수 있었다. 이 경우 Object 클래스의 clone() 메소드를 오버라이딩하여 불완전한 복제를 이루는 참조 타입들을 복제하면 문제가 해결된다. 이것이 깊은 복제(deep clone)이다.
clone()을 재정의해서 깊은 복제로 변경
import java.util.Arrays;
public class Member implements Cloneable {
public String name;
int age;
int[] scores;
Car car;
Member(String name, int age, int[] scores, Car car){
this.name = name;
this.age = age;
this.scores = scores;
this.car = car;
}
@Override
protected Object clone() throws CloneNotSupportedException {
//먼저 얕은 복사를 해서 name,age를 복제한다.
Member cloned = (Member)super.clone(); // Object 클래스의 clone() 호출
// 배열인 scores를 깊은 복제한다.
cloned.scores = Arrays.copyOf(this.scores,this.scores.length);
//car를 깊은 복제한다.
cloned.car = new Car(this.car.getModel());
//깊게 복제된 cloned를 반환한다.
return cloned;
}
Member getClone() {
Member cloned = null;
try {
cloned = (Member)this.clone(); // 재정의된 Member 클래스의 clone() 메소드 호출.
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return cloned;
}
}
class Car{
private String model;
Car(String model){
this.model = model;
}
String getModel(){
return model;
}
void setModel(String model){
this.model = model;
}
}
깊은 복제 후 복제본 변경은 원본에 영향을 미치지 않는다.
public class MemberExample {
public static void main(String[] args) {
Member member = new Member("김도형", 27, new int[]{1,2,3}, new Car("Hyundai"));
Member newMember = member.getClone();
newMember.name= "김유정";
newMember.age = 31;
newMember.scores[0] = 0;
newMember.car.setModel("Volkswagen");
//Member
System.out.println(member.name);
System.out.println(member.age);
for(int i=0; i<member.scores.length; i++){
System.out.print(member.scores[i]+" ");
}
System.out.println(member.car.getModel());
//newMember
System.out.println(newMember.name);
System.out.println(newMember.age);
for(int i=0; i<newMember.scores.length; i++){
System.out.print(newMember.scores[i]+" ");
}
System.out.println(newMember.car.getModel());
}
}
//실행결과
//김도형
//27
//1 2 3 Hyundai
//김유정
//31
//0 2 3 Volkswagen
//=> 제대로 복사가 됨을 알 수 있다.
Objects 클래스
- Object 클래스의 메소드
리턴 타입 | 메소드(매개 변수) | 설명 |
---|---|---|
int | compare(T a, T b, Comparator<T> c) | 두 객체 a와 b를 Comparator를 사용해서 비교 |
boolean | deepEquals(Object a, Object b) | 두 객체의 깊은 비교(배열의 항목까지 비교) |
boolean | equals(Object a, Object b) | 두 객체의 얕은 비교(번지만 비교) |
int | hash(Object… values) | 매개값이 저장된 배열의 해시코드 생성 |
int | hashCode(Object o) | 객체의 해시코드 생성 |
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가 같다면 0, a가 b보다 크다면 양수를 리턴하도록 구현 클래스를 만들어야 한다.
public interface Comparator<T>{
int compare(T a, T b);
}
Comparator를 구현한 StudentComparator 클래스
public class StudentComparator implements Comparator<Student> {
@Override
public int compare(Student a, Student b) {
/*
// a의 번호가 b의 번호보다 크다면
if(a.getSno()>b.getSno())
return 1;
// a의 번호와 b의 번호가 같다면
else if(a.getSno() == b.getSno())
return 0;
// a의 번호가 b의 번호보다 작다면
else
return -1;
*/
return Integer.compare(a.getSno(),b.getSno());
}
}
비교자 사용
public class CompareExample {
public static void main(String[] args) {
Student s1 = new Student(1);
Student s2 = new Student(1);
Student s3 = new Student(2);
int result = Objects.compare(s1, s2 , new StudentComparator());
System.out.println(result);
result = Objects.compare(s1,s3, new StudentComparator());
System.out.println(result);
}
}
- 동등 비교(equals()와 deepEquals())
- Objects의 equals는 Object의 equals와 다르게 논리적 비교가 가능하다.
- Objects.equals(Object a, Object b) 는 두 객체의 동등을 비교하는데 다음과 같은 결과를 리턴한다. 특이한 점은 a와 b가 모두 null 인 경우 true를 반환한다는 점이다. a와 b가 모두 null 이 아닌 경우 a.equals(b)의 결과를 리턴한다.
a | b | Objects.equals(a,b) |
---|---|---|
not null | not null | a.equals(b)의 리턴값 |
null | not null | false |
not null | null | false |
null | null | true |
- Objects.deepEquals(Object a , Object b) 역시 두 객체의 동등을 비교하는데, a와 b가 서로 다른 배열일 경우, 항목 값이 모두 같다면 true를 리턴한다. 이것은 Arrays.deepEquals(Object[] a, Object[] b) 와 동일하다.
a | b | Objects.deepEquals(a,b) |
---|---|---|
not null(not array) | not null(not array) | a.equals(b) 의 리턴값 |
not null(array) | not null(array) | Arrays.deepEquals(a,b)의 리턴값 |
not null | null | false |
null | not null | false |
null | null | true |
Objects 클래스의 equals() 와 deepEquals()
import java.util.Objects;
public class EqualsExample {
public static void main(String[] args) {
// 배열이 아닌 변수인 경우
Integer o1 = 1000;
Integer o2 = 1000;
System.out.println(Objects.equals(o1,o2)); // true
System.out.println(Objects.equals(o1, null)); // false
System.out.println(Objects.equals(null, o2));// false
System.out.println(Objects.equals(null,null)); // true
System.out.println(Objects.deepEquals(o1,o2));// true
System.out.println();
Integer[] arr1 = {1, 2}; //Integer 타입인 배열 생성
Integer[] arr2 = {1, 2};
// equals론 배열의 동등 비교 불가능
System.out.println(Objects.equals(arr1,arr2)); //false => equals로는 동등 비교 불가능 -> deepEquals로는 가능하다.
System.out.println(Objects.deepEquals(arr1,arr2)); // true
System.out.println(Objects.deepEquals(arr1, null)); // false
System.out.println(Objects.deepEquals(null, arr2)); // false
System.out.println(Objects.deepEquals(null,null)); // true
}
}
해시코드 생성(hash(),hashCode())
- Objects.hash(Object… values) 메소드는 매개값으로 주어진 값들을 이용해서 해시 코드를 생성하는 역할을 하는데, 주어진 매개값들로 배열을 생성하고 Arrays.hashCode(Object[])를 호출해서 해시코드를 얻고 이 값을 리턴한다.
=> 이 메소드는 클래스가 hashCode()를 재정의할때 러턴값을 생성하기 위해 사용하면 좋다. 왜냐하면 클래스가 여러 가지 필드를 가지고 있을 때 이 필드로부터 해시코드를 생성하게 되면 동일한 필드값을 가지는 객체는 동일한 해시코드를 가질수 있기 때문에 논리적 동등 비교가 가능하기 때문이다.
// Object 클래스의 hashCode() 메소드를 재정의할 때 이렇게 사용해봅시다! @Override public int hashCode(){ return Objects.hash(field1, field2, field3); }
import java.util.Objects;
public class HashCodeExample {
public static void main(String[] args) {
Student s1 = new Student(1,"홍길동");
Student s2 = new Student(1,"홍길동");
System.out.println(s1.hashCode()); // Objects 의 hash(sno,name);
System.out.println(Objects.hashCode(s2)); // Objects의 hashCode(s2);
}
static class Student{
int sno;
String name;
Student(int sno, String name){
this.sno = sno;
this.name = name;
}
@Override
public int hashCode() {
return Objects.hash(sno,name);
}
}
}
널 여부 조사(isNull(), nonNull(), requireNonNull())
- Objects.isNull(Object obj)는 매개값이 null일 경우 true을 리턴한다.
- 반대로 nonNull(Objects obj)는 매개값이 not null일 경우 true를 리턴한다.
- requireNonNull()은 다음같이 세 가지로 오버로딩(Overloading) 되어있다.
리턴타입 | 메소드(매개변수) | 설명 |
---|---|---|
T | requireNonNull(T obj) | not null -> obj |
null -> NullPointerException | ||
T | requireNonNull(T obj, String message ) | not null -> obj |
null -> NullPointerException(message) | ||
T | requireNonNull(T obj, Supplier<String> msgSupplier ) | not null -> obj |
null -> NullPointerException(msgSupplier.get()) //람다식이 만든 예외 메세지 출력 |
import java.util.Objects;
public class NullExample {
public static void main(String[] args) {
String str1 = "홍길동";
String str2 = null;
System.out.println(Objects.requireNonNull(str1));
try {
String name = Objects.requireNonNull(str2);
}catch (Exception e){
System.out.println(e.getMessage());
}
try{
String name = Objects.requireNonNull(str2,"error");
}catch (Exception e){
System.out.println(e.getMessage());
}
try{
String name = Objects.requireNonNull(str2, ()->"error!!");
}catch (Exception e){
System.out.println(e.getMessage());
}
}
}
객체 문자 정보(toString())
- Objects.toString() 은 객체의 문자 정보를 리턴하는데, 다음 두 가지로 오버로딩(Overloading) 되어 있다.
리턴타입 | 메소드(매개 변수) | 설명 |
---|---|---|
String | toString(Object o) | not null -> o.toString() |
null -> “null” | ||
String | toString(Objedct o , String nullDefault ) | not null -> o.toString() |
null - > nullDefault |
public class ToStringExample {
public static void main(String[] args) {
String str1 = "홍길동";
String str2 = null;
System.out.println(Objects.toString(str1));
System.out.println(Objects.toString(str2));
System.out.println(Objects.toString(str2, "이름이 없습니다."));
}
}
// 홍길동
// null
// 이름이 없습니다.