이것이 자바다 정리 #8 기본 API 클래스 2 (Objects, System, Class, Reflection, String, Tokenizer, Builder)

Jake Seo·2021년 5월 6일
0

이것이자바다

목록 보기
8/17

이것이 자바다 정리 #8 기본 API 클래스 2 (Objects, System, Class, Reflection, String, Tokenizer, Builder)

이것이 자바다 책을 참고하였습니다.

Objects 클래스 내장 메소드

equals()와 deepEquals()

  • 공통점
    • ab가 모두 null일 때 true를 리턴한다.
  • 차이점
    • 배열을 비교할 때, .equals()를 쓰면 배열의 주소 값이 똑같다면 true를 리턴한다.
    • 배열을 비교할 때, .deepEquals()를 쓰면 배열 내 항목 값이 모두 같다면 true를 리턴한다.
@Test
public void equalsAndDeepEquals() {
    Integer[] a = new Integer[]{1, 2};
    Integer[] b = new Integer[]{1, 2};

    boolean equals = Objects.equals(a, b);
    System.out.println("equals = " + equals); // false

    boolean deepEquals = Objects.deepEquals(a, b);
    System.out.println("deepEquals = " + deepEquals); // true
}

해시코드 생성(hash(), hashCode())

  • 매개변수로 주어진 값들을 이용해 해시코드를 생성한다.
  • 클래스가 .hashCode() 메소드를 재정의할 때 리턴 값을 생성하기 위한 용도로 사용하면 좋다.
@Override
public int hashCode() {
    return Objects.hash(number, name);
}

위와 같이 이를테면 Student 클래스에 numbername이라는 필드가 있을 때, .hashCode() 메소드를 위와 같이 오버라이드할 수 있다.

그러면 번호와 이름이 같은 객체에 대해 같은 해시코드를 리턴하게 된다.

널 체크(isNull(), nonNull(), requireNonNull())

  • isNull(): null인 경우, true
  • nonNull(): null이 아닌 경우, true
  • requireNonNull(): null인 경우, NullPointerException
    • requireNonNull(T obj, String message): NullPointerException과 메세지
    • requireNonNull(T obj, Supplier<String> msgSupplier): NullPointerException과 람다식 수행

requireNonNull()은 다음과 같이 다양한 인자를 받아 수행할 수 있다.

널체크 테스트 코드

@Test
public void nullCheck() {
    Object object = null;

    boolean isNull = Objects.isNull(object);
    System.out.println("isNull = " + isNull);
    boolean nonNull = Objects.nonNull(object);
    System.out.println("nonNull = " + nonNull);
    Objects.requireNonNull(object, () -> "널이라고!");
}

객체 문자 정보 (toString())

Objects.toString() 메소드를 이용해 내장된 .toString() 메소드를 끌어오거나, null에 대한 처리를 할 수도 있다.

  • toString(Object o): 기본으로 .toString()을 끌어옴. null이면 "null" 문자열 반환
  • toString(Object o, String nullDefault): null이면 nullDefault로 받은 문자열을 반환

System 클래스

java.lang 패키지에 속해있어 따로 import하지 않아도 이용할 수 있는 클래스이다. 운영체제의 기능을 이용할 때 사용된다.

  • 프로그램 종료
  • 키보드 입력
  • 모니터 출력
  • 메모리 정리
  • 현재 시간 읽기
  • 시스템 프로퍼티 읽기
  • 환경 변수 읽기

위와 같은 기능이 있다.

모든 필드와 메소드는 정적(static) 필드와 정적 메소드로 구성되어 있다.

프로그램 종료(exit())

  • .exit()는 현재 실행중인 프로세스를 강제 종료시킨다.
    • 인자로 종료 상태값이라 불리는 int 값을 받는다.
      • 0이면 정상종료
      • 0이 아니면 비정상종료이다.
      • 어떤 값을 줘도 종료는 된다.
  • System.exit()은 자동으로 SecurityManager.checkExit() 메소드를 호출시킨다.
    • SecurityManagercheckExit() 메소드를 오버라이드하여 종료 코드에 따른 예외처리가 가능하다.

예제 코드

public class Main {
    public static void main(String[] args) {

        SecurityManager securityManager = new SecurityManager() {
            @Override
            public void checkExit(int status) {
                System.out.println("status = " + status);
                if(status == 0) {
                    System.out.println("정상적으로 종료되었습니다.");
                } else {
                    System.out.println("비정상적으로 종료되었습니다.");
                    throw new SecurityException("예외처리를 해주세요.");
                }
            }
        };
        System.setSecurityManager(securityManager);

//        usualExit();
        unusualExit();
    }

