728x90

이 강의는 자바의 프로젝트를 어떻게 만드는지, 또한 빌드를 어떻게 하는지에 대한 강의를 진행하지 않는다.

그러나 자바의 기본적인, 그리고 자바에 필수적인 기능들도 분명 존재하고 이 블로그엔 그 작업에 필요한 포스팅도 함께 제공하고 있다.

그 정보를 확인하고 싶다면 여기를 참조하라. 양이 조금 되고 현재진행형으로 늘어나고 있으므로 페이지내의 찾기(ctrl+f 나 cmd+f)로 찾아보도록하자.


java.lang패키지는 language패키지라고도 부르고 lang패키지라고도 부르고 어찌됬던 자바에 포함된 여러 패키지중 하나이다.

그러나 다른 패키지들과는 특별한 점이 있다. 바로 import하지않고도 쓸 수 있는 유일한 패키지이다.

이 패키지에 속한 클래스들이 무엇이 있을까? 한번 확인해보도록 하자.


굉장히 많은 클래스들이 language패키지에 포함되어 있다. 이는 자바 언어적으로 포함된것으로 언어의 일부라고 생각해도된다.

여기서 자주쓰는 클래스들은 Object클래스나 Wrapper클래스가 있다.

그리고 중요한 interface역시 많이 들어있어서 다 공부하려면 시간이 조금 걸린다.

필자는 3타임에 걸쳐서 가장 많이 쓰이고 중요한것들을 중심으로 소개할 예정이다.

그리고 이번에는 Object클래스에 대해서 알아보도록하자.


Object클래스는 무슨 클래스인가? 원래 원칙상 여기서는 IDE에 대한 이야기는 잘 안다루려고하지만

실상 여러분이 이미 만들때 부터 Object클래스는 뻔질나게 보아왔다.



이클립스에서 만들데 Superclass가 보인다.

여기서 자동으로 Object클래스가 선택되어 있다.

즉 여러분은 항상 만들때 Object클래스를 사용했다는 것이다.

여러분이 명시적으로 Object클래스를 상속받은 적은 없을 것이다.

그러나 항상 Object클래스를 상속받아져있다.

그게 상속받았는지 안받는지 어떻게 아냐고? instanceof 연산자를 사용해보면 알 수 있다.


package com.jiharu.main;

public class MyClass {
}

아무것도 없는 빈 클래스를 만들어 보자.

이제 이 클래스를 Main에서 다음과 같이 사용해보자.


package com.jiharu.main;

public class Main {

public static void main(String[] args) {
MyClass myClass = new MyClass();
if(myClass instanceof Object){
System.out.println("Object를 상속받음!");
}
}
}

이 예제를 테스트 해보면 정상적으로 출력되는 것을 볼 수 있다. 왜냐하면 Object를 당연히도 상속받았기 때문이다.

우리가 명시적으로 선언하지 않아도 Object클래스를 항상 상속 받고 있는 것이다.

그러면 Object클래스를 한번 다시 자세히 알아보자.


Object클래스는 총 9개, 오버로딩을 따로 친다면 12개의 클래스를 소유하고 있다.

여기서 중요한건 Object클래스는 모든 클래스의 부모 클래스라는 것이다.

즉 Object클래스가 가지고 있는 9가지의 메소드 = 모든 클래스가 기본적으로 가지고 있는 9가지의 메소드라는 것이다.

여러분이 어떠한 클래스를 만들어도 이 9가지의 메소드를 소유하고 있다고 보면 되는 것이다.


이번시간에 이 9가지를 전부 다루는 것도 가능하지만 몇개의 메소드는 다루지 않겠다.

그 이유는 여러 부가적인 설명이 필요하거나 많은 설명이 들어가야하는게 있다.

wait와 notify,notifyAll은 쓰레드와 연관되어있다. 즉 이 설명은 쓰레드에서 해야하는게 맞는 것이다.

긜고 getClass는 Class class를 반환하는데 약간 말장난 같은데 이 Class class는 조금 이해가 필요하므로 좀 뒤에 다시 설명하겠다.

그리고 finalize의 경우 자바의 소멸자를 의미한다. 이 소멸자 역시 지금 설명하기에는 애매하다.

그 이유는 소멸자를 설명하기 위해서는 GC(가비지 컬렉터)를 설명해야하기 때문이다.

따라서 이번엔 이들을 제외한, 그러면서도 매우 자주쓰이는 clone, equals,hashCode,toString에 대해서 알아보자.


protected Object clone()


이름에서 알 수 있듯이 clone이라는 메소드는 특정 객체를 복사하는 메소드이다.

단 아쉽게도 이 복사라는 개념은 일반적으로 얕은 복사이다. 따라서 깊은 복사를 하고 싶다면 오버라이딩을 해줘야한다.

