[이것이자바다] chapter 11. 기본 API클래스 (API 클래스)

chapter11

기본 API클래스 (API 클래스)

API(Application Programming Interface)

java.lang 패키지

java.lang 패키지의 클래스 종류

클래스 용도
Object - 자바 클래스의 최상위 클래스로 사용
System - 표준 입력 장치(키보드)로 부터 데이터를 입력받을 때 사용
  - 자바 가상 기계를 종료시킬 때 사용
  - 쓰레기 수집기(Garbage Collector)를 실행 요청할 때 사용
Class - 클래스를 메모리에 로딩할 때 사용
String - 문자열을 저장하고 여러 가지 정보를 얻을 때 사용
StringBuffer, StringBuilder - 문자열을 저장하고 내부 문자열을 조작할 때 사용
Math - 수학 함수를 사용할 때 사용
Wrapper (Byte, Short, - 기본 타입의 데이터를 갖는 객체를 만들 때 사용
Character, Integer,Float, - 문자열을 기본 타입으로 변환 할 때 사용
Double, Boolean, Long) - 입력값 검사에 사용

java.util 패키지

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

java.lang 의 Object 클래스

객체 비교(equals())

Object 클래스의 equals() 메소드

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

=> equals() 메소드의 매개 타입은 Object 인데, 이것은 모든 객체가 매개값으로 대입될 수 있음을 말한다. 그 이유는 Object가 최상위 타입이므로 모든 객체는 Object 타입으로 자동 타입 변환될 수 있기 때문이다.

=> 그런데 우리가 흔히 쓰는 equals()는 왜 객체 비교가 아닌 논리적으로 비교할까? String 클래스만 하더라도 두 String 객체의 주소비교가 아니라 문자열 비교를 하니 말이다.
=> 정답은 바로 String 클래스가 Object의 equals() 메소드를 재정의(오버라이딩) 해서 번지 비교가 아닌 문자열 비교로 변경했기 때문이다.
=> 이처럼 Object 클래스의 equals() 메소드는 직접 사용되지 않고 하위 클래스에서 재정의하여 논리적으로 동등 비교 할 때 이용된다.

equls()메소드 오버라이딩 하는 법

  1. 매개값(비교 객체)이 기준 객체와 동일한 타입의 객체인지 먼저 확인해야 하는데 instanceof 연산자를 사용하여 확인하자.
  2. 비교 객체가 다른 타입이라면 equals() 메소드는 false를 리턴해야 한다.
  3. 비교 객체가 동일한 타입이라면 기준 객체로 Casting하여 필드값이 동일한지 검사한다.
  4. 필드값이 동일하면 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())

=> 논리적 동등 비교시 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
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())

  1. 얕은 복제(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 객체는 제대로 복사가 되지 않고 있다.
  1. 깊은 복제(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 클래스

리턴 타입 메소드(매개 변수) 설명
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와 다르게 논리적 비교가 가능하다.
a b Objects.equals(a,b)
not null not null a.equals(b)의 리턴값
null not null false
not null null false
null null true
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())

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())

리턴타입 메소드(매개변수) 설명
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())

리턴타입 메소드(매개 변수) 설명
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
// 이름이 없습니다.