java [8] java.lang패키지와 유용한 클래스

lsy·2022년 10월 22일
0

자바

목록 보기
10/14

java.lang

java.lang패키지는 자바 프로그래밍에 기본이 되는 클래스들을 포함하고 있다. 따라서 따로 import하지 않아도 사용할 수 있게 되어 있다.

Object 클래스

Object클래스는 모든 클래스의 최고 조상이다. 그렇기 때문에 이 클래스에 속한 멤버들은 모든 클래스에서 바로 사용가능하다.

equals(Object obj)

매개변수로 객체의 참조변수를 받아서 비교하여 그 결과를 boolean으로 알려 주는 역할을 한다. 이 메서드는 두 객체의 같고 다름을 참조변수의 값으로 판단한다. 따라서 서로 다른 두 객체를 비교하면 항상 false를 결과로 얻게 된다.

따라서, 만약 인스턴스가 가지고 있는 value값들을 비교하고 싶을 때는, 클래스안에서 equals메서드를 오버라이딩해서 객체에 저장된 내용을 비교하도록 변경하면 된다.

class Person {
	long id;

	public boolean equals(Object obj) {
		if(obj!=null && obj instanceof Person) {
			return id ==((Person)obj).id;
		} else {
			return false;
		}
	}

	Person(long id) {
		this.id = id;
	}
}

class EqualsEx2 {
	public static void main(String[] args) {
		Person p1 = new Person(8011081111222L);
		Person p2 = new Person(8011081111222L);

		if(p1==p2)
			System.out.println("p1과 p2는 같은 사람입니다.");
		else
			System.out.println("p1과 p2는 다른 사람입니다.");

		if(p1.equals(p2))
			System.out.println("p1과 p2는 같은 사람입니다.");
		else
			System.out.println("p1과 p2는 다른 사람입니다.");
	
	}
}

자주 사용하는 String클래스 역시 Object클래스의 equals메서드를 그대로 사용하는 것이 아니라 오버라이딩을 통해서 String인스턴스가 갖는 문자열 값을 비교하게 되어 있다.

hashCode()

이 메서드는 Hashing에 사용되는 해시함수를 구현한 것이다. 일반적으로 해시코드가 같은 두 객체가 존재하는 것이 가능하지만, Obejct클래스에 정의된 hashCode메서드는 객체의 주소값으로 해시코드를 만들어 반환하기 때문에 32bit JVM에서는 서로 다른 두 객체는 같은 해시코드를 가질 수 없었지만, 64 bit JVM에서는 8 바이트 주소 값으로 해시코드(4 바이트)를 만들기 때문에 해시코드가 중복될 수 있다.

앞에처럼 클래스의 인스턴스 변수 값으로 객체의 같고 다름을 판단해야하는 경우라면 equals메서드 뿐 만아니라 hashCode메서드도 적절히 오버라이딩해야 한다. 같은 객체라면 hashCode메서드를 호출했을 때의 결과값인 해시코드도 같아야 하기 때문이다.

toString()

이 메서드는 인스턴스에 대한 정보를 문자열로 제공할 목적으로 정의한 것이다. 인스턴스의 정보를 제공한다는 것은 대부분의 경우 인스턴스 변수에 저장된 값들을 문자열로 표현한다는 뜻이다.

Object클래스에 정의된 toString()은 아래와 같다.

public String toString() {
    return getClass().getName() + "@" + Integer.toHexString(hashCode());
}

클래스를 작성할 때 toString()을 오버라이딩하지 않으면 클래스 이름에 16진수의 해시코드를 얻게 될 것이다.

class Card {
	String kind;
	int number;

	Card() {
		this("SPADE", 1);  // Card(String kind, int number)를 호출
	}

	Card(String kind, int number) {
		this.kind = kind;
		this.number = number;
	}

	public String toString() {
		// Card인스턴스의 kind와 number를 문자열로 반환한다.
		return "kind : " + kind + ", number : " + number;
	}
}

class CardToString2 {
	public static void main(String[] args) {
		Card c1 = new Card();
		Card c2 = new Card("HEART", 10);
		System.out.println(c1.toString());
		System.out.println(c2.toString());
	}
}

위처럼 toString()을 적절히 오버라이딩하면, 인스턴스에 대한 정보를 출력할 수 있다.

추가로, 조상에 정의된 메서드를 오버라이딩할 때는 조상에 정의된 접근범위보다 같거나 더 넓어야 한다.

Clone()

