[C++ to Java] 6. Java.lang 패키지 & 유용한 클래스
Java 기본 패키지들에 대해 알아보자. 2022-01-15

Java의 기존 패키지들

안녕하세요? JustKode 입니다. 오늘은 Java기본 패키지들에 대해서 알아보는 시간을 가져 보겠습니다. 기본으로 import 되어 있는 java.lang 부터 시작해서, java.util 에 있는 패키지까지 알아 보도록 하겠습니다.

java.lang.Object

java.lang.ObjectJava내에서 모든 Class가 상속 받은, 가장 상위의 조상 클래스 입니다. 우리가 extend 문 없이 class SomeClass 이런 식으로 작성 했다고 가정하면, SomeClassjava.lang.Object를 상속 받은 것 입니다.

우리는 java.lang.Object에 있는 메서드들을 상속 받아, 특별한 구현 없이 클래스에 있는, 기본으로 구현된 기본 메서드들을 사용 할 수 있습니다. 중요 메서드들만 언급 하겠습니다.

  • public boolean equals(Object obj): 객체 자신과 객체 obj 가 같은 내용을 가지고 있는 지 알려 줍니다.
  • public Class getClass(): 객체의 클래스 정보를 담고 있는 클래스 인스턴스를 반환 합니다.
  • public String toString(): 객체의 스트링 값을 반환 합니다.
  • public int hashCode(): 객체의 주소값을 해시 변환한 값을 반환 합니다.

우리는 대체로, 위에 있는 메서드들을 오버라이딩 하여, 클래스를 구현 합니다. 아래의 예시는 위에 있는 클래스들을 오버라이딩 하지 않았을 때 입니다. java.lang.Object에 구현 되어 있는 것을 따릅니다.

class UserInfo {
    int id;
    String name;

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

public class Main {
    public static void main(String[] args) {
        UserInfo info = new UserInfo(1, "JustKode");
        UserInfo info2 = new UserInfo(1, "JustKode");

        System.out.println(info);  // toString() 에 구현 되어 있는 String 출력, 기본적으로 Class명@hashCode() 를 출력
        System.out.println(info2);

        System.out.println(info.getClass());

        if (info.equals(info2)) {
            System.out.println("일치!");
        } else {
            System.out.println("불일치!");
        }
    }
}
UserInfo@1134affc
UserInfo@d041cf
class UserInfo
불일치!

결과를 분석 해 보겠습니다.

분명 info, info2는 같은 내용을 담고 있지만, equals()를 통해 확인 해 보니 같지 않다고 출력 합니다. equals()는 단순히 각 멤버 변수의 만을 비교 하기 때문에, String 같은 경우는 주소값 기반으로 비교문 실행, 고로 같다고 하지 않는 것이 문제 입니다. 그러므로 우리는 equals()오버라이딩 할 필요가 있어 보입니다.

또한, info를 출력 했을 때, info 내의 값들이 아닌, UserInfo@hashcode 이런 식으로 출력이 됩니다. 객체를 출력 했을 때 나오는 String을 바꾸려면, toString()오버라이딩 하면 됩니다.

한 번, UserInfo에서 java.lang.Object에 있는 메서드들을 오버라이딩 해 보겠습니다.

class UserInfo {
    int id;
    String name;

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

    public boolean equals(UserInfo u) {
        return (this.id == u.id) && this.name.equals(u.name);
    }