    public static void usualExit() {
        System.out.println("정상적으로 프로그램을 종료시킵니다.");
        System.exit(0); // 0: 정상
    }

    public static void unusualExit() {
        System.out.println("비정상적으로 프로그램을 종료시킵니다.");
        System.exit(1); // 1: 비정상
    }
}

예제 코드를 실행시키면 위와 같은 결과가 나타난다.

가비지 콜렉터 실행(gc())

  • 일반적으로 가비지 콜렉터는 CPU가 한가하거나 메모리가 부족할 때 JVM에서 자동으로 실행한다.
  • System.gc()를 실행하면 가비지 콜렉터 수행 시점을 조금 앞당길 수 있다.
    • 바로 실행되는 것은 아니고 빠른 시간 내에 실행시키기 위해 노력하는 것이다.
    • 수집되는 순간 이전에 배웠던 .finalize() 메소드가 실행된다.

현재시각 읽기(currentTimeMillis(), nanoTime())

  • currentTimeMillis(): 현재 시간을 밀리 세컨드(1/1000초) 단위로 출력
  • nanoTime(): 현재 시간을 나노 세컨드(1/10^9초) 단위로 출력
  • 프로그램 실행 소요시간 측정에 사용된다.
  • 반환 타입은 long이다.

시스템 프로퍼티 읽기(getProperty())

JVM이 시작할 때 자동으로 갖게 되는 시스템의 속성 값을 반환한다.

  • java.version: 자바 버전
  • java.home: JRE의 설치 경로
  • os.name: 운영체제 이름
  • file.separator: 파일 구분자 (윈도우는 \)
  • user.name: 사용자 이름
  • user.home: 사용자의 홈디렉토리
  • user.dir: 사용자가 현재 작업중인 디렉토리 경로

System.getProperties() 메소드를 이용해 프로퍼티 정보를 받으면 위에 나열된 값 이외에도 많은 프로퍼티를 한번에 볼 수 있다.

환경 변수 읽기(getEnv())

운영체제에 설정된 환경변수를 가져올 수 있다.

System.getenv() 메소드를 이용하면 모든 환경변수가 들어있는 Map 형태의 값이 반환된다.

Class 클래스

클래스, 인터페이스의 메타데이터를 얻을 수 있다.

  • 클래스의 메타데이터란 클래스의 이름, 생성자 정보, 필드 정보, 메소드 정보 등을 말한다.

Class 객체를 얻는 방법

  • object.getClass() 메소드를 수행하여 얻을 수 있다.
  • Class.forName() 메소드를 수행하여 얻을 수 있다.
    • 해당 클래스가 존재하지 않는 경우의 예외처리를 위해 ClassNotFoundException을 처리해주어야 한다.

리플렉션

  • Class 객체를 이용해 메타데이터를 알아낼 때 이용된다.
    • 클래스 이름
    • 생성자 정보
    • 필드 정보
    • 메소드 정보
  • Class 객체에서 리플렉션을 얻는 방법
    • .getDeclaredConstructors(): Constructor 객체의 배열을 반환
    • .getDeclaredFields(): Field 객체의 배열을 반환
    • .getDeclaredMethods(): Method 객체의 배열을 반환

예제 코드

public class Myself {
    String name = "Jake Seo";
    int birthYear = 1992;
    int koreanAge;
    String[] favoriteFoods = {"김치", "밥"};

    public Myself() {
        koreanAge = LocalDateTime.now().getYear() - birthYear + 1;
    }

    public Myself(int thatYear) {
        koreanAge = thatYear - birthYear + 1;
    }

    public String getName() {
        return name;
    }

    public int getKoreanAge() {
        return koreanAge;
    }

    public String[] getFavoriteFoods() {
        return favoriteFoods;
    }
}
public class Main {
    public static void main(String[] args) {
        Class myselfClass = Myself.class;

        Field[] declaredFields = myselfClass.getDeclaredFields();
        for (Field declaredField : declaredFields) {
            System.out.println("declaredField = " + declaredField);
        }

        Constructor[] declaredConstructors = myselfClass.getDeclaredConstructors();
        for (Constructor declaredConstructor : declaredConstructors) {
            System.out.println("declaredConstructor = " + declaredConstructor);
        }

        Method[] declaredMethods = myselfClass.getDeclaredMethods();
        for (Method declaredMethod : declaredMethods) {
            System.out.println("declaredMethod = " + declaredMethod);
        }
    }
}