이 메서드는 자신을 복제하여 새로운 인스턴스를 생성하는 역할을 한다. Object클래스에 정의된 Clone()은 단순히 인스턴스 변수의 값을 복사하기 때문에 참조 타입의 인스턴스 변수가 있는 클래스는 완전한 인스턴스 복제가 이루어지지 않는다. 따라서, clone메서드를 오버라이딩하여 새로운 인스턴스를 만들고, 복사하고자 하는 인스턴스의 내용을 복사해야 한다.

class Point implements Cloneable {
	int x;
	int y;

	Point(int x, int y) {
		this.x = x;
		this.y = y;
	}

	public String toString() {
		return "x="+x +", y="+y;
	}

	public Object clone() {
		Object obj = null;
		try {
			obj = super.clone();  // clone()은 반드시 예외처리를 해주어야 한다.
		} catch(CloneNotSupportedException e) {}
		return obj;
	}
}

class CloneEx1 {
	public static void main(String[] args){
		Point original = new Point(3, 5);
		Point copy = (Point)original.clone(); // 복제(clone)해서 새로운 객체를 생성
		System.out.println(original);
		System.out.println(copy);
	}
}

clone()을 사용하려면 먼저 복제할 클래스가 Cloneable 인터페이스를 구현해야하고, clone()을 오버라이딩하면서 접근 제어자를 protected에서 public으로 변경한다. 그래야만 상속관계가 없는 다른 클래스에서 clone()을 호출할 수 있다. 마지막으로 clone()을 호출하는 코드가 포함된 try-catch문을 작성한다.

Cloneable인터페이스를 구현한 클래스의 인스턴스만 clone()을 통한 복제가 가능한데, 그 이유는 인스턴스의 데이터를 보호하기 위해서이다. Cloneable인터페이스가 구현되어 있다는 것은 클래스 작성자가 복제를 허용한다는 의미이다.

공변 반환타입

JDK1.5부터 공변 반환타입이라는 것이 추가되었는데, 이 기능은 오버라이딩할 때 조상 메서드의 반환타입을 자손 클래스의 타입으로 변경을 허용하는 것이다.

public Point clone() {
Object obj = null;

try {
    obj = super.clone();
} catch(CloneNotSupportedException e) {}
return (Point)obj;

}
이처럼 공변 반환타입을 사용하면 조상의 타입이 아닌, 실제로 반환되는 자손 객체의 타입으로 변환할 수 있어서 번거로운 형변환이 줄어든다는 장점이 있다.

얇은 복사와 깊은 복사

clone()을 사용하면 원본과 복사본이 같은 인스턴스를 가르키는 얇은 복사가 일어난다. 원본과 복사본은 같은 인스턴스 변수 값을 가지지만, 서로 다른 인스턴스를 가르키는 것을 깊은 복사라고 한다.

따라서 깊은 복사를 원한다면, 새로운 인스터스를 생성후에 복사하도록 clone()을 오버라이딩하면 된다.

import java.util.*;

class Circle implements Cloneable {
	Point p;  // 원점
	double r; // 반지름

	Circle(Point p, double r) {
		this.p = p;
		this.r = r;
	}

	public Circle shallowCopy() { // 얕은 복사
		Object obj = null;

		try {
			obj = super.clone();
		} catch (CloneNotSupportedException e) {}

		return (Circle)obj;
	}

	public Circle deepCopy() { // 깊은 복사
		Object obj = null;

		try {
			obj = super.clone();
		} catch (CloneNotSupportedException e) {}

		Circle c = (Circle)obj; 
		c.p = new Point(this.p.x, this.p.y); 

		return c;
	}

	public String toString() {
		return "[p=" + p + ", r="+ r +"]";
	}
}

class Point {
	int x;
	int y;

	Point(int x, int y) {
		this.x = x;
		this.y = y;
	}

	public String toString() {
		return "("+x +", "+y+")";
	}
}

class ShallowCopy {
	public static void main(String[] args) {
		Circle c1 = new Circle(new Point(1, 1), 2.0);
		Circle c2 = c1.shallowCopy();
		Circle c3 = c1.deepCopy();
	
		System.out.println("c1="+c1);
		System.out.println("c2="+c2);
		System.out.println("c3="+c3);
		c1.p.x = 9;
		c1.p.y = 9;
		System.out.println("= c1의 변경 후 =");
		System.out.println("c1="+c1);
		System.out.println("c2="+c2);
		System.out.println("c3="+c3);
	}
}

getClass()

이 메서드는 자신이 속한 클래스의 Class객체를 반환하는 메서드인데, Class객체는 이름이 'Class'인 클래스인 객체이다. Class객체는 클래스의 모든 정보를 담고 있으며, 클래스 당 1개만 존재한다. 그리고 클래스 파일이 '클래스 로더'에 의해서 메모리에 올라갈 때, 자동으로 생성된다.