혹시 깊은 복사와 얕은 복사를 모르는 분들을 위해서 간단히 설명을 하고 넘어가도록 하자.


package com.jiharu.main;

public class WrapString {
String str;

public WrapString(String str) {
this.str = str;
}
}

먼저 WrapString이라는 클래스를 만든다. 여기는 문자열을 담고있다.


package com.jiharu.main;

public class DoubleWrapString implements Cloneable {
WrapString wstr;

public DoubleWrapString(WrapString wstr) {
this.wstr = wstr;
}

@Override
public DoubleWrapString clone() {
try {
return (DoubleWrapString) super.clone();
} catch (CloneNotSupportedException e) {
System.out.println(e);
}
return null;
}
}

그 WrapString클래스를 감싸는 DoubleWrapString 클래스를 만든다.

그리고 클로닝을 가능하게 하기 위해서 Cloneable이라는 인터페이스를 상속받는다.

만약 이 인터페이스를 상속받지 않고 외부에서 clone메소드를 호출하면 CloneNotSupportedException이라는 예외가 뜨게된다.

따라서 clone메소드를 사용하고 싶다면 반드시 Cloneable을 상속시켜주어야만 한다.

이제 준비는 끝났으니 Main클래스를 보도록 하자.


package com.jiharu.main;

public class Main {

public static void main(String[] args) {
DoubleWrapString dw1 = new DoubleWrapString(new WrapString("지디라고 아시오?"));
DoubleWrapString dw2 = null;
dw2 = dw1.clone();
dw2.wstr.str = "오늘 밤은 삐딱하게";
System.out.println(dw1.wstr.str);
System.out.println(dw2.wstr.str);
}
}

이 예제를 보면 알겠지만 dw1은 생성자를 만들었고 dw2는 dw1을 복사했다.

그리고 dw2의 문자열을 바꾸어 주었다. 만약 dw1과 dw2를 출력한다면 어떻게 될까?

여러분의 생각에는 어떻게 될거 같은가? 매우 중요한 이야기이다.


여러분의 바램과는 무색하게 dw1과 dw2가 모두 바뀐걸 알 수 있다.

이는 얕은 복사가 일어낳기 때문이다.



여러분이 생각한 클로닝한 직후의 상황은 이럴것이다.

각각 따로 wstr을가지고 각각 따로  str을 가지고...

그러나 실상은 그렇지 않다.



실제로 실행해보면 dw2는 dw1을 가리키고 있다.

즉 dw1이 wstr과 dw2의 wstr은 같은 녀석이라는 이야기가 된다.

그래서 dw2를 바꿔도 dw1이 같이 바뀌는 것이다. 애당초 두녀석은 같은 녀석을 가르키니까.

이렇게 복사가 얕게 일어나는것을 얕은 복사라고 한다.

이를 해결하기 위해서는 얕은 복사가 아닌 아예 따로 래퍼런스를 가지는 깊은 복사를 시행하여야한다.


package com.jiharu.main;

public class DoubleWrapString implements Cloneable {
WrapString wstr;

public DoubleWrapString(WrapString wstr) {
this.wstr = wstr;
}

@Override
public DoubleWrapString clone() {
try {
DoubleWrapString copy = (DoubleWrapString) super.clone();
copy.wstr = new WrapString(this.wstr.str);
return copy;
} catch (CloneNotSupportedException e) {
System.out.println(e);
}
return null;
}
}

깊은 복사를 시행하게 코드를 변경하였다.

이제 다시 실행해보라.


이제 여러분이 원하는대로 실행된것을 확인할 수 있다.


boolean equals(Object obj)


equals메소드는 두 클래스의 동등성을 비교한다.

String클래스나 래퍼클래스(아직 배운개념은 아니지만)의 equals는 두 클래스의 동등성 비교는 아니고 내부 값을 비교한다.

그래서 equals는 있는 그대로 사용하는 경우는 드물고 보통은 오버라이딩을 해서 사용한다.

동등성 비교가 무슨개념인지 조금 애매하다면 아래의 예제를 보자.


package com.jiharu.main;

public class Main {

public static void main(String[] args) {
WrapString wstr1 = new WrapString("피카츄");
WrapString wstr2 = new WrapString("피카츄");
System.out.println(wstr1 == wstr2);
System.out.println(wstr1.equals(wstr2));
}
}

WrapString예제는 위엣걸 그대로 쓰도록하자. 여기서 보면 두 WrapString은 모두 피카츄라는 문자열을 가지고 있다.

여러분에게 묻겠다. 두개의 결과를 예측할 수 있는가?

아마 ==의 경우 false가 나올것임을 쉽게 예측할 수 있다. 그 이유는 wstr1과 wstr2은 서로 다른 개체이기 때문이다.