동적 객체 생성(newInstance())

  • new 연산자를 사용하지 않고 객체를 생성하는 방법이다.
  • 클래스의 이름이 런타임 시에 결정되는 경우에 사용된다.
  • 기본 생성자를 호출해서 객체를 생성하기 때문에, 반드시 기본 생성자가 있어야 한다.
    • 매개변수가 있는 생성자를 호출하고 싶다면 리플렉션을 이용해서 Constructor 객체를 얻어야 한다.
  • 추상 클래스나 인터페이스를 이용해 인스턴스를 만드려하면 InstantiationException 예외가 발생한다.
  • 클래스나 생성자가 접근 제한자로 인해 접근할 수 없다면 IllegalAccessException 예외가 발생한다.
  • 메소드의 반환 타입이 Object라서 강제 형변환을 해야 하는데, 보통 인터페이스를 이용한 다형성으로 처리한다.

String 클래스

  • java.lang 패키지에 속해있다.
  • 문자열 리터럴은 자동으로 String 객체로 생성된다.
  • Deprecated된 생성자 외에도 13개의 생성자를 더 제공한다.
  • 파일의 내용을 읽거나, 네트워크를 통해 받은 데이터는 보통 byte[] 이므로 이것을 문자열로 변환하기 위해 사용한다.
// 특정 인코딩으로 넘어온 바이트 배열을 문자열로 변경하는 생성자
String str = new String(byte[] bytes, String charsetName);

키보드에서 문자열을 입력받아 출력하는 생성자 예제 코드