    public String toString() {
        return String.format("(id: %d, name: %s)", this.id, this.name);
    }
}

public class Main {
    public static void main(String[] args) {
        UserInfo info = new UserInfo(1, "JustKode");
        UserInfo info2 = new UserInfo(1, "JustKode");

        System.out.println(info);  // toString() 에 구현 되어 있는 String 출력
        System.out.println(info2);

        System.out.println(info.getClass());

        if (info.equals(info2)) {
            System.out.println("일치!");
        } else {
            System.out.println("불일치!");
        }
    }
}
(id: 1, name: JustKode)
(id: 1, name: JustKode)
class UserInfo
일치!

우리는 이를 통해, 더 완성도 있는 클래스를 만들 수 있었습니다.

clone()

이는 따로 다룰 필요가 있을 것 같아, 분리 하였습니다. 생각해 보면, 우리는 **C++**에서, 복사 생성자에 대해서 배웠던 것 같지만, 여태까지 다루지 않았습니다. 사실, java.lang.Object객체 스스로를 복사하여 반환하는 clone()을 가지고 있습니다. 자바 내에서 기본적으로 구현 되어 있는 클래스는 clone() 함수가 내부적으로 구현 되어 있는 경우가 있습니다. 예를 들어 Array 객체에 구현 되어 있는 clone()을 보겠습니다.

import java.util.Arrays;

public class Main {
    public static void main(String[] args) {
        int[] arr1 = {1, 2, 3, 4};
        int[] arr2 = arr1.clone();

        System.out.printf("주솟값: %d / %d%n", arr1.hashCode(), arr2.hashCode());
        System.out.println("객체 값: " + Arrays.toString(arr1) + " / " + Arrays.toString(arr2));  // Arrays.toString(arr) 은 배열을 String으로 변환
    }
}
주솟값: 1456208737 / 288665596
객체 값: [1, 2, 3, 4] / [1, 2, 3, 4]

이처럼 우리는 주소값은 다르지만, 내부의 값은 같은 객체를 만들 수 있었습니다. 이를 우리가 만든 클래스에 적용 하려면 어떻게 해야 할까요? 일단 직접 클래스를 만들어 보는 시간을 가져 보도록 하겠습니다.

클래스에서 clone()을 오버라이딩 하여 구현 하고자 할 때, Cloneable 인터페이스를 implement 한다는 문구가 있어야 합니다. 그 다음에는 public 클래스명 clone() 을 구현 해야 합니다. 이는 대부분 부모 클래스clone()을 호출 하여 부모 클래스의 멤버들을 복사 하고, 이를 자식 클래스의 멤버들을 따로 복사 해 주는 방식으로 진행 합니다. 일단 부모 클래스clone() 만 호출하여 clone()을 구현 해 보겠습니다.

import java.util.Arrays;

class MyObj implements Cloneable{
    public String name;  // 예시를 위해 이렇게 구현
    public int[] array;

    public MyObj(String name, int... array) {  // 첫 번째 파라미터를 제외, 두 번째 ~ n 번째 파라미터가 배열로 변환 됨.
        this.name = name;
        this.array = array;
    }

    public MyObj clone() {
        try {
            MyObj myObj = (MyObj) super.clone();
            return myObj;
        } catch (CloneNotSupportedException e) {  // 사실 부모 클래스에 clone이 구현 되어 있어 이 코드를 실행 할 일이 없음.
            e.printStackTrace();
            return null;
        }
    }
}

public class Main {
    public static void main(String[] args) {
        MyObj myObj1 = new MyObj("JustKode", 1, 2, 3, 4);
        MyObj myObj2 = myObj1.clone();

        System.out.printf("배열 주솟값: %d / %d%n", myObj1.array.hashCode(), myObj2.array.hashCode());
        System.out.printf("객체 내부 배열 값: %s / %s%n", Arrays.toString(myObj1.array), Arrays.toString(myObj2.array));

        System.out.printf("문자열 주솟값: %d / %d%n", myObj1.name.hashCode(), myObj2.name.hashCode());
        System.out.printf("객체 내부 문자열 값: %s / %s%n", myObj1.name, myObj2.name);
    }
}
배열 주솟값: 288665596 / 288665596
객체 내부 배열 값: [1, 2, 3, 4] / [1, 2, 3, 4]
문자열 주솟값: -15480815 / -15480815
객체 내부 문자열 값: JustKode / JustKode

이처럼 부모 클래스clone() 만 사용 한다면, 사실 그냥 주솟값을 대입한 수준에서 복사가 진행 됩니다. 즉, 얕은 복사가 일어 나는 것 입니다. 그러므로 우리는 이를 수정 해 줄 필요가 있어 보입니다. 일단 String 은 문자열이 새로 할당이 되는 경우는 있어도, 문자열이 변경이 되는 경우는 없으니, 복사는 생략 해도 됩니다. 관련 게시글

그러면 우리가 건들어야 할 멤버 변수는 array 뿐이니, 그냥 해당 코드만 건들어 주면 됩니다. Array 내부에 있는 clone()을 이용하면 간단 할 것 입니다. 그럼 단 한 줄만 추가하면 될 것으로 예상 됩니다.

import java.util.Arrays;

class MyObj implements Cloneable{
    public String name;
    public int[] array;