즉 다른 객체를 가르키고 있으므로 동등하지 않기 때문이다.

 반면 equals경우에는 어떨까? 이경우에도 마찬가지로 false가 반환된다.

왜냐하면 두 객체는 동등하기 때문이다. 그렇다. 일반적으로 여러분이 만든 클래스들의 경우(그리고 래퍼클래스와 String클래스를 제외한 대부분의 클래스)

일반적으로는 equals와 ==은 같은 역활을 한다.

그러나 일반적으로 위와같은 비교를 원치 않을 것이다. ==는 우리가 바꿀수 없다.

그러나 equals는 우리가 오버라이딩으로 기능을 변하게 할 수 있다.


package com.jiharu.main;

public class WrapString {
String str;

public WrapString(String str) {
this.str = str;
}

@Override
public boolean equals(Object obj) {
if(this.str.equals(((WrapString)obj).str)){
return true;
}
return false;
}
}

String클래스의 ==과 equals는 다르다고 하였다. 이는 이미 내부적으로 오버라이딩되어 있기 때문이다.

우리는 WrapString클래스의 동등성 비교를 가지고 있는 str필드를 이용해서 하려고했다.

물론 비교하려는 대상은 여러분의 상황에 맞게 하면된다.

예를 들어서 나는 문자열이 서로 영어기만 하면 equlas로 true를 반환하겠다! 이런것도 얼마든지 짤 수 있다.

위와 같이 짤경우에는 실행한 결과가 아래와 같이 나온다.


이제 equals는 우리가 의도하는대로 실행됨을 알 수있다.


int hashCode()


이 메소드는 클래스의 해시값을 반환하게 된다.

클래스 인스턴스를 해시한 값을 반환하기 때문에 HashTable이나 HashSet등을 사용하기에 유용한 상태가 된다.


package com.jiharu.main;

public class Main {

public static void main(String[] args) {
WrapString wstr1 = new WrapString("피카츄");
WrapString wstr2 = new WrapString("피카츄");
WrapString wstr3 = wstr1;
System.out.println(wstr1.hashCode());
System.out.println(wstr2.hashCode());
System.out.println(wstr3.hashCode());
}
}

이 예제의 경우 wstr1과 wstr3은 같은 값이 나온다. 왜냐하면 서로 같은 인스턴스를 가리키고 있기 때문이다.


보통 해시값을 이용한 색인등을 사용할때 많이 이용한다.


String toString()


equals와 마찬기자로 Object클래스의 메소드중에 사용률 부동의 two top의 양대산맥인 메소드이다.

특정 클래스의 반환값을 문자열로 받는것이다.

보통 특정 클래스의 필드 값을 반환하는 역활로 주로 쓰이나 경우에 따라서 손볼 수도 있다.

아래와 같은 예를 들어보자.


package com.jiharu.main;

public class People {
String name;
int age;
String phone;

public People(String name, int age, String phone) {
this.name = name;
this.age = age;
this.phone = phone;
}
}

여러분이 이런 클래스를 만들었다고 치자.

그런데 만약 다른 클래스에서 이 클래스의 정보를 보고싶을때 여러분은 어떻게 하는가?

getter setter를 덕지덕지? 아마 불편하게 볼것이다.

그러한것을 방지하고자 대부분 프로그래머들은 toString을 사용한다.

toString은 오버라이딩하지 않고 사용하면 클래스명과 해시값을 합쳐서 반환한다.

우리는 그렇게 쓰지 않을꺼고 무조건 오버라이딩해서 사용할 것이다.


package com.jiharu.main;

public class People {
String name;
int age;
String phone;

public People(String name, int age, String phone) {
this.name = name;
this.age = age;
this.phone = phone;
}

@Override
public String toString() {
return "People{" +
"name='" + name + '\'' +
", age=" + age +
", phone='" + phone + '\'' +
'}';
}
}

이렇게 사용할 경우 외부에서 이 정보를 알고 싶으면 toString을 호출하면된다.

그리고 toString의 좋은점은 System.out.println등으로 호출할경우(즉 Object타입으로 호출할 경우)자동으로 toString값으로 출력된다는 것이다.

아래의 예시를 보자.


package com.jiharu.main;

public class Main {

public static void main(String[] args) {
People p = new People("kukaro",22,"IPhone");
System.out.println(p);
}
}

이 예제를 실행해보자.


매우 적은 노력으로 많은 정보를 확인할 수 있다.

toString은 유용하지만 만들때도 타이핑 양이 많아서 귀찮다.

이걸 편하게 하기위해서 각 IDE별로 자동완성을 제공하고 있다.

사용방법은 3대 IDE(Eclpse,IntelliJ,NetBeans)별 toString 만들기 포스팅을 참조하라.

+ Recent posts