public class Main {
    public static void main(String[] args) {
        byte[] bytes = new byte[100];

        try {
            System.out.print("문자열 입력: ");
            int readByteLength = System.in.read(bytes);
            System.out.println("\n읽어들인 문자의 수 = " + readByteLength);
            System.out.println("입력된 내용 = " + new String(bytes, 0, readByteLength - 1));

        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

.charAt() 메소드

  • char / 특정 위치의 문자를 반환한다.

.equals() 메소드

  • boolean / 두 문자열을 비교한다.

.getBytes() 메소드

  • byte[] / 문자열을 바이트 배열로 반환한다.
    • 인자로 UTF-8 같은 캐릭터셋 유형을 주면, 해당 캐릭터셋에 맞게 반환해준다.
  • 네트워크로 문자열을 전송하거나, 문자열을 암호화할 때 유용하다.

EUC-KR 문자셋을 이용하여 인코딩하면 영어 1바이트 한글 2바이트가 된다.
UTF-8 문자셋을 이용하여 인코딩하면 영어 1바이트 한글 3바이트가 된다.
기본 값은 시스템의 기본 문자셋을 이용한다.
존재하지 않는 문자셋을 입력하면 java.io.UnsupportedEncodingException 예외가 발생한다.

.indexOf() 메소드

  • 문자열 내에서 주어진 문자열의 위치를 인덱스 기준의 숫자로 반환한다.
    • 없으면 -1 반환

.length() 메소드

  • 총 문자열의 길이를 반환한다.

.replace() 메소드

  • 인자로 targetreplacement를 받아서 targetreplacement로 대체된 결과 문자열을 반환한다.

.substring()

  • 지정된 위치에서 끝까지 잘라낸 새로운 문자열을 반환한다.
  • 인자를 2개 주면 두번째 인자까지 잘라낸 새로운 문자열을 반환한다.

toLowerCase(), toUpperCase()

  • 대문자 혹은 소문자로 변환한다.
    • 대소문자 문자 비교 때는 .equalsIgnoreCase() 메소드를 이용하면 대소문자 무시 비교를 할 수 있다.

trim()

  • 앞뒤 공백을 제거한 문자를 반환한다.

valueOf()

  • 기본 타입값을 문자열로 반환한다.

StringTokenizer 클래스

주로 문자열이 특정한 구분자(delimiter)로 연결된 구조일 때, 특정한 구분자를 기준으로 문자열을 분리할 때 사용된다.

String 클래스에도 .split() 메소드가 있어서 특정 구분자를 기준으로 문자열을 나눌 수 있지만, 정규표현식을 이용해야 한다. StringTokenizer 클래스가 제공하는 .split()은 단순히 문자 delimeter만 구분해서 나누는 방식이다.

String.split()

public class StringTokenizerTest {
    String exampleString = "김철수,이미영-김문수";
    
    @Test
    public void stringSplitTest() {
        String[] split = exampleString.split(",|-");
        for (String s : split) {
            System.out.println("s = " + s);
        }
    }
}

위와 같이 정규표현식을 이용하여 구분할 수 있다. 예제 문자열은 이름이 , 혹은 -로 구분되어 있는데, 두 문자를 모두 구분자로 이용하기 위한 정규표현식이 ",|-"라는 문자열로 표현되었다.

StringTokenizer 생성자

문자열이 한 종류의 구분자로 되어있을 때 용이하며, 말 그대로 문자열을 토큰화한다. 토큰화한 문자열을 다루기 위해 3가지 내장 메소드를 제공한다.

  • int countTokens(): 꺼내지 않고 남아있는 토큰의 수를 반환한다.
  • boolean hasMoreTokens(): 남아 있는 토큰이 있는지 여부에 대해 반환한다.
  • String nextToken(): 토큰을 하나씩 꺼내온다.
    • 토큰이 남아있지 않다면, NoSuchElementException 예외를 발생시킨다.
@Test
public void stringTokenizerTest() {
    String exampleString2 = "김철수,이미영,김문수";
    StringTokenizer stringTokenizer = new StringTokenizer(exampleString2, ",");

    int countTokens = stringTokenizer.countTokens();
    System.out.println("countTokens = " + countTokens);

    boolean hasMoreTokens = stringTokenizer.hasMoreTokens();
    System.out.println("hasMoreTokens = " + hasMoreTokens);

    while(stringTokenizer.hasMoreTokens()) {
        String token = stringTokenizer.nextToken();
        System.out.println("token = " + token);
    }
}

StringBuffer, StringBuilder 클래스

필요성

String은 구조적으로는 내부의 문자열을 수정할 수 없다. 단, 새로운 문자열을 String 타입의 변수에 할당함으로써, 새로운 문자를 저장할 수 있다.

이를테면 String.replace()는 내부의 문자를 변경하는 것이 아니라, 변경된 새로운 문자열을 반환하는 것이다. String 타입끼리의 + 연산도 마찬가지이다.

이러한 방식의 단점은 아무래도 많은 문자열 변환 연산이 있을 때, 속도가 현저히 느려질 수 있다는 단점이 있다.

문자열 변경 작업이 많을 때, 그로 인한 속도 저하를 막기 위해 StringBuffer 혹은 StringBuilder 클래스가 이용되는 것이다.

위 두 클래스는 내부 버퍼(데이터를 임시로 저장하는 메모리)에 문자열을 저장해두고 그 안에서 추가, 수정, 삭제 작업을 할 수 있도록 설계되어 있다.

String타입처럼 새로운 문자열 객체를 만들어내고 다시 저장하는 방식이 아니라 진짜 기존의 문자열을 수정하는 방식이다.

두 클래스의 차이점

기본적으로 StringBufferStringBuilder의 사용 방법은 동일하다.

  • StringBuffer는 멀티 스레드 환경에서 사용할 수 있도록 동기화가 적용되어 있어 스레드에 안전하다.
  • StringBuilder는 단일 스레드 환경에서만 사용하도록 설계되어 있다.

생성자

  • StringBuilder(): 기본 생성자는 16개의 문자를 저장할 수 있는 초기 버퍼를 만든다.
  • StringBuilder(int capacity): capacity 만큼의 초기 버퍼를 만든다.

사실 버퍼가 부족하면 버퍼 크기를 자동으로 늘리기 때문에 초기 버퍼 사이즈가 크게 중요하진 않다.

메소드들

  • append(...): 문자열 끝에 매개값을 추가한다.
  • insert(int offset, ...): offset으로 지정된 자리에 매개값을 추가한다.
  • delete(int start, int end): 문자열의 일부분을 삭제한다.
  • deleteCharAt(int index): 문자열에서 주어진 index의 문자를 삭제한다.
  • replace(int start, int end, String str): 문자열의 일부를 다른 문자열로 변환한다.
  • reverse(): 문자열의 순서를 뒤바꾼다.
  • setCharAt(int index, char ch): 문자열에서 주어진 index의 문자를 다른 문자로 바꾼다.

append()insert() 메소드는 다양한 타입으로 오버로딩되어 있어서 다양한 타입을 문자로 추가, 삽입할 수 있다.

profile
풀스택 웹개발자로 일하고 있는 Jake Seo입니다. 주로 Jake Seo라는 닉네임을 많이 씁니다. 프론트엔드: Javascript, React 백엔드: Spring Framework에 관심이 있습니다.

0개의 댓글