    public MyObj(String name, int... array) {
        this.name = name;
        this.array = array;
    }

    public MyObj clone() {
        try {
            MyObj myObj = (MyObj) super.clone();
            myObj.array = this.array.clone();  // 해당 코드가 추가 됨
            return myObj;
        } catch (CloneNotSupportedException e) {  // 사실 부모 클래스에 clone이 구현 되어 있어 이 코드를 실행 할 일이 없음.
            e.printStackTrace();
            return null;
        }
    }
}

public class Main {
    public static void main(String[] args) {
        MyObj myObj1 = new MyObj("JustKode", 1, 2, 3, 4);
        MyObj myObj2 = myObj1.clone();

        System.out.printf("배열 주솟값: %d / %d%n", myObj1.array.hashCode(), myObj2.array.hashCode());
        System.out.printf("객체 내부 배열 값: %s / %s%n", Arrays.toString(myObj1.array), Arrays.toString(myObj2.array));

        System.out.printf("문자열 주솟값: %d / %d%n", myObj1.name.hashCode(), myObj2.name.hashCode());
        System.out.printf("객체 내부 문자열 값: %s / %s%n", myObj1.name, myObj2.name);
    }
}
배열 주솟값: 288665596 / 13648335
객체 내부 배열 값: [1, 2, 3, 4] / [1, 2, 3, 4]
문자열 주솟값: -15480815 / -15480815
객체 내부 문자열 값: JustKode / JustKode

java.lang.String

우리가 자주 사용하게 될 문자열 클래스 String 입니다. 일단 String 객체는 위에서도 언급 했듯, 수정 불가능 한(immutable) 클래스 입니다. 만약 "a" 라는 문자열이 기존에 존재하고, 해당 문자열에 "b"를 더했다고 가정해, "ab"가 되었다면, 해당 변수는 "a"의 주솟값이 아닌, 새롭게 만들어진 "ab"라는 문자열의 주솟값을 할당 받습니다.

public class Main {
    public static void main(String[] args) {
        String s = "Hello ";
        System.out.println(s.hashCode());
        s += "World!";
        System.out.println(s.hashCode());
    }
}
-2137068114
-969099747

문자열의 비교는 == 연산자를 이용하여 비교 하지 않습니다. 문자열은 String 클래스에 내장 된, equals() 라는 메서드를 이용하여, 비교 할 수 있습니다.

public class Main {
    public static void main(String[] args) {
        String s1 = "Hello";
        String s2 = "Hello";
        String s3 = "Hello?";
        System.out.println(s1.equals(s2));
        System.out.println(s1.equals(s3));
    }
}
true
false

기타 주요 메서드들은 다음과 같습니다.

