공부했던 자료 정리하는 용도입니다.
재배포, 수정하지 마세요.
java.lang 패키지
java.lang 패키지는 자바 프로그래밍에 가장 기본이 되는 클래스들을 포함하고 있어서 import 문 없이도 사용할 수 있다.
Object 클래스
Object 클래스는 멤버 변수는 없고 오직 11개의 메서드만 가지고 있다. 모든 클래스의 최고 조상이기 때문에 Object 클래스의 멤버들은 모든 클래스에서 바로 사용 가능하다.
Object 클래스의 메서드 | 설 명 |
protected Object clone( ) | 객체 자신의 복사본을 반환한다. |
public boolean equals(Object obj) | 객체 자신과 객체 obj가 같은 객체인지 알려준다 (같으면 true) |
protected void finalize( ) | 객체가 소멸될 때 가비지 컬렉터에 의해 자동적으로 호출된다. 이 때 수행되어야 하는 코드가 잇을 때 오버라이딩한다. (거의 사용안함) |
public Class getClass( ) | 객체 자신의 클래스 정보를 담고있는 Class인스턴스를 반환 |
public int hashCode( ) | 객체 자신의 해시코드를 반환 |
public String toString( ) | 객체 자신의 정보를 문자열로 반환 |
public void notify( ) | 객체 자신을 사용하려고 기다리는 쓰레드를 하나만 깨운다. |
public void notifyAll( ) | 객체 자신을 사용하려고 기다리는 모든 쓰레드를 깨운다. |
public void wait( ) public void wait(long timout) public void wait(long timeout, int nanos) |
다른 쓰레드가 notify( ) 나 notifyAll( ) 을 호출할 때까지 현재 쓰레드를 무한히 또는 지정된 시간(timeout, nanos)동안 기다리게 한다.(timeout은 1/1000초, nanos는 1/(10^9)초) |
clone( )
: 이 메서드는 자신을 복제하여 새로운 인스턴스를 생성한다. 이를 이용해서 인스턴스를 복제해놨다가 값을 원래대로 되돌린다거나 변경되기 전의 값을 참고하는 식으로 활용할 수 있다. Object 클래스에 정의된 clone( ) 은 단순히 인스턴스 변수의 값만을 복사하기 때문에 참조 타입의 인스턴스 변수가 있는 클래스는 완전한 인스턴스 복제가 이뤄지지 않는다. 배열의 경우 복제된 인스턴스도 같은 배열의 주소를 갖기 때문에 복제된 인스턴스의 작업이 원래의 인스턴스에 영향을 미치게 된다. 이런 경우 clone 메서드를 오버라이딩해서 새로운 배열을 생성하고 배열의 내용을 복사하도록 해야 한다.
clone( ) 을 사용하려면, 먼저 복제할 클래스가 Cloneable 인터페이스를 구현해야 하고, clone( ) 을 오버라이딩하면서 접근제어자를 protected 에서 public 으로 변경해야 한다. 그래야 상속관계가 없는 다른 클래스에서 clone( ) 을 호출할 수 있다. 또한 데이터 보호를 위해서 Cloneable 인터페이스를 구현한 클래스의 인스턴스만 clone( ) 을 통한 복제가 가능하다. Cloneable 인터페이스가 구현되어 있다는 것은 클래스 작성자가 복제를 허용했다는 의미이기 때문이다.
■ 얕은 복사와 깊은 복사
clone( ) 은 단순히 객체에 저장된 값을 그대로 복제할 뿐, 객체가 참조하고 있는 객체까지 복제하지는 않는다. 예를 들어 기본형이 아닌 객체 배열을 clone( ) 으로 복제하는 경우에는 원본과 복제본이 같은 객체를 공유하므로 원본을 변경하면 복사본도 영향을 받는다. 이러한 복제(복사)를 '얕은 복사(shallow copy)'라고 한다.
반면에 원본이 참조하고 있는 객체까지 복사하는 것을 '깊은 복사(deep copy)'라고 하며, 깊은 복사에서는 원본과 복사본이 서로 다른 객체를 참조하기 때문에 원본을 변경해도 복사본에 영향을 미치지 않는다.
package Example;
class Circle implements Cloneable{ //clone()을 사용하기 위해서 Cloneable 인터페이스를 구현한다.(Cloneable인터페이스를 구현한 클래스의 인스턴스만 clone()을 이용한 복제가 가능)
Point p;
double r;
Circle(Point p, double r){ //생성자
this.p = p;
this.r = r;
}
public Circle shallowCopy() { //얕은복사
Object obj = null;
try { //clone()을 사용하기 위해서 clone()을 호출하는 코드가 포함된 try - catch문을 사용한다.
obj = super.clone();
}catch(CloneNotSupportedException e) {}
return (Circle)obj;
}
public Circle deepCopy() { //깊은복사
Object obj = null;
try { //clone()을 사용하기 위해서 clone()을 호출하는 코드가 포함된 try - catch문을 사용한다.
obj = super.clone();
}catch(CloneNotSupportedException e) {}
//복제된 객체가 새로운 Point인스턴스를 참조하게 만듦.(원본이 참조하고 있는 객체까지 복사)
Circle c = (Circle)obj;
c.p = new Point(this.p.x, this.p.y);
return c;
}
public String toString() { //출력을 위한 toString메서드 오버라이딩
return "[ p = " + p + ", r = " + r + "]";
}
}
class Point{
int x, y;
Point(int x, int y){
this.x = x;
this.y = y;
}
public String toString() { //출력을 위한 toString메서드 오버라이딩
return "(" + x + ", " + y + ")";
}
}
public class Example {
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 = 5;
c1.p.y = 7;
System.out.println("\n------ c1의 변경 후 ------");
System.out.println("c1 = " + c1);
System.out.println("c2 = " + c2);
System.out.println("c3 = " + c3);
}
}
얕은 복사와 깊은 복사의 차이점을 확인하는 예제이다.
■ 공변 반환 타입
JDK1.5부터 '공변 반환 타입(convariant return type)'이 추가되었는데, 이 기능은 오버라이딩 할 때 부모 메서드의 반환 타입을 자식 클래스의 타입으로 변경을 허용하는 것이다. 공변 반환 타입을 사용하면 부모의 타입이 아닌, 실제 반환되는 자식 객체의 타입으로 반환할 수 있어서 번거로운 형변환이 줄어든다는 장점이 있다.
equals(Object obj)
: 두 객체의 같고 다름을 참조 변수의 값으로 판단한다. 그렇기 때문에 서로 다른 두 객체를 equals 메서드로 비교하면 항상 false 를 반환한다. 주소 값이 아닌 값을 비교하고 싶다면 equals 메서드를 오버라이딩해서 값을 비교하도록 변경하면 된다.
package Example;
public class Example {
public static void main(String[] args) {
Value v1 = new Value(10);
Value v2 = new Value(10);
if(v1 == v2) { // ==은 객체타입인경우 주소값을 비교한다. 서로다른 객체는 다른 주소를 가지고 있기 때문에 false가 출력됨
System.out.println("v1 == v2는 true입니다.");
}else {
System.out.println("v1 == v2는 false입니다.");
}
if(v1.equals(v2)) { // equals또한 객체타입인경우 주소값을 비교하기 때문에 false가 출력된다.
System.out.println("v1.equals(v2)는 true입니다.");
}else {
System.out.println("v1.equals(v2)는 false입니다.");
}
}
}
class Value{
int value;
Value(int value){
this.value = value;
}
}
== 연산자와 equals 는 객체가 기본 타입일 경우 값을 비교하고, 참조형 타입일 때는 주소 값을 비교한다. 그래서 위의 코드는 서로 두 객체를 비교하는 것이기 때문에 항상 false 가 나온다.
package Example;
public class Example {
public static void main(String[] args) {
String s1 = "same";
String s2 = "same";
if(s1 == s2) { // ==은 객체타입인경우 주소값을 비교한다. 서로다른 객체는 다른 주소를 가지고 있기 때문에 false가 출력됨
System.out.println("s1 == s2는 true입니다.");
}else {
System.out.println("s1 == s2는 false입니다.");
}
if(s1.equals(s2)) { // equals또한 객체타입인경우 주소값을 비교하기 때문에 false가 출력된다.
System.out.println("s1.equals(s2)는 true입니다.");
}else {
System.out.println("s1.equals(s2)는 false입니다.");
}
}
}
String 클래스는 생성 방법이 좀 독특한데 다른 객체처럼 new 생성자를 이용해서 인스턴스를 생성하고, heap 메모리에서 관리한다는 것은 같지만 String Constant Pool 라는 것이 있어서 new String("문자열") 로 생성하지 않고 그냥 String = "문자열" 하면 이미 생성되어있는 문자열 리터럴의 주소를 가리키게 된다. 그래서 객체임에도 == 연산자로 비교하면 true 가 나온다.( new String("문자열") 하면 새로운 객체가 생성되므로 == 연산시 false 나옴)
또한 String 클래스의 equals 는 기본적으로 equals 가 인스턴스의 문자열 값을 비교하도록 오버라이딩 되어있기 때문에 별도의 오버라이딩을 하지 않아도 equals 가 값을 비교한다!
getClass( )
: 자신이 속한 클래스의 Class 객체를 반환하는 메서드인데, Class 객체는 이름이 'Class'인 클래스의 객체이다. Class 객체는 클래스의 모든 정보를 담고 있으며, 클래스당 1개만 존재한다. 그리고 클래스 파일이 '클래스 로더(ClassLoader)'에 의해서 메모리에 올라갈 때 자동으로 생성된다.
클래스 로더는 실행 시에 필요한 필요한 클래스를 동적으로 메모리에 로드하는 역할을 한다. 먼저 기존에 생성된 클래스 객체가 메모리에 존재하는지 확인하고, 있으면 객체의 참조를 반환, 없으면 클래스 패스(classpath)에 지정된 경로를 따라서 클래스 파일을 찾는다. 못 찾으면 ClassNotFoundException 이 발생하고, 찾으면 해당 클래스 파일을 읽어서 Class 객체로 변환한다.(파일 형태로 저장되어 있는 클래스를 읽어서 Class 에 정의된 형식으로 변환함)
■ Class객체를 얻는 방법
Class obj = new ClassName().getClass(); //생성된 객체로부터 얻는 방법
Class obj = ClassName.class; //클래스 리터럴(*.class)로부터 얻는 방법
Class obj = Class.forName("ClassName"); //클래스 이름으로부터 얻는 방법
클래스에 대한 정보가 필요할 때, 먼저 Class 객체에 대한 참조를 얻어와야 하는데, 해당 Class 객체에 대한 참조를 얻는 방법은 위의 코드처럼 여러 가지가 있다(위의 코드는 클래스의 이름이 ClassName 인 경우) 특히 forName( ) 은 특정 클래스 파일(ex 데이터베이스 드라이버)을 메모리에 올릴 때 주로 사용한다.
Class 객체를 이용하면 클래스에 대한 모든 정보(클래스의 정의된 멤버의 이름이나 개수 등)를 얻을 수 있기 때문에 Class 객체를 통해서 객체를 생성하고 메서드를 호출하는 등 보다 동적인 코드를 작성할 수 있다. 동적으로 객체를 생성하고 메서드를 호출하는 방법을 더 알고 싶다면 '리플렉션 API(Reflection API)'를 검색하면 된다.
hashCode( )
: 해싱(hashing) 기법에 사용되는 '해시함수(hash function)'를 구현한 것이다. 해시함수는 찾고자 하는 값을 입력하면, 그 값이 저장된 위치를 알려주는 해시 코드(hashcode)를 반환한다. 일반적으로 해시 코드가 같은 두 객체가 존재하는 것이 가능하지만, Object 클래스에 정의된 hashCode 메서드는 객체의 주소 값을 이용해서 해시 코드를 만든 후 반환하기 때문에 서로 다른 두 객체는 절대 같은 해시 코드를 가질 수 없다. 클래스의 인스턴스 변수 값으로 객체를 비교해야 한다면 hashCode 메서드도 오버라이딩 해야 한다.
해싱 기법을 사용하는 HashMap 이나 HashSet 과 같은 클래스 저장할 객체라면 반드시 hashCode 메서드를 오버라이딩해야 한다.
package Example;
public class Example {
public static void main(String[] args) {
String s1 = new String("abc");
String s2 = new String("abc");
System.out.println("s1.equals(str) : "+ s1.equals(s2));
System.out.println("s1.hashCode() : " + s1.hashCode());
System.out.println("s2.hashCode() : " + s2.hashCode());
System.out.println("System.identityHashCode(s1) : " + System.identityHashCode(s1));
System.out.println("System.identityHashCode(s2) : " + System.identityHashCode(s2));
}
}
String 클래스는 문자열의 내용이 같으면 동일한 해시코드를 반환하도록 hashCode 메서드가 오버라이딩 되어있어서 같은 문자열들에 대해 hashCode( ) 를 호출하면 항상 동일한 코드값을 얻는다. 반면에 System.identityHashCode(Object x) 는 Object 클래스의 hashCode 메서드처럼 객체의 주소 값을 이용해서 해시 코드를 생성하기 때문에 모든 객체에 대해 항상 다른 해시 코드값을 반환할 것을 보장한다.
toString( )
public String toString(){
return getClass().getName() + "@" + Integer.toHexString(hashCode());
}
이 메서드는 인스턴스에 대한 정보를 문자열( String )로 제공할 목적으로 정의한 것이다. Object 클래스에 정의된 기본적인 toString( ) 메서드는 위의 코드와 같아서 오버라이딩 하지 않으면 클래스 이름에 16진수의 해시 코드를 얻게 된다.
'Back end > Java' 카테고리의 다른 글
[Java] java.lang 패키지 - StringBuffer 클래스와 StringBuilder 클래스 (0) | 2019.07.15 |
---|---|
[Java] java.lang 패키지 - String클래스 (0) | 2019.07.12 |
[Java] 예외처리(Exception Handling) (0) | 2019.07.11 |
[Java] 내부 클래스(Inner Class)와 익명 클래스(Anonymous class) (0) | 2019.07.10 |
[Java] 추상클래스(Abstract Class)와 인터페이스(Interface) (0) | 2019.07.09 |