클래스 로더는 실행 시에 필요한 클래스를 동적으로 메모리에 로드하는 역할을 한다. 먼저 기존에 생성된 클래스 객체가 메모리에 존재하는지 확인하고, 있으면 객체의 참조를 반환하고 없으면 클래스 패스에 지정된 경로를 따라서 클래스 파일을 찾는다. 못 찾으면 ClassNotFoundException이 발생하고, 찾으면 해당 클래스 파일을 읽어서 Class객체로 변환한다.

즉, 파일 형태로 저장되어 있는 클래스를 읽어서 Class클래스에 정의된 형식으로 변환하여 사용하기 편한 형태로 저장해 놓은 것이 클래스 객체이다.

String클래스

String클래스에서 문자열은 문자형 배열(char[])로 저장된다. 그리고 String인스턴스는 문자열 리터럴의 주소를 가르키는 형태로 되어있다. 또, String인스턴스가 갖고 있는 문자열은 읽어 올 수 있고, 변경할 수 없다. 만약 +를 이용하여 두 문자열을 결합하는 경우, 두 개가 합쳐진 새로운 인스턴스가 탄생한다.

따라서 문자열을 다루는 작업이 많이 필요한 경우에는 StringBuffer클래스를 이용하는 것이 좋다. StringBuffer인스턴스에 저장된 문자열은 변경이 가능하다.

자바 소스파일에 포함된 모든 문자열 리터럴은 컴파일 시에 클래스 파일에 저장된다. 이 때 같은 내용의 문자열 리터럴은 한번만 저장된다.

클래스 파일에는 소스파일에 포함된 모든 리터럴의 목록이 있다. 해당 클래스 파일이 클래스 로더에 의해 메모리에 올라갈 때, 이 리터럴의 목록에 있는 리터럴들이 JVM내에 있는 상수 저장소에 저장된다. 이 때, 이 곳에 "AAA"같은 문자열 리터럴이 자동적으로 생성되어 저장되는 것이다.

또한, 자바에서는 길이가 0인 배열을 생성할 수 있다. String s = "" 라고 선언하면, 길이가 0인 char형 배열을 가지게 된다.

wrapper클래스

때로는 기본형 변수도 어쩔 수 없이 객체로 다루어야 하는 경우가 있다. 이 때 사용되는 것이 래퍼 클래스이다. 이 클래스를 이용하면 현재 자바의 8개의 기본형 변수를 객체로 다룰 수 있다.

(기본자료형-래퍼클래스)

boolean-Boolean
char-Character
byte-Byte
short-Short
int-Integer
long-Long
float-Float
double-Double

래퍼 클래스들은 모두 equals()가 오버라이딩되어 있어서 주소값이 아닌 객체가 가지고 있는 값을 비교한다. 또 toString()도 오버라이딩되어 있어서 객체가 가지고 있는 값을 문자열로 변환하여 반환한다.

Number클래스

Number클래스는 추상클래스로 내부적으로 숫자를 멤버변수로 갖는 래퍼 클래스들의 조상이다.

위 사진은 래퍼 클래스에 대한 상속계층도인데, 기본형 중에서 숫자와 관련된 래퍼 클래스들은 모두 Number의 조상이라는 것을 알 수 있다.

그 외에도, BigInteger는 long으로도 다룰 수 없는 큰 범위의 정수를, BigDecimal은 double로도 다룰 수 없는 큰 범위의 부동 소수점수를 처리하기 위한 것으로 연산자의 역할을 대신하는 다양한 메서드를 제공한다.

오토박싱 & 언박싱

JDK1.5이전에는 기본형과 참조형 간의 연산이 불가능했기 때문에, 래퍼 클래스로 기본형을 객체로 만들어서 연산해야 했다. 그러나 지금은 기본형과 참조형간의 연산이 가능하다. 컴파일러가 자동으로 변환하는 코드를 넣어주기 때문이다.

이에 기본형 값을 래퍼 클래스의 객체로 자동 변환해주는 것을 오토박싱이라고 하고, 반대로 변환하는 것을 언박싱이라고 한다.

유용한 클래스

나중에 필요할 때 찾아보면 되니, 간략하게 클래스 이름만 적어놓는다.

  • StringBuffer와 StringBuilder

  • Math

  • java.util.Objects

  • java.util.Random

  • java.util.regex

  • java.util.Scanner

  • java.util.StringTokenizer

  • java.math.BigInteger

  • java.math.BigDecimal

profile
server를 공부하고 있습니다.

0개의 댓글