  • String(String s): 주어진 문자열(s)의 값을 가지는 String 인스턴스를 생성 합니다.
  • String(char[] value): 주어진 문자열(value)의 값을 가지는 String 인스턴스를 생성 합니다.
  • char charAt(int index): 지정된 위치(index)에 있는 문자를 반환 합니다.
  • int compareTo(String str): 사전순으로 앞서면 음수, 같으면 0, 뒤쳐지면 양수를 반환 합니다. 반환 하는 숫자는, 제일 먼저 비교 가능한 문자의 아스키코드상 문자의 차이 (ex: "aaaa", "aabb" 면 3번째 글자인 'b'와 'a'의 차이), 혹은 서로 포함하는 관계일 시 (ex: "aaa", "aaaaa") 문자열 길이의 차이를 반환 합니다.
  • String concat(String str): str를 뒤에 이어서 붙여 준다.
  • boolean contains(CharSequence s): 지정된 문자열에 s가 포함되었는지 여부를 반환 합니다.
  • boolean startsWith(String prefix): 지정된 문자열이 prefix로 시작 하는지 여부를 반환 합니다.
  • boolean endsWith(String suffix): 지정된 문자열이 suffix로 끝나는지 여부를 반환 합니다.
  • boolean equalsIgnoreCase(String str): 문자열과 str를 대소문자 구분 없이 비교 하여, 일치 여부를 반환 합니다.
  • int indexOf(int ch): ch 가 문자열에 존재하는지 확인하여 위치를 반환 하고, 없으면 -1을 반환 합니다.
  • int indexOf(int ch, int pos): 문자열의 pos 위치 부터, ch가 문자열에 존재하는지 확인하여 위치를 반환 하고, 없으면 -1을 반환 합니다.
  • int indexOf(String str): str 가 문자열에 존재하는지 확인하여 위치를 반환 하고, 없으면 -1을 반환 합니다.
  • int length(): 문자열의 길이를 반환 합니다.
  • String replace(char old, char nw): 문자열에 old에 해당하는 부분을 new로 바꿉니다.
  • String replace(CharSequence old, CharSequence nw): 문자열에 old에 해당하는 부분을 new로 바꿉니다.
  • String replaceAll(String regex, String replacement): 정규식 regex에 해당하는 부분을 replacement로 바꿉니다.
  • String replaceFirst(String regex, String replacement): 정규식 regex에 해당하는 부분 중 첫번째로 등장한 부분을 replacement로 바꿉니다.
  • String[] split(String regex): 문자열을 정규식 regex에 해당 하는 부분으로 나누어, String 배열을 반환 합니다.
  • String substring(int begin), String substring(int begin, int end): 문자열을 begin 위치 부터, end (없으면 끝)까지 추출 한 문자열을 반환 합니다.
  • String toLowerCase(): 문자열을 모두 소문자로 변환하여 반환 합니다.
  • String toUpperCase(): 문자열을 모두 대문자로 변환하여 반환 합니다.
  • String trim(): 문자열 왼쪽과 오른쪽 끝의 공백을 제거 합니다.
  • static String valueOf(obj): obj를 문자열로 치환 합니다.
import java.util.Arrays;
import java.util.Locale;

public class Main {
    public static void main(String[] args) {
        char[] s = {'H', 'e', 'l', 'l', 'o', ' ', 'W', 'o', 'r', 'l', 'd', '!'};
        String s1 = new String(s);
        String s2 = new String(s1);
        System.out.println(s1.charAt(1));
        System.out.println(s1.compareTo("Hello"));
        System.out.println(s1.concat(s2));
        System.out.println(s1.contains("Hello"));
        System.out.println(s1.startsWith("Hel"));
        System.out.println(s1.endsWith("ld!"));
        System.out.println(s1.equalsIgnoreCase("HELLO WORLD!"));
        System.out.println(s1.indexOf('o'));
        System.out.println(s1.indexOf('o', 5));
        System.out.println(s1.indexOf(" Wor"));
        System.out.println(s1.length());
        System.out.println(s1.replace("Hello", "Goodbye"));
        System.out.println(s1.replace('o', 'O'));
        System.out.println(s1.replaceAll("o", "O"));
        System.out.println(s1.replaceFirst("o", "O"));
        System.out.println(Arrays.toString(s1.split(" ")));
        System.out.println(s1.substring(3));
        System.out.println(s1.substring(3, 7));
        System.out.println(s1.toLowerCase());
        System.out.println(s1.toUpperCase());
        System.out.println(" asdfasdf ".trim());
        System.out.println(String.valueOf(12345 + 12345));
    }
}
e
7
Hello World!Hello World!
true
true
true
true
4
7
5
12
Goodbye World!
HellO WOrld!
HellO WOrld!
HellO World!
[Hello, World!]
lo World!
lo W
hello world!
HELLO WORLD!
asdfasdf
24690

java.lang.Math

java.lang.MathJava 내에서 수학적인 연산을 위해 주로 사용 하는 함수 및 상수를 모아 놓은 클래스 입니다. 우리는 이에 대해서 정리를 해 보고자 합니다.

Constant

  • Math.E: 자연로그의 밑값 (e) 입니다. 2.718... 의 값을 가집니다.
  • Math.PI: 원주율로, 3.14159... 의 값을 가집니다.

random()

Math.random()C++와 유사하게, 0.0 이상 1.0 미만의 double형 값을 생성, 이를 반환 합니다.

abs()

abs()는 들어온 파라미터의 절댓값을 반환 합니다.

floor(), ceil(), round()

floor()는 들어온 파라미터의 같거나 작은 수 중, 가장 큰 정수를, ceil()은 들어온 파라미터의 같거나 큰 수중, 가장 작은 정수를, round()반올림값을 반환 합니다.

max(), min()

max()는 두 값을 비교하여 더 큰 값을, min()은 두 값을 비교하여 더 작은 값을 반환 합니다.

pow(), sqrt()

pow(a, b)a의 b승을 반환 하고, sqrt()는 입력 된 값의 제곱근 값을 반환 합니다.

sin(), cos(), tan()

sin() 메소드는 입력 값의 사인 값, cos() 메소드는 입력 값의 코사인 값tan() 메소드는 입력 값의 탄젠트 값을 반환 합니다.

public class Main {
    public static void main(String[] args) {
        System.out.println(Math.E);
        System.out.println(Math.PI);
        System.out.println(Math.random());
        System.out.println(Math.abs(3.14));
        System.out.println(Math.abs(-4.5));
        System.out.println(Math.floor(4.3));
        System.out.println(Math.ceil(3.14));
        System.out.println(Math.max(1, -1));
        System.out.println(Math.min(1, -1));
        System.out.println(Math.pow(4, 2.5));
        System.out.println(Math.sin(Math.PI / 6));
        System.out.println(Math.cos(Math.PI / 3));
        System.out.println(Math.tan(Math.PI / 4));
    }
}
2.718281828459045
3.141592653589793
0.20121282949220287
3.14
4.5
4.0
4.0
1
-1
32.0
0.49999999999999994
0.5000000000000001
0.9999999999999999

Wrapper Class

우리는 기본형 변수에 대해서는 객체로 다루지 않습니다. 예를 들어, int, double, float, boolean, byte, short, long 등은 객체가 아니므로, 메서드가 존재 하지 않습니다. 그러면, 기본 자료형에 대해서 이를 하나의 객체로써 동작 하기 위해서는 어떻게 해야 할까요? 답은 Wrapper Class를 사용 하는 것입니다. Wrapper Class를 이용하여, 기본 자료형을 객체 처럼 사용할 수 있습니다. Integer integer = new Integer(10); 처럼 말입니다. int -> Integer, char -> Character로 매핑 되는 것을 제외하면, byte -> Byte, double -> Double 등, 첫 번째 글자만 대문자로 바꾸면, Wrapper 클래스를 사용 할 수 있습니다.

Boolean, Character 을 제외 하고, parse자료형(String s)를 이용하면, 문자열을 기본 자료형으로 파싱할 수 있습니다. 또한, valueOf(String s, int radix)를 이용하여, 해당 문자열을 radix진법에 해당하는 수로 parse 합니다.

public class Main {
    public static void main(String[] args) {
        Integer integer = new Integer(10);
        Integer integer2 = Integer.parseInt("20");  // int 자체로 초기화 가능
        int integer3 = Integer.parseInt("30");
        int integer4 = Integer.valueOf("1001", 2);  // 두 번째 파라미터가 n이라면, 해당 문자열을 n진법에 해당하는 수로 parse 합니다.

        System.out.println(integer.intValue());
        System.out.println(integer.toString() + " 입니다.");
        System.out.println(integer.compareTo(integer2));
        System.out.println(integer3);
        System.out.println(integer4);

        Double d1 = new Double(3.14);
        Double d2 = Double.valueOf("6.28");
        System.out.println(d1.intValue());
        System.out.println(d1.doubleValue());
    }
}
10
10 입니다.
-1
30
9
3
3.14

마치며

다음 시간에는 Thread 에 대해서 알아 보는 시간을 가져 보도록 하겠